diff --git a/.github/actions/dependencies/action.yml b/.github/actions/dependencies/action.yml index afce1557d3..0bd28f15dd 100644 --- a/.github/actions/dependencies/action.yml +++ b/.github/actions/dependencies/action.yml @@ -2,56 +2,37 @@ name: dependencies inputs: configuration: required: true -# An implicit input is the environment variable `build_dir`. +# Implicit inputs are the environment variables `build_dir`, CONAN_REMOTE_URL, +# CONAN_REMOTE_USERNAME, and CONAN_REMOTE_PASSWORD. The latter two are only +# used to upload newly built dependencies to the Conan remote. runs: using: composite steps: - - name: unlock Conan - shell: bash - run: conan remove --locks - - name: export custom recipes - shell: bash - run: | - conan config set general.revisions_enabled=1 - conan export external/snappy snappy/1.1.10@ - conan export external/rocksdb rocksdb/9.7.3@ - conan export external/soci soci/4.0.3@ - conan export external/nudb nudb/2.0.8@ - - name: add Ripple Conan remote + - name: add Conan remote + if: ${{ env.CONAN_REMOTE_URL != '' }} shell: bash run: | + echo "Adding Conan remote 'xrplf' at ${{ env.CONAN_REMOTE_URL }}." + conan remote add --index 0 --force xrplf ${{ env.CONAN_REMOTE_URL }} + echo "Listing Conan remotes." conan remote list - conan remote remove ripple || true - # Do not quote the URL. An empty string will be accepted (with - # a non-fatal warning), but a missing argument will not. - conan remote add ripple ${{ env.CONAN_URL }} --insert 0 - - name: try to authenticate to Ripple Conan remote - id: remote - shell: bash - run: | - # `conan user` implicitly uses the environment variables - # CONAN_LOGIN_USERNAME_ and CONAN_PASSWORD_. - # https://docs.conan.io/1/reference/commands/misc/user.html#using-environment-variables - # https://docs.conan.io/1/reference/env_vars.html#conan-login-username-conan-login-username-remote-name - # https://docs.conan.io/1/reference/env_vars.html#conan-password-conan-password-remote-name - echo outcome=$(conan user --remote ripple --password >&2 \ - && echo success || echo failure) | tee ${GITHUB_OUTPUT} - - name: list missing binaries - id: binaries - shell: bash - # Print the list of dependencies that would need to be built locally. - # A non-empty list means we have "failed" to cache binaries remotely. - run: | - echo missing=$(conan info . --build missing --settings build_type=${{ inputs.configuration }} --json 2>/dev/null | grep '^\[') | tee ${GITHUB_OUTPUT} - name: install dependencies shell: bash run: | - mkdir ${build_dir} - cd ${build_dir} + mkdir -p ${{ env.build_dir }} + cd ${{ env.build_dir }} conan install \ --output-folder . \ --build missing \ - --options tests=True \ - --options xrpld=True \ - --settings build_type=${{ inputs.configuration }} \ + --options:host "&:tests=True" \ + --options:host "&:xrpld=True" \ + --settings:all build_type=${{ inputs.configuration }} \ .. + - name: upload dependencies + if: ${{ env.CONAN_REMOTE_URL != '' && env.CONAN_REMOTE_USERNAME != '' && env.CONAN_REMOTE_PASSWORD != '' && github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch }} + shell: bash + run: | + echo "Logging into Conan remote 'xrplf' at ${{ env.CONAN_REMOTE_URL }}." + conan remote login xrplf "${{ env.CONAN_REMOTE_USERNAME }}" --password "${{ env.CONAN_REMOTE_PASSWORD }}" + echo "Uploading dependencies." + conan upload '*' --confirm --check --remote xrplf diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index ac6154ab9f..0d81f87791 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -9,24 +9,25 @@ jobs: check: if: ${{ github.event_name == 'push' || github.event.pull_request.draft != true || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') }} runs-on: ubuntu-24.04 - env: - CLANG_VERSION: 18 + container: ghcr.io/xrplf/ci/tools-rippled-clang-format steps: + # For jobs running in containers, $GITHUB_WORKSPACE and ${{ github.workspace }} might not be the + # same directory. The actions/checkout step is *supposed* to checkout into $GITHUB_WORKSPACE and + # then add it to safe.directory (see instructions at https://github.com/actions/checkout) + # but that's apparently not happening for some container images. We can't be sure what is actually + # happening, so let's pre-emptively add both directories to safe.directory. There's a + # Github issue opened in 2022 and not resolved in 2025 https://github.com/actions/runner/issues/2058 ¯\_(ツ)_/¯ + - run: | + git config --global --add safe.directory $GITHUB_WORKSPACE + git config --global --add safe.directory ${{ github.workspace }} - uses: actions/checkout@v4 - - name: Install clang-format - run: | - codename=$( lsb_release --codename --short ) - sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null <&2 \ + echo outcome=$(conan user --remote xrplf --password >&2 \ && echo success || echo failure) | tee ${GITHUB_OUTPUT} - name: Upload new package id: upload diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 63d54175ea..6c6091cc2e 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -15,6 +15,21 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# This part of Conan configuration is specific to this workflow only; we do not want +# to pollute conan/profiles directory with settings which might not work for others +env: + CONAN_REMOTE_URL: ${{ vars.CONAN_REMOTE_URL }} + CONAN_REMOTE_USERNAME: ${{ secrets.CONAN_REMOTE_USERNAME }} + CONAN_REMOTE_PASSWORD: ${{ secrets.CONAN_REMOTE_PASSWORD }} + # This part of the Conan configuration is specific to this workflow only; we + # do not want to pollute the 'conan/profiles' directory with settings that + # might not work for other workflows. + CONAN_GLOBAL_CONF: | + core.download:parallel={{os.cpu_count()}} + core.upload:parallel={{os.cpu_count()}} + tools.build:jobs={{ (os.cpu_count() * 4/5) | int }} + tools.build:verbosity=verbose + tools.compilation:verbosity=verbose jobs: @@ -28,23 +43,22 @@ jobs: - Ninja configuration: - Release - runs-on: [self-hosted, macOS] + runs-on: [self-hosted, macOS, mac-runner-m1] env: # The `build` action requires these variables. build_dir: .build NUM_PROCESSORS: 12 steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: install Conan run: | - brew install conan@1 - echo '/opt/homebrew/opt/conan@1/bin' >> $GITHUB_PATH + brew install conan - name: install Ninja if: matrix.generator == 'Ninja' run: brew install ninja - name: install python - run: | + run: | if which python > /dev/null 2>&1; then echo "Python executable exists" else @@ -76,14 +90,11 @@ jobs: clang --version - name: configure Conan run : | - conan profile new default --detect || true - conan profile update settings.compiler.cppstd=20 default + echo "${CONAN_GLOBAL_CONF}" > $(conan config home)/global.conf + conan config install conan/profiles/ -tf $(conan config home)/profiles/ + conan profile show - name: build dependencies uses: ./.github/actions/dependencies - env: - CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod - CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }} - CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }} with: configuration: ${{ matrix.configuration }} - name: build @@ -96,4 +107,7 @@ jobs: run: | n=$(nproc) echo "Using $n test jobs" - ${build_dir}/rippled --unittest --unittest-jobs $n + + cd ${build_dir} + ./rippled --unittest --unittest-jobs $n + ctest -j $n --output-on-failure diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index de59e07761..5beb5d291d 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -16,6 +16,20 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + CONAN_REMOTE_URL: ${{ vars.CONAN_REMOTE_URL }} + CONAN_REMOTE_USERNAME: ${{ secrets.CONAN_REMOTE_USERNAME }} + CONAN_REMOTE_PASSWORD: ${{ secrets.CONAN_REMOTE_PASSWORD }} + # This part of the Conan configuration is specific to this workflow only; we + # do not want to pollute the 'conan/profiles' directory with settings that + # might not work for other workflows. + CONAN_GLOBAL_CONF: | + core.download:parallel={{ os.cpu_count() }} + core.upload:parallel={{ os.cpu_count() }} + tools.build:jobs={{ (os.cpu_count() * 4/5) | int }} + tools.build:verbosity=verbose + tools.compilation:verbosity=verbose + # This workflow has multiple job matrixes. # They can be considered phases because most of the matrices ("test", # "coverage", "conan", ) depend on the first ("dependencies"). @@ -54,59 +68,45 @@ jobs: - Release include: - compiler: gcc - profile: - version: 11 - cc: /usr/bin/gcc - cxx: /usr/bin/g++ + compiler_version: 12 + distro: ubuntu + codename: jammy - compiler: clang - profile: - version: 14 - cc: /usr/bin/clang-14 - cxx: /usr/bin/clang++-14 + compiler_version: 16 + distro: debian + codename: bookworm runs-on: [self-hosted, heavy] - container: ghcr.io/xrplf/rippled-build-ubuntu:aaf5e3e + container: ghcr.io/xrplf/ci/${{ matrix.distro }}-${{ matrix.codename }}:${{ matrix.compiler }}-${{ matrix.compiler_version }} env: build_dir: .build steps: - - name: upgrade conan - run: | - pip install --upgrade "conan<2" - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: check environment run: | echo ${PATH} | tr ':' '\n' lsb_release -a || true - ${{ matrix.profile.cc }} --version + ${{ matrix.compiler }}-${{ matrix.compiler_version }} --version conan --version cmake --version env | sort - name: configure Conan run: | - conan profile new default --detect - conan profile update settings.compiler.cppstd=20 default - conan profile update settings.compiler=${{ matrix.compiler }} default - conan profile update settings.compiler.version=${{ matrix.profile.version }} default - conan profile update settings.compiler.libcxx=libstdc++11 default - conan profile update env.CC=${{ matrix.profile.cc }} default - conan profile update env.CXX=${{ matrix.profile.cxx }} default - conan profile update conf.tools.build:compiler_executables='{"c": "${{ matrix.profile.cc }}", "cpp": "${{ matrix.profile.cxx }}"}' default + echo "${CONAN_GLOBAL_CONF}" >> $(conan config home)/global.conf + conan config install conan/profiles/ -tf $(conan config home)/profiles/ + conan profile show - name: archive profile # Create this archive before dependencies are added to the local cache. - run: tar -czf conan.tar -C ~/.conan . + run: tar -czf conan.tar.gz -C ${CONAN_HOME} . - name: build dependencies uses: ./.github/actions/dependencies - env: - CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod - CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }} - CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }} with: configuration: ${{ matrix.configuration }} - name: upload archive - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: name: ${{ matrix.platform }}-${{ matrix.compiler }}-${{ matrix.configuration }} - path: conan.tar + path: conan.tar.gz if-no-files-found: error test: @@ -121,26 +121,32 @@ jobs: configuration: - Debug - Release + include: + - compiler: gcc + compiler_version: 12 + distro: ubuntu + codename: jammy + - compiler: clang + compiler_version: 16 + distro: debian + codename: bookworm cmake-args: - - "-Dunity=ON" needs: dependencies runs-on: [self-hosted, heavy] - container: ghcr.io/xrplf/rippled-build-ubuntu:aaf5e3e + container: ghcr.io/xrplf/ci/${{ matrix.distro }}-${{ matrix.codename }}:${{ matrix.compiler }}-${{ matrix.compiler_version }} env: build_dir: .build steps: - - name: upgrade conan - run: | - pip install --upgrade "conan<2" - name: download cache - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: name: ${{ matrix.platform }}-${{ matrix.compiler }}-${{ matrix.configuration }} - name: extract cache run: | - mkdir -p ~/.conan - tar -xzf conan.tar -C ~/.conan + mkdir -p ${CONAN_HOME} + tar -xzf conan.tar.gz -C ${CONAN_HOME} - name: check environment run: | env | sort @@ -148,11 +154,9 @@ jobs: conan --version cmake --version - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: dependencies uses: ./.github/actions/dependencies - env: - CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod with: configuration: ${{ matrix.configuration }} - name: build @@ -161,9 +165,21 @@ jobs: generator: Ninja configuration: ${{ matrix.configuration }} cmake-args: "-Dassert=TRUE -Dwerr=TRUE ${{ matrix.cmake-args }}" + - name: check linking + run: | + cd ${build_dir} + ldd ./rippled + if [ "$(ldd ./rippled | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then + echo 'The binary is statically linked.' + else + echo 'The binary is dynamically linked.' + exit 1 + fi - name: test run: | - ${build_dir}/rippled --unittest --unittest-jobs $(nproc) + cd ${build_dir} + ./rippled --unittest --unittest-jobs $(nproc) + ctest -j $(nproc) --output-on-failure reference-fee-test: strategy: @@ -180,21 +196,18 @@ jobs: - "-DUNIT_TEST_REFERENCE_FEE=1000" needs: dependencies runs-on: [self-hosted, heavy] - container: ghcr.io/xrplf/rippled-build-ubuntu:aaf5e3e + container: ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12 env: build_dir: .build steps: - - name: upgrade conan - run: | - pip install --upgrade "conan<2" - name: download cache - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: name: ${{ matrix.platform }}-${{ matrix.compiler }}-${{ matrix.configuration }} - name: extract cache run: | - mkdir -p ~/.conan - tar -xzf conan.tar -C ~/.conan + mkdir -p ${CONAN_HOME} + tar -xzf conan.tar.gz -C ${CONAN_HOME} - name: check environment run: | env | sort @@ -202,11 +215,9 @@ jobs: conan --version cmake --version - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: dependencies uses: ./.github/actions/dependencies - env: - CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod with: configuration: ${{ matrix.configuration }} - name: build @@ -217,7 +228,9 @@ jobs: cmake-args: "-Dassert=TRUE -Dwerr=TRUE ${{ matrix.cmake-args }}" - name: test run: | - ${build_dir}/rippled --unittest --unittest-jobs $(nproc) + cd ${build_dir} + ./rippled --unittest --unittest-jobs $(nproc) + ctest -j $(nproc) --output-on-failure coverage: strategy: @@ -231,23 +244,18 @@ jobs: - Debug needs: dependencies runs-on: [self-hosted, heavy] - container: ghcr.io/xrplf/rippled-build-ubuntu:aaf5e3e + container: ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12 env: build_dir: .build steps: - - name: upgrade conan - run: | - pip install --upgrade "conan<2" - name: download cache - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: name: ${{ matrix.platform }}-${{ matrix.compiler }}-${{ matrix.configuration }} - name: extract cache run: | - mkdir -p ~/.conan - tar -xzf conan.tar -C ~/.conan - - name: install gcovr - run: pip install "gcovr>=7,<9" + mkdir -p ${CONAN_HOME} + tar -xzf conan.tar.gz -C ${CONAN_HOME} - name: check environment run: | echo ${PATH} | tr ':' '\n' @@ -255,13 +263,11 @@ jobs: cmake --version gcovr --version env | sort - ls ~/.conan + ls ${CONAN_HOME} - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: dependencies uses: ./.github/actions/dependencies - env: - CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod with: configuration: ${{ matrix.configuration }} - name: build @@ -283,7 +289,7 @@ jobs: run: | mv "${build_dir}/coverage.xml" ./ - name: archive coverage report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 with: name: coverage.xml path: coverage.xml @@ -305,22 +311,23 @@ jobs: conan: needs: dependencies runs-on: [self-hosted, heavy] - container: ghcr.io/xrplf/rippled-build-ubuntu:aaf5e3e + container: + image: ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12 env: build_dir: .build + platform: linux + compiler: gcc + compiler_version: 12 configuration: Release steps: - - name: upgrade conan - run: | - pip install --upgrade "conan<2" - name: download cache - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 with: - name: linux-gcc-${{ env.configuration }} + name: ${{ env.platform }}-${{ env.compiler }}-${{ env.configuration }} - name: extract cache run: | - mkdir -p ~/.conan - tar -xzf conan.tar -C ~/.conan + mkdir -p ${CONAN_HOME} + tar -xzf conan.tar.gz -C ${CONAN_HOME} - name: check environment run: | env | sort @@ -328,95 +335,66 @@ jobs: conan --version cmake --version - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: dependencies uses: ./.github/actions/dependencies - env: - CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod with: configuration: ${{ env.configuration }} - name: export run: | - version=$(conan inspect --raw version .) - reference="xrpl/${version}@local/test" - conan remove -f ${reference} || true - conan export . local/test - echo "reference=${reference}" >> "${GITHUB_ENV}" + conan export . --version head - name: build run: | cd tests/conan - mkdir ${build_dir} - cd ${build_dir} - conan install .. --output-folder . \ - --require-override ${reference} --build missing + mkdir ${build_dir} && cd ${build_dir} + conan install .. \ + --settings:all build_type=${configuration} \ + --output-folder . \ + --build missing cmake .. \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH=./build/${configuration}/generators/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE=${configuration} cmake --build . ./example | grep '^[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+' - # NOTE we are not using dependencies built above because it lags with - # compiler versions. Instrumentation requires clang version 16 or - # later - instrumentation-build: - if: ${{ github.event_name == 'push' || github.event.pull_request.draft != true || contains(github.event.pull_request.labels.*.name, 'DraftRunCI') }} - env: - CLANG_RELEASE: 16 - strategy: - fail-fast: false + needs: dependencies runs-on: [self-hosted, heavy] - container: debian:bookworm + container: ghcr.io/xrplf/ci/debian-bookworm:clang-16 + env: + build_dir: .build steps: - - name: install prerequisites - env: - DEBIAN_FRONTEND: noninteractive + - name: download cache + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: linux-clang-Debug + + - name: extract cache run: | - apt-get update - apt-get install --yes --no-install-recommends \ - clang-${CLANG_RELEASE} clang++-${CLANG_RELEASE} \ - python3-pip python-is-python3 make cmake git wget - apt-get clean - update-alternatives --install \ - /usr/bin/clang clang /usr/bin/clang-${CLANG_RELEASE} 100 \ - --slave /usr/bin/clang++ clang++ /usr/bin/clang++-${CLANG_RELEASE} - update-alternatives --auto clang - pip install --no-cache --break-system-packages "conan<2" + mkdir -p ${CONAN_HOME} + tar -xzf conan.tar.gz -C ${CONAN_HOME} + + - name: check environment + run: | + echo ${PATH} | tr ':' '\n' + conan --version + cmake --version + env | sort + ls ${CONAN_HOME} - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + - name: dependencies + uses: ./.github/actions/dependencies + with: + configuration: Debug - name: prepare environment run: | - mkdir ${GITHUB_WORKSPACE}/.build - echo "SOURCE_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV - echo "BUILD_DIR=$GITHUB_WORKSPACE/.build" >> $GITHUB_ENV - echo "CC=/usr/bin/clang" >> $GITHUB_ENV - echo "CXX=/usr/bin/clang++" >> $GITHUB_ENV - - - name: configure Conan - run: | - conan profile new --detect default - conan profile update settings.compiler=clang default - conan profile update settings.compiler.version=${CLANG_RELEASE} default - conan profile update settings.compiler.libcxx=libstdc++11 default - conan profile update settings.compiler.cppstd=20 default - conan profile update options.rocksdb=False default - conan profile update \ - 'conf.tools.build:compiler_executables={"c": "/usr/bin/clang", "cpp": "/usr/bin/clang++"}' default - conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_DISABLE_CONCEPTS"' default - conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default - conan export external/snappy snappy/1.1.10@ - conan export external/soci soci/4.0.3@ - - - name: build dependencies - run: | - cd ${BUILD_DIR} - conan install ${SOURCE_DIR} \ - --output-folder ${BUILD_DIR} \ - --install-folder ${BUILD_DIR} \ - --build missing \ - --settings build_type=Debug + mkdir -p ${build_dir} + echo "SOURCE_DIR=$(pwd)" >> $GITHUB_ENV + echo "BUILD_DIR=$(pwd)/${build_dir}" >> $GITHUB_ENV - name: build with instrumentation run: | @@ -441,3 +419,4 @@ jobs: run: | cd ${BUILD_DIR} ./rippled -u --unittest-jobs $(( $(nproc)/4 )) + ctest -j $(nproc) --output-on-failure diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1d90c2ef58..9e2322b119 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -18,6 +18,19 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + CONAN_REMOTE_URL: ${{ vars.CONAN_REMOTE_URL }} + CONAN_REMOTE_USERNAME: ${{ secrets.CONAN_REMOTE_USERNAME }} + CONAN_REMOTE_PASSWORD: ${{ secrets.CONAN_REMOTE_PASSWORD }} + # This part of the Conan configuration is specific to this workflow only; we + # do not want to pollute the 'conan/profiles' directory with settings that + # might not work for other workflows. + CONAN_GLOBAL_CONF: | + core.download:parallel={{os.cpu_count()}} + core.upload:parallel={{os.cpu_count()}} + tools.build:jobs=24 + tools.build:verbosity=verbose + tools.compilation:verbosity=verbose jobs: @@ -42,11 +55,11 @@ jobs: build_dir: .build steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: choose Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 with: - python-version: 3.9 + python-version: 3.13 - name: learn Python cache directory id: pip-cache shell: bash @@ -54,12 +67,12 @@ jobs: python -m pip install --upgrade pip echo "dir=$(pip cache dir)" | tee ${GITHUB_OUTPUT} - name: restore Python cache directory - uses: actions/cache@v4 + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 with: path: ${{ steps.pip-cache.outputs.dir }} key: ${{ runner.os }}-${{ hashFiles('.github/workflows/windows.yml') }} - name: install Conan - run: pip install wheel 'conan<2' + run: pip install wheel conan - name: check environment run: | dir env: @@ -70,17 +83,11 @@ jobs: - name: configure Conan shell: bash run: | - conan profile new default --detect - conan profile update settings.compiler.cppstd=20 default - conan profile update \ - settings.compiler.runtime=MT${{ matrix.configuration.runtime }} \ - default + echo "${CONAN_GLOBAL_CONF}" > $(conan config home)/global.conf + conan config install conan/profiles/ -tf $(conan config home)/profiles/ + conan profile show - name: build dependencies uses: ./.github/actions/dependencies - env: - CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/conan-non-prod - CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }} - CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }} with: configuration: ${{ matrix.configuration.type }} - name: build @@ -95,5 +102,6 @@ jobs: shell: bash if: ${{ matrix.configuration.tests }} run: | - ${build_dir}/${{ matrix.configuration.type }}/rippled --unittest \ - --unittest-jobs $(nproc) + cd ${build_dir}/${{ matrix.configuration.type }} + ./rippled --unittest --unittest-jobs $(nproc) + ctest -j $(nproc) --output-on-failure diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f69d41379..abfbd887c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ # .pre-commit-config.yaml repos: - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v18.1.3 + rev: v18.1.8 hooks: - id: clang-format diff --git a/BUILD.md b/BUILD.md index 1ba767cd88..e1567e297c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -3,29 +3,29 @@ | These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). | > These instructions also assume a basic familiarity with Conan and CMake. -> If you are unfamiliar with Conan, -> you can read our [crash course](./docs/build/conan.md) -> or the official [Getting Started][3] walkthrough. +> If you are unfamiliar with Conan, you can read our +> [crash course](./docs/build/conan.md) or the official [Getting Started][3] +> walkthrough. ## Branches For a stable release, choose the `master` branch or one of the [tagged releases](https://github.com/ripple/rippled/releases). -``` +```bash git checkout master ``` For the latest release candidate, choose the `release` branch. -``` +```bash git checkout release ``` For the latest set of untested features, or to contribute, choose the `develop` branch. -``` +```bash git checkout develop ``` @@ -33,176 +33,295 @@ git checkout develop See [System Requirements](https://xrpl.org/system-requirements.html). -Building rippled generally requires git, Python, Conan, CMake, and a C++ compiler. Some guidance on setting up such a [C++ development environment can be found here](./docs/build/environment.md). +Building rippled generally requires git, Python, Conan, CMake, and a C++ +compiler. Some guidance on setting up such a [C++ development environment can be +found here](./docs/build/environment.md). -- [Python 3.7](https://www.python.org/downloads/) -- [Conan 1.60](https://conan.io/downloads.html)[^1] -- [CMake 3.16](https://cmake.org/download/) +- [Python 3.11](https://www.python.org/downloads/), or higher +- [Conan 2.17](https://conan.io/downloads.html)[^1], or higher +- [CMake 3.22](https://cmake.org/download/)[^2], or higher -[^1]: It is possible to build with Conan 2.x, -but the instructions are significantly different, -which is why we are not recommending it yet. -Notably, the `conan profile update` command is removed in 2.x. -Profiles must be edited by hand. +[^1]: It is possible to build with Conan 1.60+, but the instructions are +significantly different, which is why we are not recommending it. + +[^2]: CMake 4 is not yet supported by all dependencies required by this project. +If you are affected by this issue, follow [conan workaround for cmake +4](#workaround-for-cmake-4) `rippled` is written in the C++20 dialect and includes the `` header. The [minimum compiler versions][2] required are: | Compiler | Version | -|-------------|---------| -| GCC | 11 | -| Clang | 13 | -| Apple Clang | 13.1.6 | -| MSVC | 19.23 | +|-------------|-----| +| GCC | 12 | +| Clang | 16 | +| Apple Clang | 16 | +| MSVC | 19.44[^3] | ### Linux -The Ubuntu operating system has received the highest level of -quality assurance, testing, and support. +The Ubuntu Linux distribution has received the highest level of quality +assurance, testing, and support. We also support Red Hat and use Debian +internally. -Here are [sample instructions for setting up a C++ development environment on Linux](./docs/build/environment.md#linux). +Here are [sample instructions for setting up a C++ development environment on +Linux](./docs/build/environment.md#linux). ### Mac Many rippled engineers use macOS for development. -Here are [sample instructions for setting up a C++ development environment on macOS](./docs/build/environment.md#macos). +Here are [sample instructions for setting up a C++ development environment on +macOS](./docs/build/environment.md#macos). ### Windows -Windows is not recommended for production use at this time. +Windows is used by some engineers for development only. -- Additionally, 32-bit Windows development is not supported. - -[Boost]: https://www.boost.org/ +[^3]: Windows is not recommended for production use. ## Steps ### Set Up Conan -After you have a [C++ development environment](./docs/build/environment.md) ready with Git, Python, Conan, CMake, and a C++ compiler, you may need to set up your Conan profile. +After you have a [C++ development environment](./docs/build/environment.md) ready with Git, Python, +Conan, CMake, and a C++ compiler, you may need to set up your Conan profile. -These instructions assume a basic familiarity with Conan and CMake. +These instructions assume a basic familiarity with Conan and CMake. If you are +unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official +[Getting Started][3] walkthrough. -If you are unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official [Getting Started][3] walkthrough. +#### Default profile +We recommend that you import the provided `conan/profiles/default` profile: -You'll need at least one Conan profile: - - ``` - conan profile new default --detect - ``` - -Update the compiler settings: - - ``` - conan profile update settings.compiler.cppstd=20 default - ``` - -Configure Conan (1.x only) to use recipe revisions: - - ``` - conan config set general.revisions_enabled=1 - ``` - -**Linux** developers will commonly have a default Conan [profile][] that compiles -with GCC and links with libstdc++. -If you are linking with libstdc++ (see profile setting `compiler.libcxx`), -then you will need to choose the `libstdc++11` ABI: - - ``` - conan profile update settings.compiler.libcxx=libstdc++11 default - ``` - - -Ensure inter-operability between `boost::string_view` and `std::string_view` types: - -``` -conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_BEAST_USE_STD_STRING_VIEW"]' default -conan profile update 'env.CXXFLAGS="-DBOOST_BEAST_USE_STD_STRING_VIEW"' default +```bash +conan config install conan/profiles/ -tf $(conan config home)/profiles/ ``` -If you have other flags in the `conf.tools.build` or `env.CXXFLAGS` sections, make sure to retain the existing flags and append the new ones. You can check them with: -``` -conan profile show default +You can check your Conan profile by running: + +```bash +conan profile show ``` +#### Custom profile -**Windows** developers may need to use the x64 native build tools. -An easy way to do that is to run the shortcut "x64 Native Tools Command -Prompt" for the version of Visual Studio that you have installed. +If the default profile does not work for you and you do not yet have a Conan +profile, you can create one by running: - Windows developers must also build `rippled` and its dependencies for the x64 - architecture: - - ``` - conan profile update settings.arch=x86_64 default - ``` - -### Multiple compilers - -When `/usr/bin/g++` exists on a platform, it is the default cpp compiler. This -default works for some users. - -However, if this compiler cannot build rippled or its dependencies, then you can -install another compiler and set Conan and CMake to use it. -Update the `conf.tools.build:compiler_executables` setting in order to set the correct variables (`CMAKE__COMPILER`) in the -generated CMake toolchain file. -For example, on Ubuntu 20, you may have gcc at `/usr/bin/gcc` and g++ at `/usr/bin/g++`; if that is the case, you can select those compilers with: -``` -conan profile update 'conf.tools.build:compiler_executables={"c": "/usr/bin/gcc", "cpp": "/usr/bin/g++"}' default +```bash +conan profile detect ``` -Replace `/usr/bin/gcc` and `/usr/bin/g++` with paths to the desired compilers. +You may need to make changes to the profile to suit your environment. You can +refer to the provided `conan/profiles/default` profile for inspiration, and you +may also need to apply the required [tweaks](#conan-profile-tweaks) to this +default profile. -It should choose the compiler for dependencies as well, -but not all of them have a Conan recipe that respects this setting (yet). -For the rest, you can set these environment variables. -Replace `` with paths to the desired compilers: +### Patched recipes -- `conan profile update env.CC= default` -- `conan profile update env.CXX= default` +The recipes in Conan Center occasionally need to be patched for compatibility +with the latest version of `rippled`. We maintain a fork of the Conan Center +[here](https://github.com/XRPLF/conan-center-index/) containing the patches. -Export our [Conan recipe for Snappy](./external/snappy). -It does not explicitly link the C++ standard library, -which allows you to statically link it with GCC, if you want. +To ensure our patched recipes are used, you must add our Conan remote at a +higher index than the default Conan Center remote, so it is consulted first. You +can do this by running: - ``` - # Conan 1.x - conan export external/snappy snappy/1.1.10@ - # Conan 2.x - conan export --version 1.1.10 external/snappy +```bash +conan remote add --index 0 xrplf "https://conan.ripplex.io" +``` + +Alternatively, you can pull the patched recipes into the repository and use them +locally: + +```bash +cd external +git init +git remote add origin git@github.com:XRPLF/conan-center-index.git +git sparse-checkout init +git sparse-checkout set recipes/snappy +git sparse-checkout add recipes/soci +git fetch origin master +git checkout master +conan export --version 1.1.10 external/recipes/snappy +conan export --version 4.0.3 external/recipes/soci +``` + +In the case we switch to a newer version of a dependency that still requires a +patch, it will be necessary for you to pull in the changes and re-export the +updated dependencies with the newer version. However, if we switch to a newer +version that no longer requires a patch, no action is required on your part, as +the new recipe will be automatically pulled from the official Conan Center. + +### Conan profile tweaks + +#### Missing compiler version + +If you see an error similar to the following after running `conan profile show`: + +```bash +ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value. +Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1', +'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15', +'15.0', '16', '16.0'] +Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting" +``` + +you need to amend the list of compiler versions in +`$(conan config home)/settings.yml`, by appending the required version number(s) +to the `version` array specific for your compiler. For example: + +```yaml + apple-clang: + version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0", + "9.1", "10.0", "11.0", "12.0", "13", "13.0", "13.1", "14", + "14.0", "15", "15.0", "16", "16.0", "17", "17.0"] +``` + +#### Multiple compilers + +If you have multiple compilers installed, make sure to select the one to use in +your default Conan configuration **before** running `conan profile detect`, by +setting the `CC` and `CXX` environment variables. + +For example, if you are running MacOS and have [homebrew +LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a +compiler in the new Conan profile: + + ```bash + export CC=$(brew --prefix llvm@18)/bin/clang + export CXX=$(brew --prefix llvm@18)/bin/clang++ + conan profile detect ``` -Export our [Conan recipe for RocksDB](./external/rocksdb). -It does not override paths to dependencies when building with Visual Studio. +You should also explicitly set the path to the compiler in the profile file, +which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the +selected Conan profile. For example: - ``` - # Conan 1.x - conan export external/rocksdb rocksdb/9.7.3@ - # Conan 2.x - conan export --version 9.7.3 external/rocksdb - ``` +```text +[conf] +tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'} +``` -Export our [Conan recipe for SOCI](./external/soci). -It patches their CMake to correctly import its dependencies. +#### Multiple profiles - ``` - # Conan 1.x - conan export external/soci soci/4.0.3@ - # Conan 2.x - conan export --version 4.0.3 external/soci - ``` +You can manage multiple Conan profiles in the directory +`$(conan config home)/profiles`, for example renaming `default` to a different +name and then creating a new `default` profile for a different compiler. -Export our [Conan recipe for NuDB](./external/nudb). -It fixes some source files to add missing `#include`s. +#### Select language +The default profile created by Conan will typically select different C++ dialect +than C++20 used by this project. You should set `20` in the profile line +starting with `compiler.cppstd=`. For example: - ``` - # Conan 1.x - conan export external/nudb nudb/2.0.8@ - # Conan 2.x - conan export --version 2.0.8 external/nudb - ``` +```bash +sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=20|' $(conan config home)/profiles/default +``` + +#### Select standard library in Linux + +**Linux** developers will commonly have a default Conan [profile][] that +compiles with GCC and links with libstdc++. If you are linking with libstdc++ +(see profile setting `compiler.libcxx`), then you will need to choose the +`libstdc++11` ABI: + +```bash +sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default +``` + +#### Select architecture and runtime in Windows + +**Windows** developers may need to use the x64 native build tools. An easy way +to do that is to run the shortcut "x64 Native Tools Command Prompt" for the +version of Visual Studio that you have installed. + +Windows developers must also build `rippled` and its dependencies for the x64 +architecture: + +```bash +sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default +``` + +**Windows** developers also must select static runtime: + +```bash +sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default +``` + +#### Workaround for CMake 4 + +If your system CMake is version 4 rather than 3, you may have to configure Conan +profile to use CMake version 3 for dependencies, by adding the following two +lines to your profile: + +```text +[tool_requires] +!cmake/*: cmake/[>=3 <4] +``` + +This will force Conan to download and use a locally cached CMake 3 version, and +is needed because some of the dependencies used by this project do not support +CMake 4. + +#### Clang workaround for grpc + +If your compiler is clang, version 19 or later, or apple-clang, version 17 or +later, you may encounter a compilation error while building the `grpc` +dependency: + +```text +In file included from .../lib/promise/try_seq.h:26: +.../lib/promise/detail/basic_seq.h:499:38: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw] + 499 | Traits::template CallSeqFactory(f_, *cur_, std::move(arg))); + | ^ +``` + +The workaround for this error is to add two lines to profile: + +```text +[conf] +tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw'] +``` + +#### Workaround for gcc 12 + +If your compiler is gcc, version 12, and you have enabled `werr` option, you may +encounter a compilation error such as: + +```text +/usr/include/c++/12/bits/char_traits.h:435:56: error: 'void* __builtin_memcpy(void*, const void*, long unsigned int)' accessing 9223372036854775810 or more bytes at offsets [2, 9223372036854775807] and 1 may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict] + 435 | return static_cast(__builtin_memcpy(__s1, __s2, __n)); + | ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~ +cc1plus: all warnings being treated as errors +``` + +The workaround for this error is to add two lines to your profile: + +```text +[conf] +tools.build:cxxflags=['-Wno-restrict'] +``` + +#### Workaround for clang 16 + +If your compiler is clang, version 16, you may encounter compilation error such +as: + +```text +In file included from .../boost/beast/websocket/stream.hpp:2857: +.../boost/beast/websocket/impl/read.hpp:695:17: error: call to 'async_teardown' is ambiguous + async_teardown(impl.role, impl.stream(), + ^~~~~~~~~~~~~~ +``` + +The workaround for this error is to add two lines to your profile: + +```text +[conf] +tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS'] +``` ### Build and Test @@ -270,7 +389,6 @@ It fixes some source files to add missing `#include`s. cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release -Dxrpld=ON -Dtests=ON .. ``` - Multi-config generators: ``` @@ -282,13 +400,13 @@ It fixes some source files to add missing `#include`s. 5. Build `rippled`. For a single-configuration generator, it will build whatever configuration - you passed for `CMAKE_BUILD_TYPE`. For a multi-configuration generator, - you must pass the option `--config` to select the build configuration. + you passed for `CMAKE_BUILD_TYPE`. For a multi-configuration generator, you + must pass the option `--config` to select the build configuration. Single-config generators: ``` - cmake --build . -j $(nproc) + cmake --build . ``` Multi-config generators: @@ -303,18 +421,22 @@ It fixes some source files to add missing `#include`s. Single-config generators: ``` - ./rippled --unittest + ./rippled --unittest --unittest-jobs N ``` Multi-config generators: ``` - ./Release/rippled --unittest - ./Debug/rippled --unittest + ./Release/rippled --unittest --unittest-jobs N + ./Debug/rippled --unittest --unittest-jobs N ``` - The location of `rippled` in your build directory depends on your CMake - generator. Pass `--help` to see the rest of the command line options. + Replace the `--unittest-jobs` parameter N with the desired unit tests + concurrency. Recommended setting is half of the number of available CPU + cores. + + The location of `rippled` binary in your build directory depends on your + CMake generator. Pass `--help` to see the rest of the command line options. ## Coverage report @@ -372,7 +494,7 @@ cmake --build . --target coverage After the `coverage` target is completed, the generated coverage report will be stored inside the build directory, as either of: -- file named `coverage.`_extension_ , with a suitable extension for the report format, or +- file named `coverage.`_extension_, with a suitable extension for the report format, or - directory named `coverage`, with the `index.html` and other files inside, for the `html-details` or `html-nested` report formats. @@ -380,12 +502,14 @@ stored inside the build directory, as either of: | Option | Default Value | Description | | --- | ---| ---| -| `assert` | OFF | Enable assertions. +| `assert` | OFF | Enable assertions. | | `coverage` | OFF | Prepare the coverage report. | | `san` | N/A | Enable a sanitizer with Clang. Choices are `thread` and `address`. | | `tests` | OFF | Build tests. | -| `unity` | ON | Configure a unity build. | +| `unity` | OFF | Configure a unity build. | | `xrpld` | OFF | Build the xrpld (`rippled`) application, and not just the libxrpl library. | +| `werr` | OFF | Treat compilation warnings as errors | +| `wextra` | OFF | Enable additional compilation warnings | [Unity builds][5] may be faster for the first build (at the cost of much more memory) since they concatenate sources into fewer @@ -395,22 +519,33 @@ and can be helpful for detecting `#include` omissions. ## Troubleshooting - ### Conan After any updates or changes to dependencies, you may need to do the following: 1. Remove your build directory. -2. Remove the Conan cache: +2. Remove individual libraries from the Conan cache, e.g. + + ```bash + conan remove 'grpc/*' ``` - rm -rf ~/.conan/data + + **or** + + Remove all libraries from Conan cache: + + ```bash + conan remove '*' ``` + +3. Re-run [conan export](#patched-recipes) if needed. 4. Re-run [conan install](#build-and-test). +### `protobuf/port_def.inc` file not found -### 'protobuf/port_def.inc' file not found - -If `cmake --build .` results in an error due to a missing a protobuf file, then you might have generated CMake files for a different `build_type` than the `CMAKE_BUILD_TYPE` you passed to conan. +If `cmake --build .` results in an error due to a missing a protobuf file, then +you might have generated CMake files for a different `build_type` than the +`CMAKE_BUILD_TYPE` you passed to Conan. ``` /rippled/.build/pb-xrpl.libpb/xrpl/proto/ripple.pb.h:10:10: fatal error: 'google/protobuf/port_def.inc' file not found @@ -424,54 +559,6 @@ For example, if you want to build Debug: 1. For conan install, pass `--settings build_type=Debug` 2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug` - -### no std::result_of - -If your compiler version is recent enough to have removed `std::result_of` as -part of C++20, e.g. Apple Clang 15.0, then you might need to add a preprocessor -definition to your build. - -``` -conan profile update 'options.boost:extra_b2_flags="define=BOOST_ASIO_HAS_STD_INVOKE_RESULT"' default -conan profile update 'env.CFLAGS="-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"' default -conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"' default -conan profile update 'conf.tools.build:cflags+=["-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"]' default -conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"]' default -``` - - -### call to 'async_teardown' is ambiguous - -If you are compiling with an early version of Clang 16, then you might hit -a [regression][6] when compiling C++20 that manifests as an [error in a Boost -header][7]. You can workaround it by adding this preprocessor definition: - -``` -conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_DISABLE_CONCEPTS"' default -conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default -``` - - -### recompile with -fPIC - -If you get a linker error suggesting that you recompile Boost with -position-independent code, such as: - -``` -/usr/bin/ld.gold: error: /home/username/.conan/data/boost/1.77.0/_/_/package/.../lib/libboost_container.a(alloc_lib.o): - requires unsupported dynamic reloc 11; recompile with -fPIC -``` - -Conan most likely downloaded a bad binary distribution of the dependency. -This seems to be a [bug][1] in Conan just for Boost 1.77.0 compiled with GCC -for Linux. The solution is to build the dependency locally by passing -`--build boost` when calling `conan install`. - -``` -conan install --build boost ... -``` - - ## Add a Dependency If you want to experiment with a new package, follow these steps: diff --git a/Builds/levelization/results/ordering.txt b/Builds/levelization/results/ordering.txt index eca7fc6dc2..ce22d8edb0 100644 --- a/Builds/levelization/results/ordering.txt +++ b/Builds/levelization/results/ordering.txt @@ -132,6 +132,7 @@ test.shamap > xrpl.protocol test.toplevel > test.csf test.toplevel > xrpl.json test.unit_test > xrpl.basics +tests.libxrpl > xrpl.basics xrpl.json > xrpl.basics xrpl.protocol > xrpl.basics xrpl.protocol > xrpl.json diff --git a/CMakeLists.txt b/CMakeLists.txt index a9f063db57..c71fb68599 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,11 @@ set_target_properties(OpenSSL::SSL PROPERTIES INTERFACE_COMPILE_DEFINITIONS OPENSSL_NO_SSL2 ) set(SECP256K1_INSTALL TRUE) +set(SECP256K1_BUILD_BENCHMARK FALSE) +set(SECP256K1_BUILD_TESTS FALSE) +set(SECP256K1_BUILD_EXHAUSTIVE_TESTS FALSE) +set(SECP256K1_BUILD_CTIME_TESTS FALSE) +set(SECP256K1_BUILD_EXAMPLES FALSE) add_subdirectory(external/secp256k1) add_library(secp256k1::secp256k1 ALIAS secp256k1) add_subdirectory(external/ed25519-donna) @@ -144,3 +149,8 @@ set(PROJECT_EXPORT_SET RippleExports) include(RippledCore) include(RippledInstall) include(RippledValidatorKeys) + +if(tests) + include(CTest) + add_subdirectory(src/tests/libxrpl) +endif() diff --git a/cmake/RippledCompiler.cmake b/cmake/RippledCompiler.cmake index 7485605d95..30058fd503 100644 --- a/cmake/RippledCompiler.cmake +++ b/cmake/RippledCompiler.cmake @@ -90,28 +90,15 @@ if (MSVC) -errorreport:none -machine:X64) else () - # HACK : because these need to come first, before any warning demotion - string (APPEND CMAKE_CXX_FLAGS " -Wall -Wdeprecated") - if (wextra) - string (APPEND CMAKE_CXX_FLAGS " -Wextra -Wno-unused-parameter") - endif () - # not MSVC target_compile_options (common INTERFACE + -Wall + -Wdeprecated + $<$:-Wextra -Wno-unused-parameter> $<$:-Werror> - $<$: - -frtti - -Wnon-virtual-dtor - > - -Wno-sign-compare - -Wno-char-subscripts - -Wno-format - -Wno-unused-local-typedefs -fstack-protector - $<$: - -Wno-unused-but-set-variable - -Wno-deprecated - > + -Wno-sign-compare + -Wno-unused-but-set-variable $<$>:-fno-strict-aliasing> # tweak gcc optimization for debug $<$,$>:-O0> diff --git a/cmake/RippledSettings.cmake b/cmake/RippledSettings.cmake index b2d7b0d9a5..9dc8609f58 100644 --- a/cmake/RippledSettings.cmake +++ b/cmake/RippledSettings.cmake @@ -18,7 +18,7 @@ if(tests) endif() endif() -option(unity "Creates a build using UNITY support in cmake. This is the default" ON) +option(unity "Creates a build using UNITY support in cmake." OFF) if(unity) if(NOT is_ci) set(CMAKE_UNITY_BUILD_BATCH_SIZE 15 CACHE STRING "") diff --git a/cmake/deps/Boost.cmake b/cmake/deps/Boost.cmake index 041c2380e1..031202f4d2 100644 --- a/cmake/deps/Boost.cmake +++ b/cmake/deps/Boost.cmake @@ -2,7 +2,6 @@ find_package(Boost 1.82 REQUIRED COMPONENTS chrono container - context coroutine date_time filesystem @@ -24,7 +23,7 @@ endif() target_link_libraries(ripple_boost INTERFACE - Boost::boost + Boost::headers Boost::chrono Boost::container Boost::coroutine diff --git a/cmake/xrpl_add_test.cmake b/cmake/xrpl_add_test.cmake new file mode 100644 index 0000000000..d61f4ece3d --- /dev/null +++ b/cmake/xrpl_add_test.cmake @@ -0,0 +1,41 @@ +include(isolate_headers) + +function(xrpl_add_test name) + set(target ${PROJECT_NAME}.test.${name}) + + file(GLOB_RECURSE sources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp" + ) + add_executable(${target} EXCLUDE_FROM_ALL ${ARGN} ${sources}) + + isolate_headers( + ${target} + "${CMAKE_SOURCE_DIR}" + "${CMAKE_SOURCE_DIR}/tests/${name}" + PRIVATE + ) + + # Make sure the test isn't optimized away in unity builds + set_target_properties(${target} PROPERTIES + UNITY_BUILD_MODE GROUP + UNITY_BUILD_BATCH_SIZE 0) # Adjust as needed + + add_test(NAME ${target} COMMAND ${target}) + set_tests_properties( + ${target} PROPERTIES + FIXTURES_REQUIRED ${target}_fixture + ) + + add_test( + NAME ${target}.build + COMMAND + ${CMAKE_COMMAND} + --build ${CMAKE_BINARY_DIR} + --config $ + --target ${target} + ) + set_tests_properties(${target}.build PROPERTIES + FIXTURES_SETUP ${target}_fixture + ) +endfunction() diff --git a/conan/profiles/default b/conan/profiles/default new file mode 100644 index 0000000000..3a7bcda1c6 --- /dev/null +++ b/conan/profiles/default @@ -0,0 +1,34 @@ +{% set os = detect_api.detect_os() %} +{% set arch = detect_api.detect_arch() %} +{% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %} +{% set compiler_version = version %} +{% if os == "Linux" %} +{% set compiler_version = detect_api.default_compiler_version(compiler, version) %} +{% endif %} + +[settings] +os={{ os }} +arch={{ arch }} +build_type=Debug +compiler={{compiler}} +compiler.version={{ compiler_version }} +compiler.cppstd=20 +{% if os == "Windows" %} +compiler.runtime=static +{% else %} +compiler.libcxx={{detect_api.detect_libcxx(compiler, version, compiler_exe)}} +{% endif %} + +[conf] +{% if compiler == "clang" and compiler_version >= 19 %} +tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw'] +{% endif %} +{% if compiler == "apple-clang" and compiler_version >= 17 %} +tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw'] +{% endif %} +{% if compiler == "gcc" and compiler_version < 13 %} +tools.build:cxxflags=['-Wno-restrict'] +{% endif %} + +[tool_requires] +!cmake/*: cmake/[>=3 <4] diff --git a/conanfile.py b/conanfile.py index da8a09611d..399c9d6e1f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -25,15 +25,19 @@ class Xrpl(ConanFile): requires = [ 'grpc/1.50.1', - 'libarchive/3.7.6', - 'nudb/2.0.8', - 'openssl/1.1.1v', + 'libarchive/3.8.1', + 'nudb/2.0.9', + 'openssl/1.1.1w', 'soci/4.0.3', 'zlib/1.3.1', ] + test_requires = [ + 'doctest/2.4.11', + ] + tool_requires = [ - 'protobuf/3.21.9', + 'protobuf/3.21.12', ] default_options = { @@ -85,12 +89,13 @@ class Xrpl(ConanFile): } def set_version(self): - path = f'{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp' - regex = r'versionString\s?=\s?\"(.*)\"' - with open(path, 'r') as file: - matches = (re.search(regex, line) for line in file) - match = next(m for m in matches if m) - self.version = match.group(1) + if self.version is None: + path = f'{self.recipe_folder}/src/libxrpl/protocol/BuildInfo.cpp' + regex = r'versionString\s?=\s?\"(.*)\"' + with open(path, encoding='utf-8') as file: + matches = (re.search(regex, line) for line in file) + match = next(m for m in matches if m) + self.version = match.group(1) def configure(self): if self.settings.compiler == 'apple-clang': @@ -99,16 +104,16 @@ class Xrpl(ConanFile): def requirements(self): # Conan 2 requires transitive headers to be specified transitive_headers_opt = {'transitive_headers': True} if conan_version.split('.')[0] == '2' else {} - self.requires('boost/1.83.0', force=True, **transitive_headers_opt) - self.requires('date/3.0.3', **transitive_headers_opt) + self.requires('boost/1.86.0', force=True, **transitive_headers_opt) + self.requires('date/3.0.4', **transitive_headers_opt) self.requires('lz4/1.10.0', force=True) - self.requires('protobuf/3.21.9', force=True) - self.requires('sqlite3/3.47.0', force=True) + self.requires('protobuf/3.21.12', force=True) + self.requires('sqlite3/3.49.1', force=True) if self.options.jemalloc: self.requires('jemalloc/5.3.0') if self.options.rocksdb: - self.requires('rocksdb/9.7.3') - self.requires('xxhash/0.8.2', **transitive_headers_opt) + self.requires('rocksdb/10.0.1') + self.requires('xxhash/0.8.3', **transitive_headers_opt) exports_sources = ( 'CMakeLists.txt', @@ -163,7 +168,17 @@ class Xrpl(ConanFile): # `include/`, not `include/ripple/proto/`. libxrpl.includedirs = ['include', 'include/ripple/proto'] libxrpl.requires = [ - 'boost::boost', + 'boost::headers', + 'boost::chrono', + 'boost::container', + 'boost::coroutine', + 'boost::date_time', + 'boost::filesystem', + 'boost::json', + 'boost::program_options', + 'boost::regex', + 'boost::system', + 'boost::thread', 'date::date', 'grpc::grpc++', 'libarchive::libarchive', diff --git a/docs/build/environment.md b/docs/build/environment.md index 760be144d8..7301879d09 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -10,37 +10,35 @@ platforms: Linux, macOS, or Windows. Package ecosystems vary across Linux distributions, so there is no one set of instructions that will work for every Linux user. -These instructions are written for Ubuntu 22.04. -They are largely copied from the [script][1] used to configure our Docker -container for continuous integration. -That script handles many more responsibilities. -These instructions are just the bare minimum to build one configuration of -rippled. -You can check that codebase for other Linux distributions and versions. -If you cannot find yours there, -then we hope that these instructions can at least guide you in the right -direction. +The instructions below are written for Debian 12 (Bookworm). ``` -apt update -apt install --yes curl git libssl-dev pipx python3.10-dev python3-pip make g++-11 libprotobuf-dev protobuf-compiler +export GCC_RELEASE=12 +sudo apt update +sudo apt install --yes gcc-${GCC_RELEASE} g++-${GCC_RELEASE} python3-pip \ + python-is-python3 python3-venv python3-dev curl wget ca-certificates \ + git build-essential cmake ninja-build libc6-dev +sudo pip install --break-system-packages conan -curl --location --remote-name \ - "https://github.com/Kitware/CMake/releases/download/v3.25.1/cmake-3.25.1.tar.gz" -tar -xzf cmake-3.25.1.tar.gz -rm cmake-3.25.1.tar.gz -cd cmake-3.25.1 -./bootstrap --parallel=$(nproc) -make --jobs $(nproc) -make install -cd .. - -pipx install 'conan<2' -pipx ensurepath +sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-${GCC_RELEASE} 999 +sudo update-alternatives --install \ + /usr/bin/gcc gcc /usr/bin/gcc-${GCC_RELEASE} 100 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-${GCC_RELEASE} \ + --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-${GCC_RELEASE} \ + --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-${GCC_RELEASE} \ + --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-${GCC_RELEASE} \ + --slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_RELEASE} \ + --slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-${GCC_RELEASE} \ + --slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-${GCC_RELEASE} \ + --slave /usr/bin/lto-dump lto-dump /usr/bin/lto-dump-${GCC_RELEASE} +sudo update-alternatives --auto cc +sudo update-alternatives --auto gcc ``` -[1]: https://github.com/thejohnfreeman/rippled-docker/blob/master/ubuntu-22.04/install.sh - +If you use different Linux distribution, hope the instruction above can guide +you in the right direction. We try to maintain compatibility with all recent +compiler releases, so if you use a rolling distribution like e.g. Arch or CentOS +then there is a chance that everything will "just work". ## macOS @@ -53,6 +51,34 @@ minimum required (see [BUILD.md][]). clang --version ``` +### Install Xcode Specific Version (Optional) + +If you develop other applications using XCode you might be consistently updating to the newest version of Apple Clang. +This will likely cause issues building rippled. You may want to install a specific version of Xcode: + +1. **Download Xcode** + + - Visit [Apple Developer Downloads](https://developer.apple.com/download/more/) + - Sign in with your Apple Developer account + - Search for an Xcode version that includes **Apple Clang (Expected Version)** + - Download the `.xip` file + +2. **Install and Configure Xcode** + + ```bash + # Extract the .xip file and rename for version management + # Example: Xcode_16.2.app + + # Move to Applications directory + sudo mv Xcode_16.2.app /Applications/ + + # Set as default toolchain (persistent) + sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + + # Set as environment variable (temporary) + export DEVELOPER_DIR=/Applications/Xcode_16.2.app/Contents/Developer + ``` + The command line developer tools should include Git too: ``` @@ -72,10 +98,10 @@ and use it to install Conan: brew update brew install xz brew install pyenv -pyenv install 3.10-dev -pyenv global 3.10-dev +pyenv install 3.11 +pyenv global 3.11 eval "$(pyenv init -)" -pip install 'conan<2' +pip install 'conan' ``` Install CMake with Homebrew too: diff --git a/external/README.md b/external/README.md index c810539fd7..a3d04da264 100644 --- a/external/README.md +++ b/external/README.md @@ -1,14 +1,10 @@ # External Conan recipes -The subdirectories in this directory contain either copies or Conan recipes -of external libraries used by rippled. -The Conan recipes include patches we have not yet pushed upstream. +The subdirectories in this directory contain copies of external libraries used +by rippled. | Folder | Upstream | Description | |:----------------|:---------------------------------------------|:------------| | `antithesis-sdk`| [Project](https://github.com/antithesishq/antithesis-sdk-cpp/) | [Antithesis](https://antithesis.com/docs/using_antithesis/sdk/cpp/overview.html) SDK for C++ | | `ed25519-donna` | [Project](https://github.com/floodyberry/ed25519-donna) | [Ed25519](http://ed25519.cr.yp.to/) digital signatures | -| `rocksdb` | [Recipe](https://github.com/conan-io/conan-center-index/tree/master/recipes/rocksdb) | Fast key/value database. (Supports rotational disks better than NuDB.) | | `secp256k1` | [Project](https://github.com/bitcoin-core/secp256k1) | ECDSA digital signatures using the **secp256k1** curve | -| `snappy` | [Recipe](https://github.com/conan-io/conan-center-index/tree/master/recipes/snappy) | "Snappy" lossless compression algorithm. | -| `soci` | [Recipe](https://github.com/conan-io/conan-center-index/tree/master/recipes/soci) | Abstraction layer for database access. | diff --git a/external/antithesis-sdk/CMakeLists.txt b/external/antithesis-sdk/CMakeLists.txt index d2c1f536af..46c7b4bf7a 100644 --- a/external/antithesis-sdk/CMakeLists.txt +++ b/external/antithesis-sdk/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.25) +cmake_minimum_required(VERSION 3.18) # Note, version set explicitly by rippled project project(antithesis-sdk-cpp VERSION 0.4.4 LANGUAGES CXX) diff --git a/external/ed25519-donna/CMakeLists.txt b/external/ed25519-donna/CMakeLists.txt index 418dc38326..f060d530aa 100644 --- a/external/ed25519-donna/CMakeLists.txt +++ b/external/ed25519-donna/CMakeLists.txt @@ -17,6 +17,9 @@ add_library(ed25519 STATIC ) add_library(ed25519::ed25519 ALIAS ed25519) target_link_libraries(ed25519 PUBLIC OpenSSL::SSL) +if(NOT MSVC) + target_compile_options(ed25519 PRIVATE -Wno-implicit-fallthrough) +endif() include(GNUInstallDirs) diff --git a/external/nudb/conandata.yml b/external/nudb/conandata.yml deleted file mode 100644 index 721129f88e..0000000000 --- a/external/nudb/conandata.yml +++ /dev/null @@ -1,10 +0,0 @@ -sources: - "2.0.8": - url: "https://github.com/CPPAlliance/NuDB/archive/2.0.8.tar.gz" - sha256: "9b71903d8ba111cd893ab064b9a8b6ac4124ed8bd6b4f67250205bc43c7f13a8" -patches: - "2.0.8": - - patch_file: "patches/2.0.8-0001-add-include-stdexcept-for-msvc.patch" - patch_description: "Fix build for MSVC by including stdexcept" - patch_type: "portability" - patch_source: "https://github.com/cppalliance/NuDB/pull/100/files" diff --git a/external/nudb/conanfile.py b/external/nudb/conanfile.py deleted file mode 100644 index a046e2ba89..0000000000 --- a/external/nudb/conanfile.py +++ /dev/null @@ -1,72 +0,0 @@ -import os - -from conan import ConanFile -from conan.tools.build import check_min_cppstd -from conan.tools.files import apply_conandata_patches, copy, export_conandata_patches, get -from conan.tools.layout import basic_layout - -required_conan_version = ">=1.52.0" - - -class NudbConan(ConanFile): - name = "nudb" - description = "A fast key/value insert-only database for SSD drives in C++11" - license = "BSL-1.0" - url = "https://github.com/conan-io/conan-center-index" - homepage = "https://github.com/CPPAlliance/NuDB" - topics = ("header-only", "KVS", "insert-only") - - package_type = "header-library" - settings = "os", "arch", "compiler", "build_type" - no_copy_source = True - - @property - def _min_cppstd(self): - return 11 - - def export_sources(self): - export_conandata_patches(self) - - def layout(self): - basic_layout(self, src_folder="src") - - def requirements(self): - self.requires("boost/1.83.0") - - def package_id(self): - self.info.clear() - - def validate(self): - if self.settings.compiler.cppstd: - check_min_cppstd(self, self._min_cppstd) - - def source(self): - get(self, **self.conan_data["sources"][self.version], strip_root=True) - - def build(self): - apply_conandata_patches(self) - - def package(self): - copy(self, "LICENSE*", - dst=os.path.join(self.package_folder, "licenses"), - src=self.source_folder) - copy(self, "*", - dst=os.path.join(self.package_folder, "include"), - src=os.path.join(self.source_folder, "include")) - - def package_info(self): - self.cpp_info.bindirs = [] - self.cpp_info.libdirs = [] - - self.cpp_info.set_property("cmake_target_name", "NuDB") - self.cpp_info.set_property("cmake_target_aliases", ["NuDB::nudb"]) - self.cpp_info.set_property("cmake_find_mode", "both") - - self.cpp_info.components["core"].set_property("cmake_target_name", "nudb") - self.cpp_info.components["core"].names["cmake_find_package"] = "nudb" - self.cpp_info.components["core"].names["cmake_find_package_multi"] = "nudb" - self.cpp_info.components["core"].requires = ["boost::thread", "boost::system"] - - # TODO: to remove in conan v2 once cmake_find_package_* generators removed - self.cpp_info.names["cmake_find_package"] = "NuDB" - self.cpp_info.names["cmake_find_package_multi"] = "NuDB" diff --git a/external/nudb/patches/2.0.8-0001-add-include-stdexcept-for-msvc.patch b/external/nudb/patches/2.0.8-0001-add-include-stdexcept-for-msvc.patch deleted file mode 100644 index 2d5264f3ce..0000000000 --- a/external/nudb/patches/2.0.8-0001-add-include-stdexcept-for-msvc.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/include/nudb/detail/stream.hpp b/include/nudb/detail/stream.hpp -index 6c07bf1..e0ce8ed 100644 ---- a/include/nudb/detail/stream.hpp -+++ b/include/nudb/detail/stream.hpp -@@ -14,6 +14,7 @@ - #include - #include - #include -+#include - - namespace nudb { - namespace detail { -diff --git a/include/nudb/impl/context.ipp b/include/nudb/impl/context.ipp -index beb7058..ffde0b3 100644 ---- a/include/nudb/impl/context.ipp -+++ b/include/nudb/impl/context.ipp -@@ -9,6 +9,7 @@ - #define NUDB_IMPL_CONTEXT_IPP - - #include -+#include - - namespace nudb { - diff --git a/external/rocksdb/conandata.yml b/external/rocksdb/conandata.yml deleted file mode 100644 index 7d7a575d98..0000000000 --- a/external/rocksdb/conandata.yml +++ /dev/null @@ -1,12 +0,0 @@ -sources: - "9.7.3": - url: "https://github.com/facebook/rocksdb/archive/refs/tags/v9.7.3.tar.gz" - sha256: "acfabb989cbfb5b5c4d23214819b059638193ec33dad2d88373c46448d16d38b" -patches: - "9.7.3": - - patch_file: "patches/9.x.x-0001-exclude-thirdparty.patch" - patch_description: "Do not include thirdparty.inc" - patch_type: "portability" - - patch_file: "patches/9.7.3-0001-memory-leak.patch" - patch_description: "Fix a leak of obsolete blob files left open until DB::Close()" - patch_type: "portability" diff --git a/external/rocksdb/conanfile.py b/external/rocksdb/conanfile.py deleted file mode 100644 index 8b85ce1540..0000000000 --- a/external/rocksdb/conanfile.py +++ /dev/null @@ -1,235 +0,0 @@ -import os -import glob -import shutil - -from conan import ConanFile -from conan.errors import ConanInvalidConfiguration -from conan.tools.build import check_min_cppstd -from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout -from conan.tools.files import apply_conandata_patches, collect_libs, copy, export_conandata_patches, get, rm, rmdir -from conan.tools.microsoft import check_min_vs, is_msvc, is_msvc_static_runtime -from conan.tools.scm import Version - -required_conan_version = ">=1.53.0" - - -class RocksDBConan(ConanFile): - name = "rocksdb" - description = "A library that provides an embeddable, persistent key-value store for fast storage" - license = ("GPL-2.0-only", "Apache-2.0") - url = "https://github.com/conan-io/conan-center-index" - homepage = "https://github.com/facebook/rocksdb" - topics = ("database", "leveldb", "facebook", "key-value") - package_type = "library" - settings = "os", "arch", "compiler", "build_type" - options = { - "shared": [True, False], - "fPIC": [True, False], - "lite": [True, False], - "with_gflags": [True, False], - "with_snappy": [True, False], - "with_lz4": [True, False], - "with_zlib": [True, False], - "with_zstd": [True, False], - "with_tbb": [True, False], - "with_jemalloc": [True, False], - "enable_sse": [False, "sse42", "avx2"], - "use_rtti": [True, False], - } - default_options = { - "shared": False, - "fPIC": True, - "lite": False, - "with_snappy": False, - "with_lz4": False, - "with_zlib": False, - "with_zstd": False, - "with_gflags": False, - "with_tbb": False, - "with_jemalloc": False, - "enable_sse": False, - "use_rtti": False, - } - - @property - def _min_cppstd(self): - return "11" if Version(self.version) < "8.8.1" else "17" - - @property - def _compilers_minimum_version(self): - return {} if self._min_cppstd == "11" else { - "apple-clang": "10", - "clang": "7", - "gcc": "7", - "msvc": "191", - "Visual Studio": "15", - } - - def export_sources(self): - export_conandata_patches(self) - - def config_options(self): - if self.settings.os == "Windows": - del self.options.fPIC - if self.settings.arch != "x86_64": - del self.options.with_tbb - if self.settings.build_type == "Debug": - self.options.use_rtti = True # Rtti are used in asserts for debug mode... - - def configure(self): - if self.options.shared: - self.options.rm_safe("fPIC") - - def layout(self): - cmake_layout(self, src_folder="src") - - def requirements(self): - if self.options.with_gflags: - self.requires("gflags/2.2.2") - if self.options.with_snappy: - self.requires("snappy/1.1.10") - if self.options.with_lz4: - self.requires("lz4/1.10.0") - if self.options.with_zlib: - self.requires("zlib/[>=1.2.11 <2]") - if self.options.with_zstd: - self.requires("zstd/1.5.6") - if self.options.get_safe("with_tbb"): - self.requires("onetbb/2021.12.0") - if self.options.with_jemalloc: - self.requires("jemalloc/5.3.0") - - def validate(self): - if self.settings.compiler.get_safe("cppstd"): - check_min_cppstd(self, self._min_cppstd) - - minimum_version = self._compilers_minimum_version.get(str(self.settings.compiler), False) - if minimum_version and Version(self.settings.compiler.version) < minimum_version: - raise ConanInvalidConfiguration( - f"{self.ref} requires C++{self._min_cppstd}, which your compiler does not support." - ) - - if self.settings.arch not in ["x86_64", "ppc64le", "ppc64", "mips64", "armv8"]: - raise ConanInvalidConfiguration("Rocksdb requires 64 bits") - - check_min_vs(self, "191") - - if self.version == "6.20.3" and \ - self.settings.os == "Linux" and \ - self.settings.compiler == "gcc" and \ - Version(self.settings.compiler.version) < "5": - raise ConanInvalidConfiguration("Rocksdb 6.20.3 is not compilable with gcc <5.") # See https://github.com/facebook/rocksdb/issues/3522 - - def source(self): - get(self, **self.conan_data["sources"][self.version], strip_root=True) - - def generate(self): - tc = CMakeToolchain(self) - tc.variables["FAIL_ON_WARNINGS"] = False - tc.variables["WITH_TESTS"] = False - tc.variables["WITH_TOOLS"] = False - tc.variables["WITH_CORE_TOOLS"] = False - tc.variables["WITH_BENCHMARK_TOOLS"] = False - tc.variables["WITH_FOLLY_DISTRIBUTED_MUTEX"] = False - if is_msvc(self): - tc.variables["WITH_MD_LIBRARY"] = not is_msvc_static_runtime(self) - tc.variables["ROCKSDB_INSTALL_ON_WINDOWS"] = self.settings.os == "Windows" - tc.variables["ROCKSDB_LITE"] = self.options.lite - tc.variables["WITH_GFLAGS"] = self.options.with_gflags - tc.variables["WITH_SNAPPY"] = self.options.with_snappy - tc.variables["WITH_LZ4"] = self.options.with_lz4 - tc.variables["WITH_ZLIB"] = self.options.with_zlib - tc.variables["WITH_ZSTD"] = self.options.with_zstd - tc.variables["WITH_TBB"] = self.options.get_safe("with_tbb", False) - tc.variables["WITH_JEMALLOC"] = self.options.with_jemalloc - tc.variables["ROCKSDB_BUILD_SHARED"] = self.options.shared - tc.variables["ROCKSDB_LIBRARY_EXPORTS"] = self.settings.os == "Windows" and self.options.shared - tc.variables["ROCKSDB_DLL" ] = self.settings.os == "Windows" and self.options.shared - tc.variables["USE_RTTI"] = self.options.use_rtti - if not bool(self.options.enable_sse): - tc.variables["PORTABLE"] = True - tc.variables["FORCE_SSE42"] = False - elif self.options.enable_sse == "sse42": - tc.variables["PORTABLE"] = True - tc.variables["FORCE_SSE42"] = True - elif self.options.enable_sse == "avx2": - tc.variables["PORTABLE"] = False - tc.variables["FORCE_SSE42"] = False - # not available yet in CCI - tc.variables["WITH_NUMA"] = False - tc.generate() - - deps = CMakeDeps(self) - if self.options.with_jemalloc: - deps.set_property("jemalloc", "cmake_file_name", "JeMalloc") - deps.set_property("jemalloc", "cmake_target_name", "JeMalloc::JeMalloc") - if self.options.with_zstd: - deps.set_property("zstd", "cmake_target_name", "zstd::zstd") - deps.generate() - - def build(self): - apply_conandata_patches(self) - cmake = CMake(self) - cmake.configure() - cmake.build() - - def _remove_static_libraries(self): - rm(self, "rocksdb.lib", os.path.join(self.package_folder, "lib")) - for lib in glob.glob(os.path.join(self.package_folder, "lib", "*.a")): - if not lib.endswith(".dll.a"): - os.remove(lib) - - def _remove_cpp_headers(self): - for path in glob.glob(os.path.join(self.package_folder, "include", "rocksdb", "*")): - if path != os.path.join(self.package_folder, "include", "rocksdb", "c.h"): - if os.path.isfile(path): - os.remove(path) - else: - shutil.rmtree(path) - - def package(self): - copy(self, "COPYING", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses")) - copy(self, "LICENSE*", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses")) - cmake = CMake(self) - cmake.install() - if self.options.shared: - self._remove_static_libraries() - self._remove_cpp_headers() # Force stable ABI for shared libraries - rmdir(self, os.path.join(self.package_folder, "lib", "cmake")) - rmdir(self, os.path.join(self.package_folder, "lib", "pkgconfig")) - - def package_info(self): - cmake_target = "rocksdb-shared" if self.options.shared else "rocksdb" - self.cpp_info.set_property("cmake_file_name", "RocksDB") - self.cpp_info.set_property("cmake_target_name", f"RocksDB::{cmake_target}") - # TODO: back to global scope in conan v2 once cmake_find_package* generators removed - self.cpp_info.components["librocksdb"].libs = collect_libs(self) - if self.settings.os == "Windows": - self.cpp_info.components["librocksdb"].system_libs = ["shlwapi", "rpcrt4"] - if self.options.shared: - self.cpp_info.components["librocksdb"].defines = ["ROCKSDB_DLL"] - elif self.settings.os in ["Linux", "FreeBSD"]: - self.cpp_info.components["librocksdb"].system_libs = ["pthread", "m"] - if self.options.lite: - self.cpp_info.components["librocksdb"].defines.append("ROCKSDB_LITE") - - # TODO: to remove in conan v2 once cmake_find_package* generators removed - self.cpp_info.names["cmake_find_package"] = "RocksDB" - self.cpp_info.names["cmake_find_package_multi"] = "RocksDB" - self.cpp_info.components["librocksdb"].names["cmake_find_package"] = cmake_target - self.cpp_info.components["librocksdb"].names["cmake_find_package_multi"] = cmake_target - self.cpp_info.components["librocksdb"].set_property("cmake_target_name", f"RocksDB::{cmake_target}") - if self.options.with_gflags: - self.cpp_info.components["librocksdb"].requires.append("gflags::gflags") - if self.options.with_snappy: - self.cpp_info.components["librocksdb"].requires.append("snappy::snappy") - if self.options.with_lz4: - self.cpp_info.components["librocksdb"].requires.append("lz4::lz4") - if self.options.with_zlib: - self.cpp_info.components["librocksdb"].requires.append("zlib::zlib") - if self.options.with_zstd: - self.cpp_info.components["librocksdb"].requires.append("zstd::zstd") - if self.options.get_safe("with_tbb"): - self.cpp_info.components["librocksdb"].requires.append("onetbb::onetbb") - if self.options.with_jemalloc: - self.cpp_info.components["librocksdb"].requires.append("jemalloc::jemalloc") diff --git a/external/rocksdb/patches/9.7.3-0001-memory-leak.patch b/external/rocksdb/patches/9.7.3-0001-memory-leak.patch deleted file mode 100644 index bb086e6cb2..0000000000 --- a/external/rocksdb/patches/9.7.3-0001-memory-leak.patch +++ /dev/null @@ -1,319 +0,0 @@ -diff --git a/HISTORY.md b/HISTORY.md -index 36d472229..05ad1a202 100644 ---- a/HISTORY.md -+++ b/HISTORY.md -@@ -1,6 +1,10 @@ - # Rocksdb Change Log - > NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt` - -+## 9.7.4 (10/31/2024) -+### Bug Fixes -+* Fix a leak of obsolete blob files left open until DB::Close(). This bug was introduced in version 9.4.0. -+ - ## 9.7.3 (10/16/2024) - ### Behavior Changes - * OPTIONS file to be loaded by remote worker is now preserved so that it does not get purged by the primary host. A similar technique as how we are preserving new SST files from getting purged is used for this. min_options_file_numbers_ is tracked like pending_outputs_ is tracked. -diff --git a/db/blob/blob_file_cache.cc b/db/blob/blob_file_cache.cc -index 5f340aadf..1b9faa238 100644 ---- a/db/blob/blob_file_cache.cc -+++ b/db/blob/blob_file_cache.cc -@@ -42,6 +42,7 @@ Status BlobFileCache::GetBlobFileReader( - assert(blob_file_reader); - assert(blob_file_reader->IsEmpty()); - -+ // NOTE: sharing same Cache with table_cache - const Slice key = GetSliceForKey(&blob_file_number); - - assert(cache_); -@@ -98,4 +99,13 @@ Status BlobFileCache::GetBlobFileReader( - return Status::OK(); - } - -+void BlobFileCache::Evict(uint64_t blob_file_number) { -+ // NOTE: sharing same Cache with table_cache -+ const Slice key = GetSliceForKey(&blob_file_number); -+ -+ assert(cache_); -+ -+ cache_.get()->Erase(key); -+} -+ - } // namespace ROCKSDB_NAMESPACE -diff --git a/db/blob/blob_file_cache.h b/db/blob/blob_file_cache.h -index 740e67ada..6858d012b 100644 ---- a/db/blob/blob_file_cache.h -+++ b/db/blob/blob_file_cache.h -@@ -36,6 +36,15 @@ class BlobFileCache { - uint64_t blob_file_number, - CacheHandleGuard* blob_file_reader); - -+ // Called when a blob file is obsolete to ensure it is removed from the cache -+ // to avoid effectively leaking the open file and assicated memory -+ void Evict(uint64_t blob_file_number); -+ -+ // Used to identify cache entries for blob files (not normally useful) -+ static const Cache::CacheItemHelper* GetHelper() { -+ return CacheInterface::GetBasicHelper(); -+ } -+ - private: - using CacheInterface = - BasicTypedCacheInterface; -diff --git a/db/column_family.h b/db/column_family.h -index e4b7adde8..86637736a 100644 ---- a/db/column_family.h -+++ b/db/column_family.h -@@ -401,6 +401,7 @@ class ColumnFamilyData { - SequenceNumber earliest_seq); - - TableCache* table_cache() const { return table_cache_.get(); } -+ BlobFileCache* blob_file_cache() const { return blob_file_cache_.get(); } - BlobSource* blob_source() const { return blob_source_.get(); } - - // See documentation in compaction_picker.h -diff --git a/db/db_impl/db_impl.cc b/db/db_impl/db_impl.cc -index 261593423..06573ac2e 100644 ---- a/db/db_impl/db_impl.cc -+++ b/db/db_impl/db_impl.cc -@@ -659,8 +659,9 @@ Status DBImpl::CloseHelper() { - // We need to release them before the block cache is destroyed. The block - // cache may be destroyed inside versions_.reset(), when column family data - // list is destroyed, so leaving handles in table cache after -- // versions_.reset() may cause issues. -- // Here we clean all unreferenced handles in table cache. -+ // versions_.reset() may cause issues. Here we clean all unreferenced handles -+ // in table cache, and (for certain builds/conditions) assert that no obsolete -+ // files are hanging around unreferenced (leak) in the table/blob file cache. - // Now we assume all user queries have finished, so only version set itself - // can possibly hold the blocks from block cache. After releasing unreferenced - // handles here, only handles held by version set left and inside -@@ -668,6 +669,9 @@ Status DBImpl::CloseHelper() { - // time a handle is released, we erase it from the cache too. By doing that, - // we can guarantee that after versions_.reset(), table cache is empty - // so the cache can be safely destroyed. -+#ifndef NDEBUG -+ TEST_VerifyNoObsoleteFilesCached(/*db_mutex_already_held=*/true); -+#endif // !NDEBUG - table_cache_->EraseUnRefEntries(); - - for (auto& txn_entry : recovered_transactions_) { -@@ -3227,6 +3231,8 @@ Status DBImpl::MultiGetImpl( - s = Status::Aborted(); - break; - } -+ // This could be a long-running operation -+ ROCKSDB_THREAD_YIELD_HOOK(); - } - - // Post processing (decrement reference counts and record statistics) -diff --git a/db/db_impl/db_impl.h b/db/db_impl/db_impl.h -index 5e4fa310b..ccc0abfa7 100644 ---- a/db/db_impl/db_impl.h -+++ b/db/db_impl/db_impl.h -@@ -1241,9 +1241,14 @@ class DBImpl : public DB { - static Status TEST_ValidateOptions(const DBOptions& db_options) { - return ValidateOptions(db_options); - } -- - #endif // NDEBUG - -+ // In certain configurations, verify that the table/blob file cache only -+ // contains entries for live files, to check for effective leaks of open -+ // files. This can only be called when purging of obsolete files has -+ // "settled," such as during parts of DB Close(). -+ void TEST_VerifyNoObsoleteFilesCached(bool db_mutex_already_held) const; -+ - // persist stats to column family "_persistent_stats" - void PersistStats(); - -diff --git a/db/db_impl/db_impl_debug.cc b/db/db_impl/db_impl_debug.cc -index 790a50d7a..67f5b4aaf 100644 ---- a/db/db_impl/db_impl_debug.cc -+++ b/db/db_impl/db_impl_debug.cc -@@ -9,6 +9,7 @@ - - #ifndef NDEBUG - -+#include "db/blob/blob_file_cache.h" - #include "db/column_family.h" - #include "db/db_impl/db_impl.h" - #include "db/error_handler.h" -@@ -328,5 +329,49 @@ size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const { - InstrumentedMutexLock l(&const_cast(this)->stats_history_mutex_); - return EstimateInMemoryStatsHistorySize(); - } -+ -+void DBImpl::TEST_VerifyNoObsoleteFilesCached( -+ bool db_mutex_already_held) const { -+ // This check is somewhat expensive and obscure to make a part of every -+ // unit test in every build variety. Thus, we only enable it for ASAN builds. -+ if (!kMustFreeHeapAllocations) { -+ return; -+ } -+ -+ std::optional l; -+ if (db_mutex_already_held) { -+ mutex_.AssertHeld(); -+ } else { -+ l.emplace(&mutex_); -+ } -+ -+ std::vector live_files; -+ for (auto cfd : *versions_->GetColumnFamilySet()) { -+ if (cfd->IsDropped()) { -+ continue; -+ } -+ // Sneakily add both SST and blob files to the same list -+ cfd->current()->AddLiveFiles(&live_files, &live_files); -+ } -+ std::sort(live_files.begin(), live_files.end()); -+ -+ auto fn = [&live_files](const Slice& key, Cache::ObjectPtr, size_t, -+ const Cache::CacheItemHelper* helper) { -+ if (helper != BlobFileCache::GetHelper()) { -+ // Skip non-blob files for now -+ // FIXME: diagnose and fix the leaks of obsolete SST files revealed in -+ // unit tests. -+ return; -+ } -+ // See TableCache and BlobFileCache -+ assert(key.size() == sizeof(uint64_t)); -+ uint64_t file_number; -+ GetUnaligned(reinterpret_cast(key.data()), &file_number); -+ // Assert file is in sorted live_files -+ assert( -+ std::binary_search(live_files.begin(), live_files.end(), file_number)); -+ }; -+ table_cache_->ApplyToAllEntries(fn, {}); -+} - } // namespace ROCKSDB_NAMESPACE - #endif // NDEBUG -diff --git a/db/db_iter.cc b/db/db_iter.cc -index e02586377..bf4749eb9 100644 ---- a/db/db_iter.cc -+++ b/db/db_iter.cc -@@ -540,6 +540,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key, - } else { - iter_.Next(); - } -+ // This could be a long-running operation due to tombstones, etc. -+ ROCKSDB_THREAD_YIELD_HOOK(); - } while (iter_.Valid()); - - valid_ = false; -diff --git a/db/table_cache.cc b/db/table_cache.cc -index 71fc29c32..8a5be75e8 100644 ---- a/db/table_cache.cc -+++ b/db/table_cache.cc -@@ -164,6 +164,7 @@ Status TableCache::GetTableReader( - } - - Cache::Handle* TableCache::Lookup(Cache* cache, uint64_t file_number) { -+ // NOTE: sharing same Cache with BlobFileCache - Slice key = GetSliceForFileNumber(&file_number); - return cache->Lookup(key); - } -@@ -179,6 +180,7 @@ Status TableCache::FindTable( - size_t max_file_size_for_l0_meta_pin, Temperature file_temperature) { - PERF_TIMER_GUARD_WITH_CLOCK(find_table_nanos, ioptions_.clock); - uint64_t number = file_meta.fd.GetNumber(); -+ // NOTE: sharing same Cache with BlobFileCache - Slice key = GetSliceForFileNumber(&number); - *handle = cache_.Lookup(key); - TEST_SYNC_POINT_CALLBACK("TableCache::FindTable:0", -diff --git a/db/version_builder.cc b/db/version_builder.cc -index ed8ab8214..c98f53f42 100644 ---- a/db/version_builder.cc -+++ b/db/version_builder.cc -@@ -24,6 +24,7 @@ - #include - - #include "cache/cache_reservation_manager.h" -+#include "db/blob/blob_file_cache.h" - #include "db/blob/blob_file_meta.h" - #include "db/dbformat.h" - #include "db/internal_stats.h" -@@ -744,12 +745,9 @@ class VersionBuilder::Rep { - return Status::Corruption("VersionBuilder", oss.str()); - } - -- // Note: we use C++11 for now but in C++14, this could be done in a more -- // elegant way using generalized lambda capture. -- VersionSet* const vs = version_set_; -- const ImmutableCFOptions* const ioptions = ioptions_; -- -- auto deleter = [vs, ioptions](SharedBlobFileMetaData* shared_meta) { -+ auto deleter = [vs = version_set_, ioptions = ioptions_, -+ bc = cfd_ ? cfd_->blob_file_cache() -+ : nullptr](SharedBlobFileMetaData* shared_meta) { - if (vs) { - assert(ioptions); - assert(!ioptions->cf_paths.empty()); -@@ -758,6 +756,9 @@ class VersionBuilder::Rep { - vs->AddObsoleteBlobFile(shared_meta->GetBlobFileNumber(), - ioptions->cf_paths.front().path); - } -+ if (bc) { -+ bc->Evict(shared_meta->GetBlobFileNumber()); -+ } - - delete shared_meta; - }; -@@ -766,7 +767,7 @@ class VersionBuilder::Rep { - blob_file_number, blob_file_addition.GetTotalBlobCount(), - blob_file_addition.GetTotalBlobBytes(), - blob_file_addition.GetChecksumMethod(), -- blob_file_addition.GetChecksumValue(), deleter); -+ blob_file_addition.GetChecksumValue(), std::move(deleter)); - - mutable_blob_file_metas_.emplace( - blob_file_number, MutableBlobFileMetaData(std::move(shared_meta))); -diff --git a/db/version_set.h b/db/version_set.h -index 9336782b1..024f869e7 100644 ---- a/db/version_set.h -+++ b/db/version_set.h -@@ -1514,7 +1514,6 @@ class VersionSet { - void GetLiveFilesMetaData(std::vector* metadata); - - void AddObsoleteBlobFile(uint64_t blob_file_number, std::string path) { -- // TODO: Erase file from BlobFileCache? - obsolete_blob_files_.emplace_back(blob_file_number, std::move(path)); - } - -diff --git a/include/rocksdb/version.h b/include/rocksdb/version.h -index 2a19796b8..0afa2cab1 100644 ---- a/include/rocksdb/version.h -+++ b/include/rocksdb/version.h -@@ -13,7 +13,7 @@ - // minor or major version number planned for release. - #define ROCKSDB_MAJOR 9 - #define ROCKSDB_MINOR 7 --#define ROCKSDB_PATCH 3 -+#define ROCKSDB_PATCH 4 - - // Do not use these. We made the mistake of declaring macros starting with - // double underscore. Now we have to live with our choice. We'll deprecate these -diff --git a/port/port.h b/port/port.h -index 13aa56d47..141716e5b 100644 ---- a/port/port.h -+++ b/port/port.h -@@ -19,3 +19,19 @@ - #elif defined(OS_WIN) - #include "port/win/port_win.h" - #endif -+ -+#ifdef OS_LINUX -+// A temporary hook into long-running RocksDB threads to support modifying their -+// priority etc. This should become a public API hook once the requirements -+// are better understood. -+extern "C" void RocksDbThreadYield() __attribute__((__weak__)); -+#define ROCKSDB_THREAD_YIELD_HOOK() \ -+ { \ -+ if (RocksDbThreadYield) { \ -+ RocksDbThreadYield(); \ -+ } \ -+ } -+#else -+#define ROCKSDB_THREAD_YIELD_HOOK() \ -+ {} -+#endif diff --git a/external/rocksdb/patches/9.x.x-0001-exclude-thirdparty.patch b/external/rocksdb/patches/9.x.x-0001-exclude-thirdparty.patch deleted file mode 100644 index 7b5858bc1e..0000000000 --- a/external/rocksdb/patches/9.x.x-0001-exclude-thirdparty.patch +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 93b884d..b715cb6 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -106,14 +106,9 @@ endif() - include(CMakeDependentOption) - - if(MSVC) -- option(WITH_GFLAGS "build with GFlags" OFF) - option(WITH_XPRESS "build with windows built in compression" OFF) -- option(ROCKSDB_SKIP_THIRDPARTY "skip thirdparty.inc" OFF) -- -- if(NOT ROCKSDB_SKIP_THIRDPARTY) -- include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc) -- endif() --else() -+endif() -+if(TRUE) - if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" AND NOT CMAKE_SYSTEM_NAME MATCHES "kFreeBSD") - # FreeBSD has jemalloc as default malloc - # but it does not have all the jemalloc files in include/... -@@ -126,7 +121,7 @@ else() - endif() - endif() - -- if(MINGW) -+ if(MSVC OR MINGW) - option(WITH_GFLAGS "build with GFlags" OFF) - else() - option(WITH_GFLAGS "build with GFlags" ON) diff --git a/external/snappy/conandata.yml b/external/snappy/conandata.yml deleted file mode 100644 index 1488c7a2ba..0000000000 --- a/external/snappy/conandata.yml +++ /dev/null @@ -1,40 +0,0 @@ -sources: - "1.1.10": - url: "https://github.com/google/snappy/archive/1.1.10.tar.gz" - sha256: "49d831bffcc5f3d01482340fe5af59852ca2fe76c3e05df0e67203ebbe0f1d90" - "1.1.9": - url: "https://github.com/google/snappy/archive/1.1.9.tar.gz" - sha256: "75c1fbb3d618dd3a0483bff0e26d0a92b495bbe5059c8b4f1c962b478b6e06e7" - "1.1.8": - url: "https://github.com/google/snappy/archive/1.1.8.tar.gz" - sha256: "16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f" - "1.1.7": - url: "https://github.com/google/snappy/archive/1.1.7.tar.gz" - sha256: "3dfa02e873ff51a11ee02b9ca391807f0c8ea0529a4924afa645fbf97163f9d4" -patches: - "1.1.10": - - patch_file: "patches/1.1.10-0001-fix-inlining-failure.patch" - patch_description: "disable inlining for compilation error" - patch_type: "portability" - - patch_file: "patches/1.1.9-0002-no-Werror.patch" - patch_description: "disable 'warning as error' options" - patch_type: "portability" - - patch_file: "patches/1.1.10-0003-fix-clobber-list-older-llvm.patch" - patch_description: "disable inline asm on apple-clang" - patch_type: "portability" - - patch_file: "patches/1.1.9-0004-rtti-by-default.patch" - patch_description: "remove 'disable rtti'" - patch_type: "conan" - "1.1.9": - - patch_file: "patches/1.1.9-0001-fix-inlining-failure.patch" - patch_description: "disable inlining for compilation error" - patch_type: "portability" - - patch_file: "patches/1.1.9-0002-no-Werror.patch" - patch_description: "disable 'warning as error' options" - patch_type: "portability" - - patch_file: "patches/1.1.9-0003-fix-clobber-list-older-llvm.patch" - patch_description: "disable inline asm on apple-clang" - patch_type: "portability" - - patch_file: "patches/1.1.9-0004-rtti-by-default.patch" - patch_description: "remove 'disable rtti'" - patch_type: "conan" diff --git a/external/snappy/conanfile.py b/external/snappy/conanfile.py deleted file mode 100644 index 23558639f4..0000000000 --- a/external/snappy/conanfile.py +++ /dev/null @@ -1,89 +0,0 @@ -from conan import ConanFile -from conan.tools.build import check_min_cppstd -from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout -from conan.tools.files import apply_conandata_patches, copy, export_conandata_patches, get, rmdir -from conan.tools.scm import Version -import os - -required_conan_version = ">=1.54.0" - - -class SnappyConan(ConanFile): - name = "snappy" - description = "A fast compressor/decompressor" - topics = ("google", "compressor", "decompressor") - url = "https://github.com/conan-io/conan-center-index" - homepage = "https://github.com/google/snappy" - license = "BSD-3-Clause" - - package_type = "library" - settings = "os", "arch", "compiler", "build_type" - options = { - "shared": [True, False], - "fPIC": [True, False], - } - default_options = { - "shared": False, - "fPIC": True, - } - - def export_sources(self): - export_conandata_patches(self) - - def config_options(self): - if self.settings.os == 'Windows': - del self.options.fPIC - - def configure(self): - if self.options.shared: - self.options.rm_safe("fPIC") - - def layout(self): - cmake_layout(self, src_folder="src") - - def validate(self): - if self.settings.compiler.get_safe("cppstd"): - check_min_cppstd(self, 11) - - def source(self): - get(self, **self.conan_data["sources"][self.version], strip_root=True) - - def generate(self): - tc = CMakeToolchain(self) - tc.variables["SNAPPY_BUILD_TESTS"] = False - if Version(self.version) >= "1.1.8": - tc.variables["SNAPPY_FUZZING_BUILD"] = False - tc.variables["SNAPPY_REQUIRE_AVX"] = False - tc.variables["SNAPPY_REQUIRE_AVX2"] = False - tc.variables["SNAPPY_INSTALL"] = True - if Version(self.version) >= "1.1.9": - tc.variables["SNAPPY_BUILD_BENCHMARKS"] = False - tc.generate() - - def build(self): - apply_conandata_patches(self) - cmake = CMake(self) - cmake.configure() - cmake.build() - - def package(self): - copy(self, "COPYING", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses")) - cmake = CMake(self) - cmake.install() - rmdir(self, os.path.join(self.package_folder, "lib", "cmake")) - - def package_info(self): - self.cpp_info.set_property("cmake_file_name", "Snappy") - self.cpp_info.set_property("cmake_target_name", "Snappy::snappy") - # TODO: back to global scope in conan v2 once cmake_find_package* generators removed - self.cpp_info.components["snappylib"].libs = ["snappy"] - if not self.options.shared: - if self.settings.os in ["Linux", "FreeBSD"]: - self.cpp_info.components["snappylib"].system_libs.append("m") - - # TODO: to remove in conan v2 once cmake_find_package* generators removed - self.cpp_info.names["cmake_find_package"] = "Snappy" - self.cpp_info.names["cmake_find_package_multi"] = "Snappy" - self.cpp_info.components["snappylib"].names["cmake_find_package"] = "snappy" - self.cpp_info.components["snappylib"].names["cmake_find_package_multi"] = "snappy" - self.cpp_info.components["snappylib"].set_property("cmake_target_name", "Snappy::snappy") diff --git a/external/snappy/patches/1.1.10-0001-fix-inlining-failure.patch b/external/snappy/patches/1.1.10-0001-fix-inlining-failure.patch deleted file mode 100644 index 66b0f05521..0000000000 --- a/external/snappy/patches/1.1.10-0001-fix-inlining-failure.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/snappy-stubs-internal.h b/snappy-stubs-internal.h -index 1548ed7..3b4a9f3 100644 ---- a/snappy-stubs-internal.h -+++ b/snappy-stubs-internal.h -@@ -100,7 +100,7 @@ - - // Inlining hints. - #if HAVE_ATTRIBUTE_ALWAYS_INLINE --#define SNAPPY_ATTRIBUTE_ALWAYS_INLINE __attribute__((always_inline)) -+#define SNAPPY_ATTRIBUTE_ALWAYS_INLINE - #else - #define SNAPPY_ATTRIBUTE_ALWAYS_INLINE - #endif // HAVE_ATTRIBUTE_ALWAYS_INLINE diff --git a/external/snappy/patches/1.1.10-0003-fix-clobber-list-older-llvm.patch b/external/snappy/patches/1.1.10-0003-fix-clobber-list-older-llvm.patch deleted file mode 100644 index 969ce3805d..0000000000 --- a/external/snappy/patches/1.1.10-0003-fix-clobber-list-older-llvm.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/snappy.cc b/snappy.cc -index d414718..e4efb59 100644 ---- a/snappy.cc -+++ b/snappy.cc -@@ -1132,7 +1132,7 @@ inline size_t AdvanceToNextTagX86Optimized(const uint8_t** ip_p, size_t* tag) { - size_t literal_len = *tag >> 2; - size_t tag_type = *tag; - bool is_literal; --#if defined(__GCC_ASM_FLAG_OUTPUTS__) && defined(__x86_64__) -+#if defined(__GCC_ASM_FLAG_OUTPUTS__) && defined(__x86_64__) && ( (!defined(__clang__) && !defined(__APPLE__)) || (!defined(__APPLE__) && defined(__clang__) && (__clang_major__ >= 9)) || (defined(__APPLE__) && defined(__clang__) && (__clang_major__ > 11)) ) - // TODO clang misses the fact that the (c & 3) already correctly - // sets the zero flag. - asm("and $3, %k[tag_type]\n\t" diff --git a/external/snappy/patches/1.1.9-0001-fix-inlining-failure.patch b/external/snappy/patches/1.1.9-0001-fix-inlining-failure.patch deleted file mode 100644 index cdc119c0d5..0000000000 --- a/external/snappy/patches/1.1.9-0001-fix-inlining-failure.patch +++ /dev/null @@ -1,14 +0,0 @@ -Fixes the following error: -error: inlining failed in call to ‘always_inline’ ‘size_t snappy::AdvanceToNextTag(const uint8_t**, size_t*)’: function body can be overwritten at link time - ---- snappy-stubs-internal.h -+++ snappy-stubs-internal.h -@@ -100,7 +100,7 @@ - - // Inlining hints. - #ifdef HAVE_ATTRIBUTE_ALWAYS_INLINE --#define SNAPPY_ATTRIBUTE_ALWAYS_INLINE __attribute__((always_inline)) -+#define SNAPPY_ATTRIBUTE_ALWAYS_INLINE - #else - #define SNAPPY_ATTRIBUTE_ALWAYS_INLINE - #endif diff --git a/external/snappy/patches/1.1.9-0002-no-Werror.patch b/external/snappy/patches/1.1.9-0002-no-Werror.patch deleted file mode 100644 index d86e4e0a9d..0000000000 --- a/external/snappy/patches/1.1.9-0002-no-Werror.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- CMakeLists.txt -+++ CMakeLists.txt -@@ -69,7 +69,7 @@ -- # Use -Werror for clang only. -+if(0) - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - if(NOT CMAKE_CXX_FLAGS MATCHES "-Werror") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") - endif(NOT CMAKE_CXX_FLAGS MATCHES "-Werror") - endif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") -- -+endif() diff --git a/external/snappy/patches/1.1.9-0003-fix-clobber-list-older-llvm.patch b/external/snappy/patches/1.1.9-0003-fix-clobber-list-older-llvm.patch deleted file mode 100644 index 84bc674fdd..0000000000 --- a/external/snappy/patches/1.1.9-0003-fix-clobber-list-older-llvm.patch +++ /dev/null @@ -1,12 +0,0 @@ -asm clobbers do not work for clang < 9 and apple-clang < 11 (found by SpaceIm) ---- snappy.cc -+++ snappy.cc -@@ -1026,7 +1026,7 @@ - size_t literal_len = *tag >> 2; - size_t tag_type = *tag; - bool is_literal; --#if defined(__GNUC__) && defined(__x86_64__) -+#if defined(__GNUC__) && defined(__x86_64__) && ( (!defined(__clang__) && !defined(__APPLE__)) || (!defined(__APPLE__) && defined(__clang__) && (__clang_major__ >= 9)) || (defined(__APPLE__) && defined(__clang__) && (__clang_major__ > 11)) ) - // TODO clang misses the fact that the (c & 3) already correctly - // sets the zero flag. - asm("and $3, %k[tag_type]\n\t" diff --git a/external/snappy/patches/1.1.9-0004-rtti-by-default.patch b/external/snappy/patches/1.1.9-0004-rtti-by-default.patch deleted file mode 100644 index c353a489d0..0000000000 --- a/external/snappy/patches/1.1.9-0004-rtti-by-default.patch +++ /dev/null @@ -1,20 +0,0 @@ ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -53,8 +53,6 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - add_definitions(-D_HAS_EXCEPTIONS=0) - - # Disable RTTI. -- string(REGEX REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") -- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-") - else(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - # Use -Wall for clang and gcc. - if(NOT CMAKE_CXX_FLAGS MATCHES "-Wall") -@@ -78,8 +76,6 @@ endif() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") - - # Disable RTTI. -- string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") -- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") - endif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - - # BUILD_SHARED_LIBS is a standard CMake variable, but we declare it here to make diff --git a/external/soci/conandata.yml b/external/soci/conandata.yml deleted file mode 100644 index 6eb59aaffa..0000000000 --- a/external/soci/conandata.yml +++ /dev/null @@ -1,12 +0,0 @@ -sources: - "4.0.3": - url: "https://github.com/SOCI/soci/archive/v4.0.3.tar.gz" - sha256: "4b1ff9c8545c5d802fbe06ee6cd2886630e5c03bf740e269bb625b45cf934928" -patches: - "4.0.3": - - patch_file: "patches/0001-Remove-hardcoded-INSTALL_NAME_DIR-for-relocatable-li.patch" - patch_description: "Generate relocatable libraries on MacOS" - patch_type: "portability" - - patch_file: "patches/0002-Fix-soci_backend.patch" - patch_description: "Fix variable names for dependencies" - patch_type: "conan" diff --git a/external/soci/conanfile.py b/external/soci/conanfile.py deleted file mode 100644 index 7e611493d7..0000000000 --- a/external/soci/conanfile.py +++ /dev/null @@ -1,212 +0,0 @@ -from conan import ConanFile -from conan.tools.build import check_min_cppstd -from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout -from conan.tools.files import apply_conandata_patches, copy, export_conandata_patches, get, rmdir -from conan.tools.microsoft import is_msvc -from conan.tools.scm import Version -from conan.errors import ConanInvalidConfiguration -import os - -required_conan_version = ">=1.55.0" - - -class SociConan(ConanFile): - name = "soci" - homepage = "https://github.com/SOCI/soci" - url = "https://github.com/conan-io/conan-center-index" - description = "The C++ Database Access Library " - topics = ("mysql", "odbc", "postgresql", "sqlite3") - license = "BSL-1.0" - - settings = "os", "arch", "compiler", "build_type" - options = { - "shared": [True, False], - "fPIC": [True, False], - "empty": [True, False], - "with_sqlite3": [True, False], - "with_db2": [True, False], - "with_odbc": [True, False], - "with_oracle": [True, False], - "with_firebird": [True, False], - "with_mysql": [True, False], - "with_postgresql": [True, False], - "with_boost": [True, False], - } - default_options = { - "shared": False, - "fPIC": True, - "empty": False, - "with_sqlite3": False, - "with_db2": False, - "with_odbc": False, - "with_oracle": False, - "with_firebird": False, - "with_mysql": False, - "with_postgresql": False, - "with_boost": False, - } - - def export_sources(self): - export_conandata_patches(self) - - def layout(self): - cmake_layout(self, src_folder="src") - - def config_options(self): - if self.settings.os == "Windows": - self.options.rm_safe("fPIC") - - def configure(self): - if self.options.shared: - self.options.rm_safe("fPIC") - - def requirements(self): - if self.options.with_sqlite3: - self.requires("sqlite3/3.47.0") - if self.options.with_odbc and self.settings.os != "Windows": - self.requires("odbc/2.3.11") - if self.options.with_mysql: - self.requires("libmysqlclient/8.1.0") - if self.options.with_postgresql: - self.requires("libpq/15.5") - if self.options.with_boost: - self.requires("boost/1.83.0") - - @property - def _minimum_compilers_version(self): - return { - "Visual Studio": "14", - "gcc": "4.8", - "clang": "3.8", - "apple-clang": "8.0" - } - - def validate(self): - if self.settings.compiler.get_safe("cppstd"): - check_min_cppstd(self, 11) - - compiler = str(self.settings.compiler) - compiler_version = Version(self.settings.compiler.version.value) - if compiler not in self._minimum_compilers_version: - self.output.warning("{} recipe lacks information about the {} compiler support.".format(self.name, self.settings.compiler)) - elif compiler_version < self._minimum_compilers_version[compiler]: - raise ConanInvalidConfiguration("{} requires a {} version >= {}".format(self.name, compiler, compiler_version)) - - prefix = "Dependencies for" - message = "not configured in this conan package." - if self.options.with_db2: - # self.requires("db2/0.0.0") # TODO add support for db2 - raise ConanInvalidConfiguration("{} DB2 {} ".format(prefix, message)) - if self.options.with_oracle: - # self.requires("oracle_db/0.0.0") # TODO add support for oracle - raise ConanInvalidConfiguration("{} ORACLE {} ".format(prefix, message)) - if self.options.with_firebird: - # self.requires("firebird/0.0.0") # TODO add support for firebird - raise ConanInvalidConfiguration("{} firebird {} ".format(prefix, message)) - - def source(self): - get(self, **self.conan_data["sources"][self.version], strip_root=True) - - def generate(self): - tc = CMakeToolchain(self) - - tc.variables["SOCI_SHARED"] = self.options.shared - tc.variables["SOCI_STATIC"] = not self.options.shared - tc.variables["SOCI_TESTS"] = False - tc.variables["SOCI_CXX11"] = True - tc.variables["SOCI_EMPTY"] = self.options.empty - tc.variables["WITH_SQLITE3"] = self.options.with_sqlite3 - tc.variables["WITH_DB2"] = self.options.with_db2 - tc.variables["WITH_ODBC"] = self.options.with_odbc - tc.variables["WITH_ORACLE"] = self.options.with_oracle - tc.variables["WITH_FIREBIRD"] = self.options.with_firebird - tc.variables["WITH_MYSQL"] = self.options.with_mysql - tc.variables["WITH_POSTGRESQL"] = self.options.with_postgresql - tc.variables["WITH_BOOST"] = self.options.with_boost - tc.generate() - - deps = CMakeDeps(self) - deps.generate() - - def build(self): - apply_conandata_patches(self) - cmake = CMake(self) - cmake.configure() - cmake.build() - - def package(self): - copy(self, "LICENSE_1_0.txt", dst=os.path.join(self.package_folder, "licenses"), src=self.source_folder) - - cmake = CMake(self) - cmake.install() - - rmdir(self, os.path.join(self.package_folder, "lib", "cmake")) - - def package_info(self): - self.cpp_info.set_property("cmake_file_name", "SOCI") - - target_suffix = "" if self.options.shared else "_static" - lib_prefix = "lib" if is_msvc(self) and not self.options.shared else "" - version = Version(self.version) - lib_suffix = "_{}_{}".format(version.major, version.minor) if self.settings.os == "Windows" else "" - - # soci_core - self.cpp_info.components["soci_core"].set_property("cmake_target_name", "SOCI::soci_core{}".format(target_suffix)) - self.cpp_info.components["soci_core"].libs = ["{}soci_core{}".format(lib_prefix, lib_suffix)] - if self.options.with_boost: - self.cpp_info.components["soci_core"].requires.append("boost::boost") - - # soci_empty - if self.options.empty: - self.cpp_info.components["soci_empty"].set_property("cmake_target_name", "SOCI::soci_empty{}".format(target_suffix)) - self.cpp_info.components["soci_empty"].libs = ["{}soci_empty{}".format(lib_prefix, lib_suffix)] - self.cpp_info.components["soci_empty"].requires = ["soci_core"] - - # soci_sqlite3 - if self.options.with_sqlite3: - self.cpp_info.components["soci_sqlite3"].set_property("cmake_target_name", "SOCI::soci_sqlite3{}".format(target_suffix)) - self.cpp_info.components["soci_sqlite3"].libs = ["{}soci_sqlite3{}".format(lib_prefix, lib_suffix)] - self.cpp_info.components["soci_sqlite3"].requires = ["soci_core", "sqlite3::sqlite3"] - - # soci_odbc - if self.options.with_odbc: - self.cpp_info.components["soci_odbc"].set_property("cmake_target_name", "SOCI::soci_odbc{}".format(target_suffix)) - self.cpp_info.components["soci_odbc"].libs = ["{}soci_odbc{}".format(lib_prefix, lib_suffix)] - self.cpp_info.components["soci_odbc"].requires = ["soci_core"] - if self.settings.os == "Windows": - self.cpp_info.components["soci_odbc"].system_libs.append("odbc32") - else: - self.cpp_info.components["soci_odbc"].requires.append("odbc::odbc") - - # soci_mysql - if self.options.with_mysql: - self.cpp_info.components["soci_mysql"].set_property("cmake_target_name", "SOCI::soci_mysql{}".format(target_suffix)) - self.cpp_info.components["soci_mysql"].libs = ["{}soci_mysql{}".format(lib_prefix, lib_suffix)] - self.cpp_info.components["soci_mysql"].requires = ["soci_core", "libmysqlclient::libmysqlclient"] - - # soci_postgresql - if self.options.with_postgresql: - self.cpp_info.components["soci_postgresql"].set_property("cmake_target_name", "SOCI::soci_postgresql{}".format(target_suffix)) - self.cpp_info.components["soci_postgresql"].libs = ["{}soci_postgresql{}".format(lib_prefix, lib_suffix)] - self.cpp_info.components["soci_postgresql"].requires = ["soci_core", "libpq::libpq"] - - # TODO: to remove in conan v2 once cmake_find_package* generators removed - self.cpp_info.names["cmake_find_package"] = "SOCI" - self.cpp_info.names["cmake_find_package_multi"] = "SOCI" - self.cpp_info.components["soci_core"].names["cmake_find_package"] = "soci_core{}".format(target_suffix) - self.cpp_info.components["soci_core"].names["cmake_find_package_multi"] = "soci_core{}".format(target_suffix) - if self.options.empty: - self.cpp_info.components["soci_empty"].names["cmake_find_package"] = "soci_empty{}".format(target_suffix) - self.cpp_info.components["soci_empty"].names["cmake_find_package_multi"] = "soci_empty{}".format(target_suffix) - if self.options.with_sqlite3: - self.cpp_info.components["soci_sqlite3"].names["cmake_find_package"] = "soci_sqlite3{}".format(target_suffix) - self.cpp_info.components["soci_sqlite3"].names["cmake_find_package_multi"] = "soci_sqlite3{}".format(target_suffix) - if self.options.with_odbc: - self.cpp_info.components["soci_odbc"].names["cmake_find_package"] = "soci_odbc{}".format(target_suffix) - self.cpp_info.components["soci_odbc"].names["cmake_find_package_multi"] = "soci_odbc{}".format(target_suffix) - if self.options.with_mysql: - self.cpp_info.components["soci_mysql"].names["cmake_find_package"] = "soci_mysql{}".format(target_suffix) - self.cpp_info.components["soci_mysql"].names["cmake_find_package_multi"] = "soci_mysql{}".format(target_suffix) - if self.options.with_postgresql: - self.cpp_info.components["soci_postgresql"].names["cmake_find_package"] = "soci_postgresql{}".format(target_suffix) - self.cpp_info.components["soci_postgresql"].names["cmake_find_package_multi"] = "soci_postgresql{}".format(target_suffix) diff --git a/external/soci/patches/0001-Remove-hardcoded-INSTALL_NAME_DIR-for-relocatable-li.patch b/external/soci/patches/0001-Remove-hardcoded-INSTALL_NAME_DIR-for-relocatable-li.patch deleted file mode 100644 index 5de0027f75..0000000000 --- a/external/soci/patches/0001-Remove-hardcoded-INSTALL_NAME_DIR-for-relocatable-li.patch +++ /dev/null @@ -1,39 +0,0 @@ -From d491bf7b5040d314ffd0c6310ba01f78ff44c85e Mon Sep 17 00:00:00 2001 -From: Rasmus Thomsen -Date: Fri, 14 Apr 2023 09:16:29 +0200 -Subject: [PATCH] Remove hardcoded INSTALL_NAME_DIR for relocatable libraries - on MacOS - ---- - cmake/SociBackend.cmake | 2 +- - src/core/CMakeLists.txt | 1 - - 2 files changed, 1 insertion(+), 2 deletions(-) - -diff --git a/cmake/SociBackend.cmake b/cmake/SociBackend.cmake -index 5d4ef0df..39fe1f77 100644 ---- a/cmake/SociBackend.cmake -+++ b/cmake/SociBackend.cmake -@@ -171,7 +171,7 @@ macro(soci_backend NAME) - set_target_properties(${THIS_BACKEND_TARGET} - PROPERTIES - SOVERSION ${${PROJECT_NAME}_SOVERSION} -- INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib) -+ ) - - if(APPLE) - set_target_properties(${THIS_BACKEND_TARGET} -diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt -index 3e7deeae..f9eae564 100644 ---- a/src/core/CMakeLists.txt -+++ b/src/core/CMakeLists.txt -@@ -59,7 +59,6 @@ if (SOCI_SHARED) - PROPERTIES - VERSION ${SOCI_VERSION} - SOVERSION ${SOCI_SOVERSION} -- INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/lib - CLEAN_DIRECT_OUTPUT 1) - endif() - --- -2.25.1 - diff --git a/external/soci/patches/0002-Fix-soci_backend.patch b/external/soci/patches/0002-Fix-soci_backend.patch deleted file mode 100644 index eab3c3763c..0000000000 --- a/external/soci/patches/0002-Fix-soci_backend.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/cmake/SociBackend.cmake b/cmake/SociBackend.cmake -index 0a664667..3fa2ed95 100644 ---- a/cmake/SociBackend.cmake -+++ b/cmake/SociBackend.cmake -@@ -31,14 +31,13 @@ macro(soci_backend_deps_found NAME DEPS SUCCESS) - if(NOT DEPEND_FOUND) - list(APPEND DEPS_NOT_FOUND ${dep}) - else() -- string(TOUPPER "${dep}" DEPU) -- if( ${DEPU}_INCLUDE_DIR ) -- list(APPEND DEPS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIR}) -+ if( ${dep}_INCLUDE_DIR ) -+ list(APPEND DEPS_INCLUDE_DIRS ${${dep}_INCLUDE_DIR}) - endif() -- if( ${DEPU}_INCLUDE_DIRS ) -- list(APPEND DEPS_INCLUDE_DIRS ${${DEPU}_INCLUDE_DIRS}) -+ if( ${dep}_INCLUDE_DIRS ) -+ list(APPEND DEPS_INCLUDE_DIRS ${${dep}_INCLUDE_DIRS}) - endif() -- list(APPEND DEPS_LIBRARIES ${${DEPU}_LIBRARIES}) -+ list(APPEND DEPS_LIBRARIES ${${dep}_LIBRARIES}) - endif() - endforeach() - diff --git a/include/xrpl/basics/Buffer.h b/include/xrpl/basics/Buffer.h index b2f1163452..3379a923f0 100644 --- a/include/xrpl/basics/Buffer.h +++ b/include/xrpl/basics/Buffer.h @@ -25,6 +25,7 @@ #include #include +#include namespace ripple { diff --git a/include/xrpl/basics/Expected.h b/include/xrpl/basics/Expected.h index 9afb160d9d..d2440f63ab 100644 --- a/include/xrpl/basics/Expected.h +++ b/include/xrpl/basics/Expected.h @@ -22,8 +22,18 @@ #include +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + #include +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + #include namespace ripple { diff --git a/include/xrpl/basics/Log.h b/include/xrpl/basics/Log.h index 2506b8ea8d..833907eb9c 100644 --- a/include/xrpl/basics/Log.h +++ b/include/xrpl/basics/Log.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include diff --git a/include/xrpl/basics/SHAMapHash.h b/include/xrpl/basics/SHAMapHash.h index 2d2dcdc3ef..1ec326409c 100644 --- a/include/xrpl/basics/SHAMapHash.h +++ b/include/xrpl/basics/SHAMapHash.h @@ -21,7 +21,6 @@ #define RIPPLE_BASICS_SHAMAP_HASH_H_INCLUDED #include -#include #include diff --git a/include/xrpl/basics/StringUtilities.h b/include/xrpl/basics/StringUtilities.h index 23d60e2db4..5f905638cb 100644 --- a/include/xrpl/basics/StringUtilities.h +++ b/include/xrpl/basics/StringUtilities.h @@ -29,7 +29,6 @@ #include #include #include -#include #include namespace ripple { diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index 99c91fe393..7eace6fe72 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -90,9 +90,6 @@ public: int getCacheSize() const; - int - getTrackSize() const; - float getHitRate(); @@ -170,9 +167,6 @@ public: bool retrieve(key_type const& key, T& data); - mutex_type& - peekMutex(); - std::vector getKeys() const; @@ -193,11 +187,14 @@ public: private: SharedPointerType - initialFetch(key_type const& key, std::lock_guard const& l); + initialFetch(key_type const& key); void collect_metrics(); + Mutex& + lockPartition(key_type const& key) const; + private: struct Stats { @@ -300,8 +297,8 @@ private: [[maybe_unused]] clock_type::time_point const& now, typename KeyValueCacheType::map_type& partition, SweptPointersVector& stuffToSweep, - std::atomic& allRemovals, - std::lock_guard const&); + std::atomic& allRemoval, + Mutex& partitionLock); [[nodiscard]] std::thread sweepHelper( @@ -310,14 +307,12 @@ private: typename KeyOnlyCacheType::map_type& partition, SweptPointersVector&, std::atomic& allRemovals, - std::lock_guard const&); + Mutex& partitionLock); beast::Journal m_journal; clock_type& m_clock; Stats m_stats; - mutex_type mutable m_mutex; - // Used for logging std::string m_name; @@ -328,10 +323,11 @@ private: clock_type::duration const m_target_age; // Number of items cached - int m_cache_count; + std::atomic m_cache_count; cache_type m_cache; // Hold strong reference to recent objects - std::uint64_t m_hits; - std::uint64_t m_misses; + std::atomic m_hits; + std::atomic m_misses; + mutable std::vector partitionLocks_; }; } // namespace ripple diff --git a/include/xrpl/basics/TaggedCache.ipp b/include/xrpl/basics/TaggedCache.ipp index 16a3f7587a..c909ec6ad1 100644 --- a/include/xrpl/basics/TaggedCache.ipp +++ b/include/xrpl/basics/TaggedCache.ipp @@ -22,6 +22,7 @@ #include #include +#include namespace ripple { @@ -60,6 +61,7 @@ inline TaggedCache< , m_hits(0) , m_misses(0) { + partitionLocks_ = std::vector(m_cache.partitions()); } template < @@ -105,8 +107,13 @@ TaggedCache< KeyEqual, Mutex>::size() const { - std::lock_guard lock(m_mutex); - return m_cache.size(); + std::size_t totalSize = 0; + for (size_t i = 0; i < partitionLocks_.size(); ++i) + { + std::lock_guard lock(partitionLocks_[i]); + totalSize += m_cache.map()[i].size(); + } + return totalSize; } template < @@ -129,32 +136,7 @@ TaggedCache< KeyEqual, Mutex>::getCacheSize() const { - std::lock_guard lock(m_mutex); - return m_cache_count; -} - -template < - class Key, - class T, - bool IsKeyCache, - class SharedWeakUnionPointer, - class SharedPointerType, - class Hash, - class KeyEqual, - class Mutex> -inline int -TaggedCache< - Key, - T, - IsKeyCache, - SharedWeakUnionPointer, - SharedPointerType, - Hash, - KeyEqual, - Mutex>::getTrackSize() const -{ - std::lock_guard lock(m_mutex); - return m_cache.size(); + return m_cache_count.load(std::memory_order_relaxed); } template < @@ -177,9 +159,10 @@ TaggedCache< KeyEqual, Mutex>::getHitRate() { - std::lock_guard lock(m_mutex); - auto const total = static_cast(m_hits + m_misses); - return m_hits * (100.0f / std::max(1.0f, total)); + auto const hits = m_hits.load(std::memory_order_relaxed); + auto const misses = m_misses.load(std::memory_order_relaxed); + float const total = float(hits + misses); + return hits * (100.0f / std::max(1.0f, total)); } template < @@ -202,9 +185,12 @@ TaggedCache< KeyEqual, Mutex>::clear() { - std::lock_guard lock(m_mutex); + for (auto& mutex : partitionLocks_) + mutex.lock(); m_cache.clear(); - m_cache_count = 0; + for (auto& mutex : partitionLocks_) + mutex.unlock(); + m_cache_count.store(0, std::memory_order_relaxed); } template < @@ -227,11 +213,9 @@ TaggedCache< KeyEqual, Mutex>::reset() { - std::lock_guard lock(m_mutex); - m_cache.clear(); - m_cache_count = 0; - m_hits = 0; - m_misses = 0; + clear(); + m_hits.store(0, std::memory_order_relaxed); + m_misses.store(0, std::memory_order_relaxed); } template < @@ -255,7 +239,7 @@ TaggedCache< KeyEqual, Mutex>::touch_if_exists(KeyComparable const& key) { - std::lock_guard lock(m_mutex); + std::lock_guard lock(lockPartition(key)); auto const iter(m_cache.find(key)); if (iter == m_cache.end()) { @@ -297,8 +281,6 @@ TaggedCache< auto const start = std::chrono::steady_clock::now(); { - std::lock_guard lock(m_mutex); - if (m_target_size == 0 || (static_cast(m_cache.size()) <= m_target_size)) { @@ -330,12 +312,13 @@ TaggedCache< m_cache.map()[p], allStuffToSweep[p], allRemovals, - lock)); + partitionLocks_[p])); } for (std::thread& worker : workers) worker.join(); - m_cache_count -= allRemovals; + int removals = allRemovals.load(std::memory_order_relaxed); + m_cache_count.fetch_sub(removals, std::memory_order_relaxed); } // At this point allStuffToSweep will go out of scope outside the lock // and decrement the reference count on each strong pointer. @@ -369,7 +352,8 @@ TaggedCache< { // Remove from cache, if !valid, remove from map too. Returns true if // removed from cache - std::lock_guard lock(m_mutex); + + std::lock_guard lock(lockPartition(key)); auto cit = m_cache.find(key); @@ -382,7 +366,7 @@ TaggedCache< if (entry.isCached()) { - --m_cache_count; + m_cache_count.fetch_sub(1, std::memory_order_relaxed); entry.ptr.convertToWeak(); ret = true; } @@ -420,17 +404,16 @@ TaggedCache< { // Return canonical value, store if needed, refresh in cache // Return values: true=we had the data already - std::lock_guard lock(m_mutex); + std::lock_guard lock(lockPartition(key)); auto cit = m_cache.find(key); - if (cit == m_cache.end()) { m_cache.emplace( std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(m_clock.now(), data)); - ++m_cache_count; + m_cache_count.fetch_add(1, std::memory_order_relaxed); return false; } @@ -479,12 +462,12 @@ TaggedCache< data = cachedData; } - ++m_cache_count; + m_cache_count.fetch_add(1, std::memory_order_relaxed); return true; } entry.ptr = data; - ++m_cache_count; + m_cache_count.fetch_add(1, std::memory_order_relaxed); return false; } @@ -560,10 +543,11 @@ TaggedCache< KeyEqual, Mutex>::fetch(key_type const& key) { - std::lock_guard l(m_mutex); - auto ret = initialFetch(key, l); + std::lock_guard lock(lockPartition(key)); + + auto ret = initialFetch(key); if (!ret) - ++m_misses; + m_misses.fetch_add(1, std::memory_order_relaxed); return ret; } @@ -627,8 +611,8 @@ TaggedCache< Mutex>::insert(key_type const& key) -> std::enable_if_t { - std::lock_guard lock(m_mutex); clock_type::time_point const now(m_clock.now()); + std::lock_guard lock(lockPartition(key)); auto [it, inserted] = m_cache.emplace( std::piecewise_construct, std::forward_as_tuple(key), @@ -668,29 +652,6 @@ TaggedCache< return true; } -template < - class Key, - class T, - bool IsKeyCache, - class SharedWeakUnionPointer, - class SharedPointerType, - class Hash, - class KeyEqual, - class Mutex> -inline auto -TaggedCache< - Key, - T, - IsKeyCache, - SharedWeakUnionPointer, - SharedPointerType, - Hash, - KeyEqual, - Mutex>::peekMutex() -> mutex_type& -{ - return m_mutex; -} - template < class Key, class T, @@ -714,10 +675,13 @@ TaggedCache< std::vector v; { - std::lock_guard lock(m_mutex); v.reserve(m_cache.size()); - for (auto const& _ : m_cache) - v.push_back(_.first); + for (std::size_t i = 0; i < partitionLocks_.size(); ++i) + { + std::lock_guard lock(partitionLocks_[i]); + for (auto const& entry : m_cache.map()[i]) + v.push_back(entry.first); + } } return v; @@ -743,11 +707,12 @@ TaggedCache< KeyEqual, Mutex>::rate() const { - std::lock_guard lock(m_mutex); - auto const tot = m_hits + m_misses; + auto const hits = m_hits.load(std::memory_order_relaxed); + auto const misses = m_misses.load(std::memory_order_relaxed); + auto const tot = hits + misses; if (tot == 0) - return 0; - return double(m_hits) / tot; + return 0.0; + return double(hits) / tot; } template < @@ -771,18 +736,16 @@ TaggedCache< KeyEqual, Mutex>::fetch(key_type const& digest, Handler const& h) { - { - std::lock_guard l(m_mutex); - if (auto ret = initialFetch(digest, l)) - return ret; - } + std::lock_guard lock(lockPartition(digest)); + + if (auto ret = initialFetch(digest)) + return ret; auto sle = h(); if (!sle) return {}; - std::lock_guard l(m_mutex); - ++m_misses; + m_misses.fetch_add(1, std::memory_order_relaxed); auto const [it, inserted] = m_cache.emplace(digest, Entry(m_clock.now(), std::move(sle))); if (!inserted) @@ -809,9 +772,10 @@ TaggedCache< SharedPointerType, Hash, KeyEqual, - Mutex>:: - initialFetch(key_type const& key, std::lock_guard const& l) + Mutex>::initialFetch(key_type const& key) { + std::lock_guard lock(lockPartition(key)); + auto cit = m_cache.find(key); if (cit == m_cache.end()) return {}; @@ -819,7 +783,7 @@ TaggedCache< Entry& entry = cit->second; if (entry.isCached()) { - ++m_hits; + m_hits.fetch_add(1, std::memory_order_relaxed); entry.touch(m_clock.now()); return entry.ptr.getStrong(); } @@ -827,12 +791,13 @@ TaggedCache< if (entry.isCached()) { // independent of cache size, so not counted as a hit - ++m_cache_count; + m_cache_count.fetch_add(1, std::memory_order_relaxed); entry.touch(m_clock.now()); return entry.ptr.getStrong(); } m_cache.erase(cit); + return {}; } @@ -861,10 +826,11 @@ TaggedCache< { beast::insight::Gauge::value_type hit_rate(0); { - std::lock_guard lock(m_mutex); - auto const total(m_hits + m_misses); + auto const hits = m_hits.load(std::memory_order_relaxed); + auto const misses = m_misses.load(std::memory_order_relaxed); + auto const total = hits + misses; if (total != 0) - hit_rate = (m_hits * 100) / total; + hit_rate = (hits * 100) / total; } m_stats.hit_rate.set(hit_rate); } @@ -895,12 +861,16 @@ TaggedCache< typename KeyValueCacheType::map_type& partition, SweptPointersVector& stuffToSweep, std::atomic& allRemovals, - std::lock_guard const&) + Mutex& partitionLock) { return std::thread([&, this]() { + beast::setCurrentThreadName("sweep-KVCache"); + int cacheRemovals = 0; int mapRemovals = 0; + std::lock_guard lock(partitionLock); + // Keep references to all the stuff we sweep // so that we can destroy them outside the lock. stuffToSweep.reserve(partition.size()); @@ -984,12 +954,16 @@ TaggedCache< typename KeyOnlyCacheType::map_type& partition, SweptPointersVector&, std::atomic& allRemovals, - std::lock_guard const&) + Mutex& partitionLock) { return std::thread([&, this]() { + beast::setCurrentThreadName("sweep-KCache"); + int cacheRemovals = 0; int mapRemovals = 0; + std::lock_guard lock(partitionLock); + // Keep references to all the stuff we sweep // so that we can destroy them outside the lock. { @@ -1024,6 +998,29 @@ TaggedCache< }); } +template < + class Key, + class T, + bool IsKeyCache, + class SharedWeakUnionPointer, + class SharedPointerType, + class Hash, + class KeyEqual, + class Mutex> +inline Mutex& +TaggedCache< + Key, + T, + IsKeyCache, + SharedWeakUnionPointer, + SharedPointerType, + Hash, + KeyEqual, + Mutex>::lockPartition(key_type const& key) const +{ + return partitionLocks_[m_cache.partition_index(key)]; +} + } // namespace ripple #endif diff --git a/include/xrpl/basics/algorithm.h b/include/xrpl/basics/algorithm.h index ed6e8080d9..673d5e955b 100644 --- a/include/xrpl/basics/algorithm.h +++ b/include/xrpl/basics/algorithm.h @@ -20,7 +20,6 @@ #ifndef RIPPLE_ALGORITHM_H_INCLUDED #define RIPPLE_ALGORITHM_H_INCLUDED -#include #include namespace ripple { diff --git a/include/xrpl/basics/hardened_hash.h b/include/xrpl/basics/hardened_hash.h index 0b77b0a07a..aae6c55dff 100644 --- a/include/xrpl/basics/hardened_hash.h +++ b/include/xrpl/basics/hardened_hash.h @@ -24,12 +24,8 @@ #include #include -#include #include #include -#include -#include -#include #include namespace ripple { diff --git a/include/xrpl/basics/mulDiv.h b/include/xrpl/basics/mulDiv.h index e338f87c81..96d466f6c7 100644 --- a/include/xrpl/basics/mulDiv.h +++ b/include/xrpl/basics/mulDiv.h @@ -23,7 +23,6 @@ #include #include #include -#include namespace ripple { auto constexpr muldiv_max = std::numeric_limits::max(); diff --git a/include/xrpl/basics/partitioned_unordered_map.h b/include/xrpl/basics/partitioned_unordered_map.h index 4e503ad0fa..ecaf16a47e 100644 --- a/include/xrpl/basics/partitioned_unordered_map.h +++ b/include/xrpl/basics/partitioned_unordered_map.h @@ -277,6 +277,12 @@ public: return map_; } + partition_map_type const& + map() const + { + return map_; + } + iterator begin() { @@ -321,6 +327,12 @@ public: return cend(); } + std::size_t + partition_index(key_type const& key) const + { + return partitioner(key); + } + private: template void diff --git a/include/xrpl/basics/tagged_integer.h b/include/xrpl/basics/tagged_integer.h index 471fa8eb1e..ed30b6f120 100644 --- a/include/xrpl/basics/tagged_integer.h +++ b/include/xrpl/basics/tagged_integer.h @@ -24,10 +24,8 @@ #include -#include #include #include -#include namespace ripple { diff --git a/include/xrpl/beast/clock/abstract_clock.h b/include/xrpl/beast/clock/abstract_clock.h index 128ab82b4b..7b0f04225f 100644 --- a/include/xrpl/beast/clock/abstract_clock.h +++ b/include/xrpl/beast/clock/abstract_clock.h @@ -20,9 +20,6 @@ #ifndef BEAST_CHRONO_ABSTRACT_CLOCK_H_INCLUDED #define BEAST_CHRONO_ABSTRACT_CLOCK_H_INCLUDED -#include -#include - namespace beast { /** Abstract interface to a clock. diff --git a/include/xrpl/beast/clock/manual_clock.h b/include/xrpl/beast/clock/manual_clock.h index 32ff76bb07..a0e82b7014 100644 --- a/include/xrpl/beast/clock/manual_clock.h +++ b/include/xrpl/beast/clock/manual_clock.h @@ -23,6 +23,8 @@ #include #include +#include + namespace beast { /** Manual clock implementation. diff --git a/include/xrpl/beast/container/aged_container_utility.h b/include/xrpl/beast/container/aged_container_utility.h index b64cefbf5a..d315f05346 100644 --- a/include/xrpl/beast/container/aged_container_utility.h +++ b/include/xrpl/beast/container/aged_container_utility.h @@ -22,6 +22,7 @@ #include +#include #include namespace beast { diff --git a/include/xrpl/beast/container/detail/aged_associative_container.h b/include/xrpl/beast/container/detail/aged_associative_container.h index 5ff7901552..678fbe4e17 100644 --- a/include/xrpl/beast/container/detail/aged_associative_container.h +++ b/include/xrpl/beast/container/detail/aged_associative_container.h @@ -20,8 +20,6 @@ #ifndef BEAST_CONTAINER_DETAIL_AGED_ASSOCIATIVE_CONTAINER_H_INCLUDED #define BEAST_CONTAINER_DETAIL_AGED_ASSOCIATIVE_CONTAINER_H_INCLUDED -#include - namespace beast { namespace detail { diff --git a/include/xrpl/beast/container/detail/aged_ordered_container.h b/include/xrpl/beast/container/detail/aged_ordered_container.h index 8c978d0517..ef3e1b5ea1 100644 --- a/include/xrpl/beast/container/detail/aged_ordered_container.h +++ b/include/xrpl/beast/container/detail/aged_ordered_container.h @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include diff --git a/include/xrpl/beast/container/detail/aged_unordered_container.h b/include/xrpl/beast/container/detail/aged_unordered_container.h index 3b9c83a014..23200ae007 100644 --- a/include/xrpl/beast/container/detail/aged_unordered_container.h +++ b/include/xrpl/beast/container/detail/aged_unordered_container.h @@ -3257,7 +3257,6 @@ operator==(aged_unordered_container< { if (size() != other.size()) return false; - using EqRng = std::pair; for (auto iter(cbegin()), last(cend()); iter != last;) { auto const& k(extract(*iter)); diff --git a/include/xrpl/beast/core/LexicalCast.h b/include/xrpl/beast/core/LexicalCast.h index aa67bcad50..5551e1f2dc 100644 --- a/include/xrpl/beast/core/LexicalCast.h +++ b/include/xrpl/beast/core/LexicalCast.h @@ -29,11 +29,9 @@ #include #include #include -#include #include #include #include -#include namespace beast { diff --git a/include/xrpl/beast/hash/hash_append.h b/include/xrpl/beast/hash/hash_append.h index 6b11fe1eb3..e113567ab1 100644 --- a/include/xrpl/beast/hash/hash_append.h +++ b/include/xrpl/beast/hash/hash_append.h @@ -24,14 +24,36 @@ #include #include +/* + +Workaround for overzealous clang warning, which trips on libstdc++ headers + + In file included from + /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_algo.h:61: + /usr/lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_tempbuf.h:263:8: + error: 'get_temporary_buffer> *>>' is deprecated + [-Werror,-Wdeprecated-declarations] 263 | + std::get_temporary_buffer(_M_original_len)); + ^ +*/ + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +#include + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + #include #include -#include #include -#include -#include #include -#include #include #include #include diff --git a/include/xrpl/beast/net/IPAddress.h b/include/xrpl/beast/net/IPAddress.h index 62469cfda1..fb5dac90ec 100644 --- a/include/xrpl/beast/net/IPAddress.h +++ b/include/xrpl/beast/net/IPAddress.h @@ -29,11 +29,7 @@ #include #include -#include -#include -#include #include -#include //------------------------------------------------------------------------------ diff --git a/include/xrpl/beast/net/IPAddressV4.h b/include/xrpl/beast/net/IPAddressV4.h index 98a92dba20..c65adae05b 100644 --- a/include/xrpl/beast/net/IPAddressV4.h +++ b/include/xrpl/beast/net/IPAddressV4.h @@ -24,12 +24,6 @@ #include -#include -#include -#include -#include -#include - namespace beast { namespace IP { diff --git a/include/xrpl/beast/net/IPAddressV6.h b/include/xrpl/beast/net/IPAddressV6.h index 4a4ef73b86..9e24b228e5 100644 --- a/include/xrpl/beast/net/IPAddressV6.h +++ b/include/xrpl/beast/net/IPAddressV6.h @@ -24,12 +24,6 @@ #include -#include -#include -#include -#include -#include - namespace beast { namespace IP { diff --git a/include/xrpl/beast/net/IPEndpoint.h b/include/xrpl/beast/net/IPEndpoint.h index 345ba4b8da..8d43eb0ba9 100644 --- a/include/xrpl/beast/net/IPEndpoint.h +++ b/include/xrpl/beast/net/IPEndpoint.h @@ -25,7 +25,6 @@ #include #include -#include #include #include diff --git a/include/xrpl/beast/rfc2616.h b/include/xrpl/beast/rfc2616.h index 648fbc22e2..d6b3fa3cda 100644 --- a/include/xrpl/beast/rfc2616.h +++ b/include/xrpl/beast/rfc2616.h @@ -28,10 +28,8 @@ #include #include -#include #include #include -#include #include namespace beast { diff --git a/include/xrpl/beast/test/yield_to.h b/include/xrpl/beast/test/yield_to.h index 9e9f83b897..27a3a2db20 100644 --- a/include/xrpl/beast/test/yield_to.h +++ b/include/xrpl/beast/test/yield_to.h @@ -13,7 +13,6 @@ #include #include -#include #include #include #include diff --git a/include/xrpl/beast/unit_test/reporter.h b/include/xrpl/beast/unit_test/reporter.h index e7a7d4b3ad..0054daab98 100644 --- a/include/xrpl/beast/unit_test/reporter.h +++ b/include/xrpl/beast/unit_test/reporter.h @@ -16,7 +16,6 @@ #include #include -#include #include #include #include diff --git a/include/xrpl/beast/unit_test/runner.h b/include/xrpl/beast/unit_test/runner.h index 283f7c8723..977cc45035 100644 --- a/include/xrpl/beast/unit_test/runner.h +++ b/include/xrpl/beast/unit_test/runner.h @@ -13,7 +13,6 @@ #include #include -#include #include namespace beast { diff --git a/include/xrpl/beast/utility/rngfill.h b/include/xrpl/beast/utility/rngfill.h index 0188e5c529..e1b47618ba 100644 --- a/include/xrpl/beast/utility/rngfill.h +++ b/include/xrpl/beast/utility/rngfill.h @@ -31,36 +31,28 @@ namespace beast { template void -rngfill(void* buffer, std::size_t bytes, Generator& g) +rngfill(void* const buffer, std::size_t const bytes, Generator& g) { using result_type = typename Generator::result_type; + constexpr std::size_t result_size = sizeof(result_type); - while (bytes >= sizeof(result_type)) + std::uint8_t* const buffer_start = static_cast(buffer); + std::size_t const complete_iterations = bytes / result_size; + std::size_t const bytes_remaining = bytes % result_size; + + for (std::size_t count = 0; count < complete_iterations; ++count) { - auto const v = g(); - std::memcpy(buffer, &v, sizeof(v)); - buffer = reinterpret_cast(buffer) + sizeof(v); - bytes -= sizeof(v); + result_type const v = g(); + std::size_t const offset = count * result_size; + std::memcpy(buffer_start + offset, &v, result_size); } - XRPL_ASSERT( - bytes < sizeof(result_type), "beast::rngfill(void*) : maximum bytes"); - -#ifdef __GNUC__ - // gcc 11.1 (falsely) warns about an array-bounds overflow in release mode. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" -#endif - - if (bytes > 0) + if (bytes_remaining > 0) { - auto const v = g(); - std::memcpy(buffer, &v, bytes); + result_type const v = g(); + std::size_t const offset = complete_iterations * result_size; + std::memcpy(buffer_start + offset, &v, bytes_remaining); } - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif } template < diff --git a/include/xrpl/json/json_value.h b/include/xrpl/json/json_value.h index 2e815b79f2..272d12d680 100644 --- a/include/xrpl/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -26,7 +26,6 @@ #include #include #include -#include #include /** \brief JSON (JavaScript Object Notation). diff --git a/include/xrpl/protocol/AccountID.h b/include/xrpl/protocol/AccountID.h index 295cf41e4f..d546346bb4 100644 --- a/include/xrpl/protocol/AccountID.h +++ b/include/xrpl/protocol/AccountID.h @@ -29,7 +29,6 @@ #include #include -#include #include #include diff --git a/include/xrpl/protocol/ApiVersion.h b/include/xrpl/protocol/ApiVersion.h index dd09cf6bd1..deafafa513 100644 --- a/include/xrpl/protocol/ApiVersion.h +++ b/include/xrpl/protocol/ApiVersion.h @@ -20,7 +20,6 @@ #ifndef RIPPLE_PROTOCOL_APIVERSION_H_INCLUDED #define RIPPLE_PROTOCOL_APIVERSION_H_INCLUDED -#include #include #include diff --git a/include/xrpl/protocol/ErrorCodes.h b/include/xrpl/protocol/ErrorCodes.h index 9c9319ba42..f06b927566 100644 --- a/include/xrpl/protocol/ErrorCodes.h +++ b/include/xrpl/protocol/ErrorCodes.h @@ -169,6 +169,8 @@ enum warning_code_i { warnRPC_AMENDMENT_BLOCKED = 1002, warnRPC_EXPIRED_VALIDATOR_LIST = 1003, // unused = 1004 + warnRPC_FIELDS_DEPRECATED = 2004, // rippled needs to maintain + // compatibility with Clio on this code. }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 304cac5bc6..5844a70eb0 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -24,7 +24,6 @@ #include -#include #include #include #include @@ -54,12 +53,19 @@ * then change the macro parameter in features.macro to * `VoteBehavior::DefaultYes`. The communication process is beyond * the scope of these instructions. + + * 5) If a supported feature (`Supported::yes`) was _ever_ in a released + * version, it can never be changed back to `Supported::no`, because + * it _may_ still become enabled at any time. This would cause newer + * versions of `rippled` to become amendment blocked. + * Instead, to prevent newer versions from voting on the feature, use + * `VoteBehavior::Obsolete`. Obsolete features can not be voted for + * by any versions of `rippled` built with that setting, but will still + * work correctly if they get enabled. If a feature remains obsolete + * for long enough that _all_ clients that could vote for it are + * amendment blocked, the feature can be removed from the code + * as if it was unsupported. * - * 5) A feature marked as Obsolete can mean either: - * 1) It is in the ledger (marked as Supported::yes) and it is on its way to - * become Retired - * 2) The feature is not in the ledger (has always been marked as - * Supported::no) and the code to support it has been removed * * When a feature has been enabled for several years, the conditional code * may be removed, and the feature "retired". To retire a feature: diff --git a/include/xrpl/protocol/FeeUnits.h b/include/xrpl/protocol/FeeUnits.h index c6949a434c..31a1886b7f 100644 --- a/include/xrpl/protocol/FeeUnits.h +++ b/include/xrpl/protocol/FeeUnits.h @@ -27,14 +27,9 @@ #include #include -#include -#include #include #include #include -#include -#include -#include namespace ripple { diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 57c8727ae6..3e3f2843c1 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -32,6 +32,7 @@ #include #include +#include namespace ripple { diff --git a/include/xrpl/protocol/Issue.h b/include/xrpl/protocol/Issue.h index 83ef337c35..eb4861f59b 100644 --- a/include/xrpl/protocol/Issue.h +++ b/include/xrpl/protocol/Issue.h @@ -24,9 +24,6 @@ #include #include -#include -#include - namespace ripple { /** A currency issued by an account. diff --git a/include/xrpl/protocol/MultiApiJson.h b/include/xrpl/protocol/MultiApiJson.h index 1e35bdbda2..4a3d0115de 100644 --- a/include/xrpl/protocol/MultiApiJson.h +++ b/include/xrpl/protocol/MultiApiJson.h @@ -28,7 +28,6 @@ #include #include #include -#include #include #include diff --git a/include/xrpl/protocol/Permissions.h b/include/xrpl/protocol/Permissions.h index 8ba53d94d7..67f3eea8d7 100644 --- a/include/xrpl/protocol/Permissions.h +++ b/include/xrpl/protocol/Permissions.h @@ -25,7 +25,6 @@ #include #include #include -#include namespace ripple { /** diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 898fd06fbd..bd39233cca 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -22,7 +22,6 @@ #include #include -#include #include diff --git a/include/xrpl/protocol/PublicKey.h b/include/xrpl/protocol/PublicKey.h index c68656877c..9bf01e5cda 100644 --- a/include/xrpl/protocol/PublicKey.h +++ b/include/xrpl/protocol/PublicKey.h @@ -32,7 +32,6 @@ #include #include #include -#include namespace ripple { diff --git a/include/xrpl/protocol/SOTemplate.h b/include/xrpl/protocol/SOTemplate.h index 9fd4cbf19d..14497b4222 100644 --- a/include/xrpl/protocol/SOTemplate.h +++ b/include/xrpl/protocol/SOTemplate.h @@ -25,7 +25,6 @@ #include #include -#include #include namespace ripple { diff --git a/include/xrpl/protocol/STBase.h b/include/xrpl/protocol/STBase.h index eec9a97987..3f5a3b57ab 100644 --- a/include/xrpl/protocol/STBase.h +++ b/include/xrpl/protocol/STBase.h @@ -24,7 +24,6 @@ #include #include -#include #include #include #include diff --git a/include/xrpl/protocol/STBlob.h b/include/xrpl/protocol/STBlob.h index 80832b2688..374abd2a7c 100644 --- a/include/xrpl/protocol/STBlob.h +++ b/include/xrpl/protocol/STBlob.h @@ -27,7 +27,6 @@ #include #include -#include namespace ripple { diff --git a/include/xrpl/protocol/STValidation.h b/include/xrpl/protocol/STValidation.h index 11ec733c01..2aa74203a2 100644 --- a/include/xrpl/protocol/STValidation.h +++ b/include/xrpl/protocol/STValidation.h @@ -28,8 +28,6 @@ #include #include -#include -#include #include #include diff --git a/include/xrpl/protocol/Serializer.h b/include/xrpl/protocol/Serializer.h index 9c77aa4111..5ea4d3ca96 100644 --- a/include/xrpl/protocol/Serializer.h +++ b/include/xrpl/protocol/Serializer.h @@ -33,7 +33,6 @@ #include #include -#include #include namespace ripple { diff --git a/include/xrpl/protocol/Sign.h b/include/xrpl/protocol/Sign.h index 7e1156ceda..5aa9fabddc 100644 --- a/include/xrpl/protocol/Sign.h +++ b/include/xrpl/protocol/Sign.h @@ -25,8 +25,6 @@ #include #include -#include - namespace ripple { /** Sign an STObject diff --git a/include/xrpl/protocol/XChainAttestations.h b/include/xrpl/protocol/XChainAttestations.h index 721950ca9c..92fd04731d 100644 --- a/include/xrpl/protocol/XChainAttestations.h +++ b/include/xrpl/protocol/XChainAttestations.h @@ -35,8 +35,6 @@ #include #include -#include -#include #include namespace ripple { diff --git a/include/xrpl/protocol/detail/b58_utils.h b/include/xrpl/protocol/detail/b58_utils.h index 8fc85f390b..ecd301524f 100644 --- a/include/xrpl/protocol/detail/b58_utils.h +++ b/include/xrpl/protocol/detail/b58_utils.h @@ -27,7 +27,6 @@ #include #include -#include #include #include #include diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 2442abef7f..e2725d1fc0 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,12 +32,15 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo) +XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) +XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo) +XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo) // Check flags in Credential transactions @@ -94,7 +97,6 @@ XRPL_FEATURE(HardenedValidations, Supported::yes, VoteBehavior::DefaultYe // fix1781: XRPEndpointSteps should be included in the circular payment check XRPL_FIX (1781, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(RequireFullyCanonicalSig, Supported::yes, VoteBehavior::DefaultYes) -// fixQualityUpperBound should be activated before FlowCross XRPL_FIX (QualityUpperBound, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(DeletableAccounts, Supported::yes, VoteBehavior::DefaultYes) XRPL_FIX (PayChanRecipientOwnerDir, Supported::yes, VoteBehavior::DefaultYes) @@ -112,7 +114,6 @@ XRPL_FIX (1571, Supported::yes, VoteBehavior::DefaultYe XRPL_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(DepositAuth, Supported::yes, VoteBehavior::DefaultYes) XRPL_FIX (1513, Supported::yes, VoteBehavior::DefaultYes) -XRPL_FEATURE(FlowCross, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(Flow, Supported::yes, VoteBehavior::DefaultYes) // The following amendments are obsolete, but must remain supported @@ -130,8 +131,6 @@ XRPL_FIX (NFTokenNegOffer, Supported::yes, VoteBehavior::Obsolete) XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete) XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete) -// This sits here temporarily and will be moved to another section soon -XRPL_FEATURE(OwnerPaysFee, Supported::no, VoteBehavior::Obsolete) // The following amendments have been active for at least two years. Their // pre-amendment code has been removed and the identifiers are deprecated. @@ -152,3 +151,4 @@ XRPL_RETIRE(fix1201) XRPL_RETIRE(fix1512) XRPL_RETIRE(fix1523) XRPL_RETIRE(fix1528) +XRPL_RETIRE(FlowCross) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 46c6e60db3..11306ee0f5 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -482,8 +482,7 @@ LEDGER_ENTRY(ltDELEGATE, 0x0083, Delegate, delegate, ({ })) /** A ledger object representing a single asset vault. - - \sa keylet::mptoken + \sa keylet::vault */ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({ {sfPreviousTxnID, soeREQUIRED}, diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 1d59e71850..89e9a16df5 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -409,6 +409,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::de {sfTransferFee, soeOPTIONAL}, {sfMaximumAmount, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL}, + {sfDomainID, soeOPTIONAL}, })) /** This transaction type destroys a MPTokensIssuance instance */ @@ -420,6 +421,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation:: TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({ {sfMPTokenIssuanceID, soeREQUIRED}, {sfHolder, soeOPTIONAL}, + {sfDomainID, soeOPTIONAL}, })) /** This transaction type authorizes a MPToken instance */ @@ -478,7 +480,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({ {sfAsset, soeREQUIRED, soeMPTSupported}, {sfAssetsMaximum, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL}, - {sfDomainID, soeOPTIONAL}, // PermissionedDomainID + {sfDomainID, soeOPTIONAL}, {sfWithdrawalPolicy, soeOPTIONAL}, {sfData, soeOPTIONAL}, })) @@ -487,7 +489,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({ TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({ {sfVaultID, soeREQUIRED}, {sfAssetsMaximum, soeOPTIONAL}, - {sfDomainID, soeOPTIONAL}, // PermissionedDomainID + {sfDomainID, soeOPTIONAL}, {sfData, soeOPTIONAL}, })) @@ -507,6 +509,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, ({ {sfVaultID, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, {sfDestination, soeOPTIONAL}, + {sfDestinationTag, soeOPTIONAL}, })) /** This transaction claws back tokens from a vault. */ diff --git a/include/xrpl/protocol/digest.h b/include/xrpl/protocol/digest.h index efec616a0c..303fbafe4f 100644 --- a/include/xrpl/protocol/digest.h +++ b/include/xrpl/protocol/digest.h @@ -25,7 +25,6 @@ #include -#include #include namespace ripple { diff --git a/include/xrpl/protocol/json_get_or_throw.h b/include/xrpl/protocol/json_get_or_throw.h index c59b5a71a3..74d1779339 100644 --- a/include/xrpl/protocol/json_get_or_throw.h +++ b/include/xrpl/protocol/json_get_or_throw.h @@ -10,7 +10,6 @@ #include #include #include -#include namespace Json { struct JsonMissingKeyError : std::exception diff --git a/include/xrpl/resource/Charge.h b/include/xrpl/resource/Charge.h index a75ad32624..ead46ca31f 100644 --- a/include/xrpl/resource/Charge.h +++ b/include/xrpl/resource/Charge.h @@ -20,7 +20,6 @@ #ifndef RIPPLE_RESOURCE_CHARGE_H_INCLUDED #define RIPPLE_RESOURCE_CHARGE_H_INCLUDED -#include #include namespace ripple { diff --git a/include/xrpl/resource/Gossip.h b/include/xrpl/resource/Gossip.h index 6e2a86ecd7..3495de5b95 100644 --- a/include/xrpl/resource/Gossip.h +++ b/include/xrpl/resource/Gossip.h @@ -22,6 +22,8 @@ #include +#include + namespace ripple { namespace Resource { diff --git a/include/xrpl/server/detail/BaseHTTPPeer.h b/include/xrpl/server/detail/BaseHTTPPeer.h index 51ac866e1e..b065a97cf0 100644 --- a/include/xrpl/server/detail/BaseHTTPPeer.h +++ b/include/xrpl/server/detail/BaseHTTPPeer.h @@ -41,7 +41,6 @@ #include #include #include -#include #include namespace ripple { diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index 90de885579..88e19db8cd 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -37,10 +37,8 @@ #include #include -#include #include #include -#include namespace ripple { diff --git a/include/xrpl/server/detail/io_list.h b/include/xrpl/server/detail/io_list.h index fbf60c9a7f..fba8b28f87 100644 --- a/include/xrpl/server/detail/io_list.h +++ b/include/xrpl/server/detail/io_list.h @@ -26,7 +26,6 @@ #include #include #include -#include #include #include diff --git a/src/libxrpl/basics/FileUtilities.cpp b/src/libxrpl/basics/FileUtilities.cpp index 291eb43c7b..ffb9792614 100644 --- a/src/libxrpl/basics/FileUtilities.cpp +++ b/src/libxrpl/basics/FileUtilities.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -55,7 +56,7 @@ getFileContents( return {}; } - ifstream fileStream(fullPath, std::ios::in); + std::ifstream fileStream(fullPath.string(), std::ios::in); if (!fileStream) { @@ -85,7 +86,8 @@ writeFileContents( using namespace boost::filesystem; using namespace boost::system::errc; - ofstream fileStream(destPath, std::ios::out | std::ios::trunc); + std::ofstream fileStream( + destPath.string(), std::ios::out | std::ios::trunc); if (!fileStream) { diff --git a/src/libxrpl/protocol/Feature.cpp b/src/libxrpl/protocol/Feature.cpp index e6442d2663..eeeee1c185 100644 --- a/src/libxrpl/protocol/Feature.cpp +++ b/src/libxrpl/protocol/Feature.cpp @@ -254,7 +254,7 @@ FeatureCollections::registerFeature( { check(!readOnly, "Attempting to register a feature after startup."); check( - support == Supported::yes || vote != VoteBehavior::DefaultYes, + support == Supported::yes || vote == VoteBehavior::DefaultNo, "Invalid feature parameters. Must be supported to be up-voted."); Feature const* i = getByName(name); if (!i) @@ -268,7 +268,7 @@ FeatureCollections::registerFeature( features.emplace_back(name, f); auto const getAmendmentSupport = [=]() { - if (vote == VoteBehavior::Obsolete && support == Supported::yes) + if (vote == VoteBehavior::Obsolete) return AmendmentSupport::Retired; return support == Supported::yes ? AmendmentSupport::Supported : AmendmentSupport::Unsupported; diff --git a/src/libxrpl/protocol/PublicKey.cpp b/src/libxrpl/protocol/PublicKey.cpp index cdf646e0f8..54b50c80ef 100644 --- a/src/libxrpl/protocol/PublicKey.cpp +++ b/src/libxrpl/protocol/PublicKey.cpp @@ -107,8 +107,9 @@ sliceToHex(Slice const& slice) } for (int i = 0; i < slice.size(); ++i) { - s += "0123456789ABCDEF"[((slice[i] & 0xf0) >> 4)]; - s += "0123456789ABCDEF"[((slice[i] & 0x0f) >> 0)]; + constexpr char hex[] = "0123456789ABCDEF"; + s += hex[((slice[i] & 0xf0) >> 4)]; + s += hex[((slice[i] & 0x0f) >> 0)]; } return s; } diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 615012dba4..8be8f906a5 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -671,12 +671,12 @@ isMemoOkay(STObject const& st, std::string& reason) "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"); - for (char c : symbols) + for (unsigned char c : symbols) a[c] = 1; return a; }(); - for (auto c : *optData) + for (unsigned char c : *optData) { if (!allowedSymbols[c]) { diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index 2083fc8eaf..2343a6a794 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -185,6 +185,18 @@ TxMeta::getAffectedAccounts() const { auto issuer = lim->getIssuer(); + if (issuer.isNonZero()) + list.insert(issuer); + } + } + else if (field.getFName() == sfMPTokenIssuanceID) + { + auto mptID = + dynamic_cast const*>(&field); + if (mptID != nullptr) + { + auto issuer = MPTIssue(mptID->value()).getIssuer(); + if (issuer.isNonZero()) list.insert(issuer); } diff --git a/src/libxrpl/protocol/tokens.cpp b/src/libxrpl/protocol/tokens.cpp index a822b1937f..52cffd7a5c 100644 --- a/src/libxrpl/protocol/tokens.cpp +++ b/src/libxrpl/protocol/tokens.cpp @@ -544,7 +544,7 @@ b58_to_b256_be(std::string_view input, std::span out) XRPL_ASSERT( num_b_58_10_coeffs <= b_58_10_coeff.size(), "ripple::b58_fast::detail::b58_to_b256_be : maximum coeff"); - for (auto c : input.substr(0, partial_coeff_len)) + for (unsigned char c : input.substr(0, partial_coeff_len)) { auto cur_val = ::ripple::alphabetReverse[c]; if (cur_val < 0) @@ -558,7 +558,7 @@ b58_to_b256_be(std::string_view input, std::span out) { for (int j = 0; j < num_full_coeffs; ++j) { - auto c = input[partial_coeff_len + j * 10 + i]; + unsigned char c = input[partial_coeff_len + j * 10 + i]; auto cur_val = ::ripple::alphabetReverse[c]; if (cur_val < 0) { diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 77e908d5fe..9564911664 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -17,26 +17,25 @@ #include #include -#include -#include +#include + +#include #include -#include - namespace ripple { namespace test { -class AMMClawback_test : public jtx::AMMTest +class AMMClawback_test : public beast::unit_test::suite { void - testInvalidRequest(FeatureBitset features) + testInvalidRequest() { testcase("test invalid request"); using namespace jtx; // Test if holder does not exist. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(100000), gw, alice); @@ -47,8 +46,9 @@ class AMMClawback_test : public jtx::AMMTest env.close(); env.require(flags(gw, asfAllowTrustLineClawback)); + auto const USD = gw["USD"]; env.trust(USD(10000), alice); - env(pay(gw, alice, gw["USD"](100))); + env(pay(gw, alice, USD(100))); AMM amm(env, alice, XRP(100), USD(100)); env.close(); @@ -61,7 +61,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if asset pair provided does not exist. This should // return terNO_AMM error. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(100000), gw, alice); @@ -87,14 +87,14 @@ class AMMClawback_test : public jtx::AMMTest // The AMM account does not exist at all now. // It should return terNO_AMM error. - env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + env(amm::ammClawback(gw, alice, USD, gw["EUR"], std::nullopt), ter(terNO_AMM)); } // Test if the issuer field and holder field is the same. This should // return temMALFORMED error. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -124,7 +124,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if the Asset field matches the Account field. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -156,7 +156,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if the Amount field matches the Asset field. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -189,7 +189,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if the Amount is invalid, which is less than zero. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -230,7 +230,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if the issuer did not set asfAllowTrustLineClawback, AMMClawback // transaction is prohibited. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -241,7 +241,7 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(1000), alice); env(pay(gw, alice, USD(100))); env.close(); - env.require(balance(alice, gw["USD"](100))); + env.require(balance(alice, USD(100))); env.require(balance(gw, alice["USD"](-100))); // gw creates AMM pool of XRP/USD. @@ -255,7 +255,7 @@ class AMMClawback_test : public jtx::AMMTest // Test invalid flag. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -283,7 +283,7 @@ class AMMClawback_test : public jtx::AMMTest // Test if tfClawTwoAssets is set when the two assets in the AMM pool // are not issued by the same issuer. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -314,7 +314,7 @@ class AMMClawback_test : public jtx::AMMTest // Test clawing back XRP is being prohibited. { - Env env(*this, features); + Env env(*this); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(1000000), gw, alice); @@ -400,7 +400,7 @@ class AMMClawback_test : public jtx::AMMTest env(pay(gw, alice, USD(3000))); env.close(); env.require(balance(gw, alice["USD"](-3000))); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // gw2 issues 3000 EUR to Alice. auto const EUR = gw2["EUR"]; @@ -408,7 +408,7 @@ class AMMClawback_test : public jtx::AMMTest env(pay(gw2, alice, EUR(3000))); env.close(); env.require(balance(gw2, alice["EUR"](-3000))); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // Alice creates AMM pool of EUR/USD. AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); @@ -426,13 +426,13 @@ class AMMClawback_test : public jtx::AMMTest // USD into the pool, then she has 1000 USD. And 1000 USD was clawed // back from the AMM pool, so she still has 1000 USD. env.require(balance(gw, alice["USD"](-1000))); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); // Alice's initial balance for EUR is 3000 EUR. Alice deposited 1000 // EUR into the pool, 500 EUR was withdrawn proportionally. So she // has 2500 EUR now. env.require(balance(gw2, alice["EUR"](-2500))); - env.require(balance(alice, gw2["EUR"](2500))); + env.require(balance(alice, EUR(2500))); // 1000 USD and 500 EUR was withdrawn from the AMM pool, so the // current balance is 1000 USD and 500 EUR. @@ -452,12 +452,12 @@ class AMMClawback_test : public jtx::AMMTest // Alice should still has 1000 USD because gw clawed back from the // AMM pool. env.require(balance(gw, alice["USD"](-1000))); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); // Alice should has 3000 EUR now because another 500 EUR was // withdrawn. env.require(balance(gw2, alice["EUR"](-3000))); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // amm is automatically deleted. BEAST_EXPECT(!amm.ammExists()); @@ -483,7 +483,7 @@ class AMMClawback_test : public jtx::AMMTest env(pay(gw, alice, USD(3000))); env.close(); env.require(balance(gw, alice["USD"](-3000))); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // Alice creates AMM pool of XRP/USD. AMM amm(env, alice, XRP(1000), USD(2000), ter(tesSUCCESS)); @@ -503,11 +503,12 @@ class AMMClawback_test : public jtx::AMMTest // USD into the pool, then she has 1000 USD. And 1000 USD was clawed // back from the AMM pool, so she still has 1000 USD. env.require(balance(gw, alice["USD"](-1000))); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); // Alice will get 500 XRP back. BEAST_EXPECT( expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500))); + aliceXrpBalance = env.balance(alice, XRP); // 1000 USD and 500 XRP was withdrawn from the AMM pool, so the // current balance is 1000 USD and 500 XRP. @@ -527,11 +528,11 @@ class AMMClawback_test : public jtx::AMMTest // Alice should still has 1000 USD because gw clawed back from the // AMM pool. env.require(balance(gw, alice["USD"](-1000))); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); - // Alice will get another 1000 XRP back. + // Alice will get another 500 XRP back. BEAST_EXPECT( - expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(500))); // amm is automatically deleted. BEAST_EXPECT(!amm.ammExists()); @@ -568,14 +569,14 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(6000))); env.close(); - env.require(balance(alice, gw["USD"](6000))); + env.require(balance(alice, USD(6000))); // gw2 issues 6000 EUR to Alice. auto const EUR = gw2["EUR"]; env.trust(EUR(100000), alice); env(pay(gw2, alice, EUR(6000))); env.close(); - env.require(balance(alice, gw2["EUR"](6000))); + env.require(balance(alice, EUR(6000))); // Alice creates AMM pool of EUR/USD AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS)); @@ -596,12 +597,12 @@ class AMMClawback_test : public jtx::AMMTest // Alice's initial balance for USD is 6000 USD. Alice deposited 4000 // USD into the pool, then she has 2000 USD. And 1000 USD was clawed // back from the AMM pool, so she still has 2000 USD. - env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, USD(2000))); // Alice's initial balance for EUR is 6000 EUR. Alice deposited 5000 // EUR into the pool, 1250 EUR was withdrawn proportionally. So she // has 2500 EUR now. - env.require(balance(alice, gw2["EUR"](2250))); + env.require(balance(alice, EUR(2250))); // 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the // current balance is 3000 USD and 3750 EUR. @@ -627,7 +628,7 @@ class AMMClawback_test : public jtx::AMMTest // Alice should still has 2000 USD because gw clawed back from the // AMM pool. - env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, USD(2000))); if (!features[fixAMMv1_3]) BEAST_EXPECT(amm.expectBalances( @@ -650,23 +651,32 @@ class AMMClawback_test : public jtx::AMMTest env.close(); // Another 1 USD / 1.25 EUR was withdrawn. - env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, USD(2000))); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( STAmount{USD, UINT64_C(2499000000000002), -12}, STAmount{EUR, UINT64_C(3123750000000002), -12}, IOUAmount{2793966937885989, -12})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12})); + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2499000000000001), -12}, + STAmount{EUR, UINT64_C(3123750000000001), -12}, + IOUAmount{2793966937885988, -12})); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT( env.balance(alice, EUR) == - STAmount(EUR, UINT64_C(2'876'249999999998), -12)); - else + STAmount(EUR, UINT64_C(2876'249999999998), -12)); + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25)); + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT( + env.balance(alice, EUR) == + STAmount(EUR, UINT64_C(2876'249999999999), -12)); // gw clawback 4000 USD, exceeding the current balance. We // will clawback all. @@ -674,7 +684,7 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](2000))); + env.require(balance(alice, USD(2000))); // All alice's EUR in the pool goes back to alice. BEAST_EXPECT( @@ -745,6 +755,7 @@ class AMMClawback_test : public jtx::AMMTest else BEAST_EXPECT(amm2.expectBalances( EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); + amm2.deposit(alice, EUR(1000), XRP(3000)); if (!features[fixAMMv1_3]) BEAST_EXPECT(amm2.expectBalances( @@ -752,6 +763,7 @@ class AMMClawback_test : public jtx::AMMTest else BEAST_EXPECT(amm2.expectBalances( EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); + amm2.deposit(bob, EUR(1000), XRP(3000)); if (!features[fixAMMv1_3]) BEAST_EXPECT(amm2.expectBalances( @@ -772,27 +784,42 @@ class AMMClawback_test : public jtx::AMMTest // Alice's initial balance for USD is 6000 USD. Alice deposited 1000 // USD into the pool, then she has 5000 USD. And 500 USD was clawed // back from the AMM pool, so she still has 5000 USD. - env.require(balance(alice, gw["USD"](5000))); + env.require(balance(alice, USD(5000))); // Bob's balance is not changed. - env.require(balance(bob, gw["USD"](4000))); + env.require(balance(bob, USD(4000))); // Alice gets 1000 XRP back. - BEAST_EXPECT( - expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000))); + if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000) - XRPAmount(1))); + else + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000))); + aliceXrpBalance = env.balance(alice, XRP); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(2500), XRP(5000), IOUAmount{3535533905932738, -9})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(2500), XRP(5000), IOUAmount{3535533905932737, -9})); - if (!features[fixAMMv1_3]) + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + USD(2500), + XRPAmount(5000000001), + IOUAmount{3'535'533'905932738, -9})); + + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectLPTokens( alice, IOUAmount{7071067811865480, -10})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectLPTokens( alice, IOUAmount{7071067811865474, -10})); + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{707106781186548, -9})); + BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9})); @@ -800,50 +827,79 @@ class AMMClawback_test : public jtx::AMMTest env(amm::ammClawback(gw, bob, USD, XRP, USD(10)), ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](5000))); - env.require(balance(bob, gw["USD"](4000))); + env.require(balance(alice, USD(5000))); + env.require(balance(bob, USD(4000))); // Bob gets 20 XRP back. BEAST_EXPECT( expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20))); - if (!features[fixAMMv1_3]) + bobXrpBalance = env.balance(bob, XRP); + + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( STAmount{USD, UINT64_C(2490000000000001), -12}, XRP(4980), IOUAmount{3521391770309008, -9})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9})); - if (!features[fixAMMv1_3]) + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2490000000000001), -12}, + XRPAmount(4980000001), + IOUAmount{3521391'770309008, -9})); + + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectLPTokens( alice, IOUAmount{7071067811865480, -10})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectLPTokens( alice, IOUAmount{7071067811865474, -10})); - if (!features[fixAMMv1_3]) + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(alice, IOUAmount{707106781186548, -9})); + + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); // gw2 clawback 200 EUR from amm2. env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)), ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw2["EUR"](4000))); - env.require(balance(bob, gw2["EUR"](3000))); + env.require(balance(alice, EUR(4000))); + env.require(balance(bob, EUR(3000))); - // Alice gets 600 XRP back. - BEAST_EXPECT(expectLedgerEntryRoot( - env, alice, aliceXrpBalance + XRP(1000) + XRP(600))); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(600))); + else if (!features[fixAMMClawbackRounding]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(600))); + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(600) - XRPAmount{1})); + aliceXrpBalance = env.balance(alice, XRP); + + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm2.expectBalances( EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm2.expectBalances( EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm2.expectBalances( + EUR(2800), + XRPAmount(8400000001), + IOUAmount{4849742261192856, -9})); + if (!features[fixAMMv1_3]) BEAST_EXPECT(amm2.expectLPTokens( alice, IOUAmount{1385640646055103, -9})); @@ -864,38 +920,47 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](5000))); - env.require(balance(bob, gw["USD"](4000))); + env.require(balance(alice, USD(5000))); + env.require(balance(bob, USD(4000))); // Alice gets 1000 XRP back. - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); - else + env, alice, aliceXrpBalance + XRP(1000))); + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - - XRPAmount{1})); + env, alice, aliceXrpBalance + XRP(1000) - XRPAmount{1})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT(expectLedgerEntryRoot( + env, alice, aliceXrpBalance + XRP(1000))); + aliceXrpBalance = env.balance(alice, XRP); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT( amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9})); - if (!features[fixAMMv1_3]) + else if (features[fixAMMClawbackRounding] && features[fixAMMv1_3]) + BEAST_EXPECT( + amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9})); + + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( STAmount{USD, UINT64_C(1990000000000001), -12}, XRP(3980), IOUAmount{2814284989122460, -9})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(1'990), XRPAmount{3'980'000'001}, IOUAmount{2814284989122459, -9})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(1990000000000001), -12}, + XRPAmount{3'980'000'001}, + IOUAmount{2814284989122460, -9})); // gw clawback 1000 USD from bob in amm, which also exceeds bob's // balance in amm. All bob's lptoken in amm will be consumed, which @@ -904,22 +969,14 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](5000))); - env.require(balance(bob, gw["USD"](4000))); + env.require(balance(alice, USD(5000))); + env.require(balance(bob, USD(4000))); - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) - - XRPAmount{1})); - BEAST_EXPECT(expectLedgerEntryRoot( - env, bob, bobXrpBalance + XRP(20) + XRP(1980))); + BEAST_EXPECT(expectLedgerEntryRoot(env, alice, aliceXrpBalance)); + + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(1980))); + bobXrpBalance = env.balance(bob, XRP); // Now neither alice nor bob has any lptoken in amm. BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); @@ -932,35 +989,31 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw2["EUR"](4000))); - env.require(balance(bob, gw2["EUR"](3000))); + env.require(balance(alice, EUR(4000))); + env.require(balance(bob, EUR(3000))); // Alice gets another 2400 XRP back, bob's XRP balance remains the // same. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400) - XRPAmount{1})); - BEAST_EXPECT(expectLedgerEntryRoot( - env, bob, bobXrpBalance + XRP(20) + XRP(1980))); + BEAST_EXPECT( + expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(2400))); + + BEAST_EXPECT(expectLedgerEntryRoot(env, bob, bobXrpBalance)); + aliceXrpBalance = env.balance(alice, XRP); // Alice now does not have any lptoken in amm2 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm2.expectBalances( EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm2.expectBalances( EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm2.expectBalances( + EUR(2000), + XRPAmount(6000000001), + IOUAmount{3464101615137754, -9})); // gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's // balance. All bob's lptokens will be consumed, which corresponds @@ -969,36 +1022,32 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw2["EUR"](4000))); - env.require(balance(bob, gw2["EUR"](3000))); + env.require(balance(alice, EUR(4000))); + env.require(balance(bob, EUR(3000))); // Bob gets another 3000 XRP back. Alice's XRP balance remains the // same. - if (!features[fixAMMv1_3]) - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400))); - else - BEAST_EXPECT(expectLedgerEntryRoot( - env, - alice, - aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) + - XRP(2400) - XRPAmount{1})); - BEAST_EXPECT(expectLedgerEntryRoot( - env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000))); + BEAST_EXPECT(expectLedgerEntryRoot(env, alice, aliceXrpBalance)); + + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(3000))); + bobXrpBalance = env.balance(bob, XRP); // Neither alice nor bob has any lptoken in amm2 BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0))); BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0))); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm2.expectBalances( EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm2.expectBalances( EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm2.expectBalances( + EUR(1000), + XRPAmount(3000000001), + IOUAmount{1732050807568877, -9})); } } @@ -1096,12 +1145,12 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT(amm.expectLPTokens( carol, IOUAmount{1118033988749894, -12})); - env.require(balance(alice, gw["USD"](2000))); - env.require(balance(alice, gw2["EUR"](1000))); - env.require(balance(bob, gw["USD"](3000))); - env.require(balance(bob, gw2["EUR"](2500))); - env.require(balance(carol, gw["USD"](3000))); - env.require(balance(carol, gw2["EUR"](2750))); + env.require(balance(alice, USD(2000))); + env.require(balance(alice, EUR(1000))); + env.require(balance(bob, USD(3000))); + env.require(balance(bob, EUR(2500))); + env.require(balance(carol, USD(3000))); + env.require(balance(carol, EUR(2750))); // gw clawback all the bob's USD in amm. (2000 USD / 2500 EUR) env(amm::ammClawback(gw, bob, USD, EUR, std::nullopt), @@ -1134,8 +1183,8 @@ class AMMClawback_test : public jtx::AMMTest carol, IOUAmount{1118033988749894, -12})); // Bob will get 2500 EUR back. - env.require(balance(alice, gw["USD"](2000))); - env.require(balance(alice, gw2["EUR"](1000))); + env.require(balance(alice, USD(2000))); + env.require(balance(alice, EUR(1000))); BEAST_EXPECT( env.balance(bob, USD) == STAmount(USD, UINT64_C(3000000000000000), -12)); @@ -1148,8 +1197,8 @@ class AMMClawback_test : public jtx::AMMTest BEAST_EXPECT( env.balance(bob, EUR) == STAmount(EUR, UINT64_C(4999999999999999), -12)); - env.require(balance(carol, gw["USD"](3000))); - env.require(balance(carol, gw2["EUR"](2750))); + env.require(balance(carol, USD(3000))); + env.require(balance(carol, EUR(2750))); // gw2 clawback all carol's EUR in amm. (1000 USD / 1250 EUR) env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt), @@ -1180,8 +1229,8 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(carol, gw2["EUR"](2750))); - env.require(balance(carol, gw["USD"](4000))); + env.require(balance(carol, EUR(2750))); + env.require(balance(carol, USD(4000))); BEAST_EXPECT(!amm.ammExists()); } @@ -1564,11 +1613,20 @@ class AMMClawback_test : public jtx::AMMTest // gw claws back 1000 USD from gw2. env(amm::ammClawback(gw, gw2, USD, EUR, USD(1000)), ter(tesSUCCESS)); env.close(); - BEAST_EXPECT(amm.expectBalances( - USD(5000), EUR(10000), IOUAmount{7071067811865475, -12})); + if (!features[fixAMMv1_3] || !features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(5000), EUR(10000), IOUAmount{7071067811865475, -12})); + else + BEAST_EXPECT(amm.expectBalances( + USD(5000), EUR(10000), IOUAmount{7071067811865474, -12})); BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414213562373095, -12})); - BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + if (!features[fixAMMv1_3] || !features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); @@ -1580,22 +1638,37 @@ class AMMClawback_test : public jtx::AMMTest // gw2 claws back 1000 EUR from gw. env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(4500), STAmount(EUR, UINT64_C(9000000000000001), -12), IOUAmount{6363961030678928, -12})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(4500), EUR(9000), IOUAmount{6363961030678928, -12})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(4500), + STAmount(EUR, UINT64_C(9000000000000001), -12), + IOUAmount{6363961030678927, -12})); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT( amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT( amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); - BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + + if (!features[fixAMMv1_3] || !features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); + BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12})); @@ -1607,22 +1680,36 @@ class AMMClawback_test : public jtx::AMMTest // gw2 claws back 4000 EUR from alice. env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS)); env.close(); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(2500), STAmount(EUR, UINT64_C(5000000000000001), -12), IOUAmount{3535533905932738, -12})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT(amm.expectBalances( USD(2500), EUR(5000), IOUAmount{3535533905932738, -12})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT(amm.expectBalances( + USD(2500), + STAmount(EUR, UINT64_C(5000000000000001), -12), + IOUAmount{3535533905932737, -12})); - if (!features[fixAMMv1_3]) + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) BEAST_EXPECT( amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); - else + else if (!features[fixAMMClawbackRounding]) BEAST_EXPECT( amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13})); - BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13})); + + if (!features[fixAMMv1_3] || !features[fixAMMClawbackRounding]) + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12})); + else + BEAST_EXPECT( + amm.expectLPTokens(gw2, IOUAmount{1414213562373094, -12})); BEAST_EXPECT( amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12})); @@ -1689,14 +1776,14 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(3000))); env.close(); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // gw2 issues 3000 EUR to Alice. auto const EUR = gw2["EUR"]; env.trust(EUR(100000), alice); env(pay(gw2, alice, EUR(3000))); env.close(); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // Alice creates AMM pool of EUR/USD. AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); @@ -1714,8 +1801,8 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](1000))); - env.require(balance(alice, gw2["EUR"](2500))); + env.require(balance(alice, USD(1000))); + env.require(balance(alice, EUR(2500))); BEAST_EXPECT(amm.expectBalances( USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); @@ -1731,8 +1818,8 @@ class AMMClawback_test : public jtx::AMMTest // Alice should still has 1000 USD because gw clawed back from the // AMM pool. - env.require(balance(alice, gw["USD"](1000))); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, USD(1000))); + env.require(balance(alice, EUR(3000))); // amm is automatically deleted. BEAST_EXPECT(!amm.ammExists()); @@ -1757,14 +1844,14 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(3000))); env.close(); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // gw2 issues 3000 EUR to Alice. auto const EUR = gw2["EUR"]; env.trust(EUR(100000), alice); env(pay(gw2, alice, EUR(3000))); env.close(); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // Alice creates AMM pool of EUR/USD. AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); @@ -1783,8 +1870,8 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](1000))); - env.require(balance(alice, gw2["EUR"](2500))); + env.require(balance(alice, USD(1000))); + env.require(balance(alice, EUR(2500))); BEAST_EXPECT(amm.expectBalances( USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); BEAST_EXPECT( @@ -1810,14 +1897,14 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(3000))); env.close(); - env.require(balance(alice, gw["USD"](3000))); + env.require(balance(alice, USD(3000))); // gw2 issues 3000 EUR to Alice. auto const EUR = gw2["EUR"]; env.trust(EUR(100000), alice); env(pay(gw2, alice, EUR(3000))); env.close(); - env.require(balance(alice, gw2["EUR"](3000))); + env.require(balance(alice, EUR(3000))); // Alice creates AMM pool of EUR/USD. AMM amm(env, alice, EUR(1000), USD(2000), ter(tesSUCCESS)); @@ -1835,8 +1922,8 @@ class AMMClawback_test : public jtx::AMMTest ter(tesSUCCESS)); env.close(); - env.require(balance(alice, gw["USD"](1000))); - env.require(balance(alice, gw2["EUR"](2500))); + env.require(balance(alice, USD(1000))); + env.require(balance(alice, EUR(2500))); BEAST_EXPECT(amm.expectBalances( USD(1000), EUR(500), IOUAmount{7071067811865475, -13})); BEAST_EXPECT( @@ -1975,10 +2062,11 @@ class AMMClawback_test : public jtx::AMMTest { testcase("test single depoit and clawback"); using namespace jtx; + std::string logs; // Test AMMClawback for USD/XRP pool. Claw back USD, and XRP goes back // to the holder. - Env env(*this, features); + Env env(*this, features, std::make_unique(&logs)); Account gw{"gateway"}; Account alice{"alice"}; env.fund(XRP(1000000000), gw, alice); @@ -1994,7 +2082,7 @@ class AMMClawback_test : public jtx::AMMTest env.trust(USD(100000), alice); env(pay(gw, alice, USD(1000))); env.close(); - env.require(balance(alice, gw["USD"](1000))); + env.require(balance(alice, USD(1000))); // gw creates AMM pool of XRP/USD. AMM amm(env, gw, XRP(100), USD(400), ter(tesSUCCESS)); @@ -2032,26 +2120,349 @@ class AMMClawback_test : public jtx::AMMTest env, alice, aliceXrpBalance + XRP(29.289321))); } + void + testLastHolderLPTokenBalance(FeatureBitset features) + { + testcase( + "test last holder's lptoken balance not equal to AMM's lptoken " + "balance before clawback"); + using namespace jtx; + std::string logs; + + auto setupAccounts = + [&](Env& env, Account& gw, Account& alice, Account& bob) { + env.fund(XRP(100000), gw, alice, bob); + env.close(); + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(40000))); + env.close(); + + return USD; + }; + + auto getLPTokenBalances = + [&](auto& env, + auto const& amm, + auto const& account) -> std::pair { + auto const lpToken = + getAccountLines( + env, account, amm.lptIssue())[jss::lines][0u][jss::balance] + .asString(); + auto const lpTokenBalance = + amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value] + .asString(); + return {lpToken, lpTokenBalance}; + }; + + // IOU/XRP pool. AMMClawback almost last holder's USD balance + { + Env env(*this, features, std::make_unique(&logs)); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + AMM amm(env, alice, XRP(2), USD(1)); + amm.deposit(alice, IOUAmount{1'876123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000'000}); + amm.withdraw(alice, IOUAmount{1'876123487565916, -15}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1414.21356237366" && + lpTokenBalance == "1414.213562374"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (!features[fixAMMClawbackRounding] || !features[fixAMMv1_3]) + { + env(amm::ammClawback(gw, alice, USD, XRP, USD(1)), + ter(tecAMM_BALANCE)); + BEAST_EXPECT(amm.ammExists()); + } + else + { + auto const lpBalance = IOUAmount{989, -12}; + env(amm::ammClawback(gw, alice, USD, XRP, USD(1))); + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(7000000000000000), -28), + XRPAmount(1), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); + } + } + + // IOU/XRP pool. AMMClawback part of last holder's USD balance + { + Env env(*this, features, std::make_unique(&logs)); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + AMM amm(env, alice, XRP(2), USD(1)); + amm.deposit(alice, IOUAmount{1'876123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000'000}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1416.08968586066" && + lpTokenBalance == "1416.089685861"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + env(amm::ammClawback(gw, alice, USD, XRP, USD(0.5))); + + if (!features[fixAMMv1_3] && !features[fixAMMClawbackRounding]) + { + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(5013266196406), -13), + XRPAmount(1002653), + IOUAmount{708'9829046744236, -13})); + } + else if (!features[fixAMMClawbackRounding]) + { + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(5013266196407), -13), + XRPAmount(1002654), + IOUAmount{708'9829046744941, -13})); + } + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + { + auto const lpBalance = IOUAmount{708'9829046743238, -13}; + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(5013266196406999), -16), + XRPAmount(1002655), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); + } + } + + // IOU/XRP pool. AMMClawback all of last holder's USD balance + { + Env env(*this, features, std::make_unique(&logs)); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + AMM amm(env, alice, XRP(2), USD(1)); + amm.deposit(alice, IOUAmount{1'876123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000'000}); + amm.withdraw(alice, IOUAmount{1'876123487565916, -15}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1414.21356237366" && + lpTokenBalance == "1414.213562374"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (!features[fixAMMClawbackRounding] && !features[fixAMMv1_3]) + { + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), + ter(tecAMM_BALANCE)); + } + else if (!features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt)); + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(2410000000000000), -28), + XRPAmount(1), + IOUAmount{34, -11})); + } + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt)); + BEAST_EXPECT(!amm.ammExists()); + } + } + + // IOU/IOU pool, different issuers + { + Env env(*this, features, std::make_unique(&logs)); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + Account gw2{"gateway2"}; + env.fund(XRP(100000), gw2); + env.close(); + auto const EUR = gw2["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw2, alice, EUR(50000))); + env.trust(EUR(100000), bob); + env(pay(gw2, bob, EUR(50000))); + env.close(); + + AMM amm(env, alice, USD(2), EUR(1)); + amm.deposit(alice, IOUAmount{1'576123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000}); + amm.withdraw(alice, IOUAmount{1'576123487565916, -15}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1.414213562374011" && + lpTokenBalance == "1.414213562374"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt)); + BEAST_EXPECT(!amm.ammExists()); + } + else + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + ter(tecINTERNAL)); + BEAST_EXPECT(amm.ammExists()); + } + } + + // IOU/IOU pool, same issuer + { + Env env(*this, features, std::make_unique(&logs)); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + auto const EUR = gw["EUR"]; + env.trust(EUR(100000), alice); + env(pay(gw, alice, EUR(50000))); + env.trust(EUR(100000), bob); + env(pay(gw, bob, EUR(50000))); + env.close(); + + AMM amm(env, alice, USD(1), EUR(2)); + amm.deposit(alice, IOUAmount{1'076123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000}); + amm.withdraw(alice, IOUAmount{1'076123487565916, -15}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + BEAST_EXPECT( + lpToken == "1.414213562374011" && + lpTokenBalance == "1.414213562374"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + txflags(tfClawTwoAssets)); + BEAST_EXPECT(!amm.ammExists()); + } + else + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), + txflags(tfClawTwoAssets), + ter(tecINTERNAL)); + BEAST_EXPECT(amm.ammExists()); + } + } + + // IOU/IOU pool, larger asset ratio + { + Env env(*this, features, std::make_unique(&logs)); + Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + auto const USD = setupAccounts(env, gw, alice, bob); + + auto const EUR = gw["EUR"]; + env.trust(EUR(1000000000), alice); + env(pay(gw, alice, EUR(500000000))); + env.trust(EUR(1000000000), bob); + env(pay(gw, bob, EUR(500000000))); + env.close(); + + AMM amm(env, alice, USD(1), EUR(2000000)); + amm.deposit(alice, IOUAmount{1'076123487565916, -12}); + amm.deposit(bob, IOUAmount{10000}); + amm.withdraw(alice, IOUAmount{1'076123487565916, -12}); + amm.withdrawAll(bob); + + auto [lpToken, lpTokenBalance] = + getLPTokenBalances(env, amm, alice); + + BEAST_EXPECT( + lpToken == "1414.213562373101" && + lpTokenBalance == "1414.2135623731"); + + auto res = + isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (!features[fixAMMClawbackRounding] && !features[fixAMMv1_3]) + { + env(amm::ammClawback(gw, alice, USD, EUR, USD(1))); + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(4), -15), + STAmount(EUR, UINT64_C(8), -9), + IOUAmount{6, -12})); + } + else if (!features[fixAMMClawbackRounding]) + { + // sqrt(amount * amount2) >= LPTokens and exceeds the allowed + // tolerance + env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), + ter(tecINVARIANT_FAILED)); + BEAST_EXPECT(amm.ammExists()); + } + else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), + txflags(tfClawTwoAssets)); + auto const lpBalance = IOUAmount{5, -12}; + BEAST_EXPECT(amm.expectBalances( + STAmount(USD, UINT64_C(4), -15), + STAmount(EUR, UINT64_C(8), -9), + lpBalance)); + BEAST_EXPECT(amm.expectLPTokens(alice, lpBalance)); + } + } + } + void run() override { - FeatureBitset const all{jtx::supported_amendments()}; - testInvalidRequest(all); + FeatureBitset const all{ + jtx::testable_amendments() | fixAMMClawbackRounding}; + + testInvalidRequest(); testFeatureDisabled(all - featureAMMClawback); - testAMMClawbackSpecificAmount(all); - testAMMClawbackExceedBalance(all); - testAMMClawbackExceedBalance(all - fixAMMv1_3); - testAMMClawbackAll(all); - testAMMClawbackAll(all - fixAMMv1_3); - testAMMClawbackSameIssuerAssets(all); - testAMMClawbackSameIssuerAssets(all - fixAMMv1_3); - testAMMClawbackSameCurrency(all); - testAMMClawbackIssuesEachOther(all); - testNotHoldingLptoken(all); - testAssetFrozen(all); - testAssetFrozen(all - fixAMMv1_3); - testSingleDepositAndClawback(all); - testSingleDepositAndClawback(all - fixAMMv1_3); + for (auto const& features : + {all - fixAMMv1_3 - fixAMMClawbackRounding, + all - fixAMMClawbackRounding, + all}) + { + testAMMClawbackSpecificAmount(features); + testAMMClawbackExceedBalance(features); + testAMMClawbackAll(features); + testAMMClawbackSameIssuerAssets(features); + testAMMClawbackSameCurrency(features); + testAMMClawbackIssuesEachOther(features); + testNotHoldingLptoken(features); + testAssetFrozen(features); + testSingleDepositAndClawback(features); + testLastHolderLPTokenBalance(features); + } } }; BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple); diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 70b2f30e1d..893e9e4f75 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -1447,7 +1447,7 @@ private: testOffers() { using namespace jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testRmFundedOffer(all); testRmFundedOffer(all - fixAMMv1_1 - fixAMMv1_3); testEnforceNoRipple(all); @@ -2823,8 +2823,8 @@ private: for (auto const withFix : {true, false}) { auto const feats = withFix - ? supported_amendments() - : supported_amendments() - FeatureBitset{fix1781}; + ? testable_amendments() + : testable_amendments() - FeatureBitset{fix1781}; // Payment path starting with XRP Env env(*this, feats); @@ -3789,7 +3789,7 @@ private: testFlow() { using namespace jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testFalseDry(all); testBookStep(all); @@ -3803,7 +3803,7 @@ private: testCrossingLimits() { using namespace jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testStepLimit(all); testStepLimit(all - fixAMMv1_1 - fixAMMv1_3); } @@ -3812,7 +3812,7 @@ private: testDeliverMin() { using namespace jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; test_convert_all_of_an_asset(all); test_convert_all_of_an_asset(all - fixAMMv1_1 - fixAMMv1_3); } @@ -3820,7 +3820,7 @@ private: void testDepositAuth() { - auto const supported{jtx::supported_amendments()}; + auto const supported{jtx::testable_amendments()}; testPayment(supported - featureDepositPreauth); testPayment(supported); testPayIOU(); @@ -3830,7 +3830,7 @@ private: testFreeze() { using namespace test::jtx; - auto const sa = supported_amendments(); + auto const sa = testable_amendments(); testRippleState(sa); testGlobalFreeze(sa); testOffersWhenFrozen(sa); @@ -3840,7 +3840,7 @@ private: testMultisign() { using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); testTxMultisign( all - featureMultiSignReserve - featureExpandedSignerList); @@ -3852,7 +3852,7 @@ private: testPayStrand() { using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); testToStrand(all); testRIPD1373(all); diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 2ee9e5f1f3..c89aebf813 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -66,7 +66,7 @@ private: {}, 0, {}, - {supported_amendments() | featureSingleAssetVault}); + {testable_amendments() | featureSingleAssetVault}); // XRP to IOU, without featureSingleAssetVault testAMM( @@ -77,7 +77,7 @@ private: {}, 0, {}, - {supported_amendments() - featureSingleAssetVault}); + {testable_amendments() - featureSingleAssetVault}); // IOU to IOU testAMM( @@ -1385,7 +1385,7 @@ private: testcase("Deposit"); using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); // Equal deposit: 1000000 tokens, 10% of the current pool testAMM([&](AMM& ammAlice, Env& env) { @@ -1687,7 +1687,7 @@ private: testcase("Invalid Withdraw"); using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); testAMM( [&](AMM& ammAlice, Env& env) { @@ -2267,7 +2267,7 @@ private: testcase("Withdraw"); using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current // pool @@ -2688,7 +2688,7 @@ private: { testcase("Fee Vote"); using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); // One vote sets fee to 1%. testAMM([&](AMM& ammAlice, Env& env) { @@ -4855,7 +4855,7 @@ private: { testcase("Amendment"); using namespace jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; FeatureBitset const noAMM{all - featureAMM}; FeatureBitset const noNumber{all - fixUniversalNumber}; FeatureBitset const noAMMAndNumber{ @@ -5646,7 +5646,7 @@ private: testcase("Auto Delete"); using namespace jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; { Env env( @@ -6306,7 +6306,7 @@ private: { testcase("Fix Default Inner Object"); using namespace jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; auto test = [&](FeatureBitset features, TER const& err1, @@ -7043,7 +7043,7 @@ private: {{xrpPool, iouPool}}, 889, std::nullopt, - {jtx::supported_amendments() | fixAMMv1_1}); + {jtx::testable_amendments() | fixAMMv1_1}); } void @@ -7322,7 +7322,8 @@ private: } // If featureAMMClawback is enabled, AMMCreate is allowed for // clawback-enabled issuer. Clawback from the AMM Account is not - // allowed, which will return tecAMM_ACCOUNT. We can only use + // allowed, which will return tecAMM_ACCOUNT or tecPSEUDO_ACCOUNT, + // depending on whether SingleAssetVault is enabled. We can only use // AMMClawback transaction to claw back from AMM Account. else { @@ -7333,13 +7334,16 @@ private: // By doing this, we make the clawback transaction's Amount field's // subfield `issuer` to be the AMM account, which means // we are clawing back from an AMM account. This should return an - // tecAMM_ACCOUNT error because regular Clawback transaction is not + // error because regular Clawback transaction is not // allowed for clawing back from an AMM account. Please notice the // `issuer` subfield represents the account being clawed back, which // is confusing. + auto const error = features[featureSingleAssetVault] + ? ter{tecPSEUDO_ACCOUNT} + : ter{tecAMM_ACCOUNT}; Issue usd(USD.issue().currency, amm.ammAccount()); auto amount = amountFromString(usd, "10"); - env(claw(gw, amount), ter(tecAMM_ACCOUNT)); + env(claw(gw, amount), error); } } @@ -7513,10 +7517,10 @@ private: }; testCase( - "tecDUPLICATE", supported_amendments() - featureSingleAssetVault); + "tecDUPLICATE", testable_amendments() - featureSingleAssetVault); testCase( "terADDRESS_COLLISION", - supported_amendments() | featureSingleAssetVault); + testable_amendments() | featureSingleAssetVault); } void @@ -7894,7 +7898,7 @@ private: void run() override { - FeatureBitset const all{jtx::supported_amendments()}; + FeatureBitset const all{jtx::testable_amendments()}; testInvalidInstance(); testInstanceCreate(); testInvalidDeposit(all); @@ -7945,6 +7949,8 @@ private: testLPTokenBalance(all - fixAMMv1_3); testLPTokenBalance(all - fixAMMv1_1 - fixAMMv1_3); testAMMClawback(all); + testAMMClawback(all - featureSingleAssetVault); + testAMMClawback(all - featureAMMClawback - featureSingleAssetVault); testAMMClawback(all - featureAMMClawback); testAMMClawback(all - fixAMMv1_1 - fixAMMv1_3 - featureAMMClawback); testAMMDepositWithFrozenAssets(all); diff --git a/src/test/app/AccountDelete_test.cpp b/src/test/app/AccountDelete_test.cpp index 1ac0256dcb..f7c4ddc509 100644 --- a/src/test/app/AccountDelete_test.cpp +++ b/src/test/app/AccountDelete_test.cpp @@ -292,7 +292,7 @@ public: // o New-styled PayChannels with the backlink. // So we start the test using old-style PayChannels. Then we pass // the amendment to get new-style PayChannels. - Env env{*this, supported_amendments() - fixPayChanRecipientOwnerDir}; + Env env{*this, testable_amendments() - fixPayChanRecipientOwnerDir}; Account const alice("alice"); Account const becky("becky"); Account const gw("gw"); @@ -461,7 +461,7 @@ public: // We need an old-style PayChannel that doesn't provide a backlink // from the destination. So don't enable the amendment with that fix. - Env env{*this, supported_amendments() - fixPayChanRecipientOwnerDir}; + Env env{*this, testable_amendments() - fixPayChanRecipientOwnerDir}; Account const alice("alice"); Account const becky("becky"); @@ -536,7 +536,7 @@ public: testcase("Amendment enable"); - Env env{*this, supported_amendments() - featureDeletableAccounts}; + Env env{*this, testable_amendments() - featureDeletableAccounts}; Account const alice("alice"); Account const becky("becky"); @@ -1128,7 +1128,7 @@ public: Account const becky{"becky"}; Account const carol{"carol"}; - Env env{*this, supported_amendments() - featureCredentials}; + Env env{*this, testable_amendments() - featureCredentials}; env.fund(XRP(100000), alice, becky, carol); env.close(); diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 5ba820da95..407b2fafe1 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -1288,7 +1288,7 @@ public: void run() override { - FeatureBitset const all{test::jtx::supported_amendments()}; + FeatureBitset const all{test::jtx::testable_amendments()}; FeatureBitset const fixMajorityCalc{fixAmendmentMajorityCalc}; testConstruct(); diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 6ce95c56d0..92f286ca6a 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -3652,14 +3652,18 @@ class Batch_test : public beast::unit_test::suite { // Submit a tx with tfInnerBatchTxn uint256 const txBad = submitTx(tfInnerBatchTxn); - BEAST_EXPECT(env.app().getHashRouter().getFlags(txBad) == 0); + BEAST_EXPECT( + env.app().getHashRouter().getFlags(txBad) == + HashRouterFlags::UNDEFINED); } // Validate: NetworkOPs::processTransaction() { uint256 const txid = processTxn(tfInnerBatchTxn); - // HashRouter::getFlags() should return SF_BAD - BEAST_EXPECT(env.app().getHashRouter().getFlags(txid) == SF_BAD); + // HashRouter::getFlags() should return LedgerFlags::BAD + BEAST_EXPECT( + env.app().getHashRouter().getFlags(txid) == + HashRouterFlags::BAD); } } @@ -4164,7 +4168,7 @@ public: run() override { using namespace test::jtx; - auto const sa = supported_amendments(); + auto const sa = testable_amendments(); testWithFeats(sa); } }; diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 99b0c8dba3..be38b22313 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -2720,7 +2720,7 @@ public: run() override { using namespace test::jtx; - auto const sa = supported_amendments(); + auto const sa = testable_amendments(); testWithFeats(sa - featureCheckCashMakesTrustLine); testWithFeats(sa - disallowIncoming); testWithFeats(sa); diff --git a/src/test/app/Clawback_test.cpp b/src/test/app/Clawback_test.cpp index d41f6de556..adfe80133a 100644 --- a/src/test/app/Clawback_test.cpp +++ b/src/test/app/Clawback_test.cpp @@ -949,7 +949,7 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testWithFeats(all - featureMPTokensV1); testWithFeats(all); diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index fa6505e926..54826cbb12 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -1090,7 +1090,7 @@ struct Credentials_test : public beast::unit_test::suite run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testSuccessful(all); testCredentialsDelete(all); testCreateFailed(all); diff --git a/src/test/app/CrossingLimits_test.cpp b/src/test/app/CrossingLimits_test.cpp index cef0b03399..6e76936199 100644 --- a/src/test/app/CrossingLimits_test.cpp +++ b/src/test/app/CrossingLimits_test.cpp @@ -77,10 +77,8 @@ public: auto const gw = Account("gateway"); auto const USD = gw["USD"]; - // The number of allowed offers to cross is different between - // Taker and FlowCross. Taker allows 850 and FlowCross allows 1000. - // Accommodate that difference in the test. - int const maxConsumed = features[featureFlowCross] ? 1000 : 850; + // The payment engine allows 1000 offers to cross. + int const maxConsumed = 1000; env.fund(XRP(100000000), gw, "alice", "bob", "carol"); int const bobsOfferCount = maxConsumed + 150; @@ -119,11 +117,8 @@ public: env.fund(XRP(100000000), gw, "alice", "bob", "carol", "dan", "evita"); - // The number of offers allowed to cross is different between - // Taker and FlowCross. Taker allows 850 and FlowCross allows 1000. - // Accommodate that difference in the test. - bool const isFlowCross{features[featureFlowCross]}; - int const maxConsumed = isFlowCross ? 1000 : 850; + // The payment engine allows 1000 offers to cross. + int const maxConsumed = 1000; int const evitasOfferCount{maxConsumed + 49}; env.trust(USD(1000), "alice"); @@ -133,14 +128,8 @@ public: env.trust(USD(evitasOfferCount + 1), "evita"); env(pay(gw, "evita", USD(evitasOfferCount + 1))); - // Taker and FlowCross have another difference we must accommodate. - // Taker allows a total of 1000 unfunded offers to be consumed - // beyond the 850 offers it can take. FlowCross draws no such - // distinction; its limit is 1000 funded or unfunded. - // - // Give carol an extra 150 (unfunded) offers when we're using Taker - // to accommodate that difference. - int const carolsOfferCount{isFlowCross ? 700 : 850}; + // The payment engine has a limit of 1000 funded or unfunded offers. + int const carolsOfferCount{700}; n_offers(env, 400, "alice", XRP(1), USD(1)); n_offers(env, carolsOfferCount, "carol", XRP(1), USD(1)); n_offers(env, evitasOfferCount, "evita", XRP(1), USD(1)); @@ -268,9 +257,9 @@ public: } void - testAutoBridgedLimitsFlowCross(FeatureBitset features) + testAutoBridgedLimits(FeatureBitset features) { - testcase("Auto Bridged Limits FlowCross"); + testcase("Auto Bridged Limits"); // If any book step in a payment strand consumes 1000 offers, the // liquidity from the offers is used, but that strand will be marked as @@ -452,26 +441,6 @@ public: } } - void - testAutoBridgedLimits(FeatureBitset features) - { - // Taker and FlowCross are too different in the way they handle - // autobridging to make one test suit both approaches. - // - // o Taker alternates between books, completing one full increment - // before returning to make another pass. - // - // o FlowCross extracts as much as possible in one book at one Quality - // before proceeding to the other book. This reduces the number of - // times we change books. - // - // So the tests for the two forms of autobridging are separate. - if (features[featureFlowCross]) - testAutoBridgedLimitsFlowCross(features); - else - testAutoBridgedLimitsTaker(features); - } - void testOfferOverflow(FeatureBitset features) { @@ -522,11 +491,10 @@ public: n_offers(env, 998, alice, XRP(0.96), USD(1)); n_offers(env, 998, alice, XRP(0.95), USD(1)); - bool const withFlowCross = features[featureFlowCross]; bool const withSortStrands = features[featureFlowSortStrands]; auto const expectedTER = [&]() -> TER { - if (withFlowCross && !withSortStrands) + if (!withSortStrands) return TER{tecOVERSIZE}; return tesSUCCESS; }(); @@ -535,8 +503,6 @@ public: env.close(); auto const expectedUSD = [&] { - if (!withFlowCross) - return USD(850); if (!withSortStrands) return USD(0); return USD(1996); @@ -556,13 +522,11 @@ public: testOfferOverflow(features); }; using namespace jtx; - auto const sa = supported_amendments(); + auto const sa = testable_amendments(); testAll(sa); + testAll(sa - featureFlowSortStrands); testAll(sa - featurePermissionedDEX); testAll(sa - featureFlowSortStrands - featurePermissionedDEX); - testAll( - sa - featureFlowCross - featureFlowSortStrands - - featurePermissionedDEX); } }; diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index c885ed0861..1f28af2d6a 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -390,7 +390,7 @@ struct DID_test : public beast::unit_test::suite run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; FeatureBitset const emptyDID{fixEmptyDID}; testEnabled(all); testAccountReserve(all); diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index ca13e4f4cd..179532140d 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -31,7 +31,7 @@ class Delegate_test : public beast::unit_test::suite testcase("test featurePermissionDelegation not enabled"); using namespace jtx; - Env env{*this, supported_amendments() - featurePermissionDelegation}; + Env env{*this, testable_amendments() - featurePermissionDelegation}; Account gw{"gateway"}; Account alice{"alice"}; Account bob{"bob"}; diff --git a/src/test/app/DeliverMin_test.cpp b/src/test/app/DeliverMin_test.cpp index 4ee7c9c72e..a9373fb002 100644 --- a/src/test/app/DeliverMin_test.cpp +++ b/src/test/app/DeliverMin_test.cpp @@ -142,9 +142,7 @@ public: run() override { using namespace jtx; - auto const sa = supported_amendments(); - test_convert_all_of_an_asset( - sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); test_convert_all_of_an_asset(sa - featurePermissionedDEX); test_convert_all_of_an_asset(sa); } diff --git a/src/test/app/DepositAuth_test.cpp b/src/test/app/DepositAuth_test.cpp index 6f314e3a79..ffe8c4448b 100644 --- a/src/test/app/DepositAuth_test.cpp +++ b/src/test/app/DepositAuth_test.cpp @@ -53,7 +53,7 @@ struct DepositAuth_test : public beast::unit_test::suite { // featureDepositAuth is disabled. - Env env(*this, supported_amendments() - featureDepositAuth); + Env env(*this, testable_amendments() - featureDepositAuth); env.fund(XRP(10000), alice); // Note that, to support old behavior, invalid flags are ignored. @@ -352,27 +352,27 @@ struct DepositAuth_test : public beast::unit_test::suite auto const noRippleNext = i & 0x2; auto const withDepositAuth = i & 0x4; testIssuer( - supported_amendments() | featureDepositAuth, + testable_amendments() | featureDepositAuth, noRipplePrev, noRippleNext, withDepositAuth); if (!withDepositAuth) testIssuer( - supported_amendments() - featureDepositAuth, + testable_amendments() - featureDepositAuth, noRipplePrev, noRippleNext, withDepositAuth); testNonIssuer( - supported_amendments() | featureDepositAuth, + testable_amendments() | featureDepositAuth, noRipplePrev, noRippleNext, withDepositAuth); if (!withDepositAuth) testNonIssuer( - supported_amendments() - featureDepositAuth, + testable_amendments() - featureDepositAuth, noRipplePrev, noRippleNext, withDepositAuth); @@ -420,7 +420,7 @@ struct DepositPreauth_test : public beast::unit_test::suite Account const becky{"becky"}; { // featureDepositPreauth is disabled. - Env env(*this, supported_amendments() - featureDepositPreauth); + Env env(*this, testable_amendments() - featureDepositPreauth); env.fund(XRP(10000), alice, becky); env.close(); @@ -830,7 +830,7 @@ struct DepositPreauth_test : public beast::unit_test::suite { testcase("Payment failure with disabled credentials rule."); - Env env(*this, supported_amendments() - featureCredentials); + Env env(*this, testable_amendments() - featureCredentials); env.fund(XRP(5000), issuer, bob, alice); env.close(); @@ -1563,7 +1563,7 @@ struct DepositPreauth_test : public beast::unit_test::suite { testEnable(); testInvalid(); - auto const supported{jtx::supported_amendments()}; + auto const supported{jtx::testable_amendments()}; testPayment(supported - featureDepositPreauth - featureCredentials); testPayment(supported - featureDepositPreauth); testPayment(supported - featureCredentials); diff --git a/src/test/app/Discrepancy_test.cpp b/src/test/app/Discrepancy_test.cpp index bc72b2fd16..da41969885 100644 --- a/src/test/app/Discrepancy_test.cpp +++ b/src/test/app/Discrepancy_test.cpp @@ -146,8 +146,7 @@ public: run() override { using namespace test::jtx; - auto const sa = supported_amendments(); - testXRPDiscrepancy(sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); testXRPDiscrepancy(sa - featurePermissionedDEX); testXRPDiscrepancy(sa); } diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index 6ba8c48c93..e81064c825 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -3875,7 +3875,7 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testIOUWithFeats(all); testMPTWithFeats(all); } diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 21ef70a86e..3eaf0f13ea 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -294,7 +294,7 @@ struct Escrow_test : public beast::unit_test::suite { testcase("Implied Finish Time (without fix1571)"); - Env env(*this, supported_amendments() - fix1571); + Env env(*this, testable_amendments() - fix1571); auto const baseFee = env.current()->fees().base; env.fund(XRP(5000), "alice", "bob", "carol"); env.close(); @@ -1715,7 +1715,7 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testWithFeats(all); testWithFeats(all - featureTokenEscrow); } diff --git a/src/test/app/FixNFTokenPageLinks_test.cpp b/src/test/app/FixNFTokenPageLinks_test.cpp index f87d70aacf..a54e889960 100644 --- a/src/test/app/FixNFTokenPageLinks_test.cpp +++ b/src/test/app/FixNFTokenPageLinks_test.cpp @@ -139,7 +139,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::suite { // Verify that the LedgerStateFix transaction is disabled // without the fixNFTokenPageLinks amendment. - Env env{*this, supported_amendments() - fixNFTokenPageLinks}; + Env env{*this, testable_amendments() - fixNFTokenPageLinks}; env.fund(XRP(1000), alice); auto const linkFixFee = drops(env.current()->fees().increment); @@ -148,7 +148,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::suite ter(temDISABLED)); } - Env env{*this, supported_amendments()}; + Env env{*this, testable_amendments()}; env.fund(XRP(1000), alice); std::uint32_t const ticketSeq = env.seq(alice); env(ticket::create(alice, 1)); @@ -206,7 +206,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::suite Account const alice("alice"); - Env env{*this, supported_amendments()}; + Env env{*this, testable_amendments()}; env.fund(XRP(1000), alice); // These cases all return the same TER code, but they exercise @@ -259,7 +259,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::suite Account const carol("carol"); Account const daria("daria"); - Env env{*this, supported_amendments() - fixNFTokenPageLinks}; + Env env{*this, testable_amendments() - fixNFTokenPageLinks}; env.fund(XRP(1000), alice, bob, carol, daria); //********************************************************************** diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index 68485f4eee..0f40d70b57 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -1194,8 +1194,8 @@ struct Flow_test : public beast::unit_test::suite { auto const feats = [&withFix]() -> FeatureBitset { if (withFix) - return supported_amendments(); - return supported_amendments() - FeatureBitset{fix1781}; + return testable_amendments(); + return testable_amendments() - FeatureBitset{fix1781}; }(); { // Payment path starting with XRP @@ -1332,8 +1332,7 @@ struct Flow_test : public beast::unit_test::suite testRIPD1449(); using namespace jtx; - auto const sa = supported_amendments(); - testWithFeats(sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); testWithFeats(sa - featurePermissionedDEX); testWithFeats(sa); testEmptyStrand(sa); @@ -1346,13 +1345,10 @@ struct Flow_manual_test : public Flow_test run() override { using namespace jtx; - auto const all = supported_amendments(); - FeatureBitset const flowCross{featureFlowCross}; + auto const all = testable_amendments(); FeatureBitset const f1513{fix1513}; FeatureBitset const permDex{featurePermissionedDEX}; - testWithFeats(all - flowCross - f1513 - permDex); - testWithFeats(all - flowCross - permDex); testWithFeats(all - f1513 - permDex); testWithFeats(all - permDex); testWithFeats(all); diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index 8c2021d657..3bde3a30af 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -961,24 +961,12 @@ class Freeze_test : public beast::unit_test::suite env.close(); // test: A1 wants to buy, must fail - if (features[featureFlowCross]) - { - env(offer(A1, USD(1), XRP(2)), - txflags(tfFillOrKill), - ter(tecKILLED)); - env.close(); - env.require( - balance(A1, USD(1002)), - balance(A2, USD(997)), - offers(A1, 0)); - } - else - { - // The transaction that should be here would succeed. - // I don't want to adjust balances in following tests. Flow - // cross feature flag is not relevant to this particular test - // case so we're not missing out some corner cases checks. - } + env(offer(A1, USD(1), XRP(2)), + txflags(tfFillOrKill), + ter(tecKILLED)); + env.close(); + env.require( + balance(A1, USD(1002)), balance(A2, USD(997)), offers(A1, 0)); // test: A1 can create passive sell offer env(offer(A1, XRP(2), USD(1)), txflags(tfPassive)); @@ -2106,18 +2094,15 @@ public: testNFTOffersWhenFreeze(features); }; using namespace test::jtx; - auto const sa = supported_amendments(); - testAll( - sa - featureFlowCross - featureDeepFreeze - featurePermissionedDEX - - fixEnforceNFTokenTrustlineV2); - testAll( - sa - featureFlowCross - featurePermissionedDEX - - fixEnforceNFTokenTrustlineV2); + auto const sa = testable_amendments(); testAll( sa - featureDeepFreeze - featurePermissionedDEX - fixEnforceNFTokenTrustlineV2); testAll(sa - featurePermissionedDEX - fixEnforceNFTokenTrustlineV2); + testAll(sa - featureDeepFreeze - featurePermissionedDEX); + testAll(sa - featurePermissionedDEX); testAll(sa - fixEnforceNFTokenTrustlineV2); + testAll(sa - featureDeepFreeze); testAll(sa); } }; diff --git a/src/test/app/HashRouter_test.cpp b/src/test/app/HashRouter_test.cpp index 0737116f13..44170e152e 100644 --- a/src/test/app/HashRouter_test.cpp +++ b/src/test/app/HashRouter_test.cpp @@ -45,15 +45,19 @@ class HashRouter_test : public beast::unit_test::suite TestStopwatch stopwatch; HashRouter router(getSetup(2s, 1s), stopwatch); - uint256 const key1(1); - uint256 const key2(2); - uint256 const key3(3); + HashRouterFlags key1(HashRouterFlags::PRIVATE1); + HashRouterFlags key2(HashRouterFlags::PRIVATE2); + HashRouterFlags key3(HashRouterFlags::PRIVATE3); + + auto const ukey1 = uint256{static_cast(key1)}; + auto const ukey2 = uint256{static_cast(key2)}; + auto const ukey3 = uint256{static_cast(key3)}; // t=0 - router.setFlags(key1, 11111); - BEAST_EXPECT(router.getFlags(key1) == 11111); - router.setFlags(key2, 22222); - BEAST_EXPECT(router.getFlags(key2) == 22222); + router.setFlags(ukey1, HashRouterFlags::PRIVATE1); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1); + router.setFlags(ukey2, HashRouterFlags::PRIVATE2); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE2); // key1 : 0 // key2 : 0 // key3: null @@ -62,7 +66,7 @@ class HashRouter_test : public beast::unit_test::suite // Because we are accessing key1 here, it // will NOT be expired for another two ticks - BEAST_EXPECT(router.getFlags(key1) == 11111); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1); // key1 : 1 // key2 : 0 // key3 null @@ -70,9 +74,9 @@ class HashRouter_test : public beast::unit_test::suite ++stopwatch; // t=3 - router.setFlags(key3, 33333); // force expiration - BEAST_EXPECT(router.getFlags(key1) == 11111); - BEAST_EXPECT(router.getFlags(key2) == 0); + router.setFlags(ukey3, HashRouterFlags::PRIVATE3); // force expiration + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::PRIVATE1); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED); } void @@ -83,15 +87,21 @@ class HashRouter_test : public beast::unit_test::suite TestStopwatch stopwatch; HashRouter router(getSetup(2s, 1s), stopwatch); - uint256 const key1(1); - uint256 const key2(2); - uint256 const key3(3); - uint256 const key4(4); + HashRouterFlags key1(HashRouterFlags::PRIVATE1); + HashRouterFlags key2(HashRouterFlags::PRIVATE2); + HashRouterFlags key3(HashRouterFlags::PRIVATE3); + HashRouterFlags key4(HashRouterFlags::PRIVATE4); + + auto const ukey1 = uint256{static_cast(key1)}; + auto const ukey2 = uint256{static_cast(key2)}; + auto const ukey3 = uint256{static_cast(key3)}; + auto const ukey4 = uint256{static_cast(key4)}; + BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4); // t=0 - router.setFlags(key1, 12345); - BEAST_EXPECT(router.getFlags(key1) == 12345); + router.setFlags(ukey1, HashRouterFlags::BAD); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD); // key1 : 0 // key2 : null // key3 : null @@ -103,26 +113,27 @@ class HashRouter_test : public beast::unit_test::suite // so key1 will be expired after the second // call to setFlags. // t=1 - router.setFlags(key2, 9999); - BEAST_EXPECT(router.getFlags(key1) == 12345); - BEAST_EXPECT(router.getFlags(key2) == 9999); + + router.setFlags(ukey2, HashRouterFlags::PRIVATE5); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::BAD); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5); // key1 : 1 // key2 : 1 // key3 : null ++stopwatch; // t=2 - BEAST_EXPECT(router.getFlags(key2) == 9999); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5); // key1 : 1 // key2 : 2 // key3 : null ++stopwatch; // t=3 - router.setFlags(key3, 2222); - BEAST_EXPECT(router.getFlags(key1) == 0); - BEAST_EXPECT(router.getFlags(key2) == 9999); - BEAST_EXPECT(router.getFlags(key3) == 2222); + router.setFlags(ukey3, HashRouterFlags::BAD); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5); + BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD); // key1 : 3 // key2 : 3 // key3 : 3 @@ -130,10 +141,10 @@ class HashRouter_test : public beast::unit_test::suite ++stopwatch; // t=4 // No insertion, no expiration - router.setFlags(key1, 7654); - BEAST_EXPECT(router.getFlags(key1) == 7654); - BEAST_EXPECT(router.getFlags(key2) == 9999); - BEAST_EXPECT(router.getFlags(key3) == 2222); + router.setFlags(ukey1, HashRouterFlags::SAVED); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::SAVED); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::PRIVATE5); + BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::BAD); // key1 : 4 // key2 : 4 // key3 : 4 @@ -142,11 +153,11 @@ class HashRouter_test : public beast::unit_test::suite ++stopwatch; // t=6 - router.setFlags(key4, 7890); - BEAST_EXPECT(router.getFlags(key1) == 0); - BEAST_EXPECT(router.getFlags(key2) == 0); - BEAST_EXPECT(router.getFlags(key3) == 0); - BEAST_EXPECT(router.getFlags(key4) == 7890); + router.setFlags(ukey4, HashRouterFlags::TRUSTED); + BEAST_EXPECT(router.getFlags(ukey1) == HashRouterFlags::UNDEFINED); + BEAST_EXPECT(router.getFlags(ukey2) == HashRouterFlags::UNDEFINED); + BEAST_EXPECT(router.getFlags(ukey3) == HashRouterFlags::UNDEFINED); + BEAST_EXPECT(router.getFlags(ukey4) == HashRouterFlags::TRUSTED); // key1 : 6 // key2 : 6 // key3 : 6 @@ -168,18 +179,18 @@ class HashRouter_test : public beast::unit_test::suite uint256 const key4(4); BEAST_EXPECT(key1 != key2 && key2 != key3 && key3 != key4); - int flags = 12345; // This value is ignored + HashRouterFlags flags(HashRouterFlags::BAD); // This value is ignored router.addSuppression(key1); BEAST_EXPECT(router.addSuppressionPeer(key2, 15)); BEAST_EXPECT(router.addSuppressionPeer(key3, 20, flags)); - BEAST_EXPECT(flags == 0); + BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED); ++stopwatch; BEAST_EXPECT(!router.addSuppressionPeer(key1, 2)); BEAST_EXPECT(!router.addSuppressionPeer(key2, 3)); BEAST_EXPECT(!router.addSuppressionPeer(key3, 4, flags)); - BEAST_EXPECT(flags == 0); + BEAST_EXPECT(flags == HashRouterFlags::UNDEFINED); BEAST_EXPECT(router.addSuppressionPeer(key4, 5)); } @@ -192,9 +203,9 @@ class HashRouter_test : public beast::unit_test::suite HashRouter router(getSetup(2s, 1s), stopwatch); uint256 const key1(1); - BEAST_EXPECT(router.setFlags(key1, 10)); - BEAST_EXPECT(!router.setFlags(key1, 10)); - BEAST_EXPECT(router.setFlags(key1, 20)); + BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE1)); + BEAST_EXPECT(!router.setFlags(key1, HashRouterFlags::PRIVATE1)); + BEAST_EXPECT(router.setFlags(key1, HashRouterFlags::PRIVATE2)); } void @@ -250,7 +261,7 @@ class HashRouter_test : public beast::unit_test::suite HashRouter router(getSetup(5s, 1s), stopwatch); uint256 const key(1); HashRouter::PeerShortID peer = 1; - int flags; + HashRouterFlags flags; BEAST_EXPECT(router.shouldProcess(key, peer, flags, 1s)); BEAST_EXPECT(!router.shouldProcess(key, peer, flags, 1s)); @@ -364,6 +375,39 @@ class HashRouter_test : public beast::unit_test::suite } } + void + testFlagsOps() + { + testcase("Bitwise Operations"); + + using HF = HashRouterFlags; + using UHF = std::underlying_type_t; + + HF f1 = HF::BAD; + HF f2 = HF::SAVED; + HF combined = f1 | f2; + + BEAST_EXPECT( + static_cast(combined) == + (static_cast(f1) | static_cast(f2))); + + HF temp = f1; + temp |= f2; + BEAST_EXPECT(temp == combined); + + HF intersect = combined & f1; + BEAST_EXPECT(intersect == f1); + + HF temp2 = combined; + temp2 &= f1; + BEAST_EXPECT(temp2 == f1); + + BEAST_EXPECT(any(f1)); + BEAST_EXPECT(any(f2)); + BEAST_EXPECT(any(combined)); + BEAST_EXPECT(!any(HF::UNDEFINED)); + } + public: void run() override @@ -375,6 +419,7 @@ public: testRelay(); testProcess(); testSetup(); + testFlagsOps(); } }; diff --git a/src/test/app/LPTokenTransfer_test.cpp b/src/test/app/LPTokenTransfer_test.cpp index 96e621dccf..e95e974547 100644 --- a/src/test/app/LPTokenTransfer_test.cpp +++ b/src/test/app/LPTokenTransfer_test.cpp @@ -467,7 +467,7 @@ public: void run() override { - FeatureBitset const all{jtx::supported_amendments()}; + FeatureBitset const all{jtx::testable_amendments()}; for (auto const features : {all, all - fixFrozenLPTokenTransfer}) { diff --git a/src/test/app/LedgerMaster_test.cpp b/src/test/app/LedgerMaster_test.cpp index 19664616b1..828e4b09c2 100644 --- a/src/test/app/LedgerMaster_test.cpp +++ b/src/test/app/LedgerMaster_test.cpp @@ -124,7 +124,7 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testWithFeats(all); } diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index deee217aa8..2cb47780ba 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -18,10 +18,16 @@ //============================================================================== #include +#include +#include #include #include +#include +#include #include +#include +#include #include namespace ripple { @@ -61,6 +67,48 @@ class MPToken_test : public beast::unit_test::suite .metadata = "test", .err = temMALFORMED}); + if (!features[featureSingleAssetVault]) + { + // tries to set DomainID when SAV is disabled + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .metadata = "test", + .flags = tfMPTRequireAuth, + .domainID = uint256(42), + .err = temDISABLED}); + } + else if (!features[featurePermissionedDomains]) + { + // tries to set DomainID when PD is disabled + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .metadata = "test", + .flags = tfMPTRequireAuth, + .domainID = uint256(42), + .err = temDISABLED}); + } + else + { + // tries to set DomainID when RequireAuth is not set + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .metadata = "test", + .domainID = uint256(42), + .err = temMALFORMED}); + + // tries to set zero DomainID + mptAlice.create( + {.maxAmt = 100, + .assetScale = 0, + .metadata = "test", + .flags = tfMPTRequireAuth, + .domainID = beast::zero, + .err = temMALFORMED}); + } + // tries to set a txfee greater than max mptAlice.create( {.maxAmt = 100, @@ -140,6 +188,48 @@ class MPToken_test : public beast::unit_test::suite BEAST_EXPECT( result[sfMaximumAmount.getJsonName()] == "9223372036854775807"); } + + if (features[featureSingleAssetVault]) + { + // Add permissioned domain + Account const credIssuer1{"credIssuer1"}; + std::string const credType = "credential"; + + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + { + Env env{*this, features}; + env.fund(XRP(1000), credIssuer1); + + env(pdomain::setTx(credIssuer1, credentials1)); + auto const domainId1 = [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + + MPTTester mptAlice(env, alice); + mptAlice.create({ + .maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807 + .assetScale = 1, + .transferFee = 10, + .metadata = "123", + .ownerCount = 1, + .flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | + tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback, + .domainID = domainId1, + }); + + // Get the hash for the most recent transaction. + std::string const txHash{ + env.tx()->getJson(JsonOptions::none)[jss::hash].asString()}; + + Json::Value const result = env.rpc("tx", txHash)[jss::result]; + BEAST_EXPECT( + result[sfMaximumAmount.getJsonName()] == + "9223372036854775807"); + } + } } void @@ -499,6 +589,59 @@ class MPToken_test : public beast::unit_test::suite .flags = 0x00000008, .err = temINVALID_FLAG}); + if (!features[featureSingleAssetVault]) + { + // test invalid flags - nothing is being changed + mptAlice.set( + {.account = alice, + .flags = 0x00000000, + .err = tecNO_PERMISSION}); + + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = 0x00000000, + .err = tecNO_PERMISSION}); + + // cannot set DomainID since SAV is not enabled + mptAlice.set( + {.account = alice, + .domainID = uint256(42), + .err = temDISABLED}); + } + else + { + // test invalid flags - nothing is being changed + mptAlice.set( + {.account = alice, + .flags = 0x00000000, + .err = temMALFORMED}); + + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = 0x00000000, + .err = temMALFORMED}); + + if (!features[featurePermissionedDomains]) + { + // cannot set DomainID since PD is not enabled + mptAlice.set( + {.account = alice, + .domainID = uint256(42), + .err = temDISABLED}); + } + else + { + // cannot set DomainID since Holder is set + mptAlice.set( + {.account = alice, + .holder = bob, + .domainID = uint256(42), + .err = temMALFORMED}); + } + } + // set both lock and unlock flags at the same time will fail mptAlice.set( {.account = alice, @@ -582,6 +725,53 @@ class MPToken_test : public beast::unit_test::suite mptAlice.set( {.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST}); } + + if (features[featureSingleAssetVault] && + features[featurePermissionedDomains]) + { + // Add permissioned domain + Account const credIssuer1{"credIssuer1"}; + std::string const credType = "credential"; + + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice); + mptAlice.create({}); + + // Trying to set DomainID on a public MPTokenIssuance + mptAlice.set( + {.domainID = uint256(42), .err = tecNO_PERMISSION}); + + mptAlice.set( + {.domainID = beast::zero, .err = tecNO_PERMISSION}); + } + + { + Env env{*this, features}; + + MPTTester mptAlice(env, alice); + mptAlice.create({.flags = tfMPTRequireAuth}); + + // Trying to set non-existing DomainID + mptAlice.set( + {.domainID = uint256(42), .err = tecOBJECT_NOT_FOUND}); + + // Trying to lock but locking is disabled + mptAlice.set( + {.flags = tfMPTUnlock, + .domainID = uint256(42), + .err = tecNO_PERMISSION}); + + mptAlice.set( + {.flags = tfMPTUnlock, + .domainID = beast::zero, + .err = tecNO_PERMISSION}); + } + } } void @@ -590,71 +780,136 @@ class MPToken_test : public beast::unit_test::suite testcase("Enabled set transaction"); using namespace test::jtx; - - // Test locking and unlocking - Env env{*this, features}; Account const alice("alice"); // issuer Account const bob("bob"); // holder - MPTTester mptAlice(env, alice, {.holders = {bob}}); - - // create a mptokenissuance with locking - mptAlice.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock}); - - mptAlice.authorize({.account = bob, .holderCount = 1}); - - // locks bob's mptoken - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - - // trying to lock bob's mptoken again will still succeed - // but no changes to the objects - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - - // alice locks the mptissuance - mptAlice.set({.account = alice, .flags = tfMPTLock}); - - // alice tries to lock up both mptissuance and mptoken again - // it will not change the flags and both will remain locked. - mptAlice.set({.account = alice, .flags = tfMPTLock}); - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - - // alice unlocks bob's mptoken - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); - - // locks up bob's mptoken again - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - if (!features[featureSingleAssetVault]) { - // Delete bobs' mptoken even though it is locked - mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize}); + // Test locking and unlocking + Env env{*this, features}; + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // create a mptokenissuance with locking + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock}); + + mptAlice.authorize({.account = bob, .holderCount = 1}); + + // locks bob's mptoken + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // trying to lock bob's mptoken again will still succeed + // but no changes to the objects + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // alice locks the mptissuance + mptAlice.set({.account = alice, .flags = tfMPTLock}); + + // alice tries to lock up both mptissuance and mptoken again + // it will not change the flags and both will remain locked. + mptAlice.set({.account = alice, .flags = tfMPTLock}); + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + + // alice unlocks bob's mptoken mptAlice.set( - {.account = alice, - .holder = bob, - .flags = tfMPTUnlock, - .err = tecOBJECT_NOT_FOUND}); + {.account = alice, .holder = bob, .flags = tfMPTUnlock}); - return; + // locks up bob's mptoken again + mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); + if (!features[featureSingleAssetVault]) + { + // Delete bobs' mptoken even though it is locked + mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize}); + + mptAlice.set( + {.account = alice, + .holder = bob, + .flags = tfMPTUnlock, + .err = tecOBJECT_NOT_FOUND}); + + return; + } + + // Cannot delete locked MPToken + mptAlice.authorize( + {.account = bob, + .flags = tfMPTUnauthorize, + .err = tecNO_PERMISSION}); + + // alice unlocks mptissuance + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + + // alice unlocks bob's mptoken + mptAlice.set( + {.account = alice, .holder = bob, .flags = tfMPTUnlock}); + + // alice unlocks mptissuance and bob's mptoken again despite that + // they are already unlocked. Make sure this will not change the + // flags + mptAlice.set( + {.account = alice, .holder = bob, .flags = tfMPTUnlock}); + mptAlice.set({.account = alice, .flags = tfMPTUnlock}); } - // Cannot delete locked MPToken - mptAlice.authorize( - {.account = bob, - .flags = tfMPTUnauthorize, - .err = tecNO_PERMISSION}); + if (features[featureSingleAssetVault]) + { + // Add permissioned domain + std::string const credType = "credential"; - // alice unlocks mptissuance - mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + // Test setting and resetting domain ID + Env env{*this, features}; - // alice unlocks bob's mptoken - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); + auto const domainId1 = [&]() { + Account const credIssuer1{"credIssuer1"}; + env.fund(XRP(1000), credIssuer1); - // alice unlocks mptissuance and bob's mptoken again despite that - // they are already unlocked. Make sure this will not change the - // flags - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); - mptAlice.set({.account = alice, .flags = tfMPTUnlock}); + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + env(pdomain::setTx(credIssuer1, credentials1)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + + auto const domainId2 = [&]() { + Account const credIssuer2{"credIssuer2"}; + env.fund(XRP(1000), credIssuer2); + + pdomain::Credentials const credentials2{ + {.issuer = credIssuer2, .credType = credType}}; + + env(pdomain::setTx(credIssuer2, credentials2)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // create a mptokenissuance with auth. + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTRequireAuth}); + BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt)); + + // reset "domain not set" to "domain not set", i.e. no change + mptAlice.set({.domainID = beast::zero}); + BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt)); + + // reset "domain not set" to domain1 + mptAlice.set({.domainID = domainId1}); + BEAST_EXPECT(mptAlice.checkDomainID(domainId1)); + + // reset domain1 to domain2 + mptAlice.set({.domainID = domainId2}); + BEAST_EXPECT(mptAlice.checkDomainID(domainId2)); + + // reset domain to "domain not set" + mptAlice.set({.domainID = beast::zero}); + BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt)); + } } void @@ -889,6 +1144,200 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(bob, alice, 100, tecNO_AUTH); } + if (features[featureSingleAssetVault] && + features[featurePermissionedDomains]) + { + // If RequireAuth is enabled and domain is a match, payment succeeds + { + Env env{*this, features}; + std::string const credType = "credential"; + Account const credIssuer1{"credIssuer1"}; + env.fund(XRP(1000), credIssuer1, bob); + + auto const domainId1 = [&]() { + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + env(pdomain::setTx(credIssuer1, credentials1)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + // bob is authorized via domain + env(credentials::create(bob, credIssuer1, credType)); + env(credentials::accept(bob, credIssuer1, credType)); + env.close(); + + MPTTester mptAlice(env, alice, {}); + env.close(); + + mptAlice.create({ + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer, + .domainID = domainId1, + }); + + mptAlice.authorize({.account = bob}); + env.close(); + + // bob is authorized via domain + mptAlice.pay(alice, bob, 100); + mptAlice.set({.domainID = beast::zero}); + + // bob is no longer authorized + mptAlice.pay(alice, bob, 100, tecNO_AUTH); + } + + { + Env env{*this, features}; + std::string const credType = "credential"; + Account const credIssuer1{"credIssuer1"}; + env.fund(XRP(1000), credIssuer1, bob); + + auto const domainId1 = [&]() { + pdomain::Credentials const credentials1{ + {.issuer = credIssuer1, .credType = credType}}; + + env(pdomain::setTx(credIssuer1, credentials1)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + // bob is authorized via domain + env(credentials::create(bob, credIssuer1, credType)); + env(credentials::accept(bob, credIssuer1, credType)); + env.close(); + + MPTTester mptAlice(env, alice, {}); + env.close(); + + mptAlice.create({ + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer, + .domainID = domainId1, + }); + + // bob creates an empty MPToken + mptAlice.authorize({.account = bob}); + + // alice authorizes bob to hold funds + mptAlice.authorize({.account = alice, .holder = bob}); + + // alice sends 100 MPT to bob + mptAlice.pay(alice, bob, 100); + + // alice UNAUTHORIZES bob + mptAlice.authorize( + {.account = alice, + .holder = bob, + .flags = tfMPTUnauthorize}); + + // bob is still authorized, via domain + mptAlice.pay(bob, alice, 10); + + mptAlice.set({.domainID = beast::zero}); + + // bob fails to send back to alice because he is no longer + // authorize to move his funds! + mptAlice.pay(bob, alice, 10, tecNO_AUTH); + } + + { + Env env{*this, features}; + std::string const credType = "credential"; + // credIssuer1 is the owner of domainId1 and a credential issuer + Account const credIssuer1{"credIssuer1"}; + // credIssuer2 is the owner of domainId2 and a credential issuer + // Note, domainId2 also lists credentials issued by credIssuer1 + Account const credIssuer2{"credIssuer2"}; + env.fund(XRP(1000), credIssuer1, credIssuer2, bob, carol); + + auto const domainId1 = [&]() { + pdomain::Credentials const credentials{ + {.issuer = credIssuer1, .credType = credType}}; + + env(pdomain::setTx(credIssuer1, credentials)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + + auto const domainId2 = [&]() { + pdomain::Credentials const credentials{ + {.issuer = credIssuer1, .credType = credType}, + {.issuer = credIssuer2, .credType = credType}}; + + env(pdomain::setTx(credIssuer2, credentials)); + return [&]() { + auto tx = env.tx()->getJson(JsonOptions::none); + return pdomain::getNewDomain(env.meta()); + }(); + }(); + + // bob is authorized via credIssuer1 which is recognized by both + // domainId1 and domainId2 + env(credentials::create(bob, credIssuer1, credType)); + env(credentials::accept(bob, credIssuer1, credType)); + env.close(); + + // carol is authorized via credIssuer2, only recognized by + // domainId2 + env(credentials::create(carol, credIssuer2, credType)); + env(credentials::accept(carol, credIssuer2, credType)); + env.close(); + + MPTTester mptAlice(env, alice, {}); + env.close(); + + mptAlice.create({ + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTRequireAuth | tfMPTCanTransfer, + .domainID = domainId1, + }); + + // bob and carol create an empty MPToken + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + env.close(); + + // alice sends 50 MPT to bob but cannot send to carol + mptAlice.pay(alice, bob, 50); + mptAlice.pay(alice, carol, 50, tecNO_AUTH); + env.close(); + + // bob cannot send to carol because they are not on the same + // domain (since credIssuer2 is not recognized by domainId1) + mptAlice.pay(bob, carol, 10, tecNO_AUTH); + env.close(); + + // alice updates domainID to domainId2 which recognizes both + // credIssuer1 and credIssuer2 + mptAlice.set({.domainID = domainId2}); + // alice can now send to carol + mptAlice.pay(alice, carol, 10); + env.close(); + + // bob can now send to carol because both are in the same + // domain + mptAlice.pay(bob, carol, 10); + env.close(); + + // bob loses his authorization and can no longer send MPT + env(credentials::deleteCred( + credIssuer1, bob, credIssuer1, credType)); + env.close(); + + mptAlice.pay(bob, carol, 10, tecNO_AUTH); + mptAlice.pay(bob, alice, 10, tecNO_AUTH); + } + } + // Non-issuer cannot send to each other if MPTCanTransfer isn't set { Env env(*this, features); @@ -1340,10 +1789,8 @@ class MPToken_test : public beast::unit_test::suite } void - testDepositPreauth() + testDepositPreauth(FeatureBitset features) { - testcase("DepositPreauth"); - using namespace test::jtx; Account const alice("alice"); // issuer Account const bob("bob"); // holder @@ -1352,8 +1799,11 @@ class MPToken_test : public beast::unit_test::suite char const credType[] = "abcde"; + if (features[featureCredentials]) { - Env env(*this); + testcase("DepositPreauth"); + + Env env(*this, features); env.fund(XRP(50000), diana, dpIssuer); env.close(); @@ -1428,7 +1878,7 @@ class MPToken_test : public beast::unit_test::suite testcase("DepositPreauth disabled featureCredentials"); { - Env env(*this, supported_amendments() - featureCredentials); + Env env(*this, testable_amendments() - featureCredentials); std::string const credIdx = "D007AE4B6E1274B4AF872588267B810C2F82716726351D1C7D38D3E5499FC6" @@ -2293,10 +2743,12 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; // MPTokenIssuanceCreate testCreateValidation(all - featureSingleAssetVault); + testCreateValidation( + (all | featureSingleAssetVault) - featurePermissionedDomains); testCreateValidation(all | featureSingleAssetVault); testCreateEnabled(all - featureSingleAssetVault); testCreateEnabled(all | featureSingleAssetVault); @@ -2314,7 +2766,11 @@ public: testAuthorizeEnabled(all | featureSingleAssetVault); // MPTokenIssuanceSet - testSetValidation(all); + testSetValidation(all - featureSingleAssetVault); + testSetValidation( + (all | featureSingleAssetVault) - featurePermissionedDomains); + testSetValidation(all | featureSingleAssetVault); + testSetEnabled(all - featureSingleAssetVault); testSetEnabled(all | featureSingleAssetVault); @@ -2323,8 +2779,9 @@ public: testClawback(all); // Test Direct Payment - testPayment(all); - testDepositPreauth(); + testPayment(all | featureSingleAssetVault); + testDepositPreauth(all); + testDepositPreauth(all - featureCredentials); // Test MPT Amount is invalid in Tx, which don't support MPT testMPTInvalidInTx(all); diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 8c1880c1a0..571ec33417 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -1478,7 +1478,7 @@ public: Account const cheri{"cheri", KeyType::secp256k1}; Account const daria{"daria", KeyType::ed25519}; - Env env{*this, supported_amendments() - featureMultiSignReserve}; + Env env{*this, testable_amendments() - featureMultiSignReserve}; env.fund(XRP(1000), alice, becky, cheri, daria); env.close(); @@ -1729,7 +1729,7 @@ public: run() override { using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); // The reserve required on a signer list changes based on // featureMultiSignReserve. Limits on the number of signers diff --git a/src/test/app/NFTokenAuth_test.cpp b/src/test/app/NFTokenAuth_test.cpp index 9558a03f7a..1a59dc579a 100644 --- a/src/test/app/NFTokenAuth_test.cpp +++ b/src/test/app/NFTokenAuth_test.cpp @@ -599,7 +599,7 @@ public: run() override { using namespace test::jtx; - static FeatureBitset const all{supported_amendments()}; + static FeatureBitset const all{testable_amendments()}; static std::array const features = { all - fixEnforceNFTokenTrustlineV2, all}; diff --git a/src/test/app/NFTokenBurn_test.cpp b/src/test/app/NFTokenBurn_test.cpp index a970b11789..21b0a1ffd8 100644 --- a/src/test/app/NFTokenBurn_test.cpp +++ b/src/test/app/NFTokenBurn_test.cpp @@ -1385,7 +1385,7 @@ protected: run(std::uint32_t instance, bool last = false) { using namespace test::jtx; - static FeatureBitset const all{supported_amendments()}; + static FeatureBitset const all{testable_amendments()}; static FeatureBitset const fixNFTV1_2{fixNonFungibleTokensV1_2}; static FeatureBitset const fixNFTDir{fixNFTokenDirV1}; static FeatureBitset const fixNFTRemint{fixNFTokenRemint}; diff --git a/src/test/app/NFTokenDir_test.cpp b/src/test/app/NFTokenDir_test.cpp index fe21e02739..df40781590 100644 --- a/src/test/app/NFTokenDir_test.cpp +++ b/src/test/app/NFTokenDir_test.cpp @@ -1100,7 +1100,7 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; FeatureBitset const fixNFTDir{ fixNFTokenDirV1, featureNonFungibleTokensV1_1}; diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 41bcc673d5..b79ebf3c40 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -8075,7 +8075,7 @@ public: run(std::uint32_t instance, bool last = false) { using namespace test::jtx; - static FeatureBitset const all{supported_amendments()}; + static FeatureBitset const all{testable_amendments()}; static FeatureBitset const fixNFTDir{fixNFTokenDirV1}; static std::array const feats{ diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index d3481881c4..96f68fb2ad 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -1343,18 +1343,10 @@ public: // NOTE : // At this point, all offers are expected to be consumed. - // Alas, they are not - because of a bug in the Taker auto-bridging - // implementation which is addressed by fixTakerDryOfferRemoval. - // The pre-fixTakerDryOfferRemoval implementation (incorrect) leaves - // an empty offer in the second leg of the bridge. Validate both the - // old and the new behavior. { auto acctOffers = offersOnAccount(env, account_to_test); - bool const noStaleOffers{ - features[featureFlowCross] || - features[fixTakerDryOfferRemoval]}; - BEAST_EXPECT(acctOffers.size() == (noStaleOffers ? 0 : 1)); + BEAST_EXPECT(acctOffers.size() == 0); for (auto const& offerPtr : acctOffers) { auto const& offer = *offerPtr; @@ -1464,8 +1456,7 @@ public: std::uint32_t const bobOfferSeq = env.seq(bob); env(offer(bob, XRP(2000), USD(1))); - if (localFeatures[featureFlowCross] && - localFeatures[fixReducedOffersV2]) + if (localFeatures[fixReducedOffersV2]) { // With the rounding introduced by fixReducedOffersV2, bob's // offer does not cross alice's offer and goes straight into @@ -1489,8 +1480,7 @@ public: // crossing algorithms becomes apparent. The old offer crossing // would consume small_amount and transfer no XRP. The new offer // crossing transfers a single drop, rather than no drops. - auto const crossingDelta = - localFeatures[featureFlowCross] ? drops(1) : drops(0); + auto const crossingDelta = drops(1); jrr = ledgerEntryState(env, alice, gw, "USD"); BEAST_EXPECT( @@ -2044,15 +2034,9 @@ public: env.require(balance(carol, USD(0))); env.require(balance(carol, EUR(none))); - // If neither featureFlowCross nor fixTakerDryOfferRemoval are defined - // then carol's offer will be left on the books, but with zero value. - int const emptyOfferCount{ - features[featureFlowCross] || features[fixTakerDryOfferRemoval] - ? 0 - : 1}; - env.require(offers(carol, 0 + emptyOfferCount)); - env.require(owners(carol, 1 + emptyOfferCount)); + env.require(offers(carol, 0)); + env.require(owners(carol, 1)); } void @@ -4236,22 +4220,13 @@ public: }; // clang-format off - TestData const takerTests[]{ - // btcStart ------------------- actor[0] -------------------- ------------------- actor[1] -------------------- - {0, 0, 1, BTC(5), {{"deb", 0, drops(3900000'000000 - 4 * baseFee), BTC(5), USD(3000)}, {"dan", 0, drops(4100000'000000 - 3 * baseFee), BTC(0), USD(750)}}}, // no BTC xfer fee - {0, 0, 0, BTC(5), {{"flo", 0, drops(4000000'000000 - 5 * baseFee), BTC(5), USD(2000)} }} // no xfer fee - }; - - TestData const flowTests[]{ + TestData const tests[]{ // btcStart ------------------- actor[0] -------------------- ------------------- actor[1] -------------------- {0, 0, 1, BTC(5), {{"gay", 1, drops(3950000'000000 - 4 * baseFee), BTC(5), USD(2500)}, {"gar", 1, drops(4050000'000000 - 3 * baseFee), BTC(0), USD(1375)}}}, // no BTC xfer fee {0, 0, 0, BTC(5), {{"hye", 2, drops(4000000'000000 - 5 * baseFee), BTC(5), USD(2000)} }} // no xfer fee }; // clang-format on - // Pick the right tests. - auto const& tests = features[featureFlowCross] ? flowTests : takerTests; - for (auto const& t : tests) { Account const& self = t.actors[t.self].acct; @@ -4378,9 +4353,8 @@ public: // 1. alice creates an offer to acquire USD/gw, an asset for which // she does not have a trust line. At some point in the future, // gw adds lsfRequireAuth. Then, later, alice's offer is crossed. - // a. With Taker alice's unauthorized offer is consumed. - // b. With FlowCross alice's offer is deleted, not consumed, - // since alice is not authorized to hold USD/gw. + // Alice's offer is deleted, not consumed, since alice is not + // authorized to hold USD/gw. // // 2. alice tries to create an offer for USD/gw, now that gw has // lsfRequireAuth set. This time the offer create fails because @@ -4428,33 +4402,17 @@ public: // gw now requires authorization and bob has gwUSD(50). Let's see if // bob can cross alice's offer. // - // o With Taker bob's offer should cross alice's. - // o With FlowCross bob's offer shouldn't cross and alice's - // unauthorized offer should be deleted. + // Bob's offer shouldn't cross and alice's unauthorized offer should be + // deleted. env(offer(bob, XRP(4000), gwUSD(40))); env.close(); std::uint32_t const bobOfferSeq = env.seq(bob) - 1; - bool const flowCross = features[featureFlowCross]; - env.require(offers(alice, 0)); - if (flowCross) - { - // alice's unauthorized offer is deleted & bob's offer not crossed. - env.require(balance(alice, gwUSD(none))); - env.require(offers(bob, 1)); - env.require(balance(bob, gwUSD(50))); - } - else - { - // alice's offer crosses bob's - env.require(balance(alice, gwUSD(40))); - env.require(offers(bob, 0)); - env.require(balance(bob, gwUSD(10))); - - // The rest of the test verifies FlowCross behavior. - return; - } + // alice's unauthorized offer is deleted & bob's offer not crossed. + env.require(balance(alice, gwUSD(none))); + env.require(offers(bob, 1)); + env.require(balance(bob, gwUSD(50))); // See if alice can create an offer without authorization. alice // should not be able to create the offer and bob's offer should be @@ -5186,9 +5144,7 @@ public: // tfFillOrKill, TakerPays must be filled { TER const err = - features[fixFillOrKill] || !features[featureFlowCross] - ? TER(tesSUCCESS) - : tecKILLED; + features[fixFillOrKill] ? TER(tesSUCCESS) : tecKILLED; env(offer(maker, XRP(100), USD(100))); env.close(); @@ -5409,8 +5365,7 @@ public: run(std::uint32_t instance, bool last = false) { using namespace jtx; - static FeatureBitset const all{supported_amendments()}; - static FeatureBitset const flowCross{featureFlowCross}; + static FeatureBitset const all{testable_amendments()}; static FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval}; static FeatureBitset const rmSmallIncreasedQOffers{ fixRmSmallIncreasedQOffers}; @@ -5419,10 +5374,9 @@ public: FeatureBitset const fillOrKill{fixFillOrKill}; FeatureBitset const permDEX{featurePermissionedDEX}; - static std::array const feats{ + static std::array const feats{ all - takerDryOffer - immediateOfferKilled - permDEX, - all - flowCross - takerDryOffer - immediateOfferKilled - permDEX, - all - flowCross - immediateOfferKilled - permDEX, + all - immediateOfferKilled - permDEX, all - rmSmallIncreasedQOffers - immediateOfferKilled - fillOrKill - permDEX, all - fillOrKill - permDEX, @@ -5444,7 +5398,7 @@ public: } }; -class OfferWOFlowCross_test : public OfferBaseUtil_test +class OfferWTakerDryOffer_test : public OfferBaseUtil_test { void run() override @@ -5453,7 +5407,7 @@ class OfferWOFlowCross_test : public OfferBaseUtil_test } }; -class OfferWTakerDryOffer_test : public OfferBaseUtil_test +class OfferWOSmallQOffers_test : public OfferBaseUtil_test { void run() override @@ -5462,7 +5416,7 @@ class OfferWTakerDryOffer_test : public OfferBaseUtil_test } }; -class OfferWOSmallQOffers_test : public OfferBaseUtil_test +class OfferWOFillOrKill_test : public OfferBaseUtil_test { void run() override @@ -5471,7 +5425,7 @@ class OfferWOSmallQOffers_test : public OfferBaseUtil_test } }; -class OfferWOFillOrKill_test : public OfferBaseUtil_test +class OfferWOPermDEX_test : public OfferBaseUtil_test { void run() override @@ -5480,21 +5434,12 @@ class OfferWOFillOrKill_test : public OfferBaseUtil_test } }; -class OfferWOPermDEX_test : public OfferBaseUtil_test -{ - void - run() override - { - OfferBaseUtil_test::run(5); - } -}; - class OfferAllFeatures_test : public OfferBaseUtil_test { void run() override { - OfferBaseUtil_test::run(6, true); + OfferBaseUtil_test::run(5, true); } }; @@ -5504,27 +5449,24 @@ class Offer_manual_test : public OfferBaseUtil_test run() override { using namespace jtx; - FeatureBitset const all{supported_amendments()}; - FeatureBitset const flowCross{featureFlowCross}; + FeatureBitset const all{testable_amendments()}; FeatureBitset const f1513{fix1513}; FeatureBitset const immediateOfferKilled{featureImmediateOfferKilled}; FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval}; FeatureBitset const fillOrKill{fixFillOrKill}; FeatureBitset const permDEX{featurePermissionedDEX}; - testAll(all - flowCross - f1513 - immediateOfferKilled - permDEX); - testAll(all - flowCross - immediateOfferKilled - permDEX); + testAll(all - f1513 - immediateOfferKilled - permDEX); testAll(all - immediateOfferKilled - fillOrKill - permDEX); testAll(all - fillOrKill - permDEX); testAll(all - permDEX); testAll(all); - testAll(all - flowCross - takerDryOffer - permDEX); + testAll(all - takerDryOffer - permDEX); } }; BEAST_DEFINE_TESTSUITE_PRIO(OfferBaseUtil, tx, ripple, 2); -BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFlowCross, tx, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(OfferWTakerDryOffer, tx, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(OfferWOSmallQOffers, tx, ripple, 2); BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFillOrKill, tx, ripple, 2); diff --git a/src/test/app/Oracle_test.cpp b/src/test/app/Oracle_test.cpp index a968970395..fdd7ad941e 100644 --- a/src/test/app/Oracle_test.cpp +++ b/src/test/app/Oracle_test.cpp @@ -678,6 +678,61 @@ private: oracle.set( UpdateArg{.series = {{"XRP", "USD", 742, 2}}, .fee = baseFee}); } + + for (bool const withFixOrder : {false, true}) + { + // Should be same order as creation + Env env( + *this, + withFixOrder ? testable_amendments() + : testable_amendments() - fixPriceOracleOrder); + auto const baseFee = + static_cast(env.current()->fees().base.drops()); + + auto test = [&](Env& env, DataSeries const& series) { + env.fund(XRP(1'000), owner); + Oracle oracle( + env, {.owner = owner, .series = series, .fee = baseFee}); + BEAST_EXPECT(oracle.exists()); + auto sle = env.le(keylet::oracle(owner, oracle.documentID())); + BEAST_EXPECT( + sle->getFieldArray(sfPriceDataSeries).size() == + series.size()); + + auto const beforeQuoteAssetName1 = + sle->getFieldArray(sfPriceDataSeries)[0] + .getFieldCurrency(sfQuoteAsset) + .getText(); + auto const beforeQuoteAssetName2 = + sle->getFieldArray(sfPriceDataSeries)[1] + .getFieldCurrency(sfQuoteAsset) + .getText(); + + oracle.set(UpdateArg{.series = series, .fee = baseFee}); + sle = env.le(keylet::oracle(owner, oracle.documentID())); + + auto const afterQuoteAssetName1 = + sle->getFieldArray(sfPriceDataSeries)[0] + .getFieldCurrency(sfQuoteAsset) + .getText(); + auto const afterQuoteAssetName2 = + sle->getFieldArray(sfPriceDataSeries)[1] + .getFieldCurrency(sfQuoteAsset) + .getText(); + + if (env.current()->rules().enabled(fixPriceOracleOrder)) + { + BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1); + BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2); + } + else + { + BEAST_EXPECT(afterQuoteAssetName1 != beforeQuoteAssetName1); + BEAST_EXPECT(afterQuoteAssetName2 != beforeQuoteAssetName2); + } + }; + test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}}); + } } void @@ -783,7 +838,7 @@ private: testcase("Amendment"); using namespace jtx; - auto const features = supported_amendments() - featurePriceOracle; + auto const features = testable_amendments() - featurePriceOracle; Account const owner("owner"); Env env(*this, features); auto const baseFee = @@ -806,7 +861,7 @@ public: run() override { using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); testInvalidSet(); testInvalidDelete(); testCreate(); diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 7cb1542453..3a5d3d6ff5 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -1035,7 +1035,7 @@ struct PayChan_test : public beast::unit_test::suite { // Credentials amendment not enabled - Env env(*this, supported_amendments() - featureCredentials); + Env env(*this, testable_amendments() - featureCredentials); env.fund(XRP(5000), "alice", "bob"); env.close(); @@ -2344,7 +2344,7 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testWithFeats(all - disallowIncoming); testWithFeats(all); testDepositAuthCreds(); diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index 9188da62ac..936fe403d4 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -1267,16 +1267,13 @@ struct PayStrand_test : public beast::unit_test::suite run() override { using namespace jtx; - auto const sa = supported_amendments(); - testToStrand(sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); testToStrand(sa - featurePermissionedDEX); testToStrand(sa); - testRIPD1373(sa - featureFlowCross - featurePermissionedDEX); testRIPD1373(sa - featurePermissionedDEX); testRIPD1373(sa); - testLoop(sa - featureFlowCross - featurePermissionedDEX); testLoop(sa - featurePermissionedDEX); testLoop(sa); diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index 693381debf..3fd3a35f45 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -207,24 +207,6 @@ class PermissionedDEX_test : public beast::unit_test::suite env.close(); } - // test preflight: permissioned dex cannot be used without enable - // flowcross - { - Env env(*this, features - featureFlowCross); - auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = - PermissionedDEX(env); - - env(offer(bob, XRP(10), USD(10)), - domain(domainID), - ter(temDISABLED)); - env.close(); - - env.enableFeature(featureFlowCross); - env.close(); - env(offer(bob, XRP(10), USD(10)), domain(domainID)); - env.close(); - } - // preclaim - someone outside of the domain cannot create domain offer { Env env(*this, features); @@ -1569,7 +1551,7 @@ public: void run() override { - FeatureBitset const all{jtx::supported_amendments()}; + FeatureBitset const all{jtx::testable_amendments()}; // Test domain offer (w/o hyrbid) testOfferCreate(all); diff --git a/src/test/app/PermissionedDomains_test.cpp b/src/test/app/PermissionedDomains_test.cpp index e33a88fa08..31e34ccf17 100644 --- a/src/test/app/PermissionedDomains_test.cpp +++ b/src/test/app/PermissionedDomains_test.cpp @@ -53,9 +53,9 @@ exceptionExpected(Env& env, Json::Value const& jv) class PermissionedDomains_test : public beast::unit_test::suite { FeatureBitset withoutFeature_{ - supported_amendments() - featurePermissionedDomains}; + testable_amendments() - featurePermissionedDomains}; FeatureBitset withFeature_{ - supported_amendments() // + testable_amendments() // | featurePermissionedDomains | featureCredentials}; // Verify that each tx type can execute if the feature is enabled. @@ -81,7 +81,7 @@ class PermissionedDomains_test : public beast::unit_test::suite void testCredentialsDisabled() { - auto amendments = supported_amendments(); + auto amendments = testable_amendments(); amendments.set(featurePermissionedDomains); amendments.reset(featureCredentials); testcase("Credentials disabled"); diff --git a/src/test/app/PseudoTx_test.cpp b/src/test/app/PseudoTx_test.cpp index d96828a50b..53adf795c2 100644 --- a/src/test/app/PseudoTx_test.cpp +++ b/src/test/app/PseudoTx_test.cpp @@ -115,7 +115,7 @@ struct PseudoTx_test : public beast::unit_test::suite run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; FeatureBitset const xrpFees{featureXRPFees}; testPrevented(all - featureXRPFees); diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index 31c38f23b1..fce4e94048 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -229,7 +229,6 @@ class RCLValidations_test : public beast::unit_test::suite // support for a ledger hash which is already in the trie. using Seq = RCLValidatedLedger::Seq; - using ID = RCLValidatedLedger::ID; // Max known ancestors for each ledger Seq const maxAncestors = 256; diff --git a/src/test/app/ReducedOffer_test.cpp b/src/test/app/ReducedOffer_test.cpp index 546a07d93e..5142aaab0e 100644 --- a/src/test/app/ReducedOffer_test.cpp +++ b/src/test/app/ReducedOffer_test.cpp @@ -82,8 +82,8 @@ public: // Make one test run without fixReducedOffersV1 and one with. for (FeatureBitset features : - {supported_amendments() - fixReducedOffersV1, - supported_amendments() | fixReducedOffersV1}) + {testable_amendments() - fixReducedOffersV1, + testable_amendments() | fixReducedOffersV1}) { Env env{*this, features}; @@ -238,8 +238,8 @@ public: // Make one test run without fixReducedOffersV1 and one with. for (FeatureBitset features : - {supported_amendments() - fixReducedOffersV1, - supported_amendments() | fixReducedOffersV1}) + {testable_amendments() - fixReducedOffersV1, + testable_amendments() | fixReducedOffersV1}) { // Make sure none of the offers we generate are under funded. Env env{*this, features}; @@ -401,8 +401,8 @@ public: // Make one test run without fixReducedOffersV1 and one with. for (FeatureBitset features : - {supported_amendments() - fixReducedOffersV1, - supported_amendments() | fixReducedOffersV1}) + {testable_amendments() - fixReducedOffersV1, + testable_amendments() | fixReducedOffersV1}) { Env env{*this, features}; @@ -509,8 +509,8 @@ public: // Make one test run without fixReducedOffersV1 and one with. for (FeatureBitset features : - {supported_amendments() - fixReducedOffersV1, - supported_amendments() | fixReducedOffersV1}) + {testable_amendments() - fixReducedOffersV1, + testable_amendments() | fixReducedOffersV1}) { Env env{*this, features}; @@ -639,8 +639,8 @@ public: // Make one test run without fixReducedOffersV2 and one with. for (FeatureBitset features : - {supported_amendments() - fixReducedOffersV2, - supported_amendments() | fixReducedOffersV2}) + {testable_amendments() - fixReducedOffersV2, + testable_amendments() | fixReducedOffersV2}) { // Make sure none of the offers we generate are under funded. Env env{*this, features}; diff --git a/src/test/app/SetAuth_test.cpp b/src/test/app/SetAuth_test.cpp index a4c2df6228..4c63560770 100644 --- a/src/test/app/SetAuth_test.cpp +++ b/src/test/app/SetAuth_test.cpp @@ -74,8 +74,7 @@ struct SetAuth_test : public beast::unit_test::suite run() override { using namespace jtx; - auto const sa = supported_amendments(); - testAuth(sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); testAuth(sa - featurePermissionedDEX); testAuth(sa); } diff --git a/src/test/app/SetRegularKey_test.cpp b/src/test/app/SetRegularKey_test.cpp index 6a3a5ff2a9..78b75fc458 100644 --- a/src/test/app/SetRegularKey_test.cpp +++ b/src/test/app/SetRegularKey_test.cpp @@ -32,7 +32,7 @@ public: using namespace test::jtx; testcase("Set regular key"); - Env env{*this, supported_amendments() - fixMasterKeyAsRegularKey}; + Env env{*this, testable_amendments() - fixMasterKeyAsRegularKey}; Account const alice("alice"); Account const bob("bob"); env.fund(XRP(10000), alice, bob); @@ -72,7 +72,7 @@ public: using namespace test::jtx; testcase("Set regular key"); - Env env{*this, supported_amendments() | fixMasterKeyAsRegularKey}; + Env env{*this, testable_amendments() | fixMasterKeyAsRegularKey}; Account const alice("alice"); Account const bob("bob"); env.fund(XRP(10000), alice, bob); @@ -109,7 +109,7 @@ public: // See https://ripplelabs.atlassian.net/browse/RIPD-1721. testcase( "Set regular key to master key (before fixMasterKeyAsRegularKey)"); - Env env{*this, supported_amendments() - fixMasterKeyAsRegularKey}; + Env env{*this, testable_amendments() - fixMasterKeyAsRegularKey}; Account const alice("alice"); env.fund(XRP(10000), alice); @@ -139,7 +139,7 @@ public: testcase( "Set regular key to master key (after fixMasterKeyAsRegularKey)"); - Env env{*this, supported_amendments() | fixMasterKeyAsRegularKey}; + Env env{*this, testable_amendments() | fixMasterKeyAsRegularKey}; Account const alice("alice"); env.fund(XRP(10000), alice); diff --git a/src/test/app/SetTrust_test.cpp b/src/test/app/SetTrust_test.cpp index 9b4048bf9c..18457b5faf 100644 --- a/src/test/app/SetTrust_test.cpp +++ b/src/test/app/SetTrust_test.cpp @@ -648,7 +648,7 @@ public: run() override { using namespace test::jtx; - auto const sa = supported_amendments(); + auto const sa = testable_amendments(); testWithFeats(sa - disallowIncoming); testWithFeats(sa); } diff --git a/src/test/app/Taker_test.cpp b/src/test/app/Taker_test.cpp deleted file mode 100644 index 3b3f338625..0000000000 --- a/src/test/app/Taker_test.cpp +++ /dev/null @@ -1,1394 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include - -#include - -namespace ripple { - -class Taker_test : public beast::unit_test::suite -{ - static bool const Buy = false; - static bool const Sell = true; - - class TestTaker : public BasicTaker - { - STAmount funds_; - STAmount cross_funds; - - public: - TestTaker( - CrossType cross_type, - Amounts const& amount, - Quality const& quality, - STAmount const& funds, - std::uint32_t flags, - Rate const& rate_in, - Rate const& rate_out) - : BasicTaker( - cross_type, - AccountID(0x4701), - amount, - quality, - flags, - rate_in, - rate_out) - , funds_(funds) - { - } - - void - set_funds(STAmount const& funds) - { - cross_funds = funds; - } - - STAmount - get_funds(AccountID const& owner, STAmount const& funds) const override - { - if (owner == account()) - return funds_; - - return cross_funds; - } - - Amounts - cross(Amounts offer, Quality quality) - { - if (reject(quality)) - return Amounts(offer.in.zeroed(), offer.out.zeroed()); - - // we need to emulate "unfunded offers" behavior - if (get_funds(AccountID(0x4702), offer.out) == beast::zero) - return Amounts(offer.in.zeroed(), offer.out.zeroed()); - - if (done()) - return Amounts(offer.in.zeroed(), offer.out.zeroed()); - - auto result = do_cross(offer, quality, AccountID(0x4702)); - - funds_ -= result.order.in; - - return result.order; - } - - std::pair - cross( - Amounts offer1, - Quality quality1, - Amounts offer2, - Quality quality2) - { - /* check if composed quality should be rejected */ - Quality const quality(composed_quality(quality1, quality2)); - - if (reject(quality)) - return std::make_pair( - Amounts{offer1.in.zeroed(), offer1.out.zeroed()}, - Amounts{offer2.in.zeroed(), offer2.out.zeroed()}); - - if (done()) - return std::make_pair( - Amounts{offer1.in.zeroed(), offer1.out.zeroed()}, - Amounts{offer2.in.zeroed(), offer2.out.zeroed()}); - - auto result = do_cross( - offer1, - quality1, - AccountID(0x4703), - offer2, - quality2, - AccountID(0x4704)); - - return std::make_pair(result.first.order, result.second.order); - } - }; - -private: - Issue const& - usd() const - { - static Issue const issue( - Currency(0x5553440000000000), AccountID(0x4985601)); - return issue; - } - - Issue const& - eur() const - { - static Issue const issue( - Currency(0x4555520000000000), AccountID(0x4985602)); - return issue; - } - - Issue const& - xrp() const - { - static Issue const issue(xrpCurrency(), xrpAccount()); - return issue; - } - - STAmount - parse_amount(std::string const& amount, Issue const& issue) - { - return amountFromString(issue, amount); - } - - Amounts - parse_amounts( - std::string const& amount_in, - Issue const& issue_in, - std::string const& amount_out, - Issue const& issue_out) - { - STAmount const in(parse_amount(amount_in, issue_in)); - STAmount const out(parse_amount(amount_out, issue_out)); - - return {in, out}; - } - - struct cross_attempt_offer - { - cross_attempt_offer(std::string const& in_, std::string const& out_) - : in(in_), out(out_) - { - } - - std::string in; - std::string out; - }; - -private: - std::string - format_amount(STAmount const& amount) - { - std::string txt = amount.getText(); - txt += "/"; - txt += to_string(amount.issue().currency); - return txt; - } - - void - attempt( - bool sell, - std::string name, - Quality taker_quality, - cross_attempt_offer const offer, - std::string const funds, - Quality cross_quality, - cross_attempt_offer const cross, - std::string const cross_funds, - cross_attempt_offer const flow, - Issue const& issue_in, - Issue const& issue_out, - Rate rate_in = parityRate, - Rate rate_out = parityRate) - { - Amounts taker_offer( - parse_amounts(offer.in, issue_in, offer.out, issue_out)); - - Amounts cross_offer( - parse_amounts(cross.in, issue_in, cross.out, issue_out)); - - CrossType cross_type; - - if (isXRP(issue_out)) - cross_type = CrossType::IouToXrp; - else if (isXRP(issue_in)) - cross_type = CrossType::XrpToIou; - else - cross_type = CrossType::IouToIou; - - // FIXME: We are always invoking the IOU-to-IOU taker. We should select - // the correct type dynamically. - TestTaker taker( - cross_type, - taker_offer, - taker_quality, - parse_amount(funds, issue_in), - sell ? tfSell : 0, - rate_in, - rate_out); - - taker.set_funds(parse_amount(cross_funds, issue_out)); - - auto result = taker.cross(cross_offer, cross_quality); - - Amounts const expected( - parse_amounts(flow.in, issue_in, flow.out, issue_out)); - - BEAST_EXPECT(expected == result); - - if (expected != result) - { - log << "Expected: " << format_amount(expected.in) << " : " - << format_amount(expected.out) << '\n' - << " Actual: " << format_amount(result.in) << " : " - << format_amount(result.out) << std::endl; - } - } - - Quality - get_quality(std::string in, std::string out) - { - return Quality(parse_amounts(in, xrp(), out, xrp())); - } - -public: - // Notation for clamp scenario descriptions: - // - // IN:OUT (with the last in the list being limiting factor) - // N = Nothing - // T = Taker Offer Balance - // A = Taker Account Balance - // B = Owner Account Balance - // - // (s) = sell semantics: taker wants unlimited output - // (b) = buy semantics: taker wants a limited amount out - - // NIKB TODO: Augment TestTaker so currencies and rates can be specified - // once without need for repetition. - void - test_xrp_to_iou() - { - testcase("XRP Quantization: input"); - - Quality q1 = get_quality("1", "1"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - // TAKER OWNER - // QUAL OFFER FUNDS QUAL OFFER FUNDS - // EXPECTED - // XRP USD - attempt( - Sell, - "N:N", - q1, - {"2", "2"}, - "2", - q1, - {"2", "2"}, - "2", - {"2", "2"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "N:B", - q1, - {"2", "2"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"2", "1.8"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "N:B", - q1, - {"2", "2"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "N:T", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "N:BT", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "N:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "N:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"0", "0.8"}, - xrp(), - usd()); - } - attempt( - Sell, - "T:N", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "T:B", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "T:B", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "T:T", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "T:BT", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "T:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "T:TB", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "0.8", - {"0", "0.8"}, - xrp(), - usd()); - } - - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "A:T", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "3", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "A:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "2.4", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "A:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "A:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"0", "0.8"}, - xrp(), - usd()); - } - - attempt( - Sell, - "TA:N", - q1, - {"2", "2"}, - "1", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "TA:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "TA:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "TA:T", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "3", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "TA:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - attempt( - Buy, - "TA:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - - attempt( - Sell, - "AT:N", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "3", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Sell, - "AT:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - } - else - { - attempt( - Sell, - "AT:B", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - } - attempt( - Buy, - "AT:T", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "3", - {"1", "1"}, - xrp(), - usd()); - if (NumberSwitchOver) - { - attempt( - Buy, - "AT:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1"}, - xrp(), - usd()); - attempt( - Buy, - "AT:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"1", "0.8"}, - xrp(), - usd()); - } - else - { - attempt( - Buy, - "AT:BT", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "1.8", - {"1", "1.8"}, - xrp(), - usd()); - attempt( - Buy, - "AT:TB", - q1, - {"2", "2"}, - "1", - q1, - {"3", "3"}, - "0.8", - {"0", "0.8"}, - xrp(), - usd()); - } - } - } - - void - test_iou_to_xrp() - { - testcase("XRP Quantization: output"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - Quality q1 = get_quality("1", "1"); - - // TAKER OWNER - // QUAL OFFER FUNDS QUAL OFFER FUNDS - // EXPECTED - // USD XRP - attempt( - Sell, - "N:N", - q1, - {"3", "3"}, - "3", - q1, - {"3", "3"}, - "3", - {"3", "3"}, - usd(), - xrp()); - attempt( - Sell, - "N:B", - q1, - {"3", "3"}, - "3", - q1, - {"3", "3"}, - "2", - {"2", "2"}, - usd(), - xrp()); - if (NumberSwitchOver) - { - attempt( - Buy, - "N:T", - q1, - {"3", "3"}, - "2.5", - q1, - {"5", "5"}, - "5", - {"2.5", "3"}, - usd(), - xrp()); - attempt( - Buy, - "N:BT", - q1, - {"3", "3"}, - "1.5", - q1, - {"5", "5"}, - "4", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Buy, - "N:T", - q1, - {"3", "3"}, - "2.5", - q1, - {"5", "5"}, - "5", - {"2.5", "2"}, - usd(), - xrp()); - attempt( - Buy, - "N:BT", - q1, - {"3", "3"}, - "1.5", - q1, - {"5", "5"}, - "4", - {"1.5", "1"}, - usd(), - xrp()); - } - attempt( - Buy, - "N:TB", - q1, - {"3", "3"}, - "2.2", - q1, - {"5", "5"}, - "1", - {"1", "1"}, - usd(), - xrp()); - - attempt( - Sell, - "T:N", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - usd(), - xrp()); - attempt( - Sell, - "T:B", - q1, - {"2", "2"}, - "2", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - attempt( - Buy, - "T:T", - q1, - {"1", "1"}, - "2", - q1, - {"2", "2"}, - "2", - {"1", "1"}, - usd(), - xrp()); - attempt( - Buy, - "T:BT", - q1, - {"1", "1"}, - "2", - q1, - {"3", "3"}, - "2", - {"1", "1"}, - usd(), - xrp()); - attempt( - Buy, - "T:TB", - q1, - {"2", "2"}, - "2", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - - if (NumberSwitchOver) - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "2"}, - usd(), - xrp()); - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1.8", - q1, - {"3", "3"}, - "2", - {"1.8", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "1"}, - usd(), - xrp()); - attempt( - Sell, - "A:B", - q1, - {"2", "2"}, - "1.8", - q1, - {"3", "3"}, - "2", - {"1.8", "1"}, - usd(), - xrp()); - } - attempt( - Buy, - "A:T", - q1, - {"2", "2"}, - "1.2", - q1, - {"3", "3"}, - "3", - {"1.2", "1"}, - usd(), - xrp()); - if (NumberSwitchOver) - { - attempt( - Buy, - "A:BT", - q1, - {"2", "2"}, - "1.5", - q1, - {"4", "4"}, - "3", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Buy, - "A:BT", - q1, - {"2", "2"}, - "1.5", - q1, - {"4", "4"}, - "3", - {"1.5", "1"}, - usd(), - xrp()); - } - attempt( - Buy, - "A:TB", - q1, - {"2", "2"}, - "1.5", - q1, - {"4", "4"}, - "1", - {"1", "1"}, - usd(), - xrp()); - - if (NumberSwitchOver) - { - attempt( - Sell, - "TA:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Sell, - "TA:N", - q1, - {"2", "2"}, - "1.5", - q1, - {"2", "2"}, - "2", - {"1.5", "1"}, - usd(), - xrp()); - } - attempt( - Sell, - "TA:B", - q1, - {"2", "2"}, - "1.5", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - if (NumberSwitchOver) - { - attempt( - Buy, - "TA:T", - q1, - {"2", "2"}, - "1.5", - q1, - {"3", "3"}, - "3", - {"1.5", "2"}, - usd(), - xrp()); - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1.8", - q1, - {"4", "4"}, - "3", - {"1.8", "2"}, - usd(), - xrp()); - } - else - { - attempt( - Buy, - "TA:T", - q1, - {"2", "2"}, - "1.5", - q1, - {"3", "3"}, - "3", - {"1.5", "1"}, - usd(), - xrp()); - attempt( - Buy, - "TA:BT", - q1, - {"2", "2"}, - "1.8", - q1, - {"4", "4"}, - "3", - {"1.8", "1"}, - usd(), - xrp()); - } - attempt( - Buy, - "TA:TB", - q1, - {"2", "2"}, - "1.2", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - - attempt( - Sell, - "AT:N", - q1, - {"2", "2"}, - "2.5", - q1, - {"4", "4"}, - "4", - {"2", "2"}, - usd(), - xrp()); - attempt( - Sell, - "AT:B", - q1, - {"2", "2"}, - "2.5", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - attempt( - Buy, - "AT:T", - q1, - {"2", "2"}, - "2.5", - q1, - {"3", "3"}, - "3", - {"2", "2"}, - usd(), - xrp()); - attempt( - Buy, - "AT:BT", - q1, - {"2", "2"}, - "2.5", - q1, - {"4", "4"}, - "3", - {"2", "2"}, - usd(), - xrp()); - attempt( - Buy, - "AT:TB", - q1, - {"2", "2"}, - "2.5", - q1, - {"3", "3"}, - "1", - {"1", "1"}, - usd(), - xrp()); - } - } - - void - test_iou_to_iou() - { - testcase("IOU to IOU"); - - for (auto NumberSwitchOver : {false, true}) - { - NumberSO stNumberSO{NumberSwitchOver}; - Quality q1 = get_quality("1", "1"); - - // Highly exaggerated 50% transfer rate for the input and output: - Rate const rate{parityRate.value + (parityRate.value / 2)}; - - // TAKER OWNER - // QUAL OFFER FUNDS QUAL OFFER FUNDS - // EXPECTED - // EUR USD - attempt( - Sell, - "N:N", - q1, - {"2", "2"}, - "10", - q1, - {"2", "2"}, - "10", - {"2", "2"}, - eur(), - usd(), - rate, - rate); - if (NumberSwitchOver) - { - attempt( - Sell, - "N:B", - q1, - {"4", "4"}, - "10", - q1, - {"4", "4"}, - "4", - {"2.666666666666667", "2.666666666666667"}, - eur(), - usd(), - rate, - rate); - } - else - { - attempt( - Sell, - "N:B", - q1, - {"4", "4"}, - "10", - q1, - {"4", "4"}, - "4", - {"2.666666666666666", "2.666666666666666"}, - eur(), - usd(), - rate, - rate); - } - attempt( - Buy, - "N:T", - q1, - {"1", "1"}, - "10", - q1, - {"2", "2"}, - "10", - {"1", "1"}, - eur(), - usd(), - rate, - rate); - attempt( - Buy, - "N:BT", - q1, - {"2", "2"}, - "10", - q1, - {"6", "6"}, - "5", - {"2", "2"}, - eur(), - usd(), - rate, - rate); - attempt( - Buy, - "N:TB", - q1, - {"2", "2"}, - "2", - q1, - {"6", "6"}, - "1", - {"0.6666666666666667", "0.6666666666666667"}, - eur(), - usd(), - rate, - rate); - if (NumberSwitchOver) - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "2.5", - q1, - {"2", "2"}, - "10", - {"1.666666666666667", "1.666666666666667"}, - eur(), - usd(), - rate, - rate); - } - else - { - attempt( - Sell, - "A:N", - q1, - {"2", "2"}, - "2.5", - q1, - {"2", "2"}, - "10", - {"1.666666666666666", "1.666666666666666"}, - eur(), - usd(), - rate, - rate); - } - } - } - - void - run() override - { - test_xrp_to_iou(); - test_iou_to_xrp(); - test_iou_to_iou(); - } -}; - -BEAST_DEFINE_TESTSUITE(Taker, tx, ripple); - -} // namespace ripple diff --git a/src/test/app/TheoreticalQuality_test.cpp b/src/test/app/TheoreticalQuality_test.cpp index dcffd810ed..a8713ec69a 100644 --- a/src/test/app/TheoreticalQuality_test.cpp +++ b/src/test/app/TheoreticalQuality_test.cpp @@ -359,7 +359,7 @@ public: // Tests are sped up by a factor of 2 if a new environment isn't created // on every iteration. - Env env(*this, supported_amendments()); + Env env(*this, testable_amendments()); for (int i = 0; i < numTestIterations; ++i) { auto const iterAsStr = std::to_string(i); @@ -434,7 +434,7 @@ public: // Speed up tests by creating the environment outside the loop // (factor of 2 speedup on the DirectStep tests) - Env env(*this, supported_amendments()); + Env env(*this, testable_amendments()); for (int i = 0; i < numTestIterations; ++i) { auto const iterAsStr = std::to_string(i); diff --git a/src/test/app/Ticket_test.cpp b/src/test/app/Ticket_test.cpp index dd83e3036e..f8ac64679e 100644 --- a/src/test/app/Ticket_test.cpp +++ b/src/test/app/Ticket_test.cpp @@ -385,7 +385,7 @@ class Ticket_test : public beast::unit_test::suite testcase("Feature Not Enabled"); using namespace test::jtx; - Env env{*this, supported_amendments() - featureTicketBatch}; + Env env{*this, testable_amendments() - featureTicketBatch}; env(ticket::create(env.master, 1), ter(temDISABLED)); env.close(); @@ -933,7 +933,7 @@ class Ticket_test : public beast::unit_test::suite // Try the test without featureTicketBatch enabled. using namespace test::jtx; { - Env env{*this, supported_amendments() - featureTicketBatch}; + Env env{*this, testable_amendments() - featureTicketBatch}; Account alice{"alice"}; env.fund(XRP(10000), alice); @@ -957,7 +957,7 @@ class Ticket_test : public beast::unit_test::suite } // Try the test with featureTicketBatch enabled. { - Env env{*this, supported_amendments()}; + Env env{*this, testable_amendments()}; Account alice{"alice"}; env.fund(XRP(10000), alice); diff --git a/src/test/app/TrustAndBalance_test.cpp b/src/test/app/TrustAndBalance_test.cpp index 8f092a725f..f39d9e0313 100644 --- a/src/test/app/TrustAndBalance_test.cpp +++ b/src/test/app/TrustAndBalance_test.cpp @@ -480,8 +480,7 @@ public: }; using namespace test::jtx; - auto const sa = supported_amendments(); - testWithFeatures(sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); testWithFeatures(sa - featurePermissionedDEX); testWithFeatures(sa); } diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 947640495d..d0965cc8ff 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -99,40 +99,6 @@ class TxQPosNegFlows_test : public beast::unit_test::suite return calcMedFeeLevel(feeLevel, feeLevel); } - static std::unique_ptr - makeConfig( - std::map extraTxQ = {}, - std::map extraVoting = {}) - { - auto p = test::jtx::envconfig(); - auto& section = p->section("transaction_queue"); - section.set("ledgers_in_queue", "2"); - section.set("minimum_queue_size", "2"); - section.set("min_ledgers_to_compute_size_limit", "3"); - section.set("max_ledger_counts_to_store", "100"); - section.set("retry_sequence_percent", "25"); - section.set("normal_consensus_increase_percent", "0"); - - for (auto const& [k, v] : extraTxQ) - section.set(k, v); - - // Some tests specify different fee settings that are enabled by - // a FeeVote - if (!extraVoting.empty()) - { - auto& votingSection = p->section("voting"); - for (auto const& [k, v] : extraVoting) - { - votingSection.set(k, v); - } - - // In order for the vote to occur, we must run as a validator - p->section("validation_seed") - .legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH"); - } - return p; - } - std::size_t initFee( jtx::Env& env, diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index ccac0e2819..f9036719cd 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -17,18 +17,8 @@ */ //============================================================================== -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include @@ -244,6 +234,28 @@ class Vault_test : public beast::unit_test::suite env(tx, ter{tecNO_PERMISSION}); } + { + testcase(prefix + " fail to withdraw to zero destination"); + auto tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(1000)}); + tx[sfDestination] = "0"; + env(tx, ter(temMALFORMED)); + } + + { + testcase( + prefix + + " fail to withdraw with tag but without destination"); + auto tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(1000)}); + tx[sfDestinationTag] = "0"; + env(tx, ter(temMALFORMED)); + } + if (!asset.raw().native()) { testcase( @@ -350,7 +362,7 @@ class Vault_test : public beast::unit_test::suite Account const& owner, Account const& depositor, Account const& charlie)> setup) { - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; Account depositor{"depositor"}; @@ -426,7 +438,7 @@ class Vault_test : public beast::unit_test::suite struct CaseArgs { FeatureBitset features = - supported_amendments() | featureSingleAssetVault; + testable_amendments() | featureSingleAssetVault; }; auto testCase = [&, this]( @@ -504,7 +516,7 @@ class Vault_test : public beast::unit_test::suite env(tx, ter{temDISABLED}); } }, - {.features = supported_amendments() - featureSingleAssetVault}); + {.features = testable_amendments() - featureSingleAssetVault}); testCase([&](Env& env, Account const& issuer, @@ -635,7 +647,7 @@ class Vault_test : public beast::unit_test::suite env(tx, ter{temDISABLED}); } }, - {.features = (supported_amendments() | featureSingleAssetVault) - + {.features = (testable_amendments() | featureSingleAssetVault) - featurePermissionedDomains}); testCase([&](Env& env, @@ -960,7 +972,7 @@ class Vault_test : public beast::unit_test::suite Account const& depositor, Asset const& asset, Vault& vault)> test) { - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; Account depositor{"depositor"}; @@ -1102,8 +1114,7 @@ class Vault_test : public beast::unit_test::suite { { testcase("IOU fail create frozen"); - Env env{ - *this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; env.fund(XRP(1000), issuer, owner); @@ -1122,8 +1133,7 @@ class Vault_test : public beast::unit_test::suite { testcase("IOU fail create no ripling"); - Env env{ - *this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; env.fund(XRP(1000), issuer, owner); @@ -1141,8 +1151,7 @@ class Vault_test : public beast::unit_test::suite { testcase("IOU no issuer"); - Env env{ - *this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; env.fund(XRP(1000), owner); @@ -1161,7 +1170,7 @@ class Vault_test : public beast::unit_test::suite { testcase("IOU fail create vault for AMM LPToken"); - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account const gw("gateway"); Account const alice("alice"); Account const carol("carol"); @@ -1212,7 +1221,7 @@ class Vault_test : public beast::unit_test::suite { using namespace test::jtx; - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; Account depositor{"depositor"}; @@ -1234,7 +1243,7 @@ class Vault_test : public beast::unit_test::suite { using namespace test::jtx; - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; Account depositor{"depositor"}; @@ -1348,6 +1357,7 @@ class Vault_test : public beast::unit_test::suite struct CaseArgs { bool enableClawback = true; + bool requireAuth = true; }; auto testCase = [this]( @@ -1360,7 +1370,7 @@ class Vault_test : public beast::unit_test::suite Vault& vault, MPTTester& mptt)> test, CaseArgs args = {}) { - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; Account depositor{"depositor"}; @@ -1369,16 +1379,20 @@ class Vault_test : public beast::unit_test::suite Vault vault{env}; MPTTester mptt{env, issuer, mptInitNoFund}; + auto const none = LedgerSpecificFlags(0); mptt.create( {.flags = tfMPTCanTransfer | tfMPTCanLock | - (args.enableClawback ? lsfMPTCanClawback - : LedgerSpecificFlags(0)) | - tfMPTRequireAuth}); + (args.enableClawback ? tfMPTCanClawback : none) | + (args.requireAuth ? tfMPTRequireAuth : none)}); PrettyAsset asset = mptt.issuanceID(); mptt.authorize({.account = owner}); - mptt.authorize({.account = issuer, .holder = owner}); mptt.authorize({.account = depositor}); - mptt.authorize({.account = issuer, .holder = depositor}); + if (args.requireAuth) + { + mptt.authorize({.account = issuer, .holder = owner}); + mptt.authorize({.account = issuer, .holder = depositor}); + } + env(pay(issuer, depositor, asset(1000))); env.close(); @@ -1527,6 +1541,100 @@ class Vault_test : public beast::unit_test::suite } }); + testCase( + [this]( + Env& env, + Account const& issuer, + Account const& owner, + Account const& depositor, + PrettyAsset const& asset, + Vault& vault, + MPTTester& mptt) { + testcase( + "MPT 3rd party without MPToken cannot be withdrawal " + "destination"); + + auto [tx, keylet] = + vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(100)}); + env(tx); + env.close(); + + { + // Set destination to 3rd party without MPToken + Account charlie{"charlie"}; + env.fund(XRP(1000), charlie); + env.close(); + + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(100)}); + tx[sfDestination] = charlie.human(); + env(tx, ter(tecNO_AUTH)); + } + }, + {.requireAuth = false}); + + testCase( + [this]( + Env& env, + Account const& issuer, + Account const& owner, + Account const& depositor, + PrettyAsset const& asset, + Vault& vault, + MPTTester& mptt) { + testcase("MPT depositor without MPToken cannot withdraw"); + + auto [tx, keylet] = + vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(1000)}); + env(tx); + env.close(); + + { + // Remove depositor's MPToken and withdraw will fail + mptt.authorize( + {.account = depositor, .flags = tfMPTUnauthorize}); + env.close(); + auto const mptoken = + env.le(keylet::mptoken(mptt.issuanceID(), depositor)); + BEAST_EXPECT(mptoken == nullptr); + + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(100)}); + env(tx, ter(tecNO_AUTH)); + } + + { + // Restore depositor's MPToken and withdraw will succeed + mptt.authorize({.account = depositor}); + env.close(); + + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(100)}); + env(tx); + } + }, + {.requireAuth = false}); + testCase([this]( Env& env, Account const& issuer, @@ -1756,7 +1864,7 @@ class Vault_test : public beast::unit_test::suite { testcase("MPT shares to a vault"); - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account owner{"owner"}; Account issuer{"issuer"}; env.fund(XRP(1000000), owner, issuer); @@ -1805,8 +1913,7 @@ class Vault_test : public beast::unit_test::suite std::function issuanceId, std::function vaultBalance)> test) { - Env env{ - *this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account const owner{"owner"}; Account const issuer{"issuer"}; Account const charlie{"charlie"}; @@ -1817,6 +1924,7 @@ class Vault_test : public beast::unit_test::suite PrettyAsset const asset = issuer["IOU"]; env.trust(asset(1000), owner); + env.trust(asset(1000), charlie); env(pay(issuer, owner, asset(200))); env(rate(issuer, 1.25)); env.close(); @@ -2132,6 +2240,79 @@ class Vault_test : public beast::unit_test::suite env.close(); }); + testCase([&, this]( + Env& env, + Account const& owner, + Account const& issuer, + Account const& charlie, + auto, + Vault& vault, + PrettyAsset const& asset, + auto&&...) { + testcase("IOU no trust line to 3rd party"); + + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + env(vault.deposit( + {.depositor = owner, .id = keylet.key, .amount = asset(100)})); + env.close(); + + Account const erin{"erin"}; + env.fund(XRP(1000), erin); + env.close(); + + // Withdraw to 3rd party without trust line + auto const tx1 = [&](ripple::Keylet keylet) { + auto tx = vault.withdraw( + {.depositor = owner, + .id = keylet.key, + .amount = asset(10)}); + tx[sfDestination] = erin.human(); + return tx; + }(keylet); + env(tx1, ter{tecNO_LINE}); + }); + + testCase([&, this]( + Env& env, + Account const& owner, + Account const& issuer, + Account const& charlie, + auto, + Vault& vault, + PrettyAsset const& asset, + auto&&...) { + testcase("IOU no trust line to depositor"); + + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + // reset limit, so deposit of all funds will delete the trust line + env.trust(asset(0), owner); + env.close(); + + env(vault.deposit( + {.depositor = owner, .id = keylet.key, .amount = asset(200)})); + env.close(); + + auto trustline = + env.le(keylet::line(owner, asset.raw().get())); + BEAST_EXPECT(trustline == nullptr); + + // Withdraw without trust line, will succeed + auto const tx1 = [&](ripple::Keylet keylet) { + auto tx = vault.withdraw( + {.depositor = owner, + .id = keylet.key, + .amount = asset(10)}); + return tx; + }(keylet); + env(tx1); + }); + testCase([&, this]( Env& env, Account const& owner, @@ -2251,7 +2432,7 @@ class Vault_test : public beast::unit_test::suite testcase("private vault"); - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account issuer{"issuer"}; Account owner{"owner"}; Account depositor{"depositor"}; @@ -2536,7 +2717,7 @@ class Vault_test : public beast::unit_test::suite testcase("private XRP vault"); - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account owner{"owner"}; Account depositor{"depositor"}; Account alice{"charlie"}; @@ -2639,7 +2820,7 @@ class Vault_test : public beast::unit_test::suite using namespace test::jtx; testcase("failed pseudo-account allocation"); - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account const owner{"owner"}; Vault vault{env}; env.fund(XRP(1000), owner); @@ -2668,7 +2849,7 @@ class Vault_test : public beast::unit_test::suite using namespace test::jtx; testcase("RPC"); - Env env{*this, supported_amendments() | featureSingleAssetVault}; + Env env{*this, testable_amendments() | featureSingleAssetVault}; Account const owner{"owner"}; Account const issuer{"issuer"}; Vault vault{env}; diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index 85cd636b3d..311ddda59b 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -192,7 +192,7 @@ struct SEnv }; // XEnv class used for XChain tests. The only difference with SEnv is that it -// funds some default accounts, and that it enables `supported_amendments() | +// funds some default accounts, and that it enables `testable_amendments() | // FeatureBitset{featureXChainBridge}` by default. // ----------------------------------------------------------------------------- template @@ -526,7 +526,7 @@ struct XChain_test : public beast::unit_test::suite, // coverage test: BridgeCreate::preflight() - create bridge when feature // disabled. { - Env env(*this, supported_amendments() - featureXChainBridge); + Env env(*this, testable_amendments() - featureXChainBridge); env(create_bridge(Account::master, jvb), ter(temDISABLED)); } diff --git a/src/test/app/tx/apply_test.cpp b/src/test/app/tx/apply_test.cpp index 44a2c10b4e..0f5ccf5a55 100644 --- a/src/test/app/tx/apply_test.cpp +++ b/src/test/app/tx/apply_test.cpp @@ -55,7 +55,7 @@ public: { test::jtx::Env no_fully_canonical( *this, - test::jtx::supported_amendments() - + test::jtx::testable_amendments() - featureRequireFullyCanonicalSig); Validity valid = checkValidity( @@ -71,7 +71,7 @@ public: { test::jtx::Env fully_canonical( - *this, test::jtx::supported_amendments()); + *this, test::jtx::testable_amendments()); Validity valid = checkValidity( fully_canonical.app().getHashRouter(), diff --git a/src/test/basics/Buffer_test.cpp b/src/test/basics/Buffer_test.cpp index 43ca048d7f..c59805f569 100644 --- a/src/test/basics/Buffer_test.cpp +++ b/src/test/basics/Buffer_test.cpp @@ -98,8 +98,7 @@ struct Buffer_test : beast::unit_test::suite x = b0; BEAST_EXPECT(x == b0); BEAST_EXPECT(sane(x)); -#if defined(__clang__) && (!defined(__APPLE__) && (__clang_major__ >= 7)) || \ - (defined(__APPLE__) && (__apple_build_version__ >= 10010043)) +#if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wself-assign-overloaded" #endif @@ -111,8 +110,7 @@ struct Buffer_test : beast::unit_test::suite BEAST_EXPECT(y == b3); BEAST_EXPECT(sane(y)); -#if defined(__clang__) && (!defined(__APPLE__) && (__clang_major__ >= 7)) || \ - (defined(__APPLE__) && (__apple_build_version__ >= 10010043)) +#if defined(__clang__) #pragma clang diagnostic pop #endif } diff --git a/src/test/basics/RangeSet_test.cpp b/src/test/basics/RangeSet_test.cpp deleted file mode 100644 index e0136ab890..0000000000 --- a/src/test/basics/RangeSet_test.cpp +++ /dev/null @@ -1,144 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include - -namespace ripple { -class RangeSet_test : public beast::unit_test::suite -{ -public: - void - testPrevMissing() - { - testcase("prevMissing"); - - // Set will include: - // [ 0, 5] - // [10,15] - // [20,25] - // etc... - - RangeSet set; - for (std::uint32_t i = 0; i < 10; ++i) - set.insert(range(10 * i, 10 * i + 5)); - - for (std::uint32_t i = 1; i < 100; ++i) - { - std::optional expected; - // no prev missing in domain for i <= 6 - if (i > 6) - { - std::uint32_t const oneBelowRange = (10 * (i / 10)) - 1; - - expected = ((i % 10) > 6) ? (i - 1) : oneBelowRange; - } - BEAST_EXPECT(prevMissing(set, i) == expected); - } - } - - void - testToString() - { - testcase("toString"); - - RangeSet set; - BEAST_EXPECT(to_string(set) == "empty"); - - set.insert(1); - BEAST_EXPECT(to_string(set) == "1"); - - set.insert(range(4u, 6u)); - BEAST_EXPECT(to_string(set) == "1,4-6"); - - set.insert(2); - BEAST_EXPECT(to_string(set) == "1-2,4-6"); - - set.erase(range(4u, 5u)); - BEAST_EXPECT(to_string(set) == "1-2,6"); - } - - void - testFromString() - { - testcase("fromString"); - - RangeSet set; - - BEAST_EXPECT(!from_string(set, "")); - BEAST_EXPECT(boost::icl::length(set) == 0); - - BEAST_EXPECT(!from_string(set, "#")); - BEAST_EXPECT(boost::icl::length(set) == 0); - - BEAST_EXPECT(!from_string(set, ",")); - BEAST_EXPECT(boost::icl::length(set) == 0); - - BEAST_EXPECT(!from_string(set, ",-")); - BEAST_EXPECT(boost::icl::length(set) == 0); - - BEAST_EXPECT(!from_string(set, "1,,2")); - BEAST_EXPECT(boost::icl::length(set) == 0); - - BEAST_EXPECT(from_string(set, "1")); - BEAST_EXPECT(boost::icl::length(set) == 1); - BEAST_EXPECT(boost::icl::first(set) == 1); - - BEAST_EXPECT(from_string(set, "1,1")); - BEAST_EXPECT(boost::icl::length(set) == 1); - BEAST_EXPECT(boost::icl::first(set) == 1); - - BEAST_EXPECT(from_string(set, "1-1")); - BEAST_EXPECT(boost::icl::length(set) == 1); - BEAST_EXPECT(boost::icl::first(set) == 1); - - BEAST_EXPECT(from_string(set, "1,4-6")); - BEAST_EXPECT(boost::icl::length(set) == 4); - BEAST_EXPECT(boost::icl::first(set) == 1); - BEAST_EXPECT(!boost::icl::contains(set, 2)); - BEAST_EXPECT(!boost::icl::contains(set, 3)); - BEAST_EXPECT(boost::icl::contains(set, 4)); - BEAST_EXPECT(boost::icl::contains(set, 5)); - BEAST_EXPECT(boost::icl::last(set) == 6); - - BEAST_EXPECT(from_string(set, "1-2,4-6")); - BEAST_EXPECT(boost::icl::length(set) == 5); - BEAST_EXPECT(boost::icl::first(set) == 1); - BEAST_EXPECT(boost::icl::contains(set, 2)); - BEAST_EXPECT(boost::icl::contains(set, 4)); - BEAST_EXPECT(boost::icl::last(set) == 6); - - BEAST_EXPECT(from_string(set, "1-2,6")); - BEAST_EXPECT(boost::icl::length(set) == 3); - BEAST_EXPECT(boost::icl::first(set) == 1); - BEAST_EXPECT(boost::icl::contains(set, 2)); - BEAST_EXPECT(boost::icl::last(set) == 6); - } - void - run() override - { - testPrevMissing(); - testToString(); - testFromString(); - } -}; - -BEAST_DEFINE_TESTSUITE(RangeSet, ripple_basics, ripple); - -} // namespace ripple diff --git a/src/test/basics/Slice_test.cpp b/src/test/basics/Slice_test.cpp deleted file mode 100644 index 3d474def79..0000000000 --- a/src/test/basics/Slice_test.cpp +++ /dev/null @@ -1,116 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github0.com/ripple/rippled - Copyright (c) 2012-2016 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include - -#include -#include - -namespace ripple { -namespace test { - -struct Slice_test : beast::unit_test::suite -{ - void - run() override - { - std::uint8_t const data[] = { - 0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23, 0x71, 0x6d, 0x2a, - 0x18, 0xb4, 0x70, 0xcb, 0xf5, 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c, - 0xf0, 0x2c, 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3}; - - { - testcase("Equality & Inequality"); - - Slice const s0{}; - - BEAST_EXPECT(s0.size() == 0); - BEAST_EXPECT(s0.data() == nullptr); - BEAST_EXPECT(s0 == s0); - - // Test slices of equal and unequal size pointing to same data: - for (std::size_t i = 0; i != sizeof(data); ++i) - { - Slice const s1{data, i}; - - BEAST_EXPECT(s1.size() == i); - BEAST_EXPECT(s1.data() != nullptr); - - if (i == 0) - BEAST_EXPECT(s1 == s0); - else - BEAST_EXPECT(s1 != s0); - - for (std::size_t j = 0; j != sizeof(data); ++j) - { - Slice const s2{data, j}; - - if (i == j) - BEAST_EXPECT(s1 == s2); - else - BEAST_EXPECT(s1 != s2); - } - } - - // Test slices of equal size but pointing to different data: - std::array a; - std::array b; - - for (std::size_t i = 0; i != sizeof(data); ++i) - a[i] = b[i] = data[i]; - - BEAST_EXPECT(makeSlice(a) == makeSlice(b)); - b[7]++; - BEAST_EXPECT(makeSlice(a) != makeSlice(b)); - a[7]++; - BEAST_EXPECT(makeSlice(a) == makeSlice(b)); - } - - { - testcase("Indexing"); - - Slice const s{data, sizeof(data)}; - - for (std::size_t i = 0; i != sizeof(data); ++i) - BEAST_EXPECT(s[i] == data[i]); - } - - { - testcase("Advancing"); - - for (std::size_t i = 0; i < sizeof(data); ++i) - { - for (std::size_t j = 0; i + j < sizeof(data); ++j) - { - Slice s(data + i, sizeof(data) - i); - s += j; - - BEAST_EXPECT(s.data() == data + i + j); - BEAST_EXPECT(s.size() == sizeof(data) - i - j); - } - } - } - } -}; - -BEAST_DEFINE_TESTSUITE(Slice, ripple_basics, ripple); - -} // namespace test -} // namespace ripple diff --git a/src/test/basics/TaggedCache_test.cpp b/src/test/basics/TaggedCache_test.cpp index 797838fcfa..ce33455110 100644 --- a/src/test/basics/TaggedCache_test.cpp +++ b/src/test/basics/TaggedCache_test.cpp @@ -58,10 +58,10 @@ public: // Insert an item, retrieve it, and age it so it gets purged. { BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); + BEAST_EXPECT(c.size() == 0); BEAST_EXPECT(!c.insert(1, "one")); BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); + BEAST_EXPECT(c.size() == 1); { std::string s; @@ -72,7 +72,7 @@ public: ++clock; c.sweep(); BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); + BEAST_EXPECT(c.size() == 0); } // Insert an item, maintain a strong pointer, age it, and @@ -80,7 +80,7 @@ public: { BEAST_EXPECT(!c.insert(2, "two")); BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); + BEAST_EXPECT(c.size() == 1); { auto p = c.fetch(2); @@ -88,14 +88,14 @@ public: ++clock; c.sweep(); BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 1); + BEAST_EXPECT(c.size() == 1); } // Make sure its gone now that our reference is gone ++clock; c.sweep(); BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); + BEAST_EXPECT(c.size() == 0); } // Insert the same key/value pair and make sure we get the same result @@ -111,7 +111,7 @@ public: ++clock; c.sweep(); BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); + BEAST_EXPECT(c.size() == 0); } // Put an object in but keep a strong pointer to it, advance the clock a @@ -121,24 +121,24 @@ public: // Put an object in BEAST_EXPECT(!c.insert(4, "four")); BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); + BEAST_EXPECT(c.size() == 1); { // Keep a strong pointer to it auto const p1 = c.fetch(4); BEAST_EXPECT(p1 != nullptr); BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); + BEAST_EXPECT(c.size() == 1); // Advance the clock a lot ++clock; c.sweep(); BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 1); + BEAST_EXPECT(c.size() == 1); // Canonicalize a new object with the same key auto p2 = std::make_shared("four"); BEAST_EXPECT(c.canonicalize_replace_client(4, p2)); BEAST_EXPECT(c.getCacheSize() == 1); - BEAST_EXPECT(c.getTrackSize() == 1); + BEAST_EXPECT(c.size() == 1); // Make sure we get the original object BEAST_EXPECT(p1.get() == p2.get()); } @@ -146,7 +146,7 @@ public: ++clock; c.sweep(); BEAST_EXPECT(c.getCacheSize() == 0); - BEAST_EXPECT(c.getTrackSize() == 0); + BEAST_EXPECT(c.size() == 0); } } }; diff --git a/src/test/basics/base64_test.cpp b/src/test/basics/base64_test.cpp deleted file mode 100644 index b6d67c7c06..0000000000 --- a/src/test/basics/base64_test.cpp +++ /dev/null @@ -1,82 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012-2018 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -// -// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/boostorg/beast -// - -#include -#include - -namespace ripple { - -class base64_test : public beast::unit_test::suite -{ -public: - void - check(std::string const& in, std::string const& out) - { - auto const encoded = base64_encode(in); - BEAST_EXPECT(encoded == out); - BEAST_EXPECT(base64_decode(encoded) == in); - } - - void - run() override - { - check("", ""); - check("f", "Zg=="); - check("fo", "Zm8="); - check("foo", "Zm9v"); - check("foob", "Zm9vYg=="); - check("fooba", "Zm9vYmE="); - check("foobar", "Zm9vYmFy"); - - check( - "Man is distinguished, not only by his reason, but by this " - "singular passion from " - "other animals, which is a lust of the mind, that by a " - "perseverance of delight " - "in the continued and indefatigable generation of knowledge, " - "exceeds the short " - "vehemence of any carnal pleasure.", - "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dC" - "BieSB0aGlz" - "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIG" - "x1c3Qgb2Yg" - "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aG" - "UgY29udGlu" - "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleG" - "NlZWRzIHRo" - "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="); - - std::string const notBase64 = "not_base64!!"; - std::string const truncated = "not"; - BEAST_EXPECT(base64_decode(notBase64) == base64_decode(truncated)); - } -}; - -BEAST_DEFINE_TESTSUITE(base64, ripple_basics, ripple); - -} // namespace ripple diff --git a/src/test/basics/mulDiv_test.cpp b/src/test/basics/mulDiv_test.cpp deleted file mode 100644 index 61521577d9..0000000000 --- a/src/test/basics/mulDiv_test.cpp +++ /dev/null @@ -1,62 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012-2016 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include - -namespace ripple { -namespace test { - -struct mulDiv_test : beast::unit_test::suite -{ - void - run() override - { - auto const max = std::numeric_limits::max(); - std::uint64_t const max32 = std::numeric_limits::max(); - - auto result = mulDiv(85, 20, 5); - BEAST_EXPECT(result && *result == 340); - result = mulDiv(20, 85, 5); - BEAST_EXPECT(result && *result == 340); - - result = mulDiv(0, max - 1, max - 3); - BEAST_EXPECT(result && *result == 0); - result = mulDiv(max - 1, 0, max - 3); - BEAST_EXPECT(result && *result == 0); - - result = mulDiv(max, 2, max / 2); - BEAST_EXPECT(result && *result == 4); - result = mulDiv(max, 1000, max / 1000); - BEAST_EXPECT(result && *result == 1000000); - result = mulDiv(max, 1000, max / 1001); - BEAST_EXPECT(result && *result == 1001000); - result = mulDiv(max32 + 1, max32 + 1, 5); - BEAST_EXPECT(result && *result == 3689348814741910323); - - // Overflow - result = mulDiv(max - 1, max - 2, 5); - BEAST_EXPECT(!result); - } -}; - -BEAST_DEFINE_TESTSUITE(mulDiv, ripple_basics, ripple); - -} // namespace test -} // namespace ripple diff --git a/src/test/basics/scope_test.cpp b/src/test/basics/scope_test.cpp deleted file mode 100644 index 654f7e0a11..0000000000 --- a/src/test/basics/scope_test.cpp +++ /dev/null @@ -1,193 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github0.com/ripple/rippled - Copyright (c) 2021 Ripple Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include - -namespace ripple { -namespace test { - -struct scope_test : beast::unit_test::suite -{ - void - test_scope_exit() - { - // scope_exit always executes the functor on destruction, - // unless release() is called - int i = 0; - { - scope_exit x{[&i]() { i = 1; }}; - } - BEAST_EXPECT(i == 1); - { - scope_exit x{[&i]() { i = 2; }}; - x.release(); - } - BEAST_EXPECT(i == 1); - { - scope_exit x{[&i]() { i += 2; }}; - auto x2 = std::move(x); - } - BEAST_EXPECT(i == 3); - { - scope_exit x{[&i]() { i = 4; }}; - x.release(); - auto x2 = std::move(x); - } - BEAST_EXPECT(i == 3); - { - try - { - scope_exit x{[&i]() { i = 5; }}; - throw 1; - } - catch (...) - { - } - } - BEAST_EXPECT(i == 5); - { - try - { - scope_exit x{[&i]() { i = 6; }}; - x.release(); - throw 1; - } - catch (...) - { - } - } - BEAST_EXPECT(i == 5); - } - - void - test_scope_fail() - { - // scope_fail executes the functor on destruction only - // if an exception is unwinding, unless release() is called - int i = 0; - { - scope_fail x{[&i]() { i = 1; }}; - } - BEAST_EXPECT(i == 0); - { - scope_fail x{[&i]() { i = 2; }}; - x.release(); - } - BEAST_EXPECT(i == 0); - { - scope_fail x{[&i]() { i = 3; }}; - auto x2 = std::move(x); - } - BEAST_EXPECT(i == 0); - { - scope_fail x{[&i]() { i = 4; }}; - x.release(); - auto x2 = std::move(x); - } - BEAST_EXPECT(i == 0); - { - try - { - scope_fail x{[&i]() { i = 5; }}; - throw 1; - } - catch (...) - { - } - } - BEAST_EXPECT(i == 5); - { - try - { - scope_fail x{[&i]() { i = 6; }}; - x.release(); - throw 1; - } - catch (...) - { - } - } - BEAST_EXPECT(i == 5); - } - - void - test_scope_success() - { - // scope_success executes the functor on destruction only - // if an exception is not unwinding, unless release() is called - int i = 0; - { - scope_success x{[&i]() { i = 1; }}; - } - BEAST_EXPECT(i == 1); - { - scope_success x{[&i]() { i = 2; }}; - x.release(); - } - BEAST_EXPECT(i == 1); - { - scope_success x{[&i]() { i += 2; }}; - auto x2 = std::move(x); - } - BEAST_EXPECT(i == 3); - { - scope_success x{[&i]() { i = 4; }}; - x.release(); - auto x2 = std::move(x); - } - BEAST_EXPECT(i == 3); - { - try - { - scope_success x{[&i]() { i = 5; }}; - throw 1; - } - catch (...) - { - } - } - BEAST_EXPECT(i == 3); - { - try - { - scope_success x{[&i]() { i = 6; }}; - x.release(); - throw 1; - } - catch (...) - { - } - } - BEAST_EXPECT(i == 3); - } - - void - run() override - { - test_scope_exit(); - test_scope_fail(); - test_scope_success(); - } -}; - -BEAST_DEFINE_TESTSUITE(scope, ripple_basics, ripple); - -} // namespace test -} // namespace ripple diff --git a/src/test/basics/tagged_integer_test.cpp b/src/test/basics/tagged_integer_test.cpp deleted file mode 100644 index cb15d246a6..0000000000 --- a/src/test/basics/tagged_integer_test.cpp +++ /dev/null @@ -1,258 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright 2014, Nikolaos D. Bougalis - - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include - -#include - -namespace ripple { -namespace test { - -class tagged_integer_test : public beast::unit_test::suite -{ -private: - struct Tag1 - { - }; - struct Tag2 - { - }; - - // Static checks that types are not interoperable - - using TagUInt1 = tagged_integer; - using TagUInt2 = tagged_integer; - using TagUInt3 = tagged_integer; - - // Check construction of tagged_integers - static_assert( - std::is_constructible::value, - "TagUInt1 should be constructible using a std::uint32_t"); - - static_assert( - !std::is_constructible::value, - "TagUInt1 should not be constructible using a std::uint64_t"); - - static_assert( - std::is_constructible::value, - "TagUInt3 should be constructible using a std::uint32_t"); - - static_assert( - std::is_constructible::value, - "TagUInt3 should be constructible using a std::uint64_t"); - - // Check assignment of tagged_integers - static_assert( - !std::is_assignable::value, - "TagUInt1 should not be assignable with a std::uint32_t"); - - static_assert( - !std::is_assignable::value, - "TagUInt1 should not be assignable with a std::uint64_t"); - - static_assert( - !std::is_assignable::value, - "TagUInt3 should not be assignable with a std::uint32_t"); - - static_assert( - !std::is_assignable::value, - "TagUInt3 should not be assignable with a std::uint64_t"); - - static_assert( - std::is_assignable::value, - "TagUInt1 should be assignable with a TagUInt1"); - - static_assert( - !std::is_assignable::value, - "TagUInt1 should not be assignable with a TagUInt2"); - - static_assert( - std::is_assignable::value, - "TagUInt3 should be assignable with a TagUInt1"); - - static_assert( - !std::is_assignable::value, - "TagUInt1 should not be assignable with a TagUInt3"); - - static_assert( - !std::is_assignable::value, - "TagUInt3 should not be assignable with a TagUInt1"); - - // Check convertibility of tagged_integers - static_assert( - !std::is_convertible::value, - "std::uint32_t should not be convertible to a TagUInt1"); - - static_assert( - !std::is_convertible::value, - "std::uint32_t should not be convertible to a TagUInt3"); - - static_assert( - !std::is_convertible::value, - "std::uint64_t should not be convertible to a TagUInt3"); - - static_assert( - !std::is_convertible::value, - "std::uint64_t should not be convertible to a TagUInt2"); - - static_assert( - !std::is_convertible::value, - "TagUInt1 should not be convertible to TagUInt2"); - - static_assert( - !std::is_convertible::value, - "TagUInt1 should not be convertible to TagUInt3"); - - static_assert( - !std::is_convertible::value, - "TagUInt2 should not be convertible to a TagUInt3"); - -public: - void - run() override - { - using TagInt = tagged_integer; - - { - testcase("Comparison Operators"); - - TagInt const zero(0); - TagInt const one(1); - - BEAST_EXPECT(one == one); - BEAST_EXPECT(!(one == zero)); - - BEAST_EXPECT(one != zero); - BEAST_EXPECT(!(one != one)); - - BEAST_EXPECT(zero < one); - BEAST_EXPECT(!(one < zero)); - - BEAST_EXPECT(one > zero); - BEAST_EXPECT(!(zero > one)); - - BEAST_EXPECT(one >= one); - BEAST_EXPECT(one >= zero); - BEAST_EXPECT(!(zero >= one)); - - BEAST_EXPECT(zero <= one); - BEAST_EXPECT(zero <= zero); - BEAST_EXPECT(!(one <= zero)); - } - - { - testcase("Increment/Decrement Operators"); - TagInt const zero(0); - TagInt const one(1); - TagInt a{0}; - ++a; - BEAST_EXPECT(a == one); - --a; - BEAST_EXPECT(a == zero); - a++; - BEAST_EXPECT(a == one); - a--; - BEAST_EXPECT(a == zero); - } - - { - testcase("Arithmetic Operators"); - TagInt a{-2}; - BEAST_EXPECT(+a == TagInt{-2}); - BEAST_EXPECT(-a == TagInt{2}); - BEAST_EXPECT(TagInt{-3} + TagInt{4} == TagInt{1}); - BEAST_EXPECT(TagInt{-3} - TagInt{4} == TagInt{-7}); - BEAST_EXPECT(TagInt{-3} * TagInt{4} == TagInt{-12}); - BEAST_EXPECT(TagInt{8} / TagInt{4} == TagInt{2}); - BEAST_EXPECT(TagInt{7} % TagInt{4} == TagInt{3}); - - BEAST_EXPECT(~TagInt{8} == TagInt{~TagInt::value_type{8}}); - BEAST_EXPECT((TagInt{6} & TagInt{3}) == TagInt{2}); - BEAST_EXPECT((TagInt{6} | TagInt{3}) == TagInt{7}); - BEAST_EXPECT((TagInt{6} ^ TagInt{3}) == TagInt{5}); - - BEAST_EXPECT((TagInt{4} << TagInt{2}) == TagInt{16}); - BEAST_EXPECT((TagInt{16} >> TagInt{2}) == TagInt{4}); - } - { - testcase("Assignment Operators"); - TagInt a{-2}; - TagInt b{0}; - b = a; - BEAST_EXPECT(b == TagInt{-2}); - - // -3 + 4 == 1 - a = TagInt{-3}; - a += TagInt{4}; - BEAST_EXPECT(a == TagInt{1}); - - // -3 - 4 == -7 - a = TagInt{-3}; - a -= TagInt{4}; - BEAST_EXPECT(a == TagInt{-7}); - - // -3 * 4 == -12 - a = TagInt{-3}; - a *= TagInt{4}; - BEAST_EXPECT(a == TagInt{-12}); - - // 8/4 == 2 - a = TagInt{8}; - a /= TagInt{4}; - BEAST_EXPECT(a == TagInt{2}); - - // 7 % 4 == 3 - a = TagInt{7}; - a %= TagInt{4}; - BEAST_EXPECT(a == TagInt{3}); - - // 6 & 3 == 2 - a = TagInt{6}; - a /= TagInt{3}; - BEAST_EXPECT(a == TagInt{2}); - - // 6 | 3 == 7 - a = TagInt{6}; - a |= TagInt{3}; - BEAST_EXPECT(a == TagInt{7}); - - // 6 ^ 3 == 5 - a = TagInt{6}; - a ^= TagInt{3}; - BEAST_EXPECT(a == TagInt{5}); - - // 4 << 2 == 16 - a = TagInt{4}; - a <<= TagInt{2}; - BEAST_EXPECT(a == TagInt{16}); - - // 16 >> 2 == 4 - a = TagInt{16}; - a >>= TagInt{2}; - BEAST_EXPECT(a == TagInt{4}); - } - } -}; - -BEAST_DEFINE_TESTSUITE(tagged_integer, ripple_basics, ripple); - -} // namespace test -} // namespace ripple diff --git a/src/test/beast/aged_associative_container_test.cpp b/src/test/beast/aged_associative_container_test.cpp index f88d5acc27..586f486872 100644 --- a/src/test/beast/aged_associative_container_test.cpp +++ b/src/test/beast/aged_associative_container_test.cpp @@ -703,10 +703,6 @@ aged_associative_container_test_base::checkContentsRefRef( Values const& v) { using Cont = typename std::remove_reference::type; - using Traits = TestTraits< - Cont::is_unordered::value, - Cont::is_multi::value, - Cont::is_map::value>; using size_type = typename Cont::size_type; BEAST_EXPECT(c.size() == v.size()); @@ -761,10 +757,6 @@ typename std::enable_if::type aged_associative_container_test_base::testConstructEmpty() { using Traits = TestTraits; - using Value = typename Traits::Value; - using Key = typename Traits::Key; - using T = typename Traits::T; - using Clock = typename Traits::Clock; using Comp = typename Traits::Comp; using Alloc = typename Traits::Alloc; using MyComp = typename Traits::MyComp; @@ -802,10 +794,6 @@ typename std::enable_if::type aged_associative_container_test_base::testConstructEmpty() { using Traits = TestTraits; - using Value = typename Traits::Value; - using Key = typename Traits::Key; - using T = typename Traits::T; - using Clock = typename Traits::Clock; using Hash = typename Traits::Hash; using Equal = typename Traits::Equal; using Alloc = typename Traits::Alloc; @@ -870,10 +858,6 @@ typename std::enable_if::type aged_associative_container_test_base::testConstructRange() { using Traits = TestTraits; - using Value = typename Traits::Value; - using Key = typename Traits::Key; - using T = typename Traits::T; - using Clock = typename Traits::Clock; using Comp = typename Traits::Comp; using Alloc = typename Traits::Alloc; using MyComp = typename Traits::MyComp; @@ -925,10 +909,6 @@ typename std::enable_if::type aged_associative_container_test_base::testConstructRange() { using Traits = TestTraits; - using Value = typename Traits::Value; - using Key = typename Traits::Key; - using T = typename Traits::T; - using Clock = typename Traits::Clock; using Hash = typename Traits::Hash; using Equal = typename Traits::Equal; using Alloc = typename Traits::Alloc; @@ -996,14 +976,6 @@ typename std::enable_if::type aged_associative_container_test_base::testConstructInitList() { using Traits = TestTraits; - using Value = typename Traits::Value; - using Key = typename Traits::Key; - using T = typename Traits::T; - using Clock = typename Traits::Clock; - using Comp = typename Traits::Comp; - using Alloc = typename Traits::Alloc; - using MyComp = typename Traits::MyComp; - using MyAlloc = typename Traits::MyAlloc; typename Traits::ManualClock clock; // testcase (Traits::name() + " init-list"); @@ -1020,16 +992,6 @@ typename std::enable_if::type aged_associative_container_test_base::testConstructInitList() { using Traits = TestTraits; - using Value = typename Traits::Value; - using Key = typename Traits::Key; - using T = typename Traits::T; - using Clock = typename Traits::Clock; - using Hash = typename Traits::Hash; - using Equal = typename Traits::Equal; - using Alloc = typename Traits::Alloc; - using MyHash = typename Traits::MyHash; - using MyEqual = typename Traits::MyEqual; - using MyAlloc = typename Traits::MyAlloc; typename Traits::ManualClock clock; // testcase (Traits::name() + " init-list"); @@ -1050,7 +1012,6 @@ void aged_associative_container_test_base::testCopyMove() { using Traits = TestTraits; - using Value = typename Traits::Value; using Alloc = typename Traits::Alloc; typename Traits::ManualClock clock; auto const v(Traits::values()); @@ -1121,8 +1082,6 @@ void aged_associative_container_test_base::testIterator() { using Traits = TestTraits; - using Value = typename Traits::Value; - using Alloc = typename Traits::Alloc; typename Traits::ManualClock clock; auto const v(Traits::values()); @@ -1179,8 +1138,6 @@ typename std::enable_if::type aged_associative_container_test_base::testReverseIterator() { using Traits = TestTraits; - using Value = typename Traits::Value; - using Alloc = typename Traits::Alloc; typename Traits::ManualClock clock; auto const v(Traits::values()); @@ -1190,7 +1147,6 @@ aged_associative_container_test_base::testReverseIterator() typename Traits::template Cont<> c{clock}; using iterator = decltype(c.begin()); - using const_iterator = decltype(c.cbegin()); using reverse_iterator = decltype(c.rbegin()); using const_reverse_iterator = decltype(c.crbegin()); @@ -1394,7 +1350,6 @@ void aged_associative_container_test_base::testChronological() { using Traits = TestTraits; - using Value = typename Traits::Value; typename Traits::ManualClock clock; auto const v(Traits::values()); @@ -1760,7 +1715,6 @@ typename std::enable_if::type aged_associative_container_test_base::testCompare() { using Traits = TestTraits; - using Value = typename Traits::Value; typename Traits::ManualClock clock; auto const v(Traits::values()); @@ -1832,8 +1786,6 @@ template void aged_associative_container_test_base::testMaybeUnorderedMultiMap() { - using Traits = TestTraits; - testConstructEmpty(); testConstructRange(); testConstructInitList(); diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index f46fea8e6e..6ed45777f0 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -313,7 +313,6 @@ class LedgerTrie_test : public beast::unit_test::suite testSupport() { using namespace csf; - using Seq = Ledger::Seq; LedgerTrie t; LedgerHistoryHelper h; @@ -596,7 +595,6 @@ class LedgerTrie_test : public beast::unit_test::suite testRootRelated() { using namespace csf; - using Seq = Ledger::Seq; // Since the root is a special node that breaks the no-single child // invariant, do some tests that exercise it. diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index 7eb05e68bb..56558f525f 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -227,7 +227,7 @@ class NegativeUNL_test : public beast::unit_test::suite testcase("Create UNLModify Tx and apply to ledgers"); - jtx::Env env(*this, jtx::supported_amendments() | featureNegativeUNL); + jtx::Env env(*this, jtx::testable_amendments() | featureNegativeUNL); std::vector publicKeys = createPublicKeys(3); // genesis ledger auto l = std::make_shared( @@ -526,7 +526,7 @@ class NegativeUNLNoAmendment_test : public beast::unit_test::suite { testcase("No negative UNL amendment"); - jtx::Env env(*this, jtx::supported_amendments() - featureNegativeUNL); + jtx::Env env(*this, jtx::testable_amendments() - featureNegativeUNL); std::vector publicKeys = createPublicKeys(1); // genesis ledger auto l = std::make_shared( @@ -582,7 +582,7 @@ struct NetworkHistory }; NetworkHistory(beast::unit_test::suite& suite, Parameter const& p) - : env(suite, jtx::supported_amendments() | featureNegativeUNL) + : env(suite, jtx::testable_amendments() | featureNegativeUNL) , param(p) , validations(env.app().getValidations()) { diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 4424d7619d..a04e62b723 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -805,7 +805,6 @@ class Validations_test : public beast::unit_test::suite Ledger ledgerACD = h["acd"]; using Seq = Ledger::Seq; - using ID = Ledger::ID; auto pref = [](Ledger ledger) { return std::make_pair(ledger.seq(), ledger.id()); diff --git a/src/test/jtx.h b/src/test/jtx.h index 4188910085..6347b9dcf9 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -22,6 +22,7 @@ // Convenience header that includes everything +#include #include #include #include diff --git a/src/test/jtx/AMMTest.h b/src/test/jtx/AMMTest.h index 28b9affa8f..17011d7633 100644 --- a/src/test/jtx/AMMTest.h +++ b/src/test/jtx/AMMTest.h @@ -40,7 +40,7 @@ struct TestAMMArg std::optional> pool = std::nullopt; std::uint16_t tfee = 0; std::optional ter = std::nullopt; - std::vector features = {supported_amendments()}; + std::vector features = {testable_amendments()}; bool noLog = false; }; @@ -95,7 +95,7 @@ protected: std::optional> const& pool = std::nullopt, std::uint16_t tfee = 0, std::optional const& ter = std::nullopt, - std::vector const& features = {supported_amendments()}); + std::vector const& features = {testable_amendments()}); void testAMM( diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index 53417a6079..21a239e3d7 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -71,10 +71,10 @@ noripple(Account const& account, Args const&... args) } inline FeatureBitset -supported_amendments() +testable_amendments() { static FeatureBitset const ids = [] { - auto const& sa = ripple::detail::supportedAmendments(); + auto const& sa = allAmendments(); std::vector feats; feats.reserve(sa.size()); for (auto const& [s, vote] : sa) @@ -84,7 +84,7 @@ supported_amendments() feats.push_back(*f); else Throw( - "Unknown feature: " + s + " in supportedAmendments."); + "Unknown feature: " + s + " in allAmendments."); } return FeatureBitset(feats); }(); @@ -236,7 +236,7 @@ public: beast::severities::Severity thresh = beast::severities::kError) : Env(suite_, std::move(config), - supported_amendments(), + testable_amendments(), std::move(logs), thresh) { diff --git a/src/test/jtx/Env_test.cpp b/src/test/jtx/Env_test.cpp index f32343d6dd..2be20d6e33 100644 --- a/src/test/jtx/Env_test.cpp +++ b/src/test/jtx/Env_test.cpp @@ -265,7 +265,7 @@ public: { using namespace jtx; - Env env{*this, supported_amendments() | fixMasterKeyAsRegularKey}; + Env env{*this, testable_amendments() | fixMasterKeyAsRegularKey}; Account const alice("alice", KeyType::ed25519); Account const bob("bob", KeyType::secp256k1); Account const carol("carol"); @@ -776,7 +776,7 @@ public: { testcase("Env features"); using namespace jtx; - auto const supported = supported_amendments(); + auto const supported = testable_amendments(); // this finds a feature that is not in // the supported amendments list and tests that it can be @@ -827,7 +827,7 @@ public: } auto const missingSomeFeatures = - supported_amendments() - featureMultiSignReserve - featureFlow; + testable_amendments() - featureMultiSignReserve - featureFlow; BEAST_EXPECT(missingSomeFeatures.count() == (supported.count() - 2)); { // a Env supported_features_except is missing *only* those features @@ -887,7 +887,7 @@ public: // add a feature that is NOT in the supported amendments list // along with all supported amendments // the unsupported features should be enabled - Env env{*this, supported_amendments().set(*neverSupportedFeat)}; + Env env{*this, testable_amendments().set(*neverSupportedFeat)}; // this app will have all supported amendments and then the // one additional never supported feature flag diff --git a/src/test/jtx/TrustedPublisherServer.h b/src/test/jtx/TrustedPublisherServer.h index 54538032f5..7bc092cbe3 100644 --- a/src/test/jtx/TrustedPublisherServer.h +++ b/src/test/jtx/TrustedPublisherServer.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -220,9 +221,8 @@ public: getList_ = [blob = blob, sig, manifest, version](int interval) { // Build the contents of a version 1 format UNL file std::stringstream l; - l << "{\"blob\":\"" << blob << "\"" - << ",\"signature\":\"" << sig << "\"" - << ",\"manifest\":\"" << manifest << "\"" + l << "{\"blob\":\"" << blob << "\"" << ",\"signature\":\"" << sig + << "\"" << ",\"manifest\":\"" << manifest << "\"" << ",\"refresh_interval\": " << interval << ",\"version\":" << version << '}'; return l.str(); @@ -257,15 +257,14 @@ public: std::stringstream l; for (auto const& info : blobInfo) { - l << "{\"blob\":\"" << info.blob << "\"" - << ",\"signature\":\"" << info.signature << "\"},"; + l << "{\"blob\":\"" << info.blob << "\"" << ",\"signature\":\"" + << info.signature << "\"},"; } std::string blobs = l.str(); blobs.pop_back(); l.str(std::string()); l << "{\"blobs_v2\": [ " << blobs << "],\"manifest\":\"" << manifest - << "\"" - << ",\"refresh_interval\": " << interval + << "\"" << ",\"refresh_interval\": " << interval << ",\"version\":" << (version + 1) << '}'; return l.str(); }; diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index f22c5743e7..432ef28ff6 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -127,6 +127,11 @@ addGrpcConfigWithSecureGateway( std::unique_ptr, std::string const& secureGateway); +std::unique_ptr +makeConfig( + std::map extraTxQ = {}, + std::map extraVoting = {}); + } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index dd9c735465..624036196d 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -140,6 +140,39 @@ addGrpcConfigWithSecureGateway( return cfg; } +std::unique_ptr +makeConfig( + std::map extraTxQ, + std::map extraVoting) +{ + auto p = test::jtx::envconfig(); + auto& section = p->section("transaction_queue"); + section.set("ledgers_in_queue", "2"); + section.set("minimum_queue_size", "2"); + section.set("min_ledgers_to_compute_size_limit", "3"); + section.set("max_ledger_counts_to_store", "100"); + section.set("retry_sequence_percent", "25"); + section.set("normal_consensus_increase_percent", "0"); + + for (auto const& [k, v] : extraTxQ) + section.set(k, v); + + // Some tests specify different fee settings that are enabled by + // a FeeVote + if (!extraVoting.empty()) + { + auto& votingSection = p->section("voting"); + for (auto const& [k, v] : extraVoting) + { + votingSection.set(k, v); + } + + // In order for the vote to occur, we must run as a validator + p->section("validation_seed").legacy("shUwVw52ofnCUX5m7kPTKzJdr4HEH"); + } + return p; +} + } // namespace jtx } // namespace test } // namespace ripple diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index c8ff167221..9f7a611feb 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -17,8 +17,9 @@ */ //============================================================================== -#include +#include +#include #include namespace ripple { @@ -99,6 +100,8 @@ MPTTester::create(MPTCreate const& arg) jv[sfMPTokenMetadata] = strHex(*arg.metadata); if (arg.maxAmt) jv[sfMaximumAmount] = std::to_string(*arg.maxAmt); + if (arg.domainID) + jv[sfDomainID] = to_string(*arg.domainID); if (submit(arg, jv) != tesSUCCESS) { // Verify issuance doesn't exist @@ -235,6 +238,8 @@ MPTTester::set(MPTSet const& arg) jv[sfHolder] = arg.holder->human(); if (arg.delegate) jv[sfDelegate] = arg.delegate->human(); + if (arg.domainID) + jv[sfDomainID] = to_string(*arg.domainID); if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0)) { auto require = [&](std::optional const& holder, @@ -272,6 +277,16 @@ MPTTester::forObject( return false; } +[[nodiscard]] bool +MPTTester::checkDomainID(std::optional expected) const +{ + return forObject([&](SLEP const& sle) -> bool { + if (sle->isFieldPresent(sfDomainID)) + return expected == sle->getFieldH256(sfDomainID); + return (!expected.has_value()); + }); +} + [[nodiscard]] bool MPTTester::checkMPTokenAmount( Account const& holder_, diff --git a/src/test/jtx/impl/permissioned_dex.cpp b/src/test/jtx/impl/permissioned_dex.cpp index 04497ebbdc..4b09a11880 100644 --- a/src/test/jtx/impl/permissioned_dex.cpp +++ b/src/test/jtx/impl/permissioned_dex.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include #include diff --git a/src/test/jtx/impl/permissioned_domains.cpp b/src/test/jtx/impl/permissioned_domains.cpp index 866ca3bb7e..441ee325c8 100644 --- a/src/test/jtx/impl/permissioned_domains.cpp +++ b/src/test/jtx/impl/permissioned_domains.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include namespace ripple { namespace test { diff --git a/src/test/jtx/impl/xchain_bridge.cpp b/src/test/jtx/impl/xchain_bridge.cpp index 86e9deda7c..6f167d7508 100644 --- a/src/test/jtx/impl/xchain_bridge.cpp +++ b/src/test/jtx/impl/xchain_bridge.cpp @@ -389,7 +389,7 @@ XChainBridgeObjects::XChainBridgeObjects() bridge_rpc(mcDoor, xrpIssue(), Account::master, xrpIssue())) , jvb(bridge(mcDoor, xrpIssue(), Account::master, xrpIssue())) , jvub(bridge(mcuDoor, xrpIssue(), Account::master, xrpIssue())) - , features(supported_amendments() | FeatureBitset{featureXChainBridge}) + , features(testable_amendments() | FeatureBitset{featureXChainBridge}) , signers([] { constexpr int numSigners = UT_XCHAIN_DEFAULT_NUM_SIGNERS; std::vector result; diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 52ade92323..4756ca723d 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -20,7 +20,8 @@ #ifndef RIPPLE_TEST_JTX_MPT_H_INCLUDED #define RIPPLE_TEST_JTX_MPT_H_INCLUDED -#include +#include +#include #include #include @@ -105,6 +106,7 @@ struct MPTCreate std::optional holderCount = std::nullopt; bool fund = true; std::optional flags = {0}; + std::optional domainID = std::nullopt; std::optional err = std::nullopt; }; @@ -138,6 +140,7 @@ struct MPTSet std::optional holderCount = std::nullopt; std::optional flags = std::nullopt; std::optional delegate = std::nullopt; + std::optional domainID = std::nullopt; std::optional err = std::nullopt; }; @@ -164,6 +167,9 @@ public: void set(MPTSet const& set = {}); + [[nodiscard]] bool + checkDomainID(std::optional expected) const; + [[nodiscard]] bool checkMPTokenAmount(Account const& holder, std::int64_t expectedAmount) const; diff --git a/src/test/jtx/permissioned_dex.h b/src/test/jtx/permissioned_dex.h index fb32e1c1be..b95574d94d 100644 --- a/src/test/jtx/permissioned_dex.h +++ b/src/test/jtx/permissioned_dex.h @@ -19,7 +19,9 @@ #pragma once -#include +#include +#include + namespace ripple { namespace test { namespace jtx { diff --git a/src/test/jtx/permissioned_domains.h b/src/test/jtx/permissioned_domains.h index ee80c6a69f..ed086e366d 100644 --- a/src/test/jtx/permissioned_domains.h +++ b/src/test/jtx/permissioned_domains.h @@ -20,7 +20,8 @@ #ifndef RIPPLE_TEST_JTX_PERMISSIONED_DOMAINS_H_INCLUDED #define RIPPLE_TEST_JTX_PERMISSIONED_DOMAINS_H_INCLUDED -#include +#include +#include #include namespace ripple { diff --git a/src/test/ledger/BookDirs_test.cpp b/src/test/ledger/BookDirs_test.cpp index 28d9d2c102..52b618e9a0 100644 --- a/src/test/ledger/BookDirs_test.cpp +++ b/src/test/ledger/BookDirs_test.cpp @@ -103,8 +103,7 @@ struct BookDirs_test : public beast::unit_test::suite run() override { using namespace jtx; - auto const sa = supported_amendments(); - test_bookdir(sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); test_bookdir(sa - featurePermissionedDEX); test_bookdir(sa); } diff --git a/src/test/ledger/Directory_test.cpp b/src/test/ledger/Directory_test.cpp index 7aa6f149b8..9e8d40e0cc 100644 --- a/src/test/ledger/Directory_test.cpp +++ b/src/test/ledger/Directory_test.cpp @@ -421,7 +421,7 @@ struct Directory_test : public beast::unit_test::suite }; // fixPreviousTxnID is disabled. - Env env(*this, supported_amendments() - fixPreviousTxnID); + Env env(*this, testable_amendments() - fixPreviousTxnID); env.fund(XRP(10000), alice, gw); env.close(); env.trust(USD(1000), alice); diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 6178b413d5..fadd9c0eae 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -78,7 +78,7 @@ class Invariants_test : public beast::unit_test::suite Preclose const& preclose = {}) { using namespace test::jtx; - FeatureBitset amendments = supported_amendments() | + FeatureBitset amendments = testable_amendments() | featureInvariantsV1_1 | featureSingleAssetVault; Env env{*this, amendments}; diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp index 8bb0666e06..26b06a0034 100644 --- a/src/test/ledger/PaymentSandbox_test.cpp +++ b/src/test/ledger/PaymentSandbox_test.cpp @@ -420,8 +420,7 @@ public: testBalanceHook(features); }; using namespace jtx; - auto const sa = supported_amendments(); - testAll(sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); testAll(sa - featurePermissionedDEX); testAll(sa); } diff --git a/src/test/rpc/AMMInfo_test.cpp b/src/test/rpc/AMMInfo_test.cpp index 1c54580aa7..a6e866b1c8 100644 --- a/src/test/rpc/AMMInfo_test.cpp +++ b/src/test/rpc/AMMInfo_test.cpp @@ -359,7 +359,7 @@ public: run() override { using namespace jtx; - auto const all = supported_amendments(); + auto const all = testable_amendments(); testErrors(); testSimpleRpc(); testVoteAndBid(all); diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 238b739611..18c8bf5a1c 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -675,6 +675,30 @@ public: BEAST_EXPECT( !getAccountFlag(allowTrustLineClawbackFlag.first, bob)); } + + static constexpr std::pair + allowTrustLineLockingFlag{ + "allowTrustLineLocking", asfAllowTrustLineLocking}; + + if (features[featureTokenEscrow]) + { + auto const f1 = + getAccountFlag(allowTrustLineLockingFlag.first, bob); + BEAST_EXPECT(f1.has_value()); + BEAST_EXPECT(!f1.value()); + + // Set allowTrustLineLocking + env(fset(bob, allowTrustLineLockingFlag.second)); + env.close(); + auto const f2 = + getAccountFlag(allowTrustLineLockingFlag.first, bob); + BEAST_EXPECT(f2.has_value()); + BEAST_EXPECT(f2.value()); + } + else + { + BEAST_EXPECT(!getAccountFlag(allowTrustLineLockingFlag.first, bob)); + } } void @@ -686,11 +710,14 @@ public: testSignerListsV2(); FeatureBitset const allFeatures{ - ripple::test::jtx::supported_amendments()}; + ripple::test::jtx::testable_amendments()}; testAccountFlags(allFeatures); testAccountFlags(allFeatures - featureDisallowIncoming); testAccountFlags( allFeatures - featureDisallowIncoming - featureClawback); + testAccountFlags( + allFeatures - featureDisallowIncoming - featureClawback - + featureTokenEscrow); } }; diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 7a48db73bd..546bbe8715 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -577,7 +577,7 @@ public: Account const gw{"gateway"}; auto const USD = gw["USD"]; - auto const features = supported_amendments() | featureXChainBridge | + auto const features = testable_amendments() | featureXChainBridge | featurePermissionedDomains; Env env(*this, features); diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index c056279bf1..5c0ca89305 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -53,7 +53,7 @@ public: Account const alice("alice"); // Test without DepositAuth enabled initially. - Env env(*this, supported_amendments() - featureDepositAuth); + Env env(*this, testable_amendments() - featureDepositAuth); env.fund(XRP(10000), noripple(alice)); // Give alice a regular key so she can legally set and clear @@ -357,7 +357,7 @@ public: }; doTests( - supported_amendments(), + testable_amendments(), {{1.0, tesSUCCESS, 1.0}, {1.1, tesSUCCESS, 1.1}, {2.0, tesSUCCESS, 2.0}, diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 6e25c26e58..82809b5c5b 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -18,9 +18,12 @@ //============================================================================== #include +#include #include +#include #include +#include #include #include @@ -753,6 +756,85 @@ class AccountTx_test : public beast::unit_test::suite } } + void + testMPT() + { + testcase("MPT"); + + using namespace test::jtx; + using namespace std::chrono_literals; + + auto cfg = makeConfig(); + cfg->FEES.reference_fee = 10; + Env env(*this, std::move(cfg)); + + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + + MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); + + // check the latest mpt-related txn is in alice's account history + auto const checkAliceAcctTx = [&](size_t size, + Json::StaticString txType) { + Json::Value params; + params[jss::account] = alice.human(); + params[jss::limit] = 100; + auto const jv = + env.rpc("json", "account_tx", to_string(params))[jss::result]; + + BEAST_EXPECT(jv[jss::transactions].size() == size); + auto const& tx0(jv[jss::transactions][0u][jss::tx]); + BEAST_EXPECT(tx0[jss::TransactionType] == txType); + + std::string const txHash{ + env.tx()->getJson(JsonOptions::none)[jss::hash].asString()}; + BEAST_EXPECT(tx0[jss::hash] == txHash); + }; + + // alice creates issuance + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer}); + + checkAliceAcctTx(3, jss::MPTokenIssuanceCreate); + + // bob creates a MPToken; + mptAlice.authorize({.account = bob}); + checkAliceAcctTx(4, jss::MPTokenAuthorize); + env.close(); + + // TODO: windows pipeline fails validation for the hardcoded ledger hash + // due to having different test config, it can be uncommented after + // figuring out what happened + // + // ledger hash should be fixed regardless any change to account history + // BEAST_EXPECT( + // to_string(env.closed()->info().hash) == + // "0BD507BB87D3C0E73B462485E6E381798A8C82FC49BF17FE39C60E08A1AF035D"); + + // alice authorizes bob + mptAlice.authorize({.account = alice, .holder = bob}); + checkAliceAcctTx(5, jss::MPTokenAuthorize); + + // carol creates a MPToken; + mptAlice.authorize({.account = carol}); + checkAliceAcctTx(6, jss::MPTokenAuthorize); + + // alice authorizes carol + mptAlice.authorize({.account = alice, .holder = carol}); + checkAliceAcctTx(7, jss::MPTokenAuthorize); + + // alice pays bob 100 tokens + mptAlice.pay(alice, bob, 100); + checkAliceAcctTx(8, jss::Payment); + + // bob pays carol 10 tokens + mptAlice.pay(bob, carol, 10); + checkAliceAcctTx(9, jss::Payment); + } + public: void run() override @@ -761,6 +843,7 @@ public: std::bind_front(&AccountTx_test::testParameters, this)); testContents(); testAccountDelete(); + testMPT(); } }; BEAST_DEFINE_TESTSUITE(AccountTx, rpc, ripple); diff --git a/src/test/rpc/BookChanges_test.cpp b/src/test/rpc/BookChanges_test.cpp index 1f059c2bf7..1f7b6775f2 100644 --- a/src/test/rpc/BookChanges_test.cpp +++ b/src/test/rpc/BookChanges_test.cpp @@ -94,7 +94,7 @@ public: using namespace jtx; FeatureBitset const all{ - jtx::supported_amendments() | featurePermissionedDomains | + jtx::testable_amendments() | featurePermissionedDomains | featureCredentials | featurePermissionedDEX}; Env env(*this, all); diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index 0ec36eca53..e885762644 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -1737,7 +1737,7 @@ public: using namespace jtx; FeatureBitset const all{ - jtx::supported_amendments() | featurePermissionedDomains | + jtx::testable_amendments() | featurePermissionedDomains | featureCredentials | featurePermissionedDEX}; Env env(*this, all); @@ -1868,7 +1868,7 @@ public: using namespace jtx; FeatureBitset const all{ - jtx::supported_amendments() | featurePermissionedDomains | + jtx::testable_amendments() | featurePermissionedDomains | featureCredentials | featurePermissionedDEX}; Env env(*this, all); diff --git a/src/test/rpc/DeliveredAmount_test.cpp b/src/test/rpc/DeliveredAmount_test.cpp index 17763790e8..d084f92f25 100644 --- a/src/test/rpc/DeliveredAmount_test.cpp +++ b/src/test/rpc/DeliveredAmount_test.cpp @@ -21,6 +21,7 @@ #include #include +#include #include namespace ripple { @@ -329,12 +330,95 @@ class DeliveredAmount_test : public beast::unit_test::suite } } + void + testMPTDeliveredAmountRPC(FeatureBitset features) + { + testcase("MPT DeliveredAmount"); + + using namespace jtx; + Account const alice("alice"); + Account const carol("carol"); + Account const bob("bob"); + Env env{*this, features}; + + MPTTester mptAlice( + env, alice, {.holders = {bob, carol}, .close = false}); + + mptAlice.create( + {.transferFee = 25000, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer}); + auto const MPT = mptAlice["MPT"]; + + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = carol}); + + // issuer to holder + mptAlice.pay(alice, bob, 10000); + + // holder to holder + env(pay(bob, carol, mptAlice.mpt(1000)), txflags(tfPartialPayment)); + env.close(); + + // Get the hash for the most recent transaction. + std::string txHash{ + env.tx()->getJson(JsonOptions::none)[jss::hash].asString()}; + Json::Value meta = env.rpc("tx", txHash)[jss::result][jss::meta]; + + if (features[fixMPTDeliveredAmount]) + { + BEAST_EXPECT( + meta[sfDeliveredAmount.jsonName] == + STAmount{MPT(800)}.getJson(JsonOptions::none)); + BEAST_EXPECT( + meta[jss::delivered_amount] == + STAmount{MPT(800)}.getJson(JsonOptions::none)); + } + else + { + BEAST_EXPECT(!meta.isMember(sfDeliveredAmount.jsonName)); + BEAST_EXPECT( + meta[jss::delivered_amount] = Json::Value("unavailable")); + } + + env(pay(bob, carol, MPT(1000)), + sendmax(MPT(1200)), + txflags(tfPartialPayment)); + env.close(); + + txHash = env.tx()->getJson(JsonOptions::none)[jss::hash].asString(); + meta = env.rpc("tx", txHash)[jss::result][jss::meta]; + + if (features[fixMPTDeliveredAmount]) + { + BEAST_EXPECT( + meta[sfDeliveredAmount.jsonName] == + STAmount{MPT(960)}.getJson(JsonOptions::none)); + BEAST_EXPECT( + meta[jss::delivered_amount] == + STAmount{MPT(960)}.getJson(JsonOptions::none)); + } + else + { + BEAST_EXPECT(!meta.isMember(sfDeliveredAmount.jsonName)); + BEAST_EXPECT( + meta[jss::delivered_amount] = Json::Value("unavailable")); + } + } + public: void run() override { + using namespace test::jtx; + FeatureBitset const all{testable_amendments()}; + testTxDeliveredAmountRPC(); testAccountDeliveredAmountSubscribe(); + + testMPTDeliveredAmountRPC(all - fixMPTDeliveredAmount); + testMPTDeliveredAmountRPC(all); } }; diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp index 7e9273d25e..691f32317e 100644 --- a/src/test/rpc/GatewayBalances_test.cpp +++ b/src/test/rpc/GatewayBalances_test.cpp @@ -251,11 +251,8 @@ public: run() override { using namespace jtx; - auto const sa = supported_amendments(); - for (auto feature : - {sa - featureFlowCross - featurePermissionedDEX, - sa - featurePermissionedDEX, - sa}) + auto const sa = testable_amendments(); + for (auto feature : {sa - featurePermissionedDEX, sa}) { testGWB(feature); testGWBApiVersions(feature); diff --git a/src/test/rpc/LedgerData_test.cpp b/src/test/rpc/LedgerData_test.cpp index c2b22efc00..54f51255d1 100644 --- a/src/test/rpc/LedgerData_test.cpp +++ b/src/test/rpc/LedgerData_test.cpp @@ -304,8 +304,8 @@ public: // Make sure fixInnerObjTemplate2 doesn't break amendments. for (FeatureBitset const& features : - {supported_amendments() - fixInnerObjTemplate2, - supported_amendments() | fixInnerObjTemplate2}) + {testable_amendments() - fixInnerObjTemplate2, + testable_amendments() | fixInnerObjTemplate2}) { using namespace std::chrono; Env env{*this, envconfig(validator, ""), features}; diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index 83232f79c8..b5cab9d13c 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -2221,7 +2221,7 @@ class LedgerEntry_test : public beast::unit_test::suite using namespace test::jtx; - Env env(*this, supported_amendments() | featurePermissionedDomains); + Env env(*this, testable_amendments() | featurePermissionedDomains); Account const issuer{"issuer"}; Account const alice{"alice"}; Account const bob{"bob"}; diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 5b26f43161..9ba9c9a655 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -711,6 +711,7 @@ class LedgerRPC_test : public beast::unit_test::suite env.close(); std::string index; + int hashesLedgerEntryIndex = -1; { Json::Value jvParams; jvParams[jss::ledger_index] = 3u; @@ -721,11 +722,27 @@ class LedgerRPC_test : public beast::unit_test::suite env.rpc("json", "ledger", to_string(jvParams))[jss::result]; BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState)); BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray()); - BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u); + + for (auto i = 0; i < jrr[jss::ledger][jss::accountState].size(); + i++) + if (jrr[jss::ledger][jss::accountState][i]["LedgerEntryType"] == + jss::LedgerHashes) + { + index = jrr[jss::ledger][jss::accountState][i]["index"] + .asString(); + hashesLedgerEntryIndex = i; + } + + for (auto const& object : jrr[jss::ledger][jss::accountState]) + if (object["LedgerEntryType"] == jss::LedgerHashes) + index = object["index"].asString(); + + // jss::type is a deprecated field BEAST_EXPECT( - jrr[jss::ledger][jss::accountState][0u]["LedgerEntryType"] == - jss::LedgerHashes); - index = jrr[jss::ledger][jss::accountState][0u]["index"].asString(); + jrr.isMember(jss::warnings) && jrr[jss::warnings].isArray() && + jrr[jss::warnings].size() == 1 && + jrr[jss::warnings][0u][jss::id].asInt() == + warnRPC_FIELDS_DEPRECATED); } { Json::Value jvParams; @@ -737,8 +754,17 @@ class LedgerRPC_test : public beast::unit_test::suite env.rpc("json", "ledger", to_string(jvParams))[jss::result]; BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState)); BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray()); - BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u); - BEAST_EXPECT(jrr[jss::ledger][jss::accountState][0u] == index); + BEAST_EXPECT( + hashesLedgerEntryIndex > 0 && + jrr[jss::ledger][jss::accountState][hashesLedgerEntryIndex] == + index); + + // jss::type is a deprecated field + BEAST_EXPECT( + jrr.isMember(jss::warnings) && jrr[jss::warnings].isArray() && + jrr[jss::warnings].size() == 1 && + jrr[jss::warnings][0u][jss::id].asInt() == + warnRPC_FIELDS_DEPRECATED); } } diff --git a/src/test/rpc/NoRipple_test.cpp b/src/test/rpc/NoRipple_test.cpp index 42c86b34bb..926de31e83 100644 --- a/src/test/rpc/NoRipple_test.cpp +++ b/src/test/rpc/NoRipple_test.cpp @@ -293,8 +293,7 @@ public: testPairwise(features); }; using namespace jtx; - auto const sa = supported_amendments(); - withFeatsTests(sa - featureFlowCross - featurePermissionedDEX); + auto const sa = testable_amendments(); withFeatsTests(sa - featurePermissionedDEX); withFeatsTests(sa); } diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index e414b60f93..e0db79bf53 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -131,6 +131,9 @@ public: BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); } BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2); + BEAST_EXPECT( + jv[jss::result][jss::network_id] == + env.app().config().NETWORK_ID); } { @@ -139,7 +142,8 @@ public: // Check stream update BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { - return jv[jss::ledger_index] == 3; + return jv[jss::ledger_index] == 3 && + jv[jss::network_id] == env.app().config().NETWORK_ID; })); } @@ -149,7 +153,8 @@ public: // Check stream update BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { - return jv[jss::ledger_index] == 4; + return jv[jss::ledger_index] == 4 && + jv[jss::network_id] == env.app().config().NETWORK_ID; })); } @@ -509,6 +514,11 @@ public: if (!jv.isMember(jss::validated_hash)) return false; + uint32_t netID = env.app().config().NETWORK_ID; + if (!jv.isMember(jss::network_id) || + jv[jss::network_id] != netID) + return false; + // Certain fields are only added on a flag ledger. bool const isFlagLedger = (env.closed()->info().seq + 1) % 256 == 0; @@ -567,6 +577,7 @@ public: jv[jss::streams][0u] = "ledger"; jr = env.rpc("json", "subscribe", to_string(jv))[jss::result]; BEAST_EXPECT(jr[jss::status] == "success"); + BEAST_EXPECT(jr[jss::network_id] == env.app().config().NETWORK_ID); jr = env.rpc("json", "unsubscribe", to_string(jv))[jss::result]; BEAST_EXPECT(jr[jss::status] == "success"); @@ -1307,7 +1318,7 @@ public: using namespace jtx; using namespace std::chrono_literals; FeatureBitset const all{ - jtx::supported_amendments() | featurePermissionedDomains | + jtx::testable_amendments() | featurePermissionedDomains | featureCredentials | featurePermissionedDEX}; Env env(*this, all); @@ -1577,7 +1588,7 @@ public: run() override { using namespace test::jtx; - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; FeatureBitset const xrpFees{featureXRPFees}; testServer(); diff --git a/src/test/rpc/Transaction_test.cpp b/src/test/rpc/Transaction_test.cpp index 724a3a0517..e1db485572 100644 --- a/src/test/rpc/Transaction_test.cpp +++ b/src/test/rpc/Transaction_test.cpp @@ -941,7 +941,7 @@ public: forAllApiVersions( std::bind_front(&Transaction_test::testBinaryRequest, this)); - FeatureBitset const all{supported_amendments()}; + FeatureBitset const all{testable_amendments()}; testWithFeats(all); } diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index bcd355e301..b27dee6e0a 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -681,7 +681,7 @@ class ServerStatus_test : public beast::unit_test::suite, resp["Upgrade"] == "websocket"); BEAST_EXPECT( resp.find("Connection") != resp.end() && - resp["Connection"] == "upgrade"); + resp["Connection"] == "Upgrade"); } void diff --git a/src/test/unit_test/FileDirGuard.h b/src/test/unit_test/FileDirGuard.h index d247ae3015..091bc80d20 100644 --- a/src/test/unit_test/FileDirGuard.h +++ b/src/test/unit_test/FileDirGuard.h @@ -26,6 +26,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include +#include + namespace ripple { namespace test { namespace detail { diff --git a/src/test/unit_test/SuiteJournal.h b/src/test/unit_test/SuiteJournal.h index b5c59f3d29..d56c297b0a 100644 --- a/src/test/unit_test/SuiteJournal.h +++ b/src/test/unit_test/SuiteJournal.h @@ -94,6 +94,8 @@ SuiteJournalSink::writeAlways( return "FTL:"; }(); + static std::mutex log_mutex; + std::lock_guard lock(log_mutex); suite_.log << s << partition_ << text << std::endl; } diff --git a/src/tests/README.md b/src/tests/README.md new file mode 100644 index 0000000000..8065316580 --- /dev/null +++ b/src/tests/README.md @@ -0,0 +1,4 @@ +# Unit tests +This directory contains unit tests for the project. The difference from existing `src/test` folder +is that we switch to 3rd party testing framework (doctest). We intend to gradually move existing tests +from our own framework to doctest and such tests will be moved to this new folder. diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt new file mode 100644 index 0000000000..68c6fa6cb3 --- /dev/null +++ b/src/tests/libxrpl/CMakeLists.txt @@ -0,0 +1,14 @@ +include(xrpl_add_test) + +# Test requirements. +find_package(doctest REQUIRED) + +# Common library dependencies for the rest of the tests. +add_library(xrpl.imports.test INTERFACE) +target_link_libraries(xrpl.imports.test INTERFACE doctest::doctest xrpl.libxrpl) + +# One test for each module. +xrpl_add_test(basics) +target_link_libraries(xrpl.test.basics PRIVATE xrpl.imports.test) +xrpl_add_test(crypto) +target_link_libraries(xrpl.test.crypto PRIVATE xrpl.imports.test) diff --git a/src/tests/libxrpl/basics/RangeSet.cpp b/src/tests/libxrpl/basics/RangeSet.cpp new file mode 100644 index 0000000000..ac0e1d9551 --- /dev/null +++ b/src/tests/libxrpl/basics/RangeSet.cpp @@ -0,0 +1,129 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +#include +#include + +using namespace ripple; + +TEST_SUITE_BEGIN("RangeSet"); + +TEST_CASE("prevMissing") +{ + // Set will include: + // [ 0, 5] + // [10,15] + // [20,25] + // etc... + + RangeSet set; + for (std::uint32_t i = 0; i < 10; ++i) + set.insert(range(10 * i, 10 * i + 5)); + + for (std::uint32_t i = 1; i < 100; ++i) + { + std::optional expected; + // no prev missing in domain for i <= 6 + if (i > 6) + { + std::uint32_t const oneBelowRange = (10 * (i / 10)) - 1; + + expected = ((i % 10) > 6) ? (i - 1) : oneBelowRange; + } + CHECK(prevMissing(set, i) == expected); + } +} + +TEST_CASE("toString") +{ + RangeSet set; + CHECK(to_string(set) == "empty"); + + set.insert(1); + CHECK(to_string(set) == "1"); + + set.insert(range(4u, 6u)); + CHECK(to_string(set) == "1,4-6"); + + set.insert(2); + CHECK(to_string(set) == "1-2,4-6"); + + set.erase(range(4u, 5u)); + CHECK(to_string(set) == "1-2,6"); +} + +TEST_CASE("fromString") +{ + RangeSet set; + + CHECK(!from_string(set, "")); + CHECK(boost::icl::length(set) == 0); + + CHECK(!from_string(set, "#")); + CHECK(boost::icl::length(set) == 0); + + CHECK(!from_string(set, ",")); + CHECK(boost::icl::length(set) == 0); + + CHECK(!from_string(set, ",-")); + CHECK(boost::icl::length(set) == 0); + + CHECK(!from_string(set, "1,,2")); + CHECK(boost::icl::length(set) == 0); + + CHECK(from_string(set, "1")); + CHECK(boost::icl::length(set) == 1); + CHECK(boost::icl::first(set) == 1); + + CHECK(from_string(set, "1,1")); + CHECK(boost::icl::length(set) == 1); + CHECK(boost::icl::first(set) == 1); + + CHECK(from_string(set, "1-1")); + CHECK(boost::icl::length(set) == 1); + CHECK(boost::icl::first(set) == 1); + + CHECK(from_string(set, "1,4-6")); + CHECK(boost::icl::length(set) == 4); + CHECK(boost::icl::first(set) == 1); + CHECK(!boost::icl::contains(set, 2)); + CHECK(!boost::icl::contains(set, 3)); + CHECK(boost::icl::contains(set, 4)); + CHECK(boost::icl::contains(set, 5)); + CHECK(boost::icl::last(set) == 6); + + CHECK(from_string(set, "1-2,4-6")); + CHECK(boost::icl::length(set) == 5); + CHECK(boost::icl::first(set) == 1); + CHECK(boost::icl::contains(set, 2)); + CHECK(boost::icl::contains(set, 4)); + CHECK(boost::icl::last(set) == 6); + + CHECK(from_string(set, "1-2,6")); + CHECK(boost::icl::length(set) == 3); + CHECK(boost::icl::first(set) == 1); + CHECK(boost::icl::contains(set, 2)); + CHECK(boost::icl::last(set) == 6); +} + +TEST_SUITE_END(); diff --git a/src/tests/libxrpl/basics/Slice.cpp b/src/tests/libxrpl/basics/Slice.cpp new file mode 100644 index 0000000000..eabd9b7dc7 --- /dev/null +++ b/src/tests/libxrpl/basics/Slice.cpp @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +#include +#include + +using namespace ripple; + +static std::uint8_t const data[] = { + 0xa8, 0xa1, 0x38, 0x45, 0x23, 0xec, 0xe4, 0x23, 0x71, 0x6d, 0x2a, + 0x18, 0xb4, 0x70, 0xcb, 0xf5, 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c, + 0xf0, 0x2c, 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3}; + +TEST_SUITE_BEGIN("Slice"); + +TEST_CASE("equality & inequality") +{ + Slice const s0{}; + + CHECK(s0.size() == 0); + CHECK(s0.data() == nullptr); + CHECK(s0 == s0); + + // Test slices of equal and unequal size pointing to same data: + for (std::size_t i = 0; i != sizeof(data); ++i) + { + Slice const s1{data, i}; + + CHECK(s1.size() == i); + CHECK(s1.data() != nullptr); + + if (i == 0) + CHECK(s1 == s0); + else + CHECK(s1 != s0); + + for (std::size_t j = 0; j != sizeof(data); ++j) + { + Slice const s2{data, j}; + + if (i == j) + CHECK(s1 == s2); + else + CHECK(s1 != s2); + } + } + + // Test slices of equal size but pointing to different data: + std::array a; + std::array b; + + for (std::size_t i = 0; i != sizeof(data); ++i) + a[i] = b[i] = data[i]; + + CHECK(makeSlice(a) == makeSlice(b)); + b[7]++; + CHECK(makeSlice(a) != makeSlice(b)); + a[7]++; + CHECK(makeSlice(a) == makeSlice(b)); +} + +TEST_CASE("indexing") +{ + Slice const s{data, sizeof(data)}; + + for (std::size_t i = 0; i != sizeof(data); ++i) + CHECK(s[i] == data[i]); +} + +TEST_CASE("advancing") +{ + for (std::size_t i = 0; i < sizeof(data); ++i) + { + for (std::size_t j = 0; i + j < sizeof(data); ++j) + { + Slice s(data + i, sizeof(data) - i); + s += j; + + CHECK(s.data() == data + i + j); + CHECK(s.size() == sizeof(data) - i - j); + } + } +} + +TEST_SUITE_END(); diff --git a/src/tests/libxrpl/basics/base64.cpp b/src/tests/libxrpl/basics/base64.cpp new file mode 100644 index 0000000000..fe9b86abb1 --- /dev/null +++ b/src/tests/libxrpl/basics/base64.cpp @@ -0,0 +1,67 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +#include + +using namespace ripple; + +static void +check(std::string const& in, std::string const& out) +{ + auto const encoded = base64_encode(in); + CHECK(encoded == out); + CHECK(base64_decode(encoded) == in); +} + +TEST_CASE("base64") +{ + check("", ""); + check("f", "Zg=="); + check("fo", "Zm8="); + check("foo", "Zm9v"); + check("foob", "Zm9vYg=="); + check("fooba", "Zm9vYmE="); + check("foobar", "Zm9vYmFy"); + + check( + "Man is distinguished, not only by his reason, but by this " + "singular passion from " + "other animals, which is a lust of the mind, that by a " + "perseverance of delight " + "in the continued and indefatigable generation of knowledge, " + "exceeds the short " + "vehemence of any carnal pleasure.", + "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dC" + "BieSB0aGlz" + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIG" + "x1c3Qgb2Yg" + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aG" + "UgY29udGlu" + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleG" + "NlZWRzIHRo" + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="); + + std::string const notBase64 = "not_base64!!"; + std::string const truncated = "not"; + CHECK(base64_decode(notBase64) == base64_decode(truncated)); +} diff --git a/src/test/basics/contract_test.cpp b/src/tests/libxrpl/basics/contract.cpp similarity index 60% rename from src/test/basics/contract_test.cpp rename to src/tests/libxrpl/basics/contract.cpp index 9595dbabcc..9ddf044f17 100644 --- a/src/test/basics/contract_test.cpp +++ b/src/tests/libxrpl/basics/contract.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. + Copyright (c) 2012 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -18,46 +18,39 @@ //============================================================================== #include -#include +#include + +#include #include -namespace ripple { +using namespace ripple; -class contract_test : public beast::unit_test::suite +TEST_CASE("contract") { -public: - void - run() override + try { + Throw("Throw test"); + } + catch (std::runtime_error const& e1) + { + CHECK(std::string(e1.what()) == "Throw test"); + try { - Throw("Throw test"); + Rethrow(); } - catch (std::runtime_error const& e1) + catch (std::runtime_error const& e2) { - BEAST_EXPECT(std::string(e1.what()) == "Throw test"); - - try - { - Rethrow(); - } - catch (std::runtime_error const& e2) - { - BEAST_EXPECT(std::string(e2.what()) == "Throw test"); - } - catch (...) - { - BEAST_EXPECT(false); - } + CHECK(std::string(e2.what()) == "Throw test"); } catch (...) { - BEAST_EXPECT(false); + CHECK(false); } } -}; - -BEAST_DEFINE_TESTSUITE(contract, basics, ripple); - -} // namespace ripple + catch (...) + { + CHECK(false); + } +} diff --git a/src/tests/libxrpl/basics/main.cpp b/src/tests/libxrpl/basics/main.cpp new file mode 100644 index 0000000000..0a3f254ea8 --- /dev/null +++ b/src/tests/libxrpl/basics/main.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include diff --git a/src/tests/libxrpl/basics/mulDiv.cpp b/src/tests/libxrpl/basics/mulDiv.cpp new file mode 100644 index 0000000000..bdbbfdc741 --- /dev/null +++ b/src/tests/libxrpl/basics/mulDiv.cpp @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +#include +#include + +using namespace ripple; + +TEST_CASE("mulDiv") +{ + auto const max = std::numeric_limits::max(); + std::uint64_t const max32 = std::numeric_limits::max(); + + auto result = mulDiv(85, 20, 5); + REQUIRE(result); + CHECK(*result == 340); + result = mulDiv(20, 85, 5); + REQUIRE(result); + CHECK(*result == 340); + + result = mulDiv(0, max - 1, max - 3); + REQUIRE(result); + CHECK(*result == 0); + result = mulDiv(max - 1, 0, max - 3); + REQUIRE(result); + CHECK(*result == 0); + + result = mulDiv(max, 2, max / 2); + REQUIRE(result); + CHECK(*result == 4); + result = mulDiv(max, 1000, max / 1000); + REQUIRE(result); + CHECK(*result == 1000000); + result = mulDiv(max, 1000, max / 1001); + REQUIRE(result); + CHECK(*result == 1001000); + result = mulDiv(max32 + 1, max32 + 1, 5); + REQUIRE(result); + CHECK(*result == 3689348814741910323); + + // Overflow + result = mulDiv(max - 1, max - 2, 5); + CHECK(!result); +} diff --git a/src/tests/libxrpl/basics/scope.cpp b/src/tests/libxrpl/basics/scope.cpp new file mode 100644 index 0000000000..c9cfc1e7f8 --- /dev/null +++ b/src/tests/libxrpl/basics/scope.cpp @@ -0,0 +1,174 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2021 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +using namespace ripple; + +TEST_CASE("scope_exit") +{ + // scope_exit always executes the functor on destruction, + // unless release() is called + int i = 0; + { + scope_exit x{[&i]() { i = 1; }}; + } + CHECK(i == 1); + { + scope_exit x{[&i]() { i = 2; }}; + x.release(); + } + CHECK(i == 1); + { + scope_exit x{[&i]() { i += 2; }}; + auto x2 = std::move(x); + } + CHECK(i == 3); + { + scope_exit x{[&i]() { i = 4; }}; + x.release(); + auto x2 = std::move(x); + } + CHECK(i == 3); + { + try + { + scope_exit x{[&i]() { i = 5; }}; + throw 1; + } + catch (...) + { + } + } + CHECK(i == 5); + { + try + { + scope_exit x{[&i]() { i = 6; }}; + x.release(); + throw 1; + } + catch (...) + { + } + } + CHECK(i == 5); +} + +TEST_CASE("scope_fail") +{ + // scope_fail executes the functor on destruction only + // if an exception is unwinding, unless release() is called + int i = 0; + { + scope_fail x{[&i]() { i = 1; }}; + } + CHECK(i == 0); + { + scope_fail x{[&i]() { i = 2; }}; + x.release(); + } + CHECK(i == 0); + { + scope_fail x{[&i]() { i = 3; }}; + auto x2 = std::move(x); + } + CHECK(i == 0); + { + scope_fail x{[&i]() { i = 4; }}; + x.release(); + auto x2 = std::move(x); + } + CHECK(i == 0); + { + try + { + scope_fail x{[&i]() { i = 5; }}; + throw 1; + } + catch (...) + { + } + } + CHECK(i == 5); + { + try + { + scope_fail x{[&i]() { i = 6; }}; + x.release(); + throw 1; + } + catch (...) + { + } + } + CHECK(i == 5); +} + +TEST_CASE("scope_success") +{ + // scope_success executes the functor on destruction only + // if an exception is not unwinding, unless release() is called + int i = 0; + { + scope_success x{[&i]() { i = 1; }}; + } + CHECK(i == 1); + { + scope_success x{[&i]() { i = 2; }}; + x.release(); + } + CHECK(i == 1); + { + scope_success x{[&i]() { i += 2; }}; + auto x2 = std::move(x); + } + CHECK(i == 3); + { + scope_success x{[&i]() { i = 4; }}; + x.release(); + auto x2 = std::move(x); + } + CHECK(i == 3); + { + try + { + scope_success x{[&i]() { i = 5; }}; + throw 1; + } + catch (...) + { + } + } + CHECK(i == 3); + { + try + { + scope_success x{[&i]() { i = 6; }}; + x.release(); + throw 1; + } + catch (...) + { + } + } + CHECK(i == 3); +} diff --git a/src/tests/libxrpl/basics/tagged_integer.cpp b/src/tests/libxrpl/basics/tagged_integer.cpp new file mode 100644 index 0000000000..d699b64a70 --- /dev/null +++ b/src/tests/libxrpl/basics/tagged_integer.cpp @@ -0,0 +1,247 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2014 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +#include + +using namespace ripple; + +struct Tag1 +{ +}; +struct Tag2 +{ +}; + +// Static checks that types are not interoperable + +using TagUInt1 = tagged_integer; +using TagUInt2 = tagged_integer; +using TagUInt3 = tagged_integer; + +// Check construction of tagged_integers +static_assert( + std::is_constructible::value, + "TagUInt1 should be constructible using a std::uint32_t"); + +static_assert( + !std::is_constructible::value, + "TagUInt1 should not be constructible using a std::uint64_t"); + +static_assert( + std::is_constructible::value, + "TagUInt3 should be constructible using a std::uint32_t"); + +static_assert( + std::is_constructible::value, + "TagUInt3 should be constructible using a std::uint64_t"); + +// Check assignment of tagged_integers +static_assert( + !std::is_assignable::value, + "TagUInt1 should not be assignable with a std::uint32_t"); + +static_assert( + !std::is_assignable::value, + "TagUInt1 should not be assignable with a std::uint64_t"); + +static_assert( + !std::is_assignable::value, + "TagUInt3 should not be assignable with a std::uint32_t"); + +static_assert( + !std::is_assignable::value, + "TagUInt3 should not be assignable with a std::uint64_t"); + +static_assert( + std::is_assignable::value, + "TagUInt1 should be assignable with a TagUInt1"); + +static_assert( + !std::is_assignable::value, + "TagUInt1 should not be assignable with a TagUInt2"); + +static_assert( + std::is_assignable::value, + "TagUInt3 should be assignable with a TagUInt1"); + +static_assert( + !std::is_assignable::value, + "TagUInt1 should not be assignable with a TagUInt3"); + +static_assert( + !std::is_assignable::value, + "TagUInt3 should not be assignable with a TagUInt1"); + +// Check convertibility of tagged_integers +static_assert( + !std::is_convertible::value, + "std::uint32_t should not be convertible to a TagUInt1"); + +static_assert( + !std::is_convertible::value, + "std::uint32_t should not be convertible to a TagUInt3"); + +static_assert( + !std::is_convertible::value, + "std::uint64_t should not be convertible to a TagUInt3"); + +static_assert( + !std::is_convertible::value, + "std::uint64_t should not be convertible to a TagUInt2"); + +static_assert( + !std::is_convertible::value, + "TagUInt1 should not be convertible to TagUInt2"); + +static_assert( + !std::is_convertible::value, + "TagUInt1 should not be convertible to TagUInt3"); + +static_assert( + !std::is_convertible::value, + "TagUInt2 should not be convertible to a TagUInt3"); + +TEST_SUITE_BEGIN("tagged_integer"); + +using TagInt = tagged_integer; + +TEST_CASE("comparison operators") +{ + TagInt const zero(0); + TagInt const one(1); + + CHECK(one == one); + CHECK(!(one == zero)); + + CHECK(one != zero); + CHECK(!(one != one)); + + CHECK(zero < one); + CHECK(!(one < zero)); + + CHECK(one > zero); + CHECK(!(zero > one)); + + CHECK(one >= one); + CHECK(one >= zero); + CHECK(!(zero >= one)); + + CHECK(zero <= one); + CHECK(zero <= zero); + CHECK(!(one <= zero)); +} + +TEST_CASE("increment / decrement operators") +{ + TagInt const zero(0); + TagInt const one(1); + TagInt a{0}; + ++a; + CHECK(a == one); + --a; + CHECK(a == zero); + a++; + CHECK(a == one); + a--; + CHECK(a == zero); +} + +TEST_CASE("arithmetic operators") +{ + TagInt a{-2}; + CHECK(+a == TagInt{-2}); + CHECK(-a == TagInt{2}); + CHECK(TagInt{-3} + TagInt{4} == TagInt{1}); + CHECK(TagInt{-3} - TagInt{4} == TagInt{-7}); + CHECK(TagInt{-3} * TagInt{4} == TagInt{-12}); + CHECK(TagInt{8} / TagInt{4} == TagInt{2}); + CHECK(TagInt{7} % TagInt{4} == TagInt{3}); + + CHECK(~TagInt{8} == TagInt{~TagInt::value_type{8}}); + CHECK((TagInt{6} & TagInt{3}) == TagInt{2}); + CHECK((TagInt{6} | TagInt{3}) == TagInt{7}); + CHECK((TagInt{6} ^ TagInt{3}) == TagInt{5}); + + CHECK((TagInt{4} << TagInt{2}) == TagInt{16}); + CHECK((TagInt{16} >> TagInt{2}) == TagInt{4}); +} + +TEST_CASE("assignment operators") +{ + TagInt a{-2}; + TagInt b{0}; + b = a; + CHECK(b == TagInt{-2}); + + // -3 + 4 == 1 + a = TagInt{-3}; + a += TagInt{4}; + CHECK(a == TagInt{1}); + + // -3 - 4 == -7 + a = TagInt{-3}; + a -= TagInt{4}; + CHECK(a == TagInt{-7}); + + // -3 * 4 == -12 + a = TagInt{-3}; + a *= TagInt{4}; + CHECK(a == TagInt{-12}); + + // 8/4 == 2 + a = TagInt{8}; + a /= TagInt{4}; + CHECK(a == TagInt{2}); + + // 7 % 4 == 3 + a = TagInt{7}; + a %= TagInt{4}; + CHECK(a == TagInt{3}); + + // 6 & 3 == 2 + a = TagInt{6}; + a /= TagInt{3}; + CHECK(a == TagInt{2}); + + // 6 | 3 == 7 + a = TagInt{6}; + a |= TagInt{3}; + CHECK(a == TagInt{7}); + + // 6 ^ 3 == 5 + a = TagInt{6}; + a ^= TagInt{3}; + CHECK(a == TagInt{5}); + + // 4 << 2 == 16 + a = TagInt{4}; + a <<= TagInt{2}; + CHECK(a == TagInt{16}); + + // 16 >> 2 == 4 + a = TagInt{16}; + a >>= TagInt{2}; + CHECK(a == TagInt{4}); +} + +TEST_SUITE_END(); diff --git a/src/test/core/CryptoPRNG_test.cpp b/src/tests/libxrpl/crypto/csprng.cpp similarity index 58% rename from src/test/core/CryptoPRNG_test.cpp rename to src/tests/libxrpl/crypto/csprng.cpp index 21924e582c..a55d49b67c 100644 --- a/src/test/core/CryptoPRNG_test.cpp +++ b/src/tests/libxrpl/crypto/csprng.cpp @@ -17,44 +17,18 @@ */ //============================================================================== -#include - -#include #include -namespace ripple { +#include -class CryptoPRNG_test : public beast::unit_test::suite +using namespace ripple; + +TEST_CASE("get values") { - void - testGetValues() - { - testcase("Get Values"); - try - { - auto& engine = crypto_prng(); - auto rand_val = engine(); - BEAST_EXPECT(rand_val >= engine.min()); - BEAST_EXPECT(rand_val <= engine.max()); - - uint16_t twoByte{0}; - engine(&twoByte, sizeof(uint16_t)); - pass(); - } - catch (std::exception&) - { - fail(); - } - } - -public: - void - run() override - { - testGetValues(); - } -}; - -BEAST_DEFINE_TESTSUITE(CryptoPRNG, core, ripple); - -} // namespace ripple + auto& engine = crypto_prng(); + auto rand_val = engine(); + CHECK(rand_val >= engine.min()); + CHECK(rand_val <= engine.max()); + uint16_t twoByte{0}; + engine(&twoByte, sizeof(uint16_t)); +} diff --git a/src/tests/libxrpl/crypto/main.cpp b/src/tests/libxrpl/crypto/main.cpp new file mode 100644 index 0000000000..0a3f254ea8 --- /dev/null +++ b/src/tests/libxrpl/crypto/main.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include diff --git a/src/xrpld/app/ledger/Ledger.cpp b/src/xrpld/app/ledger/Ledger.cpp index 3cdf0ab1a7..6de4f2cbde 100644 --- a/src/xrpld/app/ledger/Ledger.cpp +++ b/src/xrpld/app/ledger/Ledger.cpp @@ -996,7 +996,8 @@ pendSaveValidated( bool isSynchronous, bool isCurrent) { - if (!app.getHashRouter().setFlags(ledger->info().hash, SF_SAVED)) + if (!app.getHashRouter().setFlags( + ledger->info().hash, HashRouterFlags::SAVED)) { // We have tried to save this ledger recently auto stream = app.journal("Ledger").debug(); diff --git a/src/xrpld/app/ledger/LedgerHistory.cpp b/src/xrpld/app/ledger/LedgerHistory.cpp index ccec209bd4..dcbd722120 100644 --- a/src/xrpld/app/ledger/LedgerHistory.cpp +++ b/src/xrpld/app/ledger/LedgerHistory.cpp @@ -63,8 +63,6 @@ LedgerHistory::insert( ledger->stateMap().getHash().isNonZero(), "ripple::LedgerHistory::insert : nonzero hash"); - std::unique_lock sl(m_ledgers_by_hash.peekMutex()); - bool const alreadyHad = m_ledgers_by_hash.canonicalize_replace_cache( ledger->info().hash, ledger); if (validated) @@ -76,7 +74,6 @@ LedgerHistory::insert( LedgerHash LedgerHistory::getLedgerHash(LedgerIndex index) { - std::unique_lock sl(m_ledgers_by_hash.peekMutex()); if (auto it = mLedgersByIndex.find(index); it != mLedgersByIndex.end()) return it->second; return {}; @@ -86,13 +83,11 @@ std::shared_ptr LedgerHistory::getLedgerBySeq(LedgerIndex index) { { - std::unique_lock sl(m_ledgers_by_hash.peekMutex()); auto it = mLedgersByIndex.find(index); if (it != mLedgersByIndex.end()) { uint256 hash = it->second; - sl.unlock(); return getLedgerByHash(hash); } } @@ -108,7 +103,6 @@ LedgerHistory::getLedgerBySeq(LedgerIndex index) { // Add this ledger to the local tracking by index - std::unique_lock sl(m_ledgers_by_hash.peekMutex()); XRPL_ASSERT( ret->isImmutable(), @@ -458,8 +452,6 @@ LedgerHistory::builtLedger( XRPL_ASSERT( !hash.isZero(), "ripple::LedgerHistory::builtLedger : nonzero hash"); - std::unique_lock sl(m_consensus_validated.peekMutex()); - auto entry = std::make_shared(); m_consensus_validated.canonicalize_replace_client(index, entry); @@ -500,8 +492,6 @@ LedgerHistory::validatedLedger( !hash.isZero(), "ripple::LedgerHistory::validatedLedger : nonzero hash"); - std::unique_lock sl(m_consensus_validated.peekMutex()); - auto entry = std::make_shared(); m_consensus_validated.canonicalize_replace_client(index, entry); @@ -535,10 +525,9 @@ LedgerHistory::validatedLedger( bool LedgerHistory::fixIndex(LedgerIndex ledgerIndex, LedgerHash const& ledgerHash) { - std::unique_lock sl(m_ledgers_by_hash.peekMutex()); + auto ledger = m_ledgers_by_hash.fetch(ledgerHash); auto it = mLedgersByIndex.find(ledgerIndex); - - if ((it != mLedgersByIndex.end()) && (it->second != ledgerHash)) + if (ledger && (it != mLedgersByIndex.end()) && (it->second != ledgerHash)) { it->second = ledgerHash; return false; diff --git a/src/xrpld/app/ledger/LedgerToJson.h b/src/xrpld/app/ledger/LedgerToJson.h index 40be57fc9c..be017bca86 100644 --- a/src/xrpld/app/ledger/LedgerToJson.h +++ b/src/xrpld/app/ledger/LedgerToJson.h @@ -37,9 +37,8 @@ struct LedgerFill ReadView const& l, RPC::Context* ctx, int o = 0, - std::vector q = {}, - LedgerEntryType t = ltANY) - : ledger(l), options(o), txQueue(std::move(q)), type(t), context(ctx) + std::vector q = {}) + : ledger(l), options(o), txQueue(std::move(q)), context(ctx) { if (context) closeTime = context->ledgerMaster.getCloseTimeBySeq(ledger.seq()); @@ -58,7 +57,6 @@ struct LedgerFill ReadView const& ledger; int options; std::vector txQueue; - LedgerEntryType type; RPC::Context* context; std::optional closeTime; }; diff --git a/src/xrpld/app/ledger/detail/LedgerToJson.cpp b/src/xrpld/app/ledger/detail/LedgerToJson.cpp index 3e4f4b8f0a..0e6f81dfbc 100644 --- a/src/xrpld/app/ledger/detail/LedgerToJson.cpp +++ b/src/xrpld/app/ledger/detail/LedgerToJson.cpp @@ -268,19 +268,16 @@ fillJsonState(Object& json, LedgerFill const& fill) for (auto const& sle : ledger.sles) { - if (fill.type == ltANY || sle->getType() == fill.type) + if (binary) { - if (binary) - { - auto&& obj = appendObject(array); - obj[jss::hash] = to_string(sle->key()); - obj[jss::tx_blob] = serializeHex(*sle); - } - else if (expanded) - array.append(sle->getJson(JsonOptions::none)); - else - array.append(to_string(sle->key())); + auto&& obj = appendObject(array); + obj[jss::hash] = to_string(sle->key()); + obj[jss::tx_blob] = serializeHex(*sle); } + else if (expanded) + array.append(sle->getJson(JsonOptions::none)); + else + array.append(to_string(sle->key())); } } diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index ea0b794116..c824eccfba 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -79,7 +79,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/xrpld/app/main/Main.cpp b/src/xrpld/app/main/Main.cpp index e926a38563..19c8c9910d 100644 --- a/src/xrpld/app/main/Main.cpp +++ b/src/xrpld/app/main/Main.cpp @@ -39,7 +39,7 @@ #include #include -#include +#include #include #include diff --git a/src/xrpld/app/misc/AMMUtils.h b/src/xrpld/app/misc/AMMUtils.h index b2c0007dc7..2a9f82ae60 100644 --- a/src/xrpld/app/misc/AMMUtils.h +++ b/src/xrpld/app/misc/AMMUtils.h @@ -125,6 +125,17 @@ isOnlyLiquidityProvider( Issue const& ammIssue, AccountID const& lpAccount); +/** Due to rounding, the LPTokenBalance of the last LP might + * not match the LP's trustline balance. If it's within the tolerance, + * update LPTokenBalance to match the LP's trustline balance. + */ +Expected +verifyAndAdjustLPTokenBalance( + Sandbox& sb, + STAmount const& lpTokens, + std::shared_ptr& ammSle, + AccountID const& account); + } // namespace ripple #endif // RIPPLE_APP_MISC_AMMUTILS_H_INCLUDED diff --git a/src/xrpld/app/misc/CredentialHelpers.cpp b/src/xrpld/app/misc/CredentialHelpers.cpp index 81355f1792..6d1f9f78c5 100644 --- a/src/xrpld/app/misc/CredentialHelpers.cpp +++ b/src/xrpld/app/misc/CredentialHelpers.cpp @@ -120,15 +120,15 @@ deleteSLE( } NotTEC -checkFields(PreflightContext const& ctx) +checkFields(STTx const& tx, beast::Journal j) { - if (!ctx.tx.isFieldPresent(sfCredentialIDs)) + if (!tx.isFieldPresent(sfCredentialIDs)) return tesSUCCESS; - auto const& credentials = ctx.tx.getFieldV256(sfCredentialIDs); + auto const& credentials = tx.getFieldV256(sfCredentialIDs); if (credentials.empty() || (credentials.size() > maxCredentialsArraySize)) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "Malformed transaction: Credentials array size is invalid: " << credentials.size(); return temMALFORMED; @@ -140,7 +140,7 @@ checkFields(PreflightContext const& ctx) auto [it, ins] = duplicates.insert(cred); if (!ins) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "Malformed transaction: duplicates in credentials."; return temMALFORMED; } @@ -150,24 +150,28 @@ checkFields(PreflightContext const& ctx) } TER -valid(PreclaimContext const& ctx, AccountID const& src) +valid( + STTx const& tx, + ReadView const& view, + AccountID const& src, + beast::Journal j) { - if (!ctx.tx.isFieldPresent(sfCredentialIDs)) + if (!tx.isFieldPresent(sfCredentialIDs)) return tesSUCCESS; - auto const& credIDs(ctx.tx.getFieldV256(sfCredentialIDs)); + auto const& credIDs(tx.getFieldV256(sfCredentialIDs)); for (auto const& h : credIDs) { - auto const sleCred = ctx.view.read(keylet::credential(h)); + auto const sleCred = view.read(keylet::credential(h)); if (!sleCred) { - JLOG(ctx.j.trace()) << "Credential doesn't exist. Cred: " << h; + JLOG(j.trace()) << "Credential doesn't exist. Cred: " << h; return tecBAD_CREDENTIALS; } if (sleCred->getAccountID(sfSubject) != src) { - JLOG(ctx.j.trace()) + JLOG(j.trace()) << "Credential doesn't belong to the source account. Cred: " << h; return tecBAD_CREDENTIALS; @@ -175,7 +179,7 @@ valid(PreclaimContext const& ctx, AccountID const& src) if (!(sleCred->getFlags() & lsfAccepted)) { - JLOG(ctx.j.trace()) << "Credential isn't accepted. Cred: " << h; + JLOG(j.trace()) << "Credential isn't accepted. Cred: " << h; return tecBAD_CREDENTIALS; } @@ -352,10 +356,12 @@ verifyValidDomain( TER verifyDepositPreauth( - ApplyContext& ctx, + STTx const& tx, + ApplyView& view, AccountID const& src, AccountID const& dst, - std::shared_ptr const& sleDst) + std::shared_ptr const& sleDst, + beast::Journal j) { // If depositPreauth is enabled, then an account that requires // authorization has at least two ways to get a payment in: @@ -363,24 +369,21 @@ verifyDepositPreauth( // 2. If src is deposit preauthorized by dst (either by account or by // credentials). - bool const credentialsPresent = ctx.tx.isFieldPresent(sfCredentialIDs); + bool const credentialsPresent = tx.isFieldPresent(sfCredentialIDs); if (credentialsPresent && - credentials::removeExpired( - ctx.view(), ctx.tx.getFieldV256(sfCredentialIDs), ctx.journal)) + credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j)) return tecEXPIRED; if (sleDst && (sleDst->getFlags() & lsfDepositAuth)) { if (src != dst) { - if (!ctx.view().exists(keylet::depositPreauth(dst, src))) + if (!view.exists(keylet::depositPreauth(dst, src))) return !credentialsPresent ? tecNO_PERMISSION : credentials::authorizedDepositPreauth( - ctx.view(), - ctx.tx.getFieldV256(sfCredentialIDs), - dst); + view, tx.getFieldV256(sfCredentialIDs), dst); } } diff --git a/src/xrpld/app/misc/CredentialHelpers.h b/src/xrpld/app/misc/CredentialHelpers.h index 162ddd6515..84938180ce 100644 --- a/src/xrpld/app/misc/CredentialHelpers.h +++ b/src/xrpld/app/misc/CredentialHelpers.h @@ -20,7 +20,16 @@ #ifndef RIPPLE_APP_MISC_CREDENTIALHELPERS_H_INCLUDED #define RIPPLE_APP_MISC_CREDENTIALHELPERS_H_INCLUDED -#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace ripple { namespace credentials { @@ -48,13 +57,17 @@ deleteSLE( // Amendment and parameters checks for sfCredentialIDs field NotTEC -checkFields(PreflightContext const& ctx); +checkFields(STTx const& tx, beast::Journal j); // Accessing the ledger to check if provided credentials are valid. Do not use // in doApply (only in preclaim) since it does not remove expired credentials. // If you call it in prelaim, you also must call verifyDepositPreauth in doApply TER -valid(PreclaimContext const& ctx, AccountID const& src); +valid( + STTx const& tx, + ReadView const& view, + AccountID const& src, + beast::Journal j); // Check if subject has any credential maching the given domain. If you call it // in preclaim and it returns tecEXPIRED, you should call verifyValidDomain in @@ -93,10 +106,12 @@ verifyValidDomain( // Check expired credentials and for existing DepositPreauth ledger object TER verifyDepositPreauth( - ApplyContext& ctx, + STTx const& tx, + ApplyView& view, AccountID const& src, AccountID const& dst, - std::shared_ptr const& sleDst); + std::shared_ptr const& sleDst, + beast::Journal j); } // namespace ripple diff --git a/src/xrpld/app/misc/HashRouter.cpp b/src/xrpld/app/misc/HashRouter.cpp index dc87b2bce1..b241d6a98a 100644 --- a/src/xrpld/app/misc/HashRouter.cpp +++ b/src/xrpld/app/misc/HashRouter.cpp @@ -65,7 +65,10 @@ HashRouter::addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer) } bool -HashRouter::addSuppressionPeer(uint256 const& key, PeerShortID peer, int& flags) +HashRouter::addSuppressionPeer( + uint256 const& key, + PeerShortID peer, + HashRouterFlags& flags) { std::lock_guard lock(mutex_); @@ -79,7 +82,7 @@ bool HashRouter::shouldProcess( uint256 const& key, PeerShortID peer, - int& flags, + HashRouterFlags& flags, std::chrono::seconds tx_interval) { std::lock_guard lock(mutex_); @@ -91,7 +94,7 @@ HashRouter::shouldProcess( return s.shouldProcess(suppressionMap_.clock().now(), tx_interval); } -int +HashRouterFlags HashRouter::getFlags(uint256 const& key) { std::lock_guard lock(mutex_); @@ -100,9 +103,10 @@ HashRouter::getFlags(uint256 const& key) } bool -HashRouter::setFlags(uint256 const& key, int flags) +HashRouter::setFlags(uint256 const& key, HashRouterFlags flags) { - XRPL_ASSERT(flags, "ripple::HashRouter::setFlags : valid input"); + XRPL_ASSERT( + static_cast(flags), "ripple::HashRouter::setFlags : valid input"); std::lock_guard lock(mutex_); diff --git a/src/xrpld/app/misc/HashRouter.h b/src/xrpld/app/misc/HashRouter.h index a13bcb9f8f..60a0b01155 100644 --- a/src/xrpld/app/misc/HashRouter.h +++ b/src/xrpld/app/misc/HashRouter.h @@ -27,23 +27,63 @@ #include #include +#include namespace ripple { -// TODO convert these macros to int constants or an enum -#define SF_BAD 0x02 // Temporarily bad -#define SF_SAVED 0x04 -#define SF_HELD 0x08 // Held by LedgerMaster after potential processing failure -#define SF_TRUSTED 0x10 // comes from trusted source +enum class HashRouterFlags : std::uint16_t { + // Public flags + UNDEFINED = 0x00, + BAD = 0x02, // Temporarily bad + SAVED = 0x04, + HELD = 0x08, // Held by LedgerMaster after potential processing failure + TRUSTED = 0x10, // Comes from a trusted source -// Private flags, used internally in apply.cpp. -// Do not attempt to read, set, or reuse. -#define SF_PRIVATE1 0x0100 -#define SF_PRIVATE2 0x0200 -#define SF_PRIVATE3 0x0400 -#define SF_PRIVATE4 0x0800 -#define SF_PRIVATE5 0x1000 -#define SF_PRIVATE6 0x2000 + // Private flags (used internally in apply.cpp) + // Do not attempt to read, set, or reuse. + PRIVATE1 = 0x0100, + PRIVATE2 = 0x0200, + PRIVATE3 = 0x0400, + PRIVATE4 = 0x0800, + PRIVATE5 = 0x1000, + PRIVATE6 = 0x2000 +}; + +constexpr HashRouterFlags +operator|(HashRouterFlags lhs, HashRouterFlags rhs) +{ + return static_cast( + static_cast>(lhs) | + static_cast>(rhs)); +} + +constexpr HashRouterFlags& +operator|=(HashRouterFlags& lhs, HashRouterFlags rhs) +{ + lhs = lhs | rhs; + return lhs; +} + +constexpr HashRouterFlags +operator&(HashRouterFlags lhs, HashRouterFlags rhs) +{ + return static_cast( + static_cast>(lhs) & + static_cast>(rhs)); +} + +constexpr HashRouterFlags& +operator&=(HashRouterFlags& lhs, HashRouterFlags rhs) +{ + lhs = lhs & rhs; + return lhs; +} + +constexpr bool +any(HashRouterFlags flags) +{ + return static_cast>(flags) != 0; +} class Config; @@ -100,14 +140,14 @@ private: peers_.insert(peer); } - int + HashRouterFlags getFlags(void) const { return flags_; } void - setFlags(int flagsToSet) + setFlags(HashRouterFlags flagsToSet) { flags_ |= flagsToSet; } @@ -153,7 +193,7 @@ private: } private: - int flags_ = 0; + HashRouterFlags flags_ = HashRouterFlags::UNDEFINED; std::set peers_; // This could be generalized to a map, if more // than one flag needs to expire independently. @@ -189,14 +229,17 @@ public: addSuppressionPeerWithStatus(uint256 const& key, PeerShortID peer); bool - addSuppressionPeer(uint256 const& key, PeerShortID peer, int& flags); + addSuppressionPeer( + uint256 const& key, + PeerShortID peer, + HashRouterFlags& flags); // Add a peer suppression and return whether the entry should be processed bool shouldProcess( uint256 const& key, PeerShortID peer, - int& flags, + HashRouterFlags& flags, std::chrono::seconds tx_interval); /** Set the flags on a hash. @@ -204,9 +247,9 @@ public: @return `true` if the flags were changed. `false` if unchanged. */ bool - setFlags(uint256 const& key, int flags); + setFlags(uint256 const& key, HashRouterFlags flags); - int + HashRouterFlags getFlags(uint256 const& key); /** Determines whether the hashed item should be relayed. diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index a7ddbe912c..3220ce99fc 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1207,7 +1207,7 @@ NetworkOPsImp::submitTransaction(std::shared_ptr const& iTrans) auto const txid = trans->getTransactionID(); auto const flags = app_.getHashRouter().getFlags(txid); - if ((flags & SF_BAD) != 0) + if ((flags & HashRouterFlags::BAD) != HashRouterFlags::UNDEFINED) { JLOG(m_journal.warn()) << "Submitted transaction cached bad"; return; @@ -1251,7 +1251,7 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr& transaction) { auto const newFlags = app_.getHashRouter().getFlags(transaction->getID()); - if ((newFlags & SF_BAD) != 0) + if ((newFlags & HashRouterFlags::BAD) != HashRouterFlags::UNDEFINED) { // cached bad JLOG(m_journal.warn()) << transaction->getID() << ": cached bad!\n"; @@ -1270,7 +1270,8 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr& transaction) { transaction->setStatus(INVALID); transaction->setResult(temINVALID_FLAG); - app_.getHashRouter().setFlags(transaction->getID(), SF_BAD); + app_.getHashRouter().setFlags( + transaction->getID(), HashRouterFlags::BAD); return false; } @@ -1289,7 +1290,8 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr& transaction) JLOG(m_journal.info()) << "Transaction has bad signature: " << reason; transaction->setStatus(INVALID); transaction->setResult(temBAD_SIGNATURE); - app_.getHashRouter().setFlags(transaction->getID(), SF_BAD); + app_.getHashRouter().setFlags( + transaction->getID(), HashRouterFlags::BAD); return false; } @@ -1412,7 +1414,8 @@ NetworkOPsImp::processTransactionSet(CanonicalTXSet const& set) JLOG(m_journal.trace()) << "Exception checking transaction: " << reason; } - app_.getHashRouter().setFlags(tx->getTransactionID(), SF_BAD); + app_.getHashRouter().setFlags( + tx->getTransactionID(), HashRouterFlags::BAD); continue; } @@ -1538,7 +1541,8 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) e.transaction->setResult(e.result); if (isTemMalformed(e.result)) - app_.getHashRouter().setFlags(e.transaction->getID(), SF_BAD); + app_.getHashRouter().setFlags( + e.transaction->getID(), HashRouterFlags::BAD); #ifdef DEBUG if (e.result != tesSUCCESS) @@ -1626,7 +1630,8 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) // (5) ledgers into the future. (Remember that an // unseated optional compares as less than all seated // values, so it has to be checked explicitly first.) - // 3. The SF_HELD flag is not set on the txID. (setFlags + // 3. The HashRouterFlags::BAD flag is not set on the txID. + // (setFlags // checks before setting. If the flag is set, it returns // false, which means it's been held once without one of // the other conditions, so don't hold it again. Time's @@ -1635,7 +1640,7 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) if (e.local || (ledgersLeft && ledgersLeft <= LocalTxs::holdLedgers) || app_.getHashRouter().setFlags( - e.transaction->getID(), SF_HELD)) + e.transaction->getID(), HashRouterFlags::HELD)) { // transaction should be held JLOG(m_journal.debug()) @@ -2410,6 +2415,7 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) jvObj[jss::flags] = val->getFlags(); jvObj[jss::signing_time] = *(*val)[~sfSigningTime]; jvObj[jss::data] = strHex(val->getSerializer().slice()); + jvObj[jss::network_id] = app_.config().NETWORK_ID; if (auto version = (*val)[~sfServerVersion]) jvObj[jss::server_version] = std::to_string(*version); @@ -3114,6 +3120,8 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) jvObj[jss::ledger_time] = Json::Value::UInt( lpAccepted->info().closeTime.time_since_epoch().count()); + jvObj[jss::network_id] = app_.config().NETWORK_ID; + if (!lpAccepted->rules().enabled(featureXRPFees)) jvObj[jss::fee_ref] = Config::FEE_UNITS_DEPRECATED; jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped(); @@ -4172,6 +4180,7 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult) jvResult[jss::reserve_base] = lpClosed->fees().accountReserve(0).jsonClipped(); jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); + jvResult[jss::network_id] = app_.config().NETWORK_ID; } if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger()) diff --git a/src/xrpld/app/misc/ValidatorList.h b/src/xrpld/app/misc/ValidatorList.h index 4cb32282db..1f5d728824 100644 --- a/src/xrpld/app/misc/ValidatorList.h +++ b/src/xrpld/app/misc/ValidatorList.h @@ -226,7 +226,7 @@ class ValidatorList TimeKeeper& timeKeeper_; boost::filesystem::path const dataPath_; beast::Journal const j_; - boost::shared_mutex mutable mutex_; + std::shared_mutex mutable mutex_; using lock_guard = std::lock_guard; using shared_lock = std::shared_lock; diff --git a/src/xrpld/app/misc/detail/AMMUtils.cpp b/src/xrpld/app/misc/detail/AMMUtils.cpp index ba4c741300..b56ce2748e 100644 --- a/src/xrpld/app/misc/detail/AMMUtils.cpp +++ b/src/xrpld/app/misc/detail/AMMUtils.cpp @@ -17,6 +17,7 @@ */ //============================================================================== +#include #include #include @@ -464,4 +465,32 @@ isOnlyLiquidityProvider( return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE } +Expected +verifyAndAdjustLPTokenBalance( + Sandbox& sb, + STAmount const& lpTokens, + std::shared_ptr& ammSle, + AccountID const& account) +{ + if (auto const res = isOnlyLiquidityProvider(sb, lpTokens.issue(), account); + !res) + return Unexpected(res.error()); + else if (res.value()) + { + if (withinRelativeDistance( + lpTokens, + ammSle->getFieldAmount(sfLPTokenBalance), + Number{1, -3})) + { + ammSle->setFieldAmount(sfLPTokenBalance, lpTokens); + sb.update(ammSle); + } + else + { + return Unexpected(tecAMM_INVALID_TOKENS); + } + } + return true; +} + } // namespace ripple diff --git a/src/xrpld/app/paths/detail/BookStep.cpp b/src/xrpld/app/paths/detail/BookStep.cpp index 8d20a9900c..554d2525f5 100644 --- a/src/xrpld/app/paths/detail/BookStep.cpp +++ b/src/xrpld/app/paths/detail/BookStep.cpp @@ -743,7 +743,6 @@ BookStep::forEachOffer( FlowOfferStream offers( sb, afView, book_, sb.parentCloseTime(), counter, j_); - bool const flowCross = afView.rules().enabled(featureFlowCross); bool offerAttempted = false; std::optional ofrQ; auto execOffer = [&](auto& offer) { @@ -760,8 +759,8 @@ BookStep::forEachOffer( // Make sure offer owner has authorization to own IOUs from issuer. // An account can always own XRP or their own IOUs. - if (flowCross && (!isXRP(offer.issueIn().currency)) && - (offer.owner() != offer.issueIn().account)) + if (!isXRP(offer.issueIn().currency) && + offer.owner() != offer.issueIn().account) { auto const& issuerID = offer.issueIn().account; auto const issuer = afView.read(keylet::account(issuerID)); diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp index 64a42374ec..07c5151727 100644 --- a/src/xrpld/app/tx/detail/AMMClawback.cpp +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -151,6 +151,20 @@ AMMClawback::applyGuts(Sandbox& sb) if (!accountSle) return tecINTERNAL; // LCOV_EXCL_LINE + if (sb.rules().enabled(fixAMMClawbackRounding)) + { + // retrieve LP token balance inside the amendment gate to avoid + // inconsistent error behavior + auto const lpTokenBalance = ammLPHolds(sb, *ammSle, holder, j_); + if (lpTokenBalance == beast::zero) + return tecAMM_BALANCE; + + if (auto const res = verifyAndAdjustLPTokenBalance( + sb, lpTokenBalance, ammSle, holder); + !res) + return res.error(); // LCOV_EXCL_LINE + } + auto const expected = ammHolds( sb, *ammSle, @@ -248,10 +262,11 @@ AMMClawback::equalWithdrawMatchingOneAmount( STAmount const& amount) { auto frac = Number{amount} / amountBalance; - auto const amount2Withdraw = amount2Balance * frac; + auto amount2Withdraw = amount2Balance * frac; auto const lpTokensWithdraw = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac); + if (lpTokensWithdraw > holdLPtokens) // if lptoken balance less than what the issuer intended to clawback, // clawback all the tokens. Because we are doing a two-asset withdrawal, @@ -272,6 +287,42 @@ AMMClawback::equalWithdrawMatchingOneAmount( mPriorBalance, ctx_.journal); + auto const& rules = sb.rules(); + if (rules.enabled(fixAMMClawbackRounding)) + { + auto tokensAdj = + getRoundedLPTokens(rules, lptAMMBalance, frac, IsDeposit::No); + + // LCOV_EXCL_START + if (tokensAdj == beast::zero) + return { + tecAMM_INVALID_TOKENS, STAmount{}, STAmount{}, std::nullopt}; + // LCOV_EXCL_STOP + + frac = adjustFracByTokens(rules, lptAMMBalance, tokensAdj, frac); + auto amount2Rounded = + getRoundedAsset(rules, amount2Balance, frac, IsDeposit::No); + + auto amountRounded = + getRoundedAsset(rules, amountBalance, frac, IsDeposit::No); + + return AMMWithdraw::withdraw( + sb, + ammSle, + ammAccount, + holder, + amountBalance, + amountRounded, + amount2Rounded, + lptAMMBalance, + tokensAdj, + 0, + FreezeHandling::fhIGNORE_FREEZE, + WithdrawAll::No, + mPriorBalance, + ctx_.journal); + } + // Because we are doing a two-asset withdrawal, // tfee is actually not used, so pass tfee as 0. return AMMWithdraw::withdraw( diff --git a/src/xrpld/app/tx/detail/AMMWithdraw.cpp b/src/xrpld/app/tx/detail/AMMWithdraw.cpp index 69243f3f48..2ad1a19df5 100644 --- a/src/xrpld/app/tx/detail/AMMWithdraw.cpp +++ b/src/xrpld/app/tx/detail/AMMWithdraw.cpp @@ -311,24 +311,9 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (sb.rules().enabled(fixAMMv1_1)) { if (auto const res = - isOnlyLiquidityProvider(sb, lpTokens.issue(), account_); + verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_); !res) return {res.error(), false}; - else if (res.value()) - { - if (withinRelativeDistance( - lpTokens, - ammSle->getFieldAmount(sfLPTokenBalance), - Number{1, -3})) - { - ammSle->setFieldAmount(sfLPTokenBalance, lpTokens); - sb.update(ammSle); - } - else - { - return {tecAMM_INVALID_TOKENS, false}; - } - } } auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_); diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index 7ccecd7a47..3cfae92cbd 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -26,9 +26,9 @@ #include #include #include -#include #include #include +#include #include namespace ripple { @@ -50,12 +50,6 @@ CreateOffer::preflight(PreflightContext const& ctx) !ctx.rules.enabled(featurePermissionedDEX)) return temDISABLED; - // Permissioned offers should use the PE (which must be enabled by - // featureFlowCross amendment) - if (ctx.rules.enabled(featurePermissionedDEX) && - !ctx.rules.enabled(featureFlowCross)) - return temDISABLED; - if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -317,422 +311,6 @@ CreateOffer::checkAcceptAsset( return tesSUCCESS; } -bool -CreateOffer::dry_offer(ApplyView& view, Offer const& offer) -{ - if (offer.fully_consumed()) - return true; - auto const amount = accountFunds( - view, - offer.owner(), - offer.amount().out, - fhZERO_IF_FROZEN, - ctx_.app.journal("View")); - return (amount <= beast::zero); -} - -std::pair -CreateOffer::select_path( - bool have_direct, - OfferStream const& direct, - bool have_bridge, - OfferStream const& leg1, - OfferStream const& leg2) -{ - // If we don't have any viable path, why are we here?! - XRPL_ASSERT( - have_direct || have_bridge, - "ripple::CreateOffer::select_path : valid inputs"); - - // If there's no bridged path, the direct is the best by default. - if (!have_bridge) - return std::make_pair(true, direct.tip().quality()); - - Quality const bridged_quality( - composed_quality(leg1.tip().quality(), leg2.tip().quality())); - - if (have_direct) - { - // We compare the quality of the composed quality of the bridged - // offers and compare it against the direct offer to pick the best. - Quality const direct_quality(direct.tip().quality()); - - if (bridged_quality < direct_quality) - return std::make_pair(true, direct_quality); - } - - // Either there was no direct offer, or it didn't have a better quality - // than the bridge. - return std::make_pair(false, bridged_quality); -} - -bool -CreateOffer::reachedOfferCrossingLimit(Taker const& taker) const -{ - auto const crossings = - taker.get_direct_crossings() + (2 * taker.get_bridge_crossings()); - - // The crossing limit is part of the Ripple protocol and - // changing it is a transaction-processing change. - return crossings >= 850; -} - -std::pair -CreateOffer::bridged_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when) -{ - auto const& takerAmount = taker.original_offer(); - - XRPL_ASSERT( - !isXRP(takerAmount.in) && !isXRP(takerAmount.out), - "ripple::CreateOffer::bridged_cross : neither is XRP"); - - if (isXRP(takerAmount.in) || isXRP(takerAmount.out)) - Throw("Bridging with XRP and an endpoint."); - - OfferStream offers_direct( - view, - view_cancel, - Book(taker.issue_in(), taker.issue_out(), std::nullopt), - when, - stepCounter_, - j_); - - OfferStream offers_leg1( - view, - view_cancel, - Book(taker.issue_in(), xrpIssue(), std::nullopt), - when, - stepCounter_, - j_); - - OfferStream offers_leg2( - view, - view_cancel, - Book(xrpIssue(), taker.issue_out(), std::nullopt), - when, - stepCounter_, - j_); - - TER cross_result = tesSUCCESS; - - // Note the subtle distinction here: self-offers encountered in the - // bridge are taken, but self-offers encountered in the direct book - // are not. - bool have_bridge = offers_leg1.step() && offers_leg2.step(); - bool have_direct = step_account(offers_direct, taker); - int count = 0; - - auto viewJ = ctx_.app.journal("View"); - - // Modifying the order or logic of the operations in the loop will cause - // a protocol breaking change. - while (have_direct || have_bridge) - { - bool leg1_consumed = false; - bool leg2_consumed = false; - bool direct_consumed = false; - - auto const [use_direct, quality] = select_path( - have_direct, offers_direct, have_bridge, offers_leg1, offers_leg2); - - // We are always looking at the best quality; we are done with - // crossing as soon as we cross the quality boundary. - if (taker.reject(quality)) - break; - - count++; - - if (use_direct) - { - if (auto stream = j_.debug()) - { - stream << count << " Direct:"; - stream << " offer: " << offers_direct.tip(); - stream << " in: " << offers_direct.tip().amount().in; - stream << " out: " << offers_direct.tip().amount().out; - stream << " owner: " << offers_direct.tip().owner(); - stream << " funds: " - << accountFunds( - view, - offers_direct.tip().owner(), - offers_direct.tip().amount().out, - fhIGNORE_FREEZE, - viewJ); - } - - cross_result = taker.cross(offers_direct.tip()); - - JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result); - - if (dry_offer(view, offers_direct.tip())) - { - direct_consumed = true; - have_direct = step_account(offers_direct, taker); - } - } - else - { - if (auto stream = j_.debug()) - { - auto const owner1_funds_before = accountFunds( - view, - offers_leg1.tip().owner(), - offers_leg1.tip().amount().out, - fhIGNORE_FREEZE, - viewJ); - - auto const owner2_funds_before = accountFunds( - view, - offers_leg2.tip().owner(), - offers_leg2.tip().amount().out, - fhIGNORE_FREEZE, - viewJ); - - stream << count << " Bridge:"; - stream << " offer1: " << offers_leg1.tip(); - stream << " in: " << offers_leg1.tip().amount().in; - stream << " out: " << offers_leg1.tip().amount().out; - stream << " owner: " << offers_leg1.tip().owner(); - stream << " funds: " << owner1_funds_before; - stream << " offer2: " << offers_leg2.tip(); - stream << " in: " << offers_leg2.tip().amount().in; - stream << " out: " << offers_leg2.tip().amount().out; - stream << " owner: " << offers_leg2.tip().owner(); - stream << " funds: " << owner2_funds_before; - } - - cross_result = taker.cross(offers_leg1.tip(), offers_leg2.tip()); - - JLOG(j_.debug()) << "Bridge Result: " << transToken(cross_result); - - if (view.rules().enabled(fixTakerDryOfferRemoval)) - { - // have_bridge can be true the next time 'round only if - // neither of the OfferStreams are dry. - leg1_consumed = dry_offer(view, offers_leg1.tip()); - if (leg1_consumed) - have_bridge &= offers_leg1.step(); - - leg2_consumed = dry_offer(view, offers_leg2.tip()); - if (leg2_consumed) - have_bridge &= offers_leg2.step(); - } - else - { - // This old behavior may leave an empty offer in the book for - // the second leg. - if (dry_offer(view, offers_leg1.tip())) - { - leg1_consumed = true; - have_bridge = (have_bridge && offers_leg1.step()); - } - if (dry_offer(view, offers_leg2.tip())) - { - leg2_consumed = true; - have_bridge = (have_bridge && offers_leg2.step()); - } - } - } - - if (cross_result != tesSUCCESS) - { - cross_result = tecFAILED_PROCESSING; - break; - } - - if (taker.done()) - { - JLOG(j_.debug()) << "The taker reports he's done during crossing!"; - break; - } - - if (reachedOfferCrossingLimit(taker)) - { - JLOG(j_.debug()) << "The offer crossing limit has been exceeded!"; - break; - } - - // Postcondition: If we aren't done, then we *must* have consumed at - // least one offer fully. - XRPL_ASSERT( - direct_consumed || leg1_consumed || leg2_consumed, - "ripple::CreateOffer::bridged_cross : consumed an offer"); - - if (!direct_consumed && !leg1_consumed && !leg2_consumed) - Throw( - "bridged crossing: nothing was fully consumed."); - } - - return std::make_pair(cross_result, taker.remaining_offer()); -} - -std::pair -CreateOffer::direct_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when) -{ - OfferStream offers( - view, - view_cancel, - Book(taker.issue_in(), taker.issue_out(), std::nullopt), - when, - stepCounter_, - j_); - - TER cross_result(tesSUCCESS); - int count = 0; - - bool have_offer = step_account(offers, taker); - - // Modifying the order or logic of the operations in the loop will cause - // a protocol breaking change. - while (have_offer) - { - bool direct_consumed = false; - auto& offer(offers.tip()); - - // We are done with crossing as soon as we cross the quality boundary - if (taker.reject(offer.quality())) - break; - - count++; - - if (auto stream = j_.debug()) - { - stream << count << " Direct:"; - stream << " offer: " << offer; - stream << " in: " << offer.amount().in; - stream << " out: " << offer.amount().out; - stream << "quality: " << offer.quality(); - stream << " owner: " << offer.owner(); - stream << " funds: " - << accountFunds( - view, - offer.owner(), - offer.amount().out, - fhIGNORE_FREEZE, - ctx_.app.journal("View")); - } - - cross_result = taker.cross(offer); - - JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result); - - if (dry_offer(view, offer)) - { - direct_consumed = true; - have_offer = step_account(offers, taker); - } - - if (cross_result != tesSUCCESS) - { - cross_result = tecFAILED_PROCESSING; - break; - } - - if (taker.done()) - { - JLOG(j_.debug()) << "The taker reports he's done during crossing!"; - break; - } - - if (reachedOfferCrossingLimit(taker)) - { - JLOG(j_.debug()) << "The offer crossing limit has been exceeded!"; - break; - } - - // Postcondition: If we aren't done, then we *must* have consumed the - // offer on the books fully! - XRPL_ASSERT( - direct_consumed, - "ripple::CreateOffer::direct_cross : consumed an offer"); - - if (!direct_consumed) - Throw( - "direct crossing: nothing was fully consumed."); - } - - return std::make_pair(cross_result, taker.remaining_offer()); -} - -// Step through the stream for as long as possible, skipping any offers -// that are from the taker or which cross the taker's threshold. -// Return false if the is no offer in the book, true otherwise. -bool -CreateOffer::step_account(OfferStream& stream, Taker const& taker) -{ - while (stream.step()) - { - auto const& offer = stream.tip(); - - // This offer at the tip crosses the taker's threshold. We're done. - if (taker.reject(offer.quality())) - return true; - - // This offer at the tip is not from the taker. We're done. - if (offer.owner() != taker.account()) - return true; - } - - // We ran out of offers. Can't advance. - return false; -} - -// Fill as much of the offer as possible by consuming offers -// already on the books. Return the status and the amount of -// the offer to left unfilled. -std::pair -CreateOffer::takerCross( - Sandbox& sb, - Sandbox& sbCancel, - Amounts const& takerAmount) -{ - NetClock::time_point const when = sb.parentCloseTime(); - - beast::WrappedSink takerSink(j_, "Taker "); - - Taker taker( - cross_type_, - sb, - account_, - takerAmount, - ctx_.tx.getFlags(), - beast::Journal(takerSink)); - - // If the taker is unfunded before we begin crossing - // there's nothing to do - just return an error. - // - // We check this in preclaim, but when selling XRP - // charged fees can cause a user's available balance - // to go to 0 (by causing it to dip below the reserve) - // so we check this case again. - if (taker.unfunded()) - { - JLOG(j_.debug()) << "Not crossing: taker is unfunded."; - return {tecUNFUNDED_OFFER, takerAmount}; - } - - try - { - if (cross_type_ == CrossType::IouToIou) - return bridged_cross(taker, sb, sbCancel, when); - - return direct_cross(taker, sb, sbCancel, when); - } - catch (std::exception const& e) - { - JLOG(j_.error()) << "Exception during offer crossing: " << e.what(); - return {tecINTERNAL, taker.remaining_offer()}; - } -} - std::pair CreateOffer::flowCross( PaymentSandbox& psb, @@ -937,32 +515,6 @@ CreateOffer::flowCross( return {tecINTERNAL, takerAmount}; } -std::pair -CreateOffer::cross( - Sandbox& sb, - Sandbox& sbCancel, - Amounts const& takerAmount, - std::optional const& domainID) -{ - if (sb.rules().enabled(featureFlowCross)) - { - PaymentSandbox psbFlow{&sb}; - PaymentSandbox psbCancelFlow{&sbCancel}; - auto const ret = - flowCross(psbFlow, psbCancelFlow, takerAmount, domainID); - psbFlow.apply(sb); - psbCancelFlow.apply(sbCancel); - return ret; - } - - Sandbox sbTaker{&sb}; - Sandbox sbCancelTaker{&sbCancel}; - auto const ret = takerCross(sbTaker, sbCancelTaker, takerAmount); - sbTaker.apply(sb); - sbCancelTaker.apply(sbCancel); - return ret; -} - std::string CreateOffer::format_amount(STAmount const& amount) { @@ -972,20 +524,6 @@ CreateOffer::format_amount(STAmount const& amount) return txt; } -void -CreateOffer::preCompute() -{ - cross_type_ = CrossType::IouToIou; - bool const pays_xrp = ctx_.tx.getFieldAmount(sfTakerPays).native(); - bool const gets_xrp = ctx_.tx.getFieldAmount(sfTakerGets).native(); - if (pays_xrp && !gets_xrp) - cross_type_ = CrossType::IouToXrp; - else if (gets_xrp && !pays_xrp) - cross_type_ = CrossType::XrpToIou; - - return Transactor::preCompute(); -} - TER CreateOffer::applyHybrid( Sandbox& sb, @@ -1149,11 +687,6 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) // We reverse pays and gets because during crossing we are taking. Amounts const takerAmount(saTakerGets, saTakerPays); - // The amount of the offer that is unfilled after crossing has been - // performed. It may be equal to the original amount (didn't cross), - // empty (fully crossed), or something in-between. - Amounts place_offer; - JLOG(j_.debug()) << "Attempting cross: " << to_string(takerAmount.in.issue()) << " -> " << to_string(takerAmount.out.issue()); @@ -1166,8 +699,17 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) stream << " out: " << format_amount(takerAmount.out); } + // The amount of the offer that is unfilled after crossing has been + // performed. It may be equal to the original amount (didn't cross), + // empty (fully crossed), or something in-between. + Amounts place_offer; + PaymentSandbox psbFlow{&sb}; + PaymentSandbox psbCancelFlow{&sbCancel}; + std::tie(result, place_offer) = - cross(sb, sbCancel, takerAmount, domainID); + flowCross(psbFlow, psbCancelFlow, takerAmount, domainID); + psbFlow.apply(sb); + psbCancelFlow.apply(sbCancel); // We expect the implementation of cross to succeed // or give a tec. diff --git a/src/xrpld/app/tx/detail/CreateOffer.h b/src/xrpld/app/tx/detail/CreateOffer.h index 9b35062d8a..6e3d6145b1 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.h +++ b/src/xrpld/app/tx/detail/CreateOffer.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED #define RIPPLE_TX_CREATEOFFER_H_INCLUDED -#include -#include #include +#include + namespace ripple { class PaymentSandbox; @@ -36,8 +36,7 @@ public: static constexpr ConsequencesFactoryType ConsequencesFactory{Custom}; /** Construct a Transactor subclass that creates an offer in the ledger. */ - explicit CreateOffer(ApplyContext& ctx) - : Transactor(ctx), stepCounter_(1000, j_) + explicit CreateOffer(ApplyContext& ctx) : Transactor(ctx) { } @@ -52,10 +51,6 @@ public: static TER preclaim(PreclaimContext const& ctx); - /** Gather information beyond what the Transactor base class gathers. */ - void - preCompute() override; - /** Precondition: fee collection is likely. Attempt to create the offer. */ TER doApply() override; @@ -73,49 +68,6 @@ private: beast::Journal const j, Issue const& issue); - bool - dry_offer(ApplyView& view, Offer const& offer); - - static std::pair - select_path( - bool have_direct, - OfferStream const& direct, - bool have_bridge, - OfferStream const& leg1, - OfferStream const& leg2); - - std::pair - bridged_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when); - - std::pair - direct_cross( - Taker& taker, - ApplyView& view, - ApplyView& view_cancel, - NetClock::time_point const when); - - // Step through the stream for as long as possible, skipping any offers - // that are from the taker or which cross the taker's threshold. - // Return false if the is no offer in the book, true otherwise. - static bool - step_account(OfferStream& stream, Taker const& taker); - - // True if the number of offers that have been crossed - // exceeds the limit. - bool - reachedOfferCrossingLimit(Taker const& taker) const; - - // Fill offer as much as possible by consuming offers already on the books, - // and adjusting account balances accordingly. - // - // Charges fees on top to taker. - std::pair - takerCross(Sandbox& sb, Sandbox& sbCancel, Amounts const& takerAmount); - // Use the payment flow code to perform offer crossing. std::pair flowCross( @@ -124,17 +76,6 @@ private: Amounts const& takerAmount, std::optional const& domainID); - // Temporary - // This is a central location that invokes both versions of cross - // so the results can be compared. Eventually this layer will be - // removed once flowCross is determined to be stable. - std::pair - cross( - Sandbox& sb, - Sandbox& sbCancel, - Amounts const& takerAmount, - std::optional const& domainID); - static std::string format_amount(STAmount const& amount); @@ -146,13 +87,6 @@ private: STAmount const& saTakerPays, STAmount const& saTakerGets, std::function)> const& setDir); - -private: - // What kind of offer we are placing - CrossType cross_type_; - - // The number of steps to take through order books while crossing - OfferStream::StepCounter stepCounter_; }; using OfferCreate = CreateOffer; diff --git a/src/xrpld/app/tx/detail/DeleteAccount.cpp b/src/xrpld/app/tx/detail/DeleteAccount.cpp index 7aa47e05f3..4311aa79a8 100644 --- a/src/xrpld/app/tx/detail/DeleteAccount.cpp +++ b/src/xrpld/app/tx/detail/DeleteAccount.cpp @@ -58,7 +58,8 @@ DeleteAccount::preflight(PreflightContext const& ctx) // An account cannot be deleted and give itself the resulting XRP. return temDST_IS_SRC; - if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) + if (auto const err = credentials::checkFields(ctx.tx, ctx.j); + !isTesSuccess(err)) return err; return preflight2(ctx); @@ -241,7 +242,8 @@ DeleteAccount::preclaim(PreclaimContext const& ctx) return tecDST_TAG_NEEDED; // If credentials are provided - check them anyway - if (auto const err = credentials::valid(ctx, account); !isTesSuccess(err)) + if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); + !isTesSuccess(err)) return err; // if credentials then postpone auth check to doApply, to check for expired @@ -376,7 +378,8 @@ DeleteAccount::doApply() if (ctx_.view().rules().enabled(featureDepositAuth) && ctx_.tx.isFieldPresent(sfCredentialIDs)) { - if (auto err = verifyDepositPreauth(ctx_, account_, dstID, dst); + if (auto err = verifyDepositPreauth( + ctx_.tx, ctx_.view(), account_, dstID, dst, ctx_.journal); !isTesSuccess(err)) return err; } diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 75080da9a5..dd0ffac778 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -34,13 +34,13 @@ #include #include +namespace ripple { + // During an EscrowFinish, the transaction must specify both // a condition and a fulfillment. We track whether that // fulfillment matches and validates the condition. -#define SF_CF_INVALID SF_PRIVATE5 -#define SF_CF_VALID SF_PRIVATE6 - -namespace ripple { +constexpr HashRouterFlags SF_CF_INVALID = HashRouterFlags::PRIVATE5; +constexpr HashRouterFlags SF_CF_VALID = HashRouterFlags::PRIVATE6; /* Escrow @@ -315,14 +315,14 @@ escrowCreatePreclaimHelper( // authorized auto const& mptIssue = amount.get(); if (auto const ter = - requireAuth(ctx.view, mptIssue, account, MPTAuthType::WeakAuth); + requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); ter != tesSUCCESS) return ter; // If the issuer has requireAuth set, check if the destination is // authorized if (auto const ter = - requireAuth(ctx.view, mptIssue, dest, MPTAuthType::WeakAuth); + requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); ter != tesSUCCESS) return ter; @@ -663,7 +663,7 @@ EscrowFinish::preflight(PreflightContext const& ctx) // If we haven't checked the condition, check it // now. Whether it passes or not isn't important // in preflight. - if (!(flags & (SF_CF_INVALID | SF_CF_VALID))) + if (!any(flags & (SF_CF_INVALID | SF_CF_VALID))) { if (checkCondition(*fb, *cb)) router.setFlags(id, SF_CF_VALID); @@ -672,7 +672,8 @@ EscrowFinish::preflight(PreflightContext const& ctx) } } - if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) + if (auto const err = credentials::checkFields(ctx.tx, ctx.j); + !isTesSuccess(err)) return err; return tesSUCCESS; @@ -745,7 +746,7 @@ escrowFinishPreclaimHelper( // authorized auto const& mptIssue = amount.get(); if (auto const ter = - requireAuth(ctx.view, mptIssue, dest, MPTAuthType::WeakAuth); + requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth); ter != tesSUCCESS) return ter; @@ -761,7 +762,8 @@ EscrowFinish::preclaim(PreclaimContext const& ctx) { if (ctx.view.rules().enabled(featureCredentials)) { - if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]); + if (auto const err = + credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j); !isTesSuccess(err)) return err; } @@ -1062,7 +1064,7 @@ EscrowFinish::doApply() // It's unlikely that the results of the check will // expire from the hash router, but if it happens, // simply re-run the check. - if (cb && !(flags & (SF_CF_INVALID | SF_CF_VALID))) + if (cb && !any(flags & (SF_CF_INVALID | SF_CF_VALID))) { auto const fb = ctx_.tx[~sfFulfillment]; @@ -1079,7 +1081,7 @@ EscrowFinish::doApply() // If the check failed, then simply return an error // and don't look at anything else. - if (flags & SF_CF_INVALID) + if (any(flags & SF_CF_INVALID)) return tecCRYPTOCONDITION_ERROR; // Check against condition in the ledger entry: @@ -1107,7 +1109,8 @@ EscrowFinish::doApply() if (ctx_.view().rules().enabled(featureDepositAuth)) { - if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled); + if (auto err = verifyDepositPreauth( + ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal); !isTesSuccess(err)) return err; } @@ -1256,7 +1259,7 @@ escrowCancelPreclaimHelper( // authorized auto const& mptIssue = amount.get(); if (auto const ter = - requireAuth(ctx.view, mptIssue, account, MPTAuthType::WeakAuth); + requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth); ter != tesSUCCESS) return ter; diff --git a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp index 77b21b65f3..77fe19a287 100644 --- a/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp +++ b/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp @@ -175,126 +175,18 @@ MPTokenAuthorize::createMPToken( return tesSUCCESS; } -TER -MPTokenAuthorize::authorize( - ApplyView& view, - beast::Journal journal, - MPTAuthorizeArgs const& args) -{ - auto const sleAcct = view.peek(keylet::account(args.account)); - if (!sleAcct) - return tecINTERNAL; - - // If the account that submitted the tx is a holder - // Note: `account_` is holder's account - // `holderID` is NOT used - if (!args.holderID) - { - // When a holder wants to unauthorize/delete a MPT, the ledger must - // - delete mptokenKey from owner directory - // - delete the MPToken - if (args.flags & tfMPTUnauthorize) - { - auto const mptokenKey = - keylet::mptoken(args.mptIssuanceID, args.account); - auto const sleMpt = view.peek(mptokenKey); - if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0) - return tecINTERNAL; // LCOV_EXCL_LINE - - if (!view.dirRemove( - keylet::ownerDir(args.account), - (*sleMpt)[sfOwnerNode], - sleMpt->key(), - false)) - return tecINTERNAL; // LCOV_EXCL_LINE - - adjustOwnerCount(view, sleAcct, -1, journal); - - view.erase(sleMpt); - return tesSUCCESS; - } - - // A potential holder wants to authorize/hold a mpt, the ledger must: - // - add the new mptokenKey to the owner directory - // - create the MPToken object for the holder - - // The reserve that is required to create the MPToken. Note - // that although the reserve increases with every item - // an account owns, in the case of MPTokens we only - // *enforce* a reserve if the user owns more than two - // items. This is similar to the reserve requirements of trust lines. - std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount); - XRPAmount const reserveCreate( - (uOwnerCount < 2) ? XRPAmount(beast::zero) - : view.fees().accountReserve(uOwnerCount + 1)); - - if (args.priorBalance < reserveCreate) - return tecINSUFFICIENT_RESERVE; - - auto const mptokenKey = - keylet::mptoken(args.mptIssuanceID, args.account); - auto mptoken = std::make_shared(mptokenKey); - if (auto ter = dirLink(view, args.account, mptoken)) - return ter; // LCOV_EXCL_LINE - - (*mptoken)[sfAccount] = args.account; - (*mptoken)[sfMPTokenIssuanceID] = args.mptIssuanceID; - (*mptoken)[sfFlags] = 0; - view.insert(mptoken); - - // Update owner count. - adjustOwnerCount(view, sleAcct, 1, journal); - - return tesSUCCESS; - } - - auto const sleMptIssuance = - view.read(keylet::mptIssuance(args.mptIssuanceID)); - if (!sleMptIssuance) - return tecINTERNAL; - - // If the account that submitted this tx is the issuer of the MPT - // Note: `account_` is issuer's account - // `holderID` is holder's account - if (args.account != (*sleMptIssuance)[sfIssuer]) - return tecINTERNAL; - - auto const sleMpt = - view.peek(keylet::mptoken(args.mptIssuanceID, *args.holderID)); - if (!sleMpt) - return tecINTERNAL; - - std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags); - std::uint32_t flagsOut = flagsIn; - - // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on - // their MPToken - if (args.flags & tfMPTUnauthorize) - flagsOut &= ~lsfMPTAuthorized; - // Issuer wants to authorize a holder, set lsfMPTAuthorized on their - // MPToken - else - flagsOut |= lsfMPTAuthorized; - - if (flagsIn != flagsOut) - sleMpt->setFieldU32(sfFlags, flagsOut); - - view.update(sleMpt); - return tesSUCCESS; -} - TER MPTokenAuthorize::doApply() { auto const& tx = ctx_.tx; - return authorize( + return authorizeMPToken( ctx_.view(), + mPriorBalance, + tx[sfMPTokenIssuanceID], + account_, ctx_.journal, - {.priorBalance = mPriorBalance, - .mptIssuanceID = tx[sfMPTokenIssuanceID], - .account = account_, - .flags = tx.getFlags(), - .holderID = tx[~sfHolder]}); + tx.getFlags(), + tx[~sfHolder]); } } // namespace ripple diff --git a/src/xrpld/app/tx/detail/MPTokenAuthorize.h b/src/xrpld/app/tx/detail/MPTokenAuthorize.h index a81dc7dea2..85e8edcf9f 100644 --- a/src/xrpld/app/tx/detail/MPTokenAuthorize.h +++ b/src/xrpld/app/tx/detail/MPTokenAuthorize.h @@ -48,12 +48,6 @@ public: static TER preclaim(PreclaimContext const& ctx); - static TER - authorize( - ApplyView& view, - beast::Journal journal, - MPTAuthorizeArgs const& args); - static TER createMPToken( ApplyView& view, diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index 1b96b27f24..da3b57c8fe 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -31,6 +31,11 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) if (!ctx.rules.enabled(featureMPTokensV1)) return temDISABLED; + if (ctx.tx.isFieldPresent(sfDomainID) && + !(ctx.rules.enabled(featurePermissionedDomains) && + ctx.rules.enabled(featureSingleAssetVault))) + return temDISABLED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -48,6 +53,16 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) return temMALFORMED; } + if (auto const domain = ctx.tx[~sfDomainID]) + { + if (*domain == beast::zero) + return temMALFORMED; + + // Domain present implies that MPTokenIssuance is not public + if ((ctx.tx.getFlags() & tfMPTRequireAuth) == 0) + return temMALFORMED; + } + if (auto const metadata = ctx.tx[~sfMPTokenMetadata]) { if (metadata->length() == 0 || @@ -142,6 +157,7 @@ MPTokenIssuanceCreate::doApply() .assetScale = tx[~sfAssetScale], .transferFee = tx[~sfTransferFee], .metadata = tx[~sfMPTokenMetadata], + .domainId = tx[~sfDomainID], }); return result ? tesSUCCESS : result.error(); } diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp index 06ea089526..e05862af37 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp @@ -21,6 +21,7 @@ #include #include +#include #include namespace ripple { @@ -31,6 +32,14 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) if (!ctx.rules.enabled(featureMPTokensV1)) return temDISABLED; + if (ctx.tx.isFieldPresent(sfDomainID) && + !(ctx.rules.enabled(featurePermissionedDomains) && + ctx.rules.enabled(featureSingleAssetVault))) + return temDISABLED; + + if (ctx.tx.isFieldPresent(sfDomainID) && ctx.tx.isFieldPresent(sfHolder)) + return temMALFORMED; + if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; @@ -48,6 +57,13 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) if (holderID && accountID == holderID) return temMALFORMED; + if (ctx.rules.enabled(featureSingleAssetVault)) + { + // Is this transaction actually changing anything ? + if (txFlags == 0 && !ctx.tx.isFieldPresent(sfDomainID)) + return temMALFORMED; + } + return preflight2(ctx); } @@ -97,9 +113,14 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) if (!sleMptIssuance) return tecOBJECT_NOT_FOUND; - // if the mpt has disabled locking - if (!((*sleMptIssuance)[sfFlags] & lsfMPTCanLock)) - return tecNO_PERMISSION; + if (!sleMptIssuance->isFlag(lsfMPTCanLock)) + { + // For readability two separate `if` rather than `||` of two conditions + if (!ctx.view.rules().enabled(featureSingleAssetVault)) + return tecNO_PERMISSION; + else if (ctx.tx.isFlag(tfMPTLock) || ctx.tx.isFlag(tfMPTUnlock)) + return tecNO_PERMISSION; + } // ensure it is issued by the tx submitter if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount]) @@ -117,6 +138,20 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) return tecOBJECT_NOT_FOUND; } + if (auto const domain = ctx.tx[~sfDomainID]) + { + if (not sleMptIssuance->isFlag(lsfMPTRequireAuth)) + return tecNO_PERMISSION; + + if (*domain != beast::zero) + { + auto const sleDomain = + ctx.view.read(keylet::permissionedDomain(*domain)); + if (!sleDomain) + return tecOBJECT_NOT_FOUND; + } + } + return tesSUCCESS; } @@ -126,6 +161,7 @@ MPTokenIssuanceSet::doApply() auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID]; auto const txFlags = ctx_.tx.getFlags(); auto const holderID = ctx_.tx[~sfHolder]; + auto const domainID = ctx_.tx[~sfDomainID]; std::shared_ptr sle; if (holderID) @@ -147,6 +183,24 @@ MPTokenIssuanceSet::doApply() if (flagsIn != flagsOut) sle->setFieldU32(sfFlags, flagsOut); + if (domainID) + { + // This is enforced in preflight. + XRPL_ASSERT( + sle->getType() == ltMPTOKEN_ISSUANCE, + "MPTokenIssuanceSet::doApply : modifying MPTokenIssuance"); + + if (*domainID != beast::zero) + { + sle->setFieldH256(sfDomainID, *domainID); + } + else + { + if (sle->isFieldPresent(sfDomainID)) + sle->makeFieldAbsent(sfDomainID); + } + } + view().update(sle); return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index a42902f6ac..d9e53ac75c 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -473,7 +473,8 @@ PayChanClaim::preflight(PreflightContext const& ctx) return temBAD_SIGNATURE; } - if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) + if (auto const err = credentials::checkFields(ctx.tx, ctx.j); + !isTesSuccess(err)) return err; return preflight2(ctx); @@ -485,7 +486,8 @@ PayChanClaim::preclaim(PreclaimContext const& ctx) if (!ctx.view.rules().enabled(featureCredentials)) return Transactor::preclaim(ctx); - if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]); + if (auto const err = + credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j); !isTesSuccess(err)) return err; @@ -554,7 +556,8 @@ PayChanClaim::doApply() if (depositAuth) { - if (auto err = verifyDepositPreauth(ctx_, txAccount, dst, sled); + if (auto err = verifyDepositPreauth( + ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal); !isTesSuccess(err)) return err; } diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index f36e1bfe3d..386b170ed1 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -238,7 +238,8 @@ Payment::preflight(PreflightContext const& ctx) } } - if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err)) + if (auto const err = credentials::checkFields(ctx.tx, ctx.j); + !isTesSuccess(err)) return err; return preflight2(ctx); @@ -358,7 +359,8 @@ Payment::preclaim(PreclaimContext const& ctx) } } - if (auto const err = credentials::valid(ctx, ctx.tx[sfAccount]); + if (auto const err = + credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j); !isTesSuccess(err)) return err; @@ -450,8 +452,13 @@ Payment::doApply() // 1. If Account == Destination, or // 2. If Account is deposit preauthorized by destination. - if (auto err = - verifyDepositPreauth(ctx_, account_, dstAccountID, sleDst); + if (auto err = verifyDepositPreauth( + ctx_.tx, + ctx_.view(), + account_, + dstAccountID, + sleDst, + ctx_.journal); !isTesSuccess(err)) return err; } @@ -521,8 +528,13 @@ Payment::doApply() ter != tesSUCCESS) return ter; - if (auto err = - verifyDepositPreauth(ctx_, account_, dstAccountID, sleDst); + if (auto err = verifyDepositPreauth( + ctx_.tx, + ctx_.view(), + account_, + dstAccountID, + sleDst, + ctx_.journal); !isTesSuccess(err)) return err; @@ -568,7 +580,16 @@ Payment::doApply() auto res = accountSend( pv, account_, dstAccountID, amountDeliver, ctx_.journal); if (res == tesSUCCESS) + { pv.apply(ctx_.rawView()); + + // If the actual amount delivered is different from the original + // amount due to partial payment or transfer fee, we need to update + // DelieveredAmount using the actual delivered amount + if (view().rules().enabled(fixMPTDeliveredAmount) && + amountDeliver != dstAmount) + ctx_.deliver(amountDeliver); + } else if (res == tecINSUFFICIENT_FUNDS || res == tecPATH_DRY) res = tecPATH_PARTIAL; @@ -644,8 +665,13 @@ Payment::doApply() if (dstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve) { - if (auto err = - verifyDepositPreauth(ctx_, account_, dstAccountID, sleDst); + if (auto err = verifyDepositPreauth( + ctx_.tx, + ctx_.view(), + account_, + dstAccountID, + sleDst, + ctx_.journal); !isTesSuccess(err)) return err; } diff --git a/src/xrpld/app/tx/detail/SetOracle.cpp b/src/xrpld/app/tx/detail/SetOracle.cpp index 8559c3e7b9..d598507cb7 100644 --- a/src/xrpld/app/tx/detail/SetOracle.cpp +++ b/src/xrpld/app/tx/detail/SetOracle.cpp @@ -209,6 +209,17 @@ SetOracle::doApply() { auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]); + auto populatePriceData = [](STObject& priceData, STObject const& entry) { + setPriceDataInnerObjTemplate(priceData); + priceData.setFieldCurrency( + sfBaseAsset, entry.getFieldCurrency(sfBaseAsset)); + priceData.setFieldCurrency( + sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset)); + priceData.setFieldU64(sfAssetPrice, entry.getFieldU64(sfAssetPrice)); + if (entry.isFieldPresent(sfScale)) + priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale)); + }; + if (auto sle = ctx_.view().peek(oracleID)) { // update @@ -249,15 +260,7 @@ SetOracle::doApply() { // add a token pair with the price STObject priceData{sfPriceData}; - setPriceDataInnerObjTemplate(priceData); - priceData.setFieldCurrency( - sfBaseAsset, entry.getFieldCurrency(sfBaseAsset)); - priceData.setFieldCurrency( - sfQuoteAsset, entry.getFieldCurrency(sfQuoteAsset)); - priceData.setFieldU64( - sfAssetPrice, entry.getFieldU64(sfAssetPrice)); - if (entry.isFieldPresent(sfScale)) - priceData.setFieldU8(sfScale, entry.getFieldU8(sfScale)); + populatePriceData(priceData, entry); pairs.emplace(key, std::move(priceData)); } } @@ -285,7 +288,26 @@ SetOracle::doApply() sle->setFieldVL(sfProvider, ctx_.tx[sfProvider]); if (ctx_.tx.isFieldPresent(sfURI)) sle->setFieldVL(sfURI, ctx_.tx[sfURI]); - auto const& series = ctx_.tx.getFieldArray(sfPriceDataSeries); + + STArray series; + if (!ctx_.view().rules().enabled(fixPriceOracleOrder)) + { + series = ctx_.tx.getFieldArray(sfPriceDataSeries); + } + else + { + std::map, STObject> pairs; + for (auto const& entry : ctx_.tx.getFieldArray(sfPriceDataSeries)) + { + auto const key = tokenPairKey(entry); + STObject priceData{sfPriceData}; + populatePriceData(priceData, entry); + pairs.emplace(key, std::move(priceData)); + } + for (auto const& iter : pairs) + series.push_back(std::move(iter.second)); + } + sle->setFieldArray(sfPriceDataSeries, series); sle->setFieldVL(sfAssetClass, ctx_.tx[sfAssetClass]); sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]); diff --git a/src/xrpld/app/tx/detail/Taker.cpp b/src/xrpld/app/tx/detail/Taker.cpp deleted file mode 100644 index 9bfd6dc1d3..0000000000 --- a/src/xrpld/app/tx/detail/Taker.cpp +++ /dev/null @@ -1,863 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2014 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include - -#include -#include - -namespace ripple { - -static std::string -format_amount(STAmount const& amount) -{ - std::string txt = amount.getText(); - txt += "/"; - txt += to_string(amount.issue().currency); - return txt; -} - -BasicTaker::BasicTaker( - CrossType cross_type, - AccountID const& account, - Amounts const& amount, - Quality const& quality, - std::uint32_t flags, - Rate const& rate_in, - Rate const& rate_out, - beast::Journal journal) - : account_(account) - , quality_(quality) - , threshold_(quality_) - , sell_(flags & tfSell) - , original_(amount) - , remaining_(amount) - , issue_in_(remaining_.in.issue()) - , issue_out_(remaining_.out.issue()) - , m_rate_in(rate_in) - , m_rate_out(rate_out) - , cross_type_(cross_type) - , journal_(journal) -{ - XRPL_ASSERT( - remaining_.in > beast::zero, - "ripple::BasicTaker::BasicTaker : positive remaining in"); - XRPL_ASSERT( - remaining_.out > beast::zero, - "ripple::BasicTaker::BasicTaker : positive remaining out"); - - XRPL_ASSERT( - m_rate_in.value, "ripple::BasicTaker::BasicTaker : nonzero rate in"); - XRPL_ASSERT( - m_rate_out.value, "ripple::BasicTaker::BasicTaker : nonzero rate out"); - - // If we are dealing with a particular flavor, make sure that it's the - // flavor we expect: - XRPL_ASSERT( - cross_type != CrossType::XrpToIou || - (isXRP(issue_in()) && !isXRP(issue_out())), - "ripple::BasicTaker::BasicTaker : valid cross to IOU"); - - XRPL_ASSERT( - cross_type != CrossType::IouToXrp || - (!isXRP(issue_in()) && isXRP(issue_out())), - "ripple::BasicTaker::BasicTaker : valid cross to XRP"); - - // And make sure we're not crossing XRP for XRP - XRPL_ASSERT( - !isXRP(issue_in()) || !isXRP(issue_out()), - "ripple::BasicTaker::BasicTaker : not crossing XRP for XRP"); - - // If this is a passive order, we adjust the quality so as to prevent offers - // at the same quality level from being consumed. - if (flags & tfPassive) - ++threshold_; -} - -Rate -BasicTaker::effective_rate( - Rate const& rate, - Issue const& issue, - AccountID const& from, - AccountID const& to) -{ - // If there's a transfer rate, the issuer is not involved - // and the sender isn't the same as the recipient, return - // the actual transfer rate. - if (rate != parityRate && from != to && from != issue.account && - to != issue.account) - { - return rate; - } - - return parityRate; -} - -bool -BasicTaker::unfunded() const -{ - if (get_funds(account(), remaining_.in) > beast::zero) - return false; - - JLOG(journal_.debug()) << "Unfunded: taker is out of funds."; - return true; -} - -bool -BasicTaker::done() const -{ - // We are done if we have consumed all the input currency - if (remaining_.in <= beast::zero) - { - JLOG(journal_.debug()) - << "Done: all the input currency has been consumed."; - return true; - } - - // We are done if using buy semantics and we received the - // desired amount of output currency - if (!sell_ && (remaining_.out <= beast::zero)) - { - JLOG(journal_.debug()) << "Done: the desired amount has been received."; - return true; - } - - // We are done if the taker is out of funds - if (unfunded()) - { - JLOG(journal_.debug()) << "Done: taker out of funds."; - return true; - } - - return false; -} - -Amounts -BasicTaker::remaining_offer() const -{ - // If the taker is done, then there's no offer to place. - if (done()) - return Amounts(remaining_.in.zeroed(), remaining_.out.zeroed()); - - // Avoid math altogether if we didn't cross. - if (original_ == remaining_) - return original_; - - if (sell_) - { - XRPL_ASSERT( - remaining_.in > beast::zero, - "ripple::BasicTaker::remaining_offer : positive remaining in"); - - // We scale the output based on the remaining input: - return Amounts( - remaining_.in, - divRound(remaining_.in, quality_.rate(), issue_out_, true)); - } - - XRPL_ASSERT( - remaining_.out > beast::zero, - "ripple::BasicTaker::remaining_offer : positive remaining out"); - - // We scale the input based on the remaining output: - return Amounts( - mulRound(remaining_.out, quality_.rate(), issue_in_, true), - remaining_.out); -} - -Amounts const& -BasicTaker::original_offer() const -{ - return original_; -} - -// TODO: the presence of 'output' is an artifact caused by the fact that -// Amounts carry issue information which should be decoupled. -static STAmount -qual_div(STAmount const& amount, Quality const& quality, STAmount const& output) -{ - auto result = divide(amount, quality.rate(), output.issue()); - return std::min(result, output); -} - -static STAmount -qual_mul(STAmount const& amount, Quality const& quality, STAmount const& output) -{ - auto result = multiply(amount, quality.rate(), output.issue()); - return std::min(result, output); -} - -void -BasicTaker::log_flow(char const* description, Flow const& flow) -{ - auto stream = journal_.debug(); - if (!stream) - return; - - stream << description; - - if (isXRP(issue_in())) - stream << " order in: " << format_amount(flow.order.in); - else - stream << " order in: " << format_amount(flow.order.in) - << " (issuer: " << format_amount(flow.issuers.in) << ")"; - - if (isXRP(issue_out())) - stream << " order out: " << format_amount(flow.order.out); - else - stream << " order out: " << format_amount(flow.order.out) - << " (issuer: " << format_amount(flow.issuers.out) << ")"; -} - -BasicTaker::Flow -BasicTaker::flow_xrp_to_iou( - Amounts const& order, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_out) -{ - Flow f; - f.order = order; - f.issuers.out = multiply(f.order.out, rate_out); - - log_flow("flow_xrp_to_iou", f); - - // Clamp on owner balance - if (owner_funds < f.issuers.out) - { - f.issuers.out = owner_funds; - f.order.out = divide(f.issuers.out, rate_out); - f.order.in = qual_mul(f.order.out, quality, f.order.in); - log_flow("(clamped on owner balance)", f); - } - - // Clamp if taker wants to limit the output - if (!sell_ && remaining_.out < f.order.out) - { - f.order.out = remaining_.out; - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker output)", f); - } - - // Clamp on the taker's funds - if (taker_funds < f.order.in) - { - f.order.in = taker_funds; - f.order.out = qual_div(f.order.in, quality, f.order.out); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker funds)", f); - } - - // Clamp on remaining offer if we are not handling the second leg - // of an autobridge. - if (cross_type_ == CrossType::XrpToIou && (remaining_.in < f.order.in)) - { - f.order.in = remaining_.in; - f.order.out = qual_div(f.order.in, quality, f.order.out); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker input)", f); - } - - return f; -} - -BasicTaker::Flow -BasicTaker::flow_iou_to_xrp( - Amounts const& order, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_in) -{ - Flow f; - f.order = order; - f.issuers.in = multiply(f.order.in, rate_in); - - log_flow("flow_iou_to_xrp", f); - - // Clamp on owner's funds - if (owner_funds < f.order.out) - { - f.order.out = owner_funds; - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.in = multiply(f.order.in, rate_in); - log_flow("(clamped on owner funds)", f); - } - - // Clamp if taker wants to limit the output and we are not the - // first leg of an autobridge. - if (!sell_ && cross_type_ == CrossType::IouToXrp) - { - if (remaining_.out < f.order.out) - { - f.order.out = remaining_.out; - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.in = multiply(f.order.in, rate_in); - log_flow("(clamped on taker output)", f); - } - } - - // Clamp on the taker's input offer - if (remaining_.in < f.order.in) - { - f.order.in = remaining_.in; - f.issuers.in = multiply(f.order.in, rate_in); - f.order.out = qual_div(f.order.in, quality, f.order.out); - log_flow("(clamped on taker input)", f); - } - - // Clamp on the taker's input balance - if (taker_funds < f.issuers.in) - { - f.issuers.in = taker_funds; - f.order.in = divide(f.issuers.in, rate_in); - f.order.out = qual_div(f.order.in, quality, f.order.out); - log_flow("(clamped on taker funds)", f); - } - - return f; -} - -BasicTaker::Flow -BasicTaker::flow_iou_to_iou( - Amounts const& order, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_in, - Rate const& rate_out) -{ - Flow f; - f.order = order; - f.issuers.in = multiply(f.order.in, rate_in); - f.issuers.out = multiply(f.order.out, rate_out); - - log_flow("flow_iou_to_iou", f); - - // Clamp on owner balance - if (owner_funds < f.issuers.out) - { - f.issuers.out = owner_funds; - f.order.out = divide(f.issuers.out, rate_out); - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.in = multiply(f.order.in, rate_in); - log_flow("(clamped on owner funds)", f); - } - - // Clamp on taker's offer - if (!sell_ && remaining_.out < f.order.out) - { - f.order.out = remaining_.out; - f.order.in = qual_mul(f.order.out, quality, f.order.in); - f.issuers.out = multiply(f.order.out, rate_out); - f.issuers.in = multiply(f.order.in, rate_in); - log_flow("(clamped on taker output)", f); - } - - // Clamp on the taker's input offer - if (remaining_.in < f.order.in) - { - f.order.in = remaining_.in; - f.issuers.in = multiply(f.order.in, rate_in); - f.order.out = qual_div(f.order.in, quality, f.order.out); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker input)", f); - } - - // Clamp on the taker's input balance - if (taker_funds < f.issuers.in) - { - f.issuers.in = taker_funds; - f.order.in = divide(f.issuers.in, rate_in); - f.order.out = qual_div(f.order.in, quality, f.order.out); - f.issuers.out = multiply(f.order.out, rate_out); - log_flow("(clamped on taker funds)", f); - } - - return f; -} - -// Calculates the direct flow through the specified offer -BasicTaker::Flow -BasicTaker::do_cross(Amounts offer, Quality quality, AccountID const& owner) -{ - auto const owner_funds = get_funds(owner, offer.out); - auto const taker_funds = get_funds(account(), offer.in); - - Flow result; - - if (cross_type_ == CrossType::XrpToIou) - { - result = flow_xrp_to_iou( - offer, - quality, - owner_funds, - taker_funds, - out_rate(owner, account())); - } - else if (cross_type_ == CrossType::IouToXrp) - { - result = flow_iou_to_xrp( - offer, - quality, - owner_funds, - taker_funds, - in_rate(owner, account())); - } - else - { - result = flow_iou_to_iou( - offer, - quality, - owner_funds, - taker_funds, - in_rate(owner, account()), - out_rate(owner, account())); - } - - if (!result.sanity_check()) - Throw("Computed flow fails sanity check."); - - remaining_.out -= result.order.out; - remaining_.in -= result.order.in; - - XRPL_ASSERT( - remaining_.in >= beast::zero, - "ripple::BasicTaker::do_cross : minimum remaining in"); - - return result; -} - -// Calculates the bridged flow through the specified offers -std::pair -BasicTaker::do_cross( - Amounts offer1, - Quality quality1, - AccountID const& owner1, - Amounts offer2, - Quality quality2, - AccountID const& owner2) -{ - XRPL_ASSERT( - !offer1.in.native(), - "ripple::BasicTaker::do_cross : offer1 in is not XRP"); - XRPL_ASSERT( - offer1.out.native(), - "ripple::BasicTaker::do_cross : offer1 out is XRP"); - XRPL_ASSERT( - offer2.in.native(), "ripple::BasicTaker::do_cross : offer2 in is XRP"); - XRPL_ASSERT( - !offer2.out.native(), - "ripple::BasicTaker::do_cross : offer2 out is not XRP"); - - // If the taker owns the first leg of the offer, then the taker's available - // funds aren't the limiting factor for the input - the offer itself is. - auto leg1_in_funds = get_funds(account(), offer1.in); - - if (account() == owner1) - { - JLOG(journal_.trace()) << "The taker owns the first leg of a bridge."; - leg1_in_funds = std::max(leg1_in_funds, offer1.in); - } - - // If the taker owns the second leg of the offer, then the taker's available - // funds are not the limiting factor for the output - the offer itself is. - auto leg2_out_funds = get_funds(owner2, offer2.out); - - if (account() == owner2) - { - JLOG(journal_.trace()) << "The taker owns the second leg of a bridge."; - leg2_out_funds = std::max(leg2_out_funds, offer2.out); - } - - // The amount available to flow via XRP is the amount that the owner of the - // first leg of the bridge has, up to the first leg's output. - // - // But, when both legs of a bridge are owned by the same person, the amount - // of XRP that can flow between the two legs is, essentially, infinite - // since all the owner is doing is taking out XRP of his left pocket - // and putting it in his right pocket. In that case, we set the available - // XRP to the largest of the two offers. - auto xrp_funds = get_funds(owner1, offer1.out); - - if (owner1 == owner2) - { - JLOG(journal_.trace()) - << "The bridge endpoints are owned by the same account."; - xrp_funds = std::max(offer1.out, offer2.in); - } - - if (auto stream = journal_.debug()) - { - stream << "Available bridge funds:"; - stream << " leg1 in: " << format_amount(leg1_in_funds); - stream << " leg2 out: " << format_amount(leg2_out_funds); - stream << " xrp: " << format_amount(xrp_funds); - } - - auto const leg1_rate = in_rate(owner1, account()); - auto const leg2_rate = out_rate(owner2, account()); - - // Attempt to determine the maximal flow that can be achieved across each - // leg independent of the other. - auto flow1 = - flow_iou_to_xrp(offer1, quality1, xrp_funds, leg1_in_funds, leg1_rate); - - if (!flow1.sanity_check()) - Throw("Computed flow1 fails sanity check."); - - auto flow2 = - flow_xrp_to_iou(offer2, quality2, leg2_out_funds, xrp_funds, leg2_rate); - - if (!flow2.sanity_check()) - Throw("Computed flow2 fails sanity check."); - - // We now have the maximal flows across each leg individually. We need to - // equalize them, so that the amount of XRP that flows out of the first leg - // is the same as the amount of XRP that flows into the second leg. We take - // the side which is the limiting factor (if any) and adjust the other. - if (flow1.order.out < flow2.order.in) - { - // Adjust the second leg of the offer down: - flow2.order.in = flow1.order.out; - flow2.order.out = qual_div(flow2.order.in, quality2, flow2.order.out); - flow2.issuers.out = multiply(flow2.order.out, leg2_rate); - log_flow("Balancing: adjusted second leg down", flow2); - } - else if (flow1.order.out > flow2.order.in) - { - // Adjust the first leg of the offer down: - flow1.order.out = flow2.order.in; - flow1.order.in = qual_mul(flow1.order.out, quality1, flow1.order.in); - flow1.issuers.in = multiply(flow1.order.in, leg1_rate); - log_flow("Balancing: adjusted first leg down", flow2); - } - - if (flow1.order.out != flow2.order.in) - Throw("Bridged flow is out of balance."); - - remaining_.out -= flow2.order.out; - remaining_.in -= flow1.order.in; - - return std::make_pair(flow1, flow2); -} - -//============================================================================== - -Taker::Taker( - CrossType cross_type, - ApplyView& view, - AccountID const& account, - Amounts const& offer, - std::uint32_t flags, - beast::Journal journal) - : BasicTaker( - cross_type, - account, - offer, - Quality(offer), - flags, - calculateRate(view, offer.in.getIssuer(), account), - calculateRate(view, offer.out.getIssuer(), account), - journal) - , view_(view) - , xrp_flow_(0) - , direct_crossings_(0) - , bridge_crossings_(0) -{ - XRPL_ASSERT( - issue_in() == offer.in.issue(), - "ripple::Taker::Taker : issue in is a match"); - XRPL_ASSERT( - issue_out() == offer.out.issue(), - "ripple::Taker::Taker : issue out is a match"); - - if (auto stream = journal_.debug()) - { - stream << "Crossing as: " << to_string(account); - - if (isXRP(issue_in())) - stream << " Offer in: " << format_amount(offer.in); - else - stream << " Offer in: " << format_amount(offer.in) - << " (issuer: " << issue_in().account << ")"; - - if (isXRP(issue_out())) - stream << " Offer out: " << format_amount(offer.out); - else - stream << " Offer out: " << format_amount(offer.out) - << " (issuer: " << issue_out().account << ")"; - - stream << " Balance: " - << format_amount(get_funds(account, offer.in)); - } -} - -void -Taker::consume_offer(Offer& offer, Amounts const& order) -{ - if (order.in < beast::zero) - Throw("flow with negative input."); - - if (order.out < beast::zero) - Throw("flow with negative output."); - - JLOG(journal_.debug()) << "Consuming from offer " << offer; - - if (auto stream = journal_.trace()) - { - auto const& available = offer.amount(); - - stream << " in:" << format_amount(available.in); - stream << " out:" << format_amount(available.out); - } - - offer.consume(view_, order); -} - -STAmount -Taker::get_funds(AccountID const& account, STAmount const& amount) const -{ - return accountFunds(view_, account, amount, fhZERO_IF_FROZEN, journal_); -} - -TER -Taker::transferXRP( - AccountID const& from, - AccountID const& to, - STAmount const& amount) -{ - if (!isXRP(amount)) - Throw("Using transferXRP with IOU"); - - if (from == to) - return tesSUCCESS; - - // Transferring zero is equivalent to not doing a transfer - if (amount == beast::zero) - return tesSUCCESS; - - return ripple::transferXRP(view_, from, to, amount, journal_); -} - -TER -Taker::redeemIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue) -{ - if (isXRP(amount)) - Throw("Using redeemIOU with XRP"); - - if (account == issue.account) - return tesSUCCESS; - - // Transferring zero is equivalent to not doing a transfer - if (amount == beast::zero) - return tesSUCCESS; - - // If we are trying to redeem some amount, then the account - // must have a credit balance. - if (get_funds(account, amount) <= beast::zero) - Throw("redeemIOU has no funds to redeem"); - - auto ret = ripple::redeemIOU(view_, account, amount, issue, journal_); - - if (get_funds(account, amount) < beast::zero) - Throw("redeemIOU redeemed more funds than available"); - - return ret; -} - -TER -Taker::issueIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue) -{ - if (isXRP(amount)) - Throw("Using issueIOU with XRP"); - - if (account == issue.account) - return tesSUCCESS; - - // Transferring zero is equivalent to not doing a transfer - if (amount == beast::zero) - return tesSUCCESS; - - return ripple::issueIOU(view_, account, amount, issue, journal_); -} - -// Performs funds transfers to fill the given offer and adjusts offer. -TER -Taker::fill(BasicTaker::Flow const& flow, Offer& offer) -{ - // adjust offer - consume_offer(offer, flow.order); - - TER result = tesSUCCESS; - - if (cross_type() != CrossType::XrpToIou) - { - XRPL_ASSERT( - !isXRP(flow.order.in), "ripple::Taker::fill : order in is not XRP"); - - if (result == tesSUCCESS) - result = - redeemIOU(account(), flow.issuers.in, flow.issuers.in.issue()); - - if (result == tesSUCCESS) - result = - issueIOU(offer.owner(), flow.order.in, flow.order.in.issue()); - } - else - { - XRPL_ASSERT( - isXRP(flow.order.in), "ripple::Taker::fill : order in is XRP"); - - if (result == tesSUCCESS) - result = transferXRP(account(), offer.owner(), flow.order.in); - } - - // Now send funds from the account whose offer we're taking - if (cross_type() != CrossType::IouToXrp) - { - XRPL_ASSERT( - !isXRP(flow.order.out), - "ripple::Taker::fill : order out is not XRP"); - - if (result == tesSUCCESS) - result = redeemIOU( - offer.owner(), flow.issuers.out, flow.issuers.out.issue()); - - if (result == tesSUCCESS) - result = - issueIOU(account(), flow.order.out, flow.order.out.issue()); - } - else - { - XRPL_ASSERT( - isXRP(flow.order.out), "ripple::Taker::fill : order out is XRP"); - - if (result == tesSUCCESS) - result = transferXRP(offer.owner(), account(), flow.order.out); - } - - if (result == tesSUCCESS) - direct_crossings_++; - - return result; -} - -// Performs bridged funds transfers to fill the given offers and adjusts offers. -TER -Taker::fill( - BasicTaker::Flow const& flow1, - Offer& leg1, - BasicTaker::Flow const& flow2, - Offer& leg2) -{ - // Adjust offers accordingly - consume_offer(leg1, flow1.order); - consume_offer(leg2, flow2.order); - - TER result = tesSUCCESS; - - // Taker to leg1: IOU - if (leg1.owner() != account()) - { - if (result == tesSUCCESS) - result = redeemIOU( - account(), flow1.issuers.in, flow1.issuers.in.issue()); - - if (result == tesSUCCESS) - result = - issueIOU(leg1.owner(), flow1.order.in, flow1.order.in.issue()); - } - - // leg1 to leg2: bridging over XRP - if (result == tesSUCCESS) - result = transferXRP(leg1.owner(), leg2.owner(), flow1.order.out); - - // leg2 to Taker: IOU - if (leg2.owner() != account()) - { - if (result == tesSUCCESS) - result = redeemIOU( - leg2.owner(), flow2.issuers.out, flow2.issuers.out.issue()); - - if (result == tesSUCCESS) - result = - issueIOU(account(), flow2.order.out, flow2.order.out.issue()); - } - - if (result == tesSUCCESS) - { - bridge_crossings_++; - xrp_flow_ += flow1.order.out; - } - - return result; -} - -TER -Taker::cross(Offer& offer) -{ - // In direct crossings, at least one leg must not be XRP. - if (isXRP(offer.amount().in) && isXRP(offer.amount().out)) - return tefINTERNAL; - - auto const amount = - do_cross(offer.amount(), offer.quality(), offer.owner()); - - return fill(amount, offer); -} - -TER -Taker::cross(Offer& leg1, Offer& leg2) -{ - // In bridged crossings, XRP must can't be the input to the first leg - // or the output of the second leg. - if (isXRP(leg1.amount().in) || isXRP(leg2.amount().out)) - return tefINTERNAL; - - auto ret = do_cross( - leg1.amount(), - leg1.quality(), - leg1.owner(), - leg2.amount(), - leg2.quality(), - leg2.owner()); - - return fill(ret.first, leg1, ret.second, leg2); -} - -Rate -Taker::calculateRate( - ApplyView const& view, - AccountID const& issuer, - AccountID const& account) -{ - return isXRP(issuer) || (account == issuer) ? parityRate - : transferRate(view, issuer); -} - -} // namespace ripple diff --git a/src/xrpld/app/tx/detail/Taker.h b/src/xrpld/app/tx/detail/Taker.h deleted file mode 100644 index 3702a30deb..0000000000 --- a/src/xrpld/app/tx/detail/Taker.h +++ /dev/null @@ -1,341 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2014 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_BOOK_TAKER_H_INCLUDED -#define RIPPLE_APP_BOOK_TAKER_H_INCLUDED - -#include -#include - -#include -#include -#include -#include -#include - -namespace ripple { - -/** The flavor of an offer crossing */ -enum class CrossType { XrpToIou, IouToXrp, IouToIou }; - -/** State for the active party during order book or payment operations. */ -class BasicTaker -{ -private: - AccountID account_; - Quality quality_; - Quality threshold_; - - bool sell_; - - // The original in and out quantities. - Amounts const original_; - - // The amounts still left over for us to try and take. - Amounts remaining_; - - // The issuers for the input and output - Issue const& issue_in_; - Issue const& issue_out_; - - // The rates that will be paid when the input and output currencies are - // transfered and the currency issuer isn't involved: - Rate const m_rate_in; - Rate const m_rate_out; - - // The type of crossing that we are performing - CrossType cross_type_; - -protected: - beast::Journal const journal_; - - struct Flow - { - explicit Flow() = default; - - Amounts order; - Amounts issuers; - - bool - sanity_check() const - { - using beast::zero; - - if (isXRP(order.in) && isXRP(order.out)) - return false; - - return order.in >= zero && order.out >= zero && - issuers.in >= zero && issuers.out >= zero; - } - }; - -private: - void - log_flow(char const* description, Flow const& flow); - - Flow - flow_xrp_to_iou( - Amounts const& offer, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_out); - - Flow - flow_iou_to_xrp( - Amounts const& offer, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_in); - - Flow - flow_iou_to_iou( - Amounts const& offer, - Quality quality, - STAmount const& owner_funds, - STAmount const& taker_funds, - Rate const& rate_in, - Rate const& rate_out); - - // Calculates the transfer rate that we should use when calculating - // flows for a particular issue between two accounts. - static Rate - effective_rate( - Rate const& rate, - Issue const& issue, - AccountID const& from, - AccountID const& to); - - // The transfer rate for the input currency between the given accounts - Rate - in_rate(AccountID const& from, AccountID const& to) const - { - return effective_rate(m_rate_in, original_.in.issue(), from, to); - } - - // The transfer rate for the output currency between the given accounts - Rate - out_rate(AccountID const& from, AccountID const& to) const - { - return effective_rate(m_rate_out, original_.out.issue(), from, to); - } - -public: - BasicTaker() = delete; - BasicTaker(BasicTaker const&) = delete; - - BasicTaker( - CrossType cross_type, - AccountID const& account, - Amounts const& amount, - Quality const& quality, - std::uint32_t flags, - Rate const& rate_in, - Rate const& rate_out, - beast::Journal journal = beast::Journal{beast::Journal::getNullSink()}); - - virtual ~BasicTaker() = default; - - /** Returns the amount remaining on the offer. - This is the amount at which the offer should be placed. It may either - be for the full amount when there were no crossing offers, or for zero - when the offer fully crossed, or any amount in between. - It is always at the original offer quality (quality_) - */ - Amounts - remaining_offer() const; - - /** Returns the amount that the offer was originally placed at. */ - Amounts const& - original_offer() const; - - /** Returns the account identifier of the taker. */ - AccountID const& - account() const noexcept - { - return account_; - } - - /** Returns `true` if the quality does not meet the taker's requirements. */ - bool - reject(Quality const& quality) const noexcept - { - return quality < threshold_; - } - - /** Returns the type of crossing that is being performed */ - CrossType - cross_type() const - { - return cross_type_; - } - - /** Returns the Issue associated with the input of the offer */ - Issue const& - issue_in() const - { - return issue_in_; - } - - /** Returns the Issue associated with the output of the offer */ - Issue const& - issue_out() const - { - return issue_out_; - } - - /** Returns `true` if the taker has run out of funds. */ - bool - unfunded() const; - - /** Returns `true` if order crossing should not continue. - Order processing is stopped if the taker's order quantities have - been reached, or if the taker has run out of input funds. - */ - bool - done() const; - - /** Perform direct crossing through given offer. - @return an `Amounts` describing the flow achieved during cross - */ - BasicTaker::Flow - do_cross(Amounts offer, Quality quality, AccountID const& owner); - - /** Perform bridged crossing through given offers. - @return a pair of `Amounts` describing the flow achieved during cross - */ - std::pair - do_cross( - Amounts offer1, - Quality quality1, - AccountID const& owner1, - Amounts offer2, - Quality quality2, - AccountID const& owner2); - - virtual STAmount - get_funds(AccountID const& account, STAmount const& funds) const = 0; -}; - -//------------------------------------------------------------------------------ - -class Taker : public BasicTaker -{ -public: - Taker() = delete; - Taker(Taker const&) = delete; - - Taker( - CrossType cross_type, - ApplyView& view, - AccountID const& account, - Amounts const& offer, - std::uint32_t flags, - beast::Journal journal); - ~Taker() = default; - - void - consume_offer(Offer& offer, Amounts const& order); - - STAmount - get_funds(AccountID const& account, STAmount const& funds) const override; - - STAmount const& - get_xrp_flow() const - { - return xrp_flow_; - } - - std::uint32_t - get_direct_crossings() const - { - return direct_crossings_; - } - - std::uint32_t - get_bridge_crossings() const - { - return bridge_crossings_; - } - - /** Perform a direct or bridged offer crossing as appropriate. - Funds will be transferred accordingly, and offers will be adjusted. - @return tesSUCCESS if successful, or an error code otherwise. - */ - /** @{ */ - TER - cross(Offer& offer); - - TER - cross(Offer& leg1, Offer& leg2); - /** @} */ - -private: - static Rate - calculateRate( - ApplyView const& view, - AccountID const& issuer, - AccountID const& account); - - TER - fill(BasicTaker::Flow const& flow, Offer& offer); - - TER - fill( - BasicTaker::Flow const& flow1, - Offer& leg1, - BasicTaker::Flow const& flow2, - Offer& leg2); - - TER - transferXRP( - AccountID const& from, - AccountID const& to, - STAmount const& amount); - - TER - redeemIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue); - - TER - issueIOU( - AccountID const& account, - STAmount const& amount, - Issue const& issue); - -private: - // The underlying ledger entry we are dealing with - ApplyView& view_; - - // The amount of XRP that flowed if we were autobridging - STAmount xrp_flow_; - - // The number direct crossings that we performed - std::uint32_t direct_crossings_; - - // The number autobridged crossings that we performed - std::uint32_t bridge_crossings_; -}; - -} // namespace ripple - -#endif diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index 0efddb0ff7..db1fc3bbfe 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -210,12 +210,12 @@ VaultDeposit::doApply() auto sleMpt = view().read(keylet::mptoken(mptIssuanceID, account_)); if (!sleMpt) { - if (auto const err = MPTokenAuthorize::authorize( + if (auto const err = authorizeMPToken( view(), - ctx_.journal, - {.priorBalance = mPriorBalance, - .mptIssuanceID = mptIssuanceID->value(), - .account = account_}); + mPriorBalance, + mptIssuanceID->value(), + account_, + ctx_.journal); !isTesSuccess(err)) return err; } @@ -223,15 +223,15 @@ VaultDeposit::doApply() // If the vault is private, set the authorized flag for the vault owner if (vault->isFlag(tfVaultPrivate)) { - if (auto const err = MPTokenAuthorize::authorize( + if (auto const err = authorizeMPToken( view(), + mPriorBalance, // priorBalance + mptIssuanceID->value(), // mptIssuanceID + sleIssuance->at(sfIssuer), // account ctx_.journal, - { - .priorBalance = mPriorBalance, - .mptIssuanceID = mptIssuanceID->value(), - .account = sleIssuance->at(sfIssuer), - .holderID = account_, - }); + {}, // flags + account_ // holderID + ); !isTesSuccess(err)) return err; } diff --git a/src/xrpld/app/tx/detail/VaultWithdraw.cpp b/src/xrpld/app/tx/detail/VaultWithdraw.cpp index 7a8605cdbd..09a9fd14e1 100644 --- a/src/xrpld/app/tx/detail/VaultWithdraw.cpp +++ b/src/xrpld/app/tx/detail/VaultWithdraw.cpp @@ -52,9 +52,19 @@ VaultWithdraw::preflight(PreflightContext const& ctx) return temBAD_AMOUNT; if (auto const destination = ctx.tx[~sfDestination]; - destination && *destination == beast::zero) + destination.has_value()) { - JLOG(ctx.j.debug()) << "VaultWithdraw: zero/empty destination account."; + if (*destination == beast::zero) + { + JLOG(ctx.j.debug()) + << "VaultWithdraw: zero/empty destination account."; + return temMALFORMED; + } + } + else if (ctx.tx.isFieldPresent(sfDestinationTag)) + { + JLOG(ctx.j.debug()) << "VaultWithdraw: sfDestinationTag is set but " + "sfDestination is not"; return temMALFORMED; } @@ -123,33 +133,39 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) // Withdrawal to a 3rd party destination account is essentially a transfer, // via shares in the vault. Enforce all the usual asset transfer checks. + AuthType authType = AuthType::Legacy; if (account != dstAcct) { auto const sleDst = ctx.view.read(keylet::account(dstAcct)); if (sleDst == nullptr) return tecNO_DST; - if (sleDst->getFlags() & lsfRequireDestTag) + if (sleDst->isFlag(lsfRequireDestTag) && + !ctx.tx.isFieldPresent(sfDestinationTag)) return tecDST_TAG_NEEDED; // Cannot send without a tag - if (sleDst->getFlags() & lsfDepositAuth) + if (sleDst->isFlag(lsfDepositAuth)) { if (!ctx.view.exists(keylet::depositPreauth(dstAcct, account))) return tecNO_PERMISSION; } + // The destination account must have consented to receive the asset by + // creating a RippleState or MPToken + authType = AuthType::StrongAuth; } - // Destination MPToken must exist (if asset is an MPT) - if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct); + // Destination MPToken (for an MPT) or trust line (for an IOU) must exist + // if not sending to Account. + if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType); !isTesSuccess(ter)) return ter; // Cannot withdraw from a Vault an Asset frozen for the destination account - if (isFrozen(ctx.view, dstAcct, vaultAsset)) - return vaultAsset.holds() ? tecFROZEN : tecLOCKED; + if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset)) + return ret; - if (isFrozen(ctx.view, account, vaultShare)) - return tecLOCKED; + if (auto const ret = checkFrozen(ctx.view, account, vaultShare)) + return ret; return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp index 889a520032..e2e0adae45 100644 --- a/src/xrpld/app/tx/detail/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -27,11 +27,16 @@ namespace ripple { -// These are the same flags defined as SF_PRIVATE1-4 in HashRouter.h -#define SF_SIGBAD SF_PRIVATE1 // Signature is bad -#define SF_SIGGOOD SF_PRIVATE2 // Signature is good -#define SF_LOCALBAD SF_PRIVATE3 // Local checks failed -#define SF_LOCALGOOD SF_PRIVATE4 // Local checks passed +// These are the same flags defined as HashRouterFlags::PRIVATE1-4 in +// HashRouter.h +constexpr HashRouterFlags SF_SIGBAD = + HashRouterFlags::PRIVATE1; // Signature is bad +constexpr HashRouterFlags SF_SIGGOOD = + HashRouterFlags::PRIVATE2; // Signature is good +constexpr HashRouterFlags SF_LOCALBAD = + HashRouterFlags::PRIVATE3; // Local checks failed +constexpr HashRouterFlags SF_LOCALGOOD = + HashRouterFlags::PRIVATE4; // Local checks passed //------------------------------------------------------------------------------ @@ -66,11 +71,11 @@ checkValidity( return {Validity::Valid, ""}; } - if (flags & SF_SIGBAD) + if (any(flags & SF_SIGBAD)) // Signature is known bad return {Validity::SigBad, "Transaction has bad signature."}; - if (!(flags & SF_SIGGOOD)) + if (!any(flags & SF_SIGGOOD)) { // Don't know signature state. Check it. auto const requireCanonicalSig = @@ -88,12 +93,12 @@ checkValidity( } // Signature is now known good - if (flags & SF_LOCALBAD) + if (any(flags & SF_LOCALBAD)) // ...but the local checks // are known bad. return {Validity::SigGoodOnly, "Local checks failed."}; - if (flags & SF_LOCALGOOD) + if (any(flags & SF_LOCALGOOD)) // ...and the local checks // are known good. return {Validity::Valid, ""}; @@ -112,7 +117,7 @@ checkValidity( void forceValidity(HashRouter& router, uint256 const& txid, Validity validity) { - int flags = 0; + HashRouterFlags flags = HashRouterFlags::UNDEFINED; switch (validity) { case Validity::Valid: @@ -125,7 +130,7 @@ forceValidity(HashRouter& router, uint256 const& txid, Validity validity) // would be silly to call directly break; } - if (flags) + if (any(flags)) router.setFlags(txid, flags); } diff --git a/src/xrpld/core/JobQueue.h b/src/xrpld/core/JobQueue.h index 051c298251..eda956c019 100644 --- a/src/xrpld/core/JobQueue.h +++ b/src/xrpld/core/JobQueue.h @@ -30,6 +30,8 @@ #include +#include + namespace ripple { namespace perf { diff --git a/src/xrpld/ledger/PaymentSandbox.h b/src/xrpld/ledger/PaymentSandbox.h index a41a0211a2..2cd31ea490 100644 --- a/src/xrpld/ledger/PaymentSandbox.h +++ b/src/xrpld/ledger/PaymentSandbox.h @@ -27,7 +27,6 @@ #include #include -#include namespace ripple { diff --git a/src/xrpld/ledger/View.h b/src/xrpld/ledger/View.h index 8c391499b6..07f6945dd4 100644 --- a/src/xrpld/ledger/View.h +++ b/src/xrpld/ledger/View.h @@ -175,6 +175,29 @@ isFrozen( asset.value()); } +[[nodiscard]] inline TER +checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue) +{ + return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS; +} + +[[nodiscard]] inline TER +checkFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mptIssue) +{ + return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS; +} + +[[nodiscard]] inline TER +checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset) +{ + return std::visit( + [&](auto const& issue) { return checkFrozen(view, account, issue); }, + asset.value()); +} + [[nodiscard]] bool isAnyFrozen( ReadView const& view, @@ -577,6 +600,16 @@ addEmptyHolding( asset.value()); } +[[nodiscard]] TER +authorizeMPToken( + ApplyView& view, + XRPAmount const& priorBalance, + MPTID const& mptIssuanceID, + AccountID const& account, + beast::Journal journal, + std::uint32_t flags = 0, + std::optional holderID = std::nullopt); + // VFALCO NOTE Both STAmount parameters should just // be "Amount", a unit-less number. // @@ -725,19 +758,40 @@ transferXRP( STAmount const& amount, beast::Journal j); -/* Check if MPToken exists: - * - StrongAuth - before checking lsfMPTRequireAuth is set - * - WeakAuth - after checking if lsfMPTRequireAuth is set +/* Check if MPToken (for MPT) or trust line (for IOU) exists: + * - StrongAuth - before checking if authorization is required + * - WeakAuth + * for MPT - after checking lsfMPTRequireAuth flag + * for IOU - do not check if trust line exists + * - Legacy + * for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth + * for IOU - do not check if trust line exists i.e. same as WeakAuth */ -enum class MPTAuthType : bool { StrongAuth = true, WeakAuth = false }; +enum class AuthType { StrongAuth, WeakAuth, Legacy }; /** Check if the account lacks required authorization. * - * Return tecNO_AUTH or tecNO_LINE if it does - * and tesSUCCESS otherwise. + * Return tecNO_AUTH or tecNO_LINE if it does + * and tesSUCCESS otherwise. + * + * If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return + * tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the + * RippleState does exist, and the RippleState is not authorized. + * + * If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the + * RippleState exists, and is not authorized. Return tecNO_LINE if + * lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if + * WeakAuth and lsfRequireAuth is *not* set, this function will return + * tesSUCCESS even if RippleState does *not* exist. + * + * The default "Legacy" auth type is equivalent to WeakAuth. */ [[nodiscard]] TER -requireAuth(ReadView const& view, Issue const& issue, AccountID const& account); +requireAuth( + ReadView const& view, + Issue const& issue, + AccountID const& account, + AuthType authType = AuthType::Legacy); /** Check if the account lacks required authorization. * @@ -751,32 +805,33 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account); * purely defensive, as we currently do not allow such vaults to be created. * * If StrongAuth then return tecNO_AUTH if MPToken doesn't exist or - * lsfMPTRequireAuth is set and MPToken is not authorized. If WeakAuth then - * return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken doesn't exist or is - * not authorized (explicitly or via credentials, if DomainID is set in - * MPTokenIssuance). Consequently, if WeakAuth and lsfMPTRequireAuth is *not* - * set, this function will return true even if MPToken does *not* exist. + * lsfMPTRequireAuth is set and MPToken is not authorized. + * + * If WeakAuth then return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken + * doesn't exist or is not authorized (explicitly or via credentials, if + * DomainID is set in MPTokenIssuance). Consequently, if WeakAuth and + * lsfMPTRequireAuth is *not* set, this function will return true even if + * MPToken does *not* exist. + * + * The default "Legacy" auth type is equivalent to StrongAuth. */ [[nodiscard]] TER requireAuth( ReadView const& view, MPTIssue const& mptIssue, AccountID const& account, - MPTAuthType authType = MPTAuthType::StrongAuth, + AuthType authType = AuthType::Legacy, int depth = 0); [[nodiscard]] TER inline requireAuth( ReadView const& view, Asset const& asset, AccountID const& account, - MPTAuthType authType = MPTAuthType::StrongAuth) + AuthType authType = AuthType::Legacy) { return std::visit( [&](TIss const& issue_) { - if constexpr (std::is_same_v) - return requireAuth(view, issue_, account); - else - return requireAuth(view, issue_, account, authType); + return requireAuth(view, issue_, account, authType); }, asset.value()); } diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index cb95819014..7c6e1d60f1 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -18,7 +18,6 @@ //============================================================================== #include -#include #include #include @@ -505,8 +504,8 @@ accountHolds( if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED && view.rules().enabled(featureSingleAssetVault)) { - if (auto const err = requireAuth( - view, mptIssue, account, MPTAuthType::StrongAuth); + if (auto const err = + requireAuth(view, mptIssue, account, AuthType::StrongAuth); !isTesSuccess(err)) amount.clear(mptIssue); } @@ -1215,12 +1214,115 @@ addEmptyHolding( if (view.peek(keylet::mptoken(mptID, accountID))) return tecDUPLICATE; - return MPTokenAuthorize::authorize( - view, - journal, - {.priorBalance = priorBalance, - .mptIssuanceID = mptID, - .account = accountID}); + return authorizeMPToken(view, priorBalance, mptID, accountID, journal); +} + +[[nodiscard]] TER +authorizeMPToken( + ApplyView& view, + XRPAmount const& priorBalance, + MPTID const& mptIssuanceID, + AccountID const& account, + beast::Journal journal, + std::uint32_t flags, + std::optional holderID) +{ + auto const sleAcct = view.peek(keylet::account(account)); + if (!sleAcct) + return tecINTERNAL; + + // If the account that submitted the tx is a holder + // Note: `account_` is holder's account + // `holderID` is NOT used + if (!holderID) + { + // When a holder wants to unauthorize/delete a MPT, the ledger must + // - delete mptokenKey from owner directory + // - delete the MPToken + if (flags & tfMPTUnauthorize) + { + auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); + auto const sleMpt = view.peek(mptokenKey); + if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (!view.dirRemove( + keylet::ownerDir(account), + (*sleMpt)[sfOwnerNode], + sleMpt->key(), + false)) + return tecINTERNAL; // LCOV_EXCL_LINE + + adjustOwnerCount(view, sleAcct, -1, journal); + + view.erase(sleMpt); + return tesSUCCESS; + } + + // A potential holder wants to authorize/hold a mpt, the ledger must: + // - add the new mptokenKey to the owner directory + // - create the MPToken object for the holder + + // The reserve that is required to create the MPToken. Note + // that although the reserve increases with every item + // an account owns, in the case of MPTokens we only + // *enforce* a reserve if the user owns more than two + // items. This is similar to the reserve requirements of trust lines. + std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount); + XRPAmount const reserveCreate( + (uOwnerCount < 2) ? XRPAmount(beast::zero) + : view.fees().accountReserve(uOwnerCount + 1)); + + if (priorBalance < reserveCreate) + return tecINSUFFICIENT_RESERVE; + + auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); + auto mptoken = std::make_shared(mptokenKey); + if (auto ter = dirLink(view, account, mptoken)) + return ter; // LCOV_EXCL_LINE + + (*mptoken)[sfAccount] = account; + (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID; + (*mptoken)[sfFlags] = 0; + view.insert(mptoken); + + // Update owner count. + adjustOwnerCount(view, sleAcct, 1, journal); + + return tesSUCCESS; + } + + auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID)); + if (!sleMptIssuance) + return tecINTERNAL; + + // If the account that submitted this tx is the issuer of the MPT + // Note: `account_` is issuer's account + // `holderID` is holder's account + if (account != (*sleMptIssuance)[sfIssuer]) + return tecINTERNAL; + + auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID)); + if (!sleMpt) + return tecINTERNAL; + + std::uint32_t const flagsIn = sleMpt->getFieldU32(sfFlags); + std::uint32_t flagsOut = flagsIn; + + // Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on + // their MPToken + if (flags & tfMPTUnauthorize) + flagsOut &= ~lsfMPTAuthorized; + // Issuer wants to authorize a holder, set lsfMPTAuthorized on their + // MPToken + else + flagsOut |= lsfMPTAuthorized; + + if (flagsIn != flagsOut) + sleMpt->setFieldU32(sfFlags, flagsOut); + + view.update(sleMpt); + return tesSUCCESS; } TER @@ -1418,13 +1520,14 @@ removeEmptyHolding( if (mptoken->at(sfMPTAmount) != 0) return tecHAS_OBLIGATIONS; - return MPTokenAuthorize::authorize( + return authorizeMPToken( view, + {}, // priorBalance + mptID, + accountID, journal, - {.priorBalance = {}, - .mptIssuanceID = mptID, - .account = accountID, - .flags = tfMPTUnauthorize}); + tfMPTUnauthorize // flags + ); } TER @@ -2298,15 +2401,27 @@ transferXRP( } TER -requireAuth(ReadView const& view, Issue const& issue, AccountID const& account) +requireAuth( + ReadView const& view, + Issue const& issue, + AccountID const& account, + AuthType authType) { if (isXRP(issue) || issue.account == account) return tesSUCCESS; + + auto const trustLine = + view.read(keylet::line(account, issue.account, issue.currency)); + // If account has no line, and this is a strong check, fail + if (!trustLine && authType == AuthType::StrongAuth) + return tecNO_LINE; + + // If this is a weak or legacy check, or if the account has a line, fail if + // auth is required and not set on the line if (auto const issuerAccount = view.read(keylet::account(issue.account)); issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth) { - if (auto const trustLine = - view.read(keylet::line(account, issue.account, issue.currency))) + if (trustLine) return ((*trustLine)[sfFlags] & ((account > issue.account) ? lsfLowAuth : lsfHighAuth)) ? tesSUCCESS @@ -2322,7 +2437,7 @@ requireAuth( ReadView const& view, MPTIssue const& mptIssue, AccountID const& account, - MPTAuthType authType, + AuthType authType, int depth) { auto const mptID = keylet::mptIssuance(mptIssue.getMptID()); @@ -2357,7 +2472,7 @@ requireAuth( if (auto const err = std::visit( [&](TIss const& issue) { if constexpr (std::is_same_v) - return requireAuth(view, issue, account); + return requireAuth(view, issue, account, authType); else return requireAuth( view, issue, account, authType, depth + 1); @@ -2372,7 +2487,8 @@ requireAuth( auto const sleToken = view.read(mptokenID); // if account has no MPToken, fail - if (!sleToken && authType == MPTAuthType::StrongAuth) + if (!sleToken && + (authType == AuthType::StrongAuth || authType == AuthType::Legacy)) return tecNO_AUTH; // Note, this check is not amendment-gated because DomainID will be always @@ -2484,15 +2600,12 @@ enforceMPTokenAuthorization( XRPL_ASSERT( maybeDomainID.has_value() && sleToken == nullptr, "ripple::enforceMPTokenAuthorization : new MPToken for domain"); - if (auto const err = MPTokenAuthorize::authorize( + if (auto const err = authorizeMPToken( view, - j, - { - .priorBalance = priorBalance, - .mptIssuanceID = mptIssuanceID, - .account = account, - .flags = 0, - }); + priorBalance, // priorBalance + mptIssuanceID, // mptIssuanceID + account, // account + j); !isTesSuccess(err)) return err; diff --git a/src/xrpld/overlay/Slot.h b/src/xrpld/overlay/Slot.h index 0956eb06f7..ea9fc3285b 100644 --- a/src/xrpld/overlay/Slot.h +++ b/src/xrpld/overlay/Slot.h @@ -446,6 +446,8 @@ Slot::deletePeer(PublicKey const& validator, id_t id, bool erase) auto it = peers_.find(id); if (it != peers_.end()) { + std::vector toUnsquelch; + JLOG(journal_.trace()) << "deletePeer: " << Slice(validator) << " " << id << " selected " << (it->second.state == PeerState::Selected) << " considered " @@ -457,7 +459,7 @@ Slot::deletePeer(PublicKey const& validator, id_t id, bool erase) for (auto& [k, v] : peers_) { if (v.state == PeerState::Squelched) - handler_.unsquelch(validator, k); + toUnsquelch.push_back(k); v.state = PeerState::Counting; v.count = 0; v.expire = now; @@ -479,6 +481,10 @@ Slot::deletePeer(PublicKey const& validator, id_t id, bool erase) if (erase) peers_.erase(it); + + // Must be after peers_.erase(it) + for (auto const& k : toUnsquelch) + handler_.unsquelch(validator, k); } } diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index 3cc5b2a024..53b4cad646 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -1423,7 +1423,12 @@ OverlayImpl::updateSlotAndSquelch( if (!strand_.running_in_this_thread()) return post( strand_, - [this, key, validator, peers = std::move(peers), type]() mutable { + // Must capture copies of reference parameters (i.e. key, validator) + [this, + key = key, + validator = validator, + peers = std::move(peers), + type]() mutable { updateSlotAndSquelch(key, validator, std::move(peers), type); }); @@ -1444,9 +1449,12 @@ OverlayImpl::updateSlotAndSquelch( return; if (!strand_.running_in_this_thread()) - return post(strand_, [this, key, validator, peer, type]() { - updateSlotAndSquelch(key, validator, peer, type); - }); + return post( + strand_, + // Must capture copies of reference parameters (i.e. key, validator) + [this, key = key, validator = validator, peer, type]() { + updateSlotAndSquelch(key, validator, peer, type); + }); slots_.updateSlotAndSquelch(key, validator, peer, type, [&]() { reportInboundTraffic(TrafficCount::squelch_ignored, 0); diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 1238833d0d..23b4760488 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1296,13 +1296,13 @@ PeerImp::handleTransaction( } // LCOV_EXCL_STOP - int flags; + HashRouterFlags flags; constexpr std::chrono::seconds tx_interval = 10s; if (!app_.getHashRouter().shouldProcess(txID, id_, flags, tx_interval)) { // we have seen this transaction recently - if (flags & SF_BAD) + if (any(flags & HashRouterFlags::BAD)) { fee_.update(Resource::feeUselessData, "known bad"); JLOG(p_journal_.debug()) << "Ignoring known bad tx " << txID; @@ -1329,7 +1329,7 @@ PeerImp::handleTransaction( { // Skip local checks if a server we trust // put the transaction in its open ledger - flags |= SF_TRUSTED; + flags |= HashRouterFlags::TRUSTED; } // for non-validator nodes only -- localPublicKey is set for @@ -2841,7 +2841,7 @@ PeerImp::doTransactions( void PeerImp::checkTransaction( - int flags, + HashRouterFlags flags, bool checkSignature, std::shared_ptr const& stx, bool batch) @@ -2866,7 +2866,8 @@ PeerImp::checkTransaction( (stx->getFieldU32(sfLastLedgerSequence) < app_.getLedgerMaster().getValidLedgerIndex())) { - app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD); + app_.getHashRouter().setFlags( + stx->getTransactionID(), HashRouterFlags::BAD); charge(Resource::feeUselessData, "expired tx"); return; } @@ -2925,8 +2926,10 @@ PeerImp::checkTransaction( << "Exception checking transaction: " << validReason; } - // Probably not necessary to set SF_BAD, but doesn't hurt. - app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD); + // Probably not necessary to set HashRouterFlags::BAD, but + // doesn't hurt. + app_.getHashRouter().setFlags( + stx->getTransactionID(), HashRouterFlags::BAD); charge( Resource::feeInvalidSignature, "check transaction signature failure"); @@ -2949,12 +2952,13 @@ PeerImp::checkTransaction( JLOG(p_journal_.trace()) << "Exception checking transaction: " << reason; } - app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD); + app_.getHashRouter().setFlags( + stx->getTransactionID(), HashRouterFlags::BAD); charge(Resource::feeInvalidSignature, "tx (impossible)"); return; } - bool const trusted(flags & SF_TRUSTED); + bool const trusted = any(flags & HashRouterFlags::TRUSTED); app_.getOPs().processTransaction( tx, trusted, false, NetworkOPs::FailHard::no); } @@ -2962,7 +2966,8 @@ PeerImp::checkTransaction( { JLOG(p_journal_.warn()) << "Exception in " << __func__ << ": " << ex.what(); - app_.getHashRouter().setFlags(stx->getTransactionID(), SF_BAD); + app_.getHashRouter().setFlags( + stx->getTransactionID(), HashRouterFlags::BAD); using namespace std::string_literals; charge(Resource::feeInvalidData, "tx "s + ex.what()); } diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index ecd3fc7f63..5aa49fd152 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -98,7 +99,7 @@ private: // Node public key of peer. PublicKey const publicKey_; std::string name_; - boost::shared_mutex mutable nameMutex_; + std::shared_mutex mutable nameMutex_; // The indices of the smallest and largest ledgers this peer has available // @@ -214,7 +215,7 @@ private: total_bytes() const; private: - boost::shared_mutex mutable mutex_; + std::shared_mutex mutable mutex_; boost::circular_buffer rollingAvg_{30, 0ull}; clock_type::time_point intervalStart_{clock_type::now()}; std::uint64_t totalBytes_{0}; @@ -612,7 +613,7 @@ private: void checkTransaction( - int flags, + HashRouterFlags flags, bool checkSignature, std::shared_ptr const& stx, bool batch); diff --git a/src/xrpld/rpc/handlers/AccountInfo.cpp b/src/xrpld/rpc/handlers/AccountInfo.cpp index 6416309e2e..3432021690 100644 --- a/src/xrpld/rpc/handlers/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/AccountInfo.cpp @@ -108,6 +108,10 @@ doAccountInfo(RPC::JsonContext& context) allowTrustLineClawbackFlag{ "allowTrustLineClawback", lsfAllowTrustLineClawback}; + static constexpr std::pair + allowTrustLineLockingFlag{ + "allowTrustLineLocking", lsfAllowTrustLineLocking}; + auto const sleAccepted = ledger->read(keylet::account(accountID)); if (sleAccepted) { @@ -140,6 +144,10 @@ doAccountInfo(RPC::JsonContext& context) acctFlags[allowTrustLineClawbackFlag.first.data()] = sleAccepted->isFlag(allowTrustLineClawbackFlag.second); + if (ledger->rules().enabled(featureTokenEscrow)) + acctFlags[allowTrustLineLockingFlag.first.data()] = + sleAccepted->isFlag(allowTrustLineLockingFlag.second); + result[jss::account_flags] = std::move(acctFlags); // The document[https://xrpl.org/account_info.html#account_info] states diff --git a/src/xrpld/rpc/handlers/GetCounts.cpp b/src/xrpld/rpc/handlers/GetCounts.cpp index 3c1d8cccdd..2987da46d5 100644 --- a/src/xrpld/rpc/handlers/GetCounts.cpp +++ b/src/xrpld/rpc/handlers/GetCounts.cpp @@ -114,7 +114,7 @@ getCountsJson(Application& app, int minObjectCount) ret[jss::treenode_cache_size] = app.getNodeFamily().getTreeNodeCache()->getCacheSize(); ret[jss::treenode_track_size] = - app.getNodeFamily().getTreeNodeCache()->getTrackSize(); + static_cast(app.getNodeFamily().getTreeNodeCache()->size()); std::string uptime; auto s = UptimeClock::now(); diff --git a/src/xrpld/rpc/handlers/LedgerHandler.cpp b/src/xrpld/rpc/handlers/LedgerHandler.cpp index 4015bb9fcc..8987f2d07e 100644 --- a/src/xrpld/rpc/handlers/LedgerHandler.cpp +++ b/src/xrpld/rpc/handlers/LedgerHandler.cpp @@ -54,10 +54,6 @@ LedgerHandler::check() bool const binary = params[jss::binary].asBool(); bool const owner_funds = params[jss::owner_funds].asBool(); bool const queue = params[jss::queue].asBool(); - auto type = chooseLedgerEntryType(params); - if (type.first) - return type.first; - type_ = type.second; options_ = (full ? LedgerFill::full : 0) | (expand ? LedgerFill::expand : 0) | diff --git a/src/xrpld/rpc/handlers/LedgerHandler.h b/src/xrpld/rpc/handlers/LedgerHandler.h index 0e47164ad3..a573589cbc 100644 --- a/src/xrpld/rpc/handlers/LedgerHandler.h +++ b/src/xrpld/rpc/handlers/LedgerHandler.h @@ -76,7 +76,6 @@ private: std::vector queueTxs_; Json::Value result_; int options_ = 0; - LedgerEntryType type_; }; //////////////////////////////////////////////////////////////////////////////// @@ -91,7 +90,7 @@ LedgerHandler::writeResult(Object& value) if (ledger_) { Json::copyFrom(value, result_); - addJson(value, {*ledger_, &context_, options_, queueTxs_, type_}); + addJson(value, {*ledger_, &context_, options_, queueTxs_}); } else { @@ -105,6 +104,21 @@ LedgerHandler::writeResult(Object& value) addJson(open, {*master.getCurrentLedger(), &context_, 0}); } } + + Json::Value warnings{Json::arrayValue}; + if (context_.params.isMember(jss::type)) + { + Json::Value& w = warnings.append(Json::objectValue); + w[jss::id] = warnRPC_FIELDS_DEPRECATED; + w[jss::message] = + "Some fields from your request are deprecated. Please check the " + "documentation at " + "https://xrpl.org/docs/references/http-websocket-apis/ " + "and update your request. Field `type` is deprecated."; + } + + if (warnings.size()) + value[jss::warnings] = std::move(warnings); } } // namespace RPC diff --git a/src/xrpld/rpc/handlers/LogLevel.cpp b/src/xrpld/rpc/handlers/LogLevel.cpp index 0fc266569e..a93d010706 100644 --- a/src/xrpld/rpc/handlers/LogLevel.cpp +++ b/src/xrpld/rpc/handlers/LogLevel.cpp @@ -44,7 +44,6 @@ doLogLevel(RPC::JsonContext& context) Logs::toString(Logs::fromSeverity(context.app.logs().threshold())); std::vector> logTable( context.app.logs().partition_severities()); - using stringPair = std::map::value_type; for (auto const& [k, v] : logTable) lev[k] = v; diff --git a/src/xrpld/shamap/SHAMap.h b/src/xrpld/shamap/SHAMap.h index 33c42c2d23..738cf96ecc 100644 --- a/src/xrpld/shamap/SHAMap.h +++ b/src/xrpld/shamap/SHAMap.h @@ -36,6 +36,7 @@ #include #include +#include #include #include diff --git a/tests/conan/CMakeLists.txt b/tests/conan/CMakeLists.txt index 83aa24880d..f1b37e7a69 100644 --- a/tests/conan/CMakeLists.txt +++ b/tests/conan/CMakeLists.txt @@ -9,7 +9,7 @@ project( LANGUAGES CXX ) -find_package(xrpl REQUIRED) +find_package(xrpl CONFIG REQUIRED) add_executable(example) target_sources(example PRIVATE src/example.cpp) diff --git a/tests/conan/conanfile.py b/tests/conan/conanfile.py index be3750bf9e..1ea1b333fc 100644 --- a/tests/conan/conanfile.py +++ b/tests/conan/conanfile.py @@ -1,59 +1,42 @@ -from conan import ConanFile, conan_version +from pathlib import Path + +from conan import ConanFile +from conan.tools.build import can_run from conan.tools.cmake import CMake, cmake_layout class Example(ConanFile): - def set_name(self): - if self.name is None: - self.name = 'example' + name = 'example' + license = 'ISC' + author = 'John Freeman , Michael Legleux