diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index 55df4c2672..e17bd14bbc 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -138,6 +138,7 @@ test.toplevel > test.csf test.toplevel > xrpl.json test.unit_test > xrpl.basics tests.libxrpl > xrpl.basics +tests.libxrpl > xrpl.json tests.libxrpl > xrpl.net xrpl.json > xrpl.basics xrpl.ledger > xrpl.basics diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index c1f6839d2d..9df6417c07 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -50,7 +50,12 @@ on: workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + # When a PR is merged into the develop branch it will be assigned a unique + # group identifier, so execution will continue even if another PR is merged + # while it is still running. In all other cases the group identifier is shared + # per branch, so that any in-progress runs are cancelled when a new commit is + # pushed. + group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }} cancel-in-progress: true defaults: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 66ee2f3334..d0a657dd7e 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -9,7 +9,7 @@ on: jobs: # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: - uses: XRPLF/actions/.github/workflows/pre-commit.yml@a8d7472b450eb53a1e5228f64552e5974457a21a + uses: XRPLF/actions/.github/workflows/pre-commit.yml@34790936fae4c6c751f62ec8c06696f9c1a5753a with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-a8c7be1" }' diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 24650858f9..8aa84608d4 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -101,16 +101,32 @@ jobs: --parallel $(nproc) \ --target "${CMAKE_TARGET}" + - name: Put built binaries in one location + shell: bash + working-directory: ${{ inputs.build_dir }} + env: + BUILD_TYPE_DIR: ${{ runner.os == 'Windows' && inputs.build_type || '' }} + CMAKE_TARGET: ${{ inputs.cmake_target }} + run: | + mkdir -p ./binaries/doctest/ + + cp ./${BUILD_TYPE_DIR}/rippled* ./binaries/ + if [ "${CMAKE_TARGET}" != 'coverage' ]; then + cp ./src/tests/libxrpl/${BUILD_TYPE_DIR}/xrpl.test.* ./binaries/doctest/ + fi + - name: Upload rippled artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + env: + BUILD_DIR: ${{ inputs.build_dir }} with: name: rippled-${{ inputs.config_name }} - path: ${{ inputs.build_dir }}/${{ runner.os == 'Windows' && inputs.build_type || '' }}/rippled${{ runner.os == 'Windows' && '.exe' || '' }} + path: ${{ env.BUILD_DIR }}/binaries/ retention-days: 3 if-no-files-found: error - name: Upload coverage report - if: ${{ inputs.cmake_target == 'coverage' }} + if: ${{ github.repository_owner == 'XRPLF' && inputs.cmake_target == 'coverage' }} uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: disable_search: true diff --git a/.github/workflows/reusable-test.yml b/.github/workflows/reusable-test.yml index eb6a0271a4..4b9c75334a 100644 --- a/.github/workflows/reusable-test.yml +++ b/.github/workflows/reusable-test.yml @@ -33,6 +33,10 @@ jobs: container: ${{ inputs.image != '' && inputs.image || null }} timeout-minutes: 30 steps: + - name: Cleanup workspace + if: ${{ runner.os == 'macOS' }} + uses: XRPLF/actions/.github/actions/cleanup-workspace@3f044c7478548e3c32ff68980eeb36ece02b364e + - name: Download rippled artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: @@ -62,9 +66,24 @@ jobs: run: | ./rippled --version | grep libvoidstar - - name: Test the binary + - name: Run the embedded tests if: ${{ inputs.run_tests }} shell: bash run: | ./rippled --unittest --unittest-jobs $(nproc) - ctest -j $(nproc) --output-on-failure + + - name: Run the separate tests + if: ${{ inputs.run_tests }} + env: + EXT: ${{ runner.os == 'Windows' && '.exe' || '' }} + shell: bash + run: | + for test_file in ./doctest/*${EXT}; do + echo "Executing $test_file" + chmod +x "$test_file" + if [[ "${{ runner.os }}" == "Windows" && "$test_file" == "./doctest/xrpl.test.net.exe" ]]; then + echo "Skipping $test_file on Windows" + else + "$test_file" + fi + done diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 0bea36cea7..538280676b 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -84,11 +84,11 @@ jobs: verbosity: ${{ runner.os == 'Windows' && 'quiet' || 'verbose' }} - name: Log into Conan remote - if: ${{ github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' }} + if: ${{ github.repository_owner == 'XRPLF' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }} run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.CONAN_REMOTE_USERNAME }}" --password "${{ secrets.CONAN_REMOTE_PASSWORD }}" - name: Upload Conan packages - if: ${{ github.repository_owner == 'XRPLF' && github.event_name != 'pull_request' && github.event_name != 'schedule' }} + if: ${{ github.repository_owner == 'XRPLF' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }} env: FORCE_OPTION: ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }} run: conan upload "*" --remote="${CONAN_REMOTE_NAME}" --confirm ${FORCE_OPTION} diff --git a/cmake/xrpl_add_test.cmake b/cmake/xrpl_add_test.cmake index d61f4ece3d..a1dd3847bd 100644 --- a/cmake/xrpl_add_test.cmake +++ b/cmake/xrpl_add_test.cmake @@ -7,7 +7,7 @@ function(xrpl_add_test name) "${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp" ) - add_executable(${target} EXCLUDE_FROM_ALL ${ARGN} ${sources}) + add_executable(${target} ${ARGN} ${sources}) isolate_headers( ${target} diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index bea5c0bc90..a8fe35d24d 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -38,7 +38,7 @@ XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo) +XRPL_FIX (MPTDeliveredAmount, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (AMMClawbackRounding, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo) @@ -112,7 +112,6 @@ XRPL_FEATURE(MultiSignReserve, Supported::yes, VoteBehavior::DefaultYe XRPL_FIX (1578, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(DepositPreauth, Supported::yes, VoteBehavior::DefaultYes) XRPL_FIX (1623, Supported::yes, VoteBehavior::DefaultYes) -XRPL_FIX (1543, Supported::yes, VoteBehavior::DefaultYes) XRPL_FIX (1571, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(DepositAuth, Supported::yes, VoteBehavior::DefaultYes) @@ -156,5 +155,6 @@ XRPL_RETIRE(fix1528) XRPL_RETIRE(FlowCross) XRPL_RETIRE(fix1513) XRPL_RETIRE(fix1515) +XRPL_RETIRE(fix1543) // clang-format on diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index d2b716ef04..d13202893b 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -2172,7 +2172,7 @@ class Invariants_test : public beast::unit_test::suite AccountID account; int amount; }; - struct Adjustements + struct Adjustments { std::optional assetsTotal = {}; std::optional assetsAvailable = {}; @@ -2185,7 +2185,7 @@ class Invariants_test : public beast::unit_test::suite }; auto constexpr adjust = [&](ApplyView& ac, ripple::Keylet keylet, - Adjustements args) { + Adjustments args) { auto sleVault = ac.peek(keylet); if (!sleVault) return false; @@ -2211,9 +2211,11 @@ class Invariants_test : public beast::unit_test::suite ac.update(sleVault); if (args.sharesTotal) + { (*sleShares)[sfOutstandingAmount] = *(*sleShares)[sfOutstandingAmount] + *args.sharesTotal; - ac.update(sleShares); + ac.update(sleShares); + } auto const assets = *(*sleVault)[sfAsset]; auto const pseudoId = *(*sleVault)[sfAccount]; @@ -2284,17 +2286,17 @@ class Invariants_test : public beast::unit_test::suite }; constexpr auto args = - [](AccountID id, int adjustement, auto fn) -> Adjustements { - Adjustements sample = { - .assetsTotal = adjustement, - .assetsAvailable = adjustement, + [](AccountID id, int adjustment, auto fn) -> Adjustments { + Adjustments sample = { + .assetsTotal = adjustment, + .assetsAvailable = adjustment, .lossUnrealized = 0, - .sharesTotal = adjustement, - .vaultAssets = adjustement, + .sharesTotal = adjustment, + .vaultAssets = adjustment, .accountAssets = // - AccountAmount{id, -adjustement}, + AccountAmount{id, -adjustment}, .accountShares = // - AccountAmount{id, adjustement}}; + AccountAmount{id, adjustment}}; fn(sample); return sample; }; @@ -2706,7 +2708,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) { + args(A2.id(), 0, [&](Adjustments& sample) { sample.assetsAvailable = (DROPS_PER_XRP * -100).value(); sample.assetsTotal = (DROPS_PER_XRP * -200).value(); sample.sharesTotal = -1; @@ -2775,7 +2777,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) { + args(A2.id(), 0, [&](Adjustments& sample) { sample.lossUnrealized = 13; sample.assetsTotal = 20; })); @@ -2795,7 +2797,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 100, [&](Adjustements& sample) { + args(A2.id(), 100, [&](Adjustments& sample) { sample.lossUnrealized = 13; })); }, @@ -2816,7 +2818,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) { + args(A2.id(), 0, [&](Adjustments& sample) { sample.assetsMaximum = 1; })); }, @@ -2833,7 +2835,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) { + args(A2.id(), 0, [&](Adjustments& sample) { sample.assetsMaximum = -1; })); }, @@ -2882,7 +2884,7 @@ class Invariants_test : public beast::unit_test::suite ac.view().update(sleShares); return adjust( - ac.view(), keylet, args(A2.id(), 10, [](Adjustements&) {})); + ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {})); }, XRPAmount{}, STTx{ttVAULT_DEPOSIT, [](STObject&) {}}, @@ -2895,7 +2897,7 @@ class Invariants_test : public beast::unit_test::suite [&](Account const& A1, Account const& A2, ApplyContext& ac) { auto const keylet = keylet::vault(A1.id(), ac.view().seq()); adjust( - ac.view(), keylet, args(A2.id(), 10, [](Adjustements&) {})); + ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {})); auto sleVault = ac.view().peek(keylet); if (!sleVault) @@ -3271,7 +3273,9 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) {})); + args(A2.id(), 0, [](Adjustments& sample) { + sample.vaultAssets.reset(); + })); }, XRPAmount{}, STTx{ttVAULT_DEPOSIT, [](STObject&) {}}, @@ -3285,7 +3289,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 200, [&](Adjustements& sample) { + args(A2.id(), 200, [&](Adjustments& sample) { sample.assetsMaximum = 1; })); }, @@ -3319,7 +3323,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A3.id(), -10, [&](Adjustements& sample) { + args(A3.id(), -10, [&](Adjustments& sample) { sample.accountAssets->amount = -100; })); }, @@ -3352,7 +3356,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [&](Adjustments& sample) { sample.vaultAssets = -20; sample.accountAssets->amount = 10; })); @@ -3380,7 +3384,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [&](Adjustments& sample) { sample.accountAssets->amount = 0; })); }, @@ -3399,8 +3403,8 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { - sample.accountShares->amount = 0; + args(A2.id(), 10, [&](Adjustments& sample) { + sample.accountShares.reset(); })); }, XRPAmount{}, @@ -3415,10 +3419,11 @@ class Invariants_test : public beast::unit_test::suite {"deposit must change vault shares"}, [&](Account const& A1, Account const& A2, ApplyContext& ac) { auto const keylet = keylet::vault(A1.id(), ac.view().seq()); + return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [](Adjustments& sample) { sample.sharesTotal = 0; })); }, @@ -3440,7 +3445,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [&](Adjustments& sample) { sample.accountShares->amount = -5; sample.sharesTotal = -10; })); @@ -3453,6 +3458,33 @@ class Invariants_test : public beast::unit_test::suite precloseXrp, TxAccount::A2); + doInvariantCheck( + {"deposit and assets outstanding must add up"}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto sleA3 = ac.view().peek(keylet::account(A3.id())); + (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000; + ac.view().update(sleA3); + + auto const keylet = keylet::vault(A1.id(), ac.view().seq()); + return adjust( + ac.view(), + keylet, + args(A2.id(), 10, [&](Adjustments& sample) { + sample.assetsTotal = 11; + })); + }, + XRPAmount{2000}, + STTx{ + ttVAULT_DEPOSIT, + [&](STObject& tx) { + tx[sfAmount] = XRPAmount(10); + tx[sfDelegate] = A3.id(); + tx[sfFee] = XRPAmount(2000); + }}, + {tecINVARIANT_FAILED, tecINVARIANT_FAILED}, + precloseXrp, + TxAccount::A2); + doInvariantCheck( {"deposit and assets outstanding must add up", "deposit and assets available must add up"}, @@ -3461,7 +3493,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 10, [&](Adjustements& sample) { + args(A2.id(), 10, [&](Adjustments& sample) { sample.assetsTotal = 7; sample.assetsAvailable = 7; })); @@ -3482,7 +3514,9 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) {})); + args(A2.id(), 0, [](Adjustments& sample) { + sample.vaultAssets.reset(); + })); }, XRPAmount{}, STTx{ttVAULT_WITHDRAW, [](STObject&) {}}, @@ -3508,7 +3542,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A3.id(), -10, [&](Adjustements& sample) { + args(A3.id(), -10, [&](Adjustments& sample) { sample.accountAssets->amount = -100; })); }, @@ -3544,7 +3578,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { sample.vaultAssets = 10; sample.accountAssets->amount = -20; })); @@ -3562,7 +3596,7 @@ class Invariants_test : public beast::unit_test::suite if (!adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { *sample.vaultAssets -= 5; }))) return false; @@ -3588,8 +3622,8 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { - sample.accountShares->amount = 0; + args(A2.id(), -10, [&](Adjustments& sample) { + sample.accountShares.reset(); })); }, XRPAmount{}, @@ -3605,7 +3639,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [](Adjustments& sample) { sample.sharesTotal = 0; })); }, @@ -3624,7 +3658,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { sample.accountShares->amount = 5; sample.sharesTotal = 10; })); @@ -3643,7 +3677,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { sample.assetsTotal = -15; sample.assetsAvailable = -15; })); @@ -3654,6 +3688,33 @@ class Invariants_test : public beast::unit_test::suite precloseXrp, TxAccount::A2); + doInvariantCheck( + {"withdrawal and assets outstanding must add up"}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto sleA3 = ac.view().peek(keylet::account(A3.id())); + (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000; + ac.view().update(sleA3); + + auto const keylet = keylet::vault(A1.id(), ac.view().seq()); + return adjust( + ac.view(), + keylet, + args(A2.id(), -10, [&](Adjustments& sample) { + sample.assetsTotal = -7; + })); + }, + XRPAmount{2000}, + STTx{ + ttVAULT_WITHDRAW, + [&](STObject& tx) { + tx[sfAmount] = XRPAmount(10); + tx[sfDelegate] = A3.id(); + tx[sfFee] = XRPAmount(2000); + }}, + {tecINVARIANT_FAILED, tecINVARIANT_FAILED}, + precloseXrp, + TxAccount::A2); + auto const precloseMpt = [&](Account const& A1, Account const& A2, Env& env) -> bool { env.fund(XRP(1000), A3, A4); @@ -3713,7 +3774,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -10, [&](Adjustements& sample) { + args(A2.id(), -10, [&](Adjustments& sample) { sample.accountShares->amount = 5; })); }, @@ -3733,8 +3794,8 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), -1, [&](Adjustements& sample) { - sample.vaultAssets = 0; + args(A2.id(), -1, [&](Adjustments& sample) { + sample.vaultAssets.reset(); })); }, XRPAmount{}, @@ -3752,7 +3813,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) {})); + args(A2.id(), 0, [&](Adjustments& sample) {})); }, XRPAmount{}, STTx{ttVAULT_CLAWBACK, [](STObject&) {}}, @@ -3767,7 +3828,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A2.id(), 0, [&](Adjustements& sample) {})); + args(A2.id(), 0, [&](Adjustments& sample) {})); }, XRPAmount{}, STTx{ @@ -3785,7 +3846,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A4.id(), 10, [&](Adjustements& sample) { + args(A4.id(), 10, [&](Adjustments& sample) { sample.sharesTotal = 0; })); }, @@ -3806,8 +3867,8 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A4.id(), -10, [&](Adjustements& sample) { - sample.accountShares->amount = 0; + args(A4.id(), -10, [&](Adjustments& sample) { + sample.accountShares.reset(); })); }, XRPAmount{}, @@ -3829,7 +3890,7 @@ class Invariants_test : public beast::unit_test::suite return adjust( ac.view(), keylet, - args(A4.id(), -10, [&](Adjustements& sample) { + args(A4.id(), -10, [&](Adjustments& sample) { sample.accountShares->amount = -8; sample.assetsTotal = -7; sample.assetsAvailable = -7; diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 58a836e652..cdc2e09da1 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -43,6 +44,8 @@ #include #include +#include + namespace ripple { class Vault_test : public beast::unit_test::suite @@ -303,6 +306,55 @@ class Vault_test : public beast::unit_test::suite BEAST_EXPECT( env.balance(depositor, shares) == share(200 * scale)); } + else + { + testcase(prefix + " deposit/withdrawal same or less than fee"); + auto const amount = env.current()->fees().base; + + auto tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = amount}); + env(tx); + env.close(); + + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = amount}); + env(tx); + env.close(); + + tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = amount}); + env(tx); + env.close(); + + // Withdraw to 3rd party + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = amount}); + tx[sfDestination] = charlie.human(); + env(tx); + env.close(); + + tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = amount - 1}); + env(tx); + env.close(); + + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = amount - 1}); + env(tx); + env.close(); + } { testcase( @@ -4830,6 +4882,147 @@ class Vault_test : public beast::unit_test::suite } } + void + testDelegate() + { + using namespace test::jtx; + + Env env(*this, testable_amendments()); + Account alice{"alice"}; + Account bob{"bob"}; + Account carol{"carol"}; + + struct CaseArgs + { + PrettyAsset asset = xrpIssue(); + }; + + auto const xrpBalance = + [this]( + Env const& env, Account const& account) -> std::optional { + auto sle = env.le(keylet::account(account.id())); + if (BEAST_EXPECT(sle != nullptr)) + return sle->getFieldAmount(sfBalance).xrp().drops(); + return std::nullopt; + }; + + auto testCase = [&, this](auto test, CaseArgs args = {}) { + Env env{*this, testable_amendments() | featureSingleAssetVault}; + + Vault vault{env}; + + // use different initial amount to distinguish the source balance + env.fund(XRP(10000), alice); + env.fund(XRP(20000), bob); + env.fund(XRP(30000), carol); + env.close(); + + env(delegate::set( + carol, + alice, + {"Payment", + "VaultCreate", + "VaultSet", + "VaultDelete", + "VaultDeposit", + "VaultWithdraw", + "VaultClawback"})); + + test(env, vault, args.asset); + }; + + testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) { + testcase("delegated vault creation"); + auto startBalance = xrpBalance(env, carol); + if (!BEAST_EXPECT(startBalance.has_value())) + return; + + auto [tx, keylet] = vault.create({.owner = carol, .asset = asset}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance); + }); + + testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) { + testcase("delegated deposit and withdrawal"); + auto [tx, keylet] = vault.create({.owner = carol, .asset = asset}); + env(tx); + env.close(); + + auto const amount = 1513; + auto const baseFee = env.current()->fees().base; + + auto startBalance = xrpBalance(env, carol); + if (!BEAST_EXPECT(startBalance.has_value())) + return; + + tx = vault.deposit( + {.depositor = carol, + .id = keylet.key, + .amount = asset(amount)}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - amount); + + tx = vault.withdraw( + {.depositor = carol, + .id = keylet.key, + .amount = asset(amount - 1)}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - 1); + + tx = vault.withdraw( + {.depositor = carol, .id = keylet.key, .amount = asset(1)}); + env(tx); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - baseFee); + }); + + testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) { + testcase("delegated withdrawal same as base fee and deletion"); + auto [tx, keylet] = vault.create({.owner = carol, .asset = asset}); + env(tx); + env.close(); + + auto const amount = 25537; + auto const baseFee = env.current()->fees().base; + + auto startBalance = xrpBalance(env, carol); + if (!BEAST_EXPECT(startBalance.has_value())) + return; + + tx = vault.deposit( + {.depositor = carol, + .id = keylet.key, + .amount = asset(amount)}); + env(tx); + env.close(); + BEAST_EXPECT( + xrpBalance(env, carol) == *startBalance - amount - baseFee); + + tx = vault.withdraw( + {.depositor = carol, + .id = keylet.key, + .amount = asset(baseFee)}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - amount); + + tx = vault.withdraw( + {.depositor = carol, + .id = keylet.key, + .amount = asset(amount - baseFee)}); + env(tx, delegate::as(alice)); + env.close(); + BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - baseFee); + + tx = vault.del({.owner = carol, .id = keylet.key}); + env(tx, delegate::as(alice)); + env.close(); + }); + } + public: void run() override @@ -4847,6 +5040,7 @@ public: testFailedPseudoAccount(); testScaleIOU(); testRPC(); + testDelegate(); } }; diff --git a/src/test/json/Writer_test.cpp b/src/test/json/Writer_test.cpp deleted file mode 100644 index 3739af07e1..0000000000 --- a/src/test/json/Writer_test.cpp +++ /dev/null @@ -1,217 +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 -#include - -namespace Json { - -class JsonWriter_test : public ripple::test::TestOutputSuite -{ -public: - void - testTrivial() - { - setup("trivial"); - BEAST_EXPECT(output_.empty()); - expectResult(""); - } - - void - testNearTrivial() - { - setup("near trivial"); - BEAST_EXPECT(output_.empty()); - writer_->output(0); - expectResult("0"); - } - - void - testPrimitives() - { - setup("true"); - writer_->output(true); - expectResult("true"); - - setup("false"); - writer_->output(false); - expectResult("false"); - - setup("23"); - writer_->output(23); - expectResult("23"); - - setup("23.0"); - writer_->output(23.0); - expectResult("23.0"); - - setup("23.5"); - writer_->output(23.5); - expectResult("23.5"); - - setup("a string"); - writer_->output("a string"); - expectResult("\"a string\""); - - setup("nullptr"); - writer_->output(nullptr); - expectResult("null"); - } - - void - testEmpty() - { - setup("empty array"); - writer_->startRoot(Writer::array); - writer_->finish(); - expectResult("[]"); - - setup("empty object"); - writer_->startRoot(Writer::object); - writer_->finish(); - expectResult("{}"); - } - - void - testEscaping() - { - setup("backslash"); - writer_->output("\\"); - expectResult("\"\\\\\""); - - setup("quote"); - writer_->output("\""); - expectResult("\"\\\"\""); - - setup("backslash and quote"); - writer_->output("\\\""); - expectResult("\"\\\\\\\"\""); - - setup("escape embedded"); - writer_->output("this contains a \\ in the middle of it."); - expectResult("\"this contains a \\\\ in the middle of it.\""); - - setup("remaining escapes"); - writer_->output("\b\f\n\r\t"); - expectResult("\"\\b\\f\\n\\r\\t\""); - } - - void - testArray() - { - setup("empty array"); - writer_->startRoot(Writer::array); - writer_->append(12); - writer_->finish(); - expectResult("[12]"); - } - - void - testLongArray() - { - setup("long array"); - writer_->startRoot(Writer::array); - writer_->append(12); - writer_->append(true); - writer_->append("hello"); - writer_->finish(); - expectResult("[12,true,\"hello\"]"); - } - - void - testEmbeddedArraySimple() - { - setup("embedded array simple"); - writer_->startRoot(Writer::array); - writer_->startAppend(Writer::array); - writer_->finish(); - writer_->finish(); - expectResult("[[]]"); - } - - void - testObject() - { - setup("object"); - writer_->startRoot(Writer::object); - writer_->set("hello", "world"); - writer_->finish(); - - expectResult("{\"hello\":\"world\"}"); - } - - void - testComplexObject() - { - setup("complex object"); - writer_->startRoot(Writer::object); - - writer_->set("hello", "world"); - writer_->startSet(Writer::array, "array"); - - writer_->append(true); - writer_->append(12); - writer_->startAppend(Writer::array); - writer_->startAppend(Writer::object); - writer_->set("goodbye", "cruel world."); - writer_->startSet(Writer::array, "subarray"); - writer_->append(23.5); - writer_->finishAll(); - - expectResult( - "{\"hello\":\"world\",\"array\":[true,12," - "[{\"goodbye\":\"cruel world.\"," - "\"subarray\":[23.5]}]]}"); - } - - void - testJson() - { - setup("object"); - Json::Value value(Json::objectValue); - value["foo"] = 23; - writer_->startRoot(Writer::object); - writer_->set("hello", value); - writer_->finish(); - - expectResult("{\"hello\":{\"foo\":23}}"); - } - - void - run() override - { - testTrivial(); - testNearTrivial(); - testPrimitives(); - testEmpty(); - testEscaping(); - testArray(); - testLongArray(); - testEmbeddedArraySimple(); - testObject(); - testComplexObject(); - testJson(); - } -}; - -BEAST_DEFINE_TESTSUITE(JsonWriter, json, ripple); - -} // namespace Json diff --git a/src/test/json/json_value_test.cpp b/src/test/json/json_value_test.cpp deleted file mode 100644 index 0858786f04..0000000000 --- a/src/test/json/json_value_test.cpp +++ /dev/null @@ -1,1398 +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 -#include -#include -#include - -#include -#include - -namespace ripple { - -struct json_value_test : beast::unit_test::suite -{ - void - test_StaticString() - { - static constexpr char sample[]{"Contents of a Json::StaticString"}; - - static constexpr Json::StaticString test1(sample); - char const* addrTest1{test1}; - - BEAST_EXPECT(addrTest1 == &sample[0]); - BEAST_EXPECT(test1.c_str() == &sample[0]); - - static constexpr Json::StaticString test2{ - "Contents of a Json::StaticString"}; - static constexpr Json::StaticString test3{"Another StaticString"}; - - BEAST_EXPECT(test1 == test2); - BEAST_EXPECT(test1 != test3); - - std::string str{sample}; - BEAST_EXPECT(str == test2); - BEAST_EXPECT(str != test3); - BEAST_EXPECT(test2 == str); - BEAST_EXPECT(test3 != str); - } - - void - test_types() - { - // Exercise ValueType constructor - static constexpr Json::StaticString staticStr{"staticStr"}; - - auto testCopy = [this](Json::ValueType typ) { - Json::Value val{typ}; - Json::Value cpy{val}; - BEAST_EXPECT(val.type() == typ); - BEAST_EXPECT(cpy.type() == typ); - return val; - }; - { - Json::Value const nullV{testCopy(Json::nullValue)}; - BEAST_EXPECT(nullV.isNull()); - BEAST_EXPECT(!nullV.isBool()); - BEAST_EXPECT(!nullV.isInt()); - BEAST_EXPECT(!nullV.isUInt()); - BEAST_EXPECT(!nullV.isIntegral()); - BEAST_EXPECT(!nullV.isDouble()); - BEAST_EXPECT(!nullV.isNumeric()); - BEAST_EXPECT(!nullV.isString()); - BEAST_EXPECT(!nullV.isArray()); - BEAST_EXPECT(nullV.isArrayOrNull()); - BEAST_EXPECT(!nullV.isObject()); - BEAST_EXPECT(nullV.isObjectOrNull()); - } - { - Json::Value const intV{testCopy(Json::intValue)}; - BEAST_EXPECT(!intV.isNull()); - BEAST_EXPECT(!intV.isBool()); - BEAST_EXPECT(intV.isInt()); - BEAST_EXPECT(!intV.isUInt()); - BEAST_EXPECT(intV.isIntegral()); - BEAST_EXPECT(!intV.isDouble()); - BEAST_EXPECT(intV.isNumeric()); - BEAST_EXPECT(!intV.isString()); - BEAST_EXPECT(!intV.isArray()); - BEAST_EXPECT(!intV.isArrayOrNull()); - BEAST_EXPECT(!intV.isObject()); - BEAST_EXPECT(!intV.isObjectOrNull()); - } - { - Json::Value const uintV{testCopy(Json::uintValue)}; - BEAST_EXPECT(!uintV.isNull()); - BEAST_EXPECT(!uintV.isBool()); - BEAST_EXPECT(!uintV.isInt()); - BEAST_EXPECT(uintV.isUInt()); - BEAST_EXPECT(uintV.isIntegral()); - BEAST_EXPECT(!uintV.isDouble()); - BEAST_EXPECT(uintV.isNumeric()); - BEAST_EXPECT(!uintV.isString()); - BEAST_EXPECT(!uintV.isArray()); - BEAST_EXPECT(!uintV.isArrayOrNull()); - BEAST_EXPECT(!uintV.isObject()); - BEAST_EXPECT(!uintV.isObjectOrNull()); - } - { - Json::Value const realV{testCopy(Json::realValue)}; - BEAST_EXPECT(!realV.isNull()); - BEAST_EXPECT(!realV.isBool()); - BEAST_EXPECT(!realV.isInt()); - BEAST_EXPECT(!realV.isUInt()); - BEAST_EXPECT(!realV.isIntegral()); - BEAST_EXPECT(realV.isDouble()); - BEAST_EXPECT(realV.isNumeric()); - BEAST_EXPECT(!realV.isString()); - BEAST_EXPECT(!realV.isArray()); - BEAST_EXPECT(!realV.isArrayOrNull()); - BEAST_EXPECT(!realV.isObject()); - BEAST_EXPECT(!realV.isObjectOrNull()); - } - { - Json::Value const stringV{testCopy(Json::stringValue)}; - BEAST_EXPECT(!stringV.isNull()); - BEAST_EXPECT(!stringV.isBool()); - BEAST_EXPECT(!stringV.isInt()); - BEAST_EXPECT(!stringV.isUInt()); - BEAST_EXPECT(!stringV.isIntegral()); - BEAST_EXPECT(!stringV.isDouble()); - BEAST_EXPECT(!stringV.isNumeric()); - BEAST_EXPECT(stringV.isString()); - BEAST_EXPECT(!stringV.isArray()); - BEAST_EXPECT(!stringV.isArrayOrNull()); - BEAST_EXPECT(!stringV.isObject()); - BEAST_EXPECT(!stringV.isObjectOrNull()); - } - { - Json::Value const staticStrV{staticStr}; - { - Json::Value cpy{staticStrV}; - BEAST_EXPECT(staticStrV.type() == Json::stringValue); - BEAST_EXPECT(cpy.type() == Json::stringValue); - } - BEAST_EXPECT(!staticStrV.isNull()); - BEAST_EXPECT(!staticStrV.isBool()); - BEAST_EXPECT(!staticStrV.isInt()); - BEAST_EXPECT(!staticStrV.isUInt()); - BEAST_EXPECT(!staticStrV.isIntegral()); - BEAST_EXPECT(!staticStrV.isDouble()); - BEAST_EXPECT(!staticStrV.isNumeric()); - BEAST_EXPECT(staticStrV.isString()); - BEAST_EXPECT(!staticStrV.isArray()); - BEAST_EXPECT(!staticStrV.isArrayOrNull()); - BEAST_EXPECT(!staticStrV.isObject()); - BEAST_EXPECT(!staticStrV.isObjectOrNull()); - } - { - Json::Value const boolV{testCopy(Json::booleanValue)}; - BEAST_EXPECT(!boolV.isNull()); - BEAST_EXPECT(boolV.isBool()); - BEAST_EXPECT(!boolV.isInt()); - BEAST_EXPECT(!boolV.isUInt()); - BEAST_EXPECT(boolV.isIntegral()); - BEAST_EXPECT(!boolV.isDouble()); - BEAST_EXPECT(boolV.isNumeric()); - BEAST_EXPECT(!boolV.isString()); - BEAST_EXPECT(!boolV.isArray()); - BEAST_EXPECT(!boolV.isArrayOrNull()); - BEAST_EXPECT(!boolV.isObject()); - BEAST_EXPECT(!boolV.isObjectOrNull()); - } - { - Json::Value const arrayV{testCopy(Json::arrayValue)}; - BEAST_EXPECT(!arrayV.isNull()); - BEAST_EXPECT(!arrayV.isBool()); - BEAST_EXPECT(!arrayV.isInt()); - BEAST_EXPECT(!arrayV.isUInt()); - BEAST_EXPECT(!arrayV.isIntegral()); - BEAST_EXPECT(!arrayV.isDouble()); - BEAST_EXPECT(!arrayV.isNumeric()); - BEAST_EXPECT(!arrayV.isString()); - BEAST_EXPECT(arrayV.isArray()); - BEAST_EXPECT(arrayV.isArrayOrNull()); - BEAST_EXPECT(!arrayV.isObject()); - BEAST_EXPECT(!arrayV.isObjectOrNull()); - } - { - Json::Value const objectV{testCopy(Json::objectValue)}; - BEAST_EXPECT(!objectV.isNull()); - BEAST_EXPECT(!objectV.isBool()); - BEAST_EXPECT(!objectV.isInt()); - BEAST_EXPECT(!objectV.isUInt()); - BEAST_EXPECT(!objectV.isIntegral()); - BEAST_EXPECT(!objectV.isDouble()); - BEAST_EXPECT(!objectV.isNumeric()); - BEAST_EXPECT(!objectV.isString()); - BEAST_EXPECT(!objectV.isArray()); - BEAST_EXPECT(!objectV.isArrayOrNull()); - BEAST_EXPECT(objectV.isObject()); - BEAST_EXPECT(objectV.isObjectOrNull()); - } - } - - void - test_compare() - { - auto doCompare = [this]( - Json::Value const& lhs, - Json::Value const& rhs, - bool lhsEqRhs, - bool lhsLtRhs, - int line) { - auto fmt = [this](bool cond, char const* text, int line) { - if (cond) - this->pass(); - else - this->fail(text, __FILE__, line); - }; - fmt((lhs == rhs) == lhsEqRhs, "Value ==", line); - fmt((lhs != rhs) != lhsEqRhs, "Value !=", line); - fmt((lhs < rhs) == (!(lhsEqRhs | !lhsLtRhs)), "Value <", line); - fmt((lhs <= rhs) == (lhsEqRhs | lhsLtRhs), "Value <=", line); - fmt((lhs >= rhs) == (lhsEqRhs | !lhsLtRhs), "Value >=", line); - fmt((lhs > rhs) == (!(lhsEqRhs | lhsLtRhs)), "Value >", line); - }; - - Json::Value const null0; - Json::Value const intNeg1{-1}; - Json::Value const int0{Json::intValue}; - Json::Value const intPos1{1}; - Json::Value const uint0{Json::uintValue}; - Json::Value const uint1{1u}; - Json::Value const realNeg1{-1.0}; - Json::Value const real0{Json::realValue}; - Json::Value const realPos1{1.0}; - Json::Value const str0{Json::stringValue}; - Json::Value const str1{"1"}; - Json::Value const boolF{false}; - Json::Value const boolT{true}; - Json::Value const array0{Json::arrayValue}; - Json::Value const array1{[]() { - Json::Value array1; - array1[0u] = 1; - return array1; - }()}; - Json::Value const obj0{Json::objectValue}; - Json::Value const obj1{[]() { - Json::Value obj1; - obj1["one"] = 1; - return obj1; - }()}; - // lhs == rhs lhs < rhs - doCompare(null0, Json::Value{}, true, false, __LINE__); - doCompare(null0, intNeg1, false, true, __LINE__); - doCompare(null0, int0, false, true, __LINE__); - doCompare(null0, intPos1, false, true, __LINE__); - doCompare(null0, uint0, false, true, __LINE__); - doCompare(null0, uint1, false, true, __LINE__); - doCompare(null0, realNeg1, false, true, __LINE__); - doCompare(null0, real0, false, true, __LINE__); - doCompare(null0, realPos1, false, true, __LINE__); - doCompare(null0, str0, false, true, __LINE__); - doCompare(null0, str1, false, true, __LINE__); - doCompare(null0, boolF, false, true, __LINE__); - doCompare(null0, boolT, false, true, __LINE__); - doCompare(null0, array0, false, true, __LINE__); - doCompare(null0, array1, false, true, __LINE__); - doCompare(null0, obj0, false, true, __LINE__); - doCompare(null0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(intNeg1, null0, false, false, __LINE__); - doCompare(intNeg1, intNeg1, true, false, __LINE__); - doCompare(intNeg1, int0, false, true, __LINE__); - doCompare(intNeg1, intPos1, false, true, __LINE__); - doCompare(intNeg1, uint0, false, true, __LINE__); - doCompare(intNeg1, uint1, false, true, __LINE__); - doCompare(intNeg1, realNeg1, false, true, __LINE__); - doCompare(intNeg1, real0, false, true, __LINE__); - doCompare(intNeg1, realPos1, false, true, __LINE__); - doCompare(intNeg1, str0, false, true, __LINE__); - doCompare(intNeg1, str1, false, true, __LINE__); - doCompare(intNeg1, boolF, false, true, __LINE__); - doCompare(intNeg1, boolT, false, true, __LINE__); - doCompare(intNeg1, array0, false, true, __LINE__); - doCompare(intNeg1, array1, false, true, __LINE__); - doCompare(intNeg1, obj0, false, true, __LINE__); - doCompare(intNeg1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(int0, null0, false, false, __LINE__); - doCompare(int0, intNeg1, false, false, __LINE__); - doCompare(int0, int0, true, false, __LINE__); - doCompare(int0, intPos1, false, true, __LINE__); - doCompare(int0, uint0, true, false, __LINE__); - doCompare(int0, uint1, false, true, __LINE__); - doCompare(int0, realNeg1, false, true, __LINE__); - doCompare(int0, real0, false, true, __LINE__); - doCompare(int0, realPos1, false, true, __LINE__); - doCompare(int0, str0, false, true, __LINE__); - doCompare(int0, str1, false, true, __LINE__); - doCompare(int0, boolF, false, true, __LINE__); - doCompare(int0, boolT, false, true, __LINE__); - doCompare(int0, array0, false, true, __LINE__); - doCompare(int0, array1, false, true, __LINE__); - doCompare(int0, obj0, false, true, __LINE__); - doCompare(int0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(intPos1, null0, false, false, __LINE__); - doCompare(intPos1, intNeg1, false, false, __LINE__); - doCompare(intPos1, int0, false, false, __LINE__); - doCompare(intPos1, intPos1, true, false, __LINE__); - doCompare(intPos1, uint0, false, false, __LINE__); - doCompare(intPos1, uint1, true, false, __LINE__); - doCompare(intPos1, realNeg1, false, true, __LINE__); - doCompare(intPos1, real0, false, true, __LINE__); - doCompare(intPos1, realPos1, false, true, __LINE__); - doCompare(intPos1, str0, false, true, __LINE__); - doCompare(intPos1, str1, false, true, __LINE__); - doCompare(intPos1, boolF, false, true, __LINE__); - doCompare(intPos1, boolT, false, true, __LINE__); - doCompare(intPos1, array0, false, true, __LINE__); - doCompare(intPos1, array1, false, true, __LINE__); - doCompare(intPos1, obj0, false, true, __LINE__); - doCompare(intPos1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(uint0, null0, false, false, __LINE__); - doCompare(uint0, intNeg1, false, false, __LINE__); - doCompare(uint0, int0, true, false, __LINE__); - doCompare(uint0, intPos1, false, true, __LINE__); - doCompare(uint0, uint0, true, false, __LINE__); - doCompare(uint0, uint1, false, true, __LINE__); - doCompare(uint0, realNeg1, false, true, __LINE__); - doCompare(uint0, real0, false, true, __LINE__); - doCompare(uint0, realPos1, false, true, __LINE__); - doCompare(uint0, str0, false, true, __LINE__); - doCompare(uint0, str1, false, true, __LINE__); - doCompare(uint0, boolF, false, true, __LINE__); - doCompare(uint0, boolT, false, true, __LINE__); - doCompare(uint0, array0, false, true, __LINE__); - doCompare(uint0, array1, false, true, __LINE__); - doCompare(uint0, obj0, false, true, __LINE__); - doCompare(uint0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(uint1, null0, false, false, __LINE__); - doCompare(uint1, intNeg1, false, false, __LINE__); - doCompare(uint1, int0, false, false, __LINE__); - doCompare(uint1, intPos1, true, false, __LINE__); - doCompare(uint1, uint0, false, false, __LINE__); - doCompare(uint1, uint1, true, false, __LINE__); - doCompare(uint1, realNeg1, false, true, __LINE__); - doCompare(uint1, real0, false, true, __LINE__); - doCompare(uint1, realPos1, false, true, __LINE__); - doCompare(uint1, str0, false, true, __LINE__); - doCompare(uint1, str1, false, true, __LINE__); - doCompare(uint1, boolF, false, true, __LINE__); - doCompare(uint1, boolT, false, true, __LINE__); - doCompare(uint1, array0, false, true, __LINE__); - doCompare(uint1, array1, false, true, __LINE__); - doCompare(uint1, obj0, false, true, __LINE__); - doCompare(uint1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(realNeg1, null0, false, false, __LINE__); - doCompare(realNeg1, intNeg1, false, false, __LINE__); - doCompare(realNeg1, int0, false, false, __LINE__); - doCompare(realNeg1, intPos1, false, false, __LINE__); - doCompare(realNeg1, uint0, false, false, __LINE__); - doCompare(realNeg1, uint1, false, false, __LINE__); - doCompare(realNeg1, realNeg1, true, false, __LINE__); - doCompare(realNeg1, real0, false, true, __LINE__); - doCompare(realNeg1, realPos1, false, true, __LINE__); - doCompare(realNeg1, str0, false, true, __LINE__); - doCompare(realNeg1, str1, false, true, __LINE__); - doCompare(realNeg1, boolF, false, true, __LINE__); - doCompare(realNeg1, boolT, false, true, __LINE__); - doCompare(realNeg1, array0, false, true, __LINE__); - doCompare(realNeg1, array1, false, true, __LINE__); - doCompare(realNeg1, obj0, false, true, __LINE__); - doCompare(realNeg1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(real0, null0, false, false, __LINE__); - doCompare(real0, intNeg1, false, false, __LINE__); - doCompare(real0, int0, false, false, __LINE__); - doCompare(real0, intPos1, false, false, __LINE__); - doCompare(real0, uint0, false, false, __LINE__); - doCompare(real0, uint1, false, false, __LINE__); - doCompare(real0, realNeg1, false, false, __LINE__); - doCompare(real0, real0, true, false, __LINE__); - doCompare(real0, realPos1, false, true, __LINE__); - doCompare(real0, str0, false, true, __LINE__); - doCompare(real0, str1, false, true, __LINE__); - doCompare(real0, boolF, false, true, __LINE__); - doCompare(real0, boolT, false, true, __LINE__); - doCompare(real0, array0, false, true, __LINE__); - doCompare(real0, array1, false, true, __LINE__); - doCompare(real0, obj0, false, true, __LINE__); - doCompare(real0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(realPos1, null0, false, false, __LINE__); - doCompare(realPos1, intNeg1, false, false, __LINE__); - doCompare(realPos1, int0, false, false, __LINE__); - doCompare(realPos1, intPos1, false, false, __LINE__); - doCompare(realPos1, uint0, false, false, __LINE__); - doCompare(realPos1, uint1, false, false, __LINE__); - doCompare(realPos1, realNeg1, false, false, __LINE__); - doCompare(realPos1, real0, false, false, __LINE__); - doCompare(realPos1, realPos1, true, false, __LINE__); - doCompare(realPos1, str0, false, true, __LINE__); - doCompare(realPos1, str1, false, true, __LINE__); - doCompare(realPos1, boolF, false, true, __LINE__); - doCompare(realPos1, boolT, false, true, __LINE__); - doCompare(realPos1, array0, false, true, __LINE__); - doCompare(realPos1, array1, false, true, __LINE__); - doCompare(realPos1, obj0, false, true, __LINE__); - doCompare(realPos1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(str0, null0, false, false, __LINE__); - doCompare(str0, intNeg1, false, false, __LINE__); - doCompare(str0, int0, false, false, __LINE__); - doCompare(str0, intPos1, false, false, __LINE__); - doCompare(str0, uint0, false, false, __LINE__); - doCompare(str0, uint1, false, false, __LINE__); - doCompare(str0, realNeg1, false, false, __LINE__); - doCompare(str0, real0, false, false, __LINE__); - doCompare(str0, realPos1, false, false, __LINE__); - doCompare(str0, str0, true, false, __LINE__); - doCompare(str0, str1, false, true, __LINE__); - doCompare(str0, boolF, false, true, __LINE__); - doCompare(str0, boolT, false, true, __LINE__); - doCompare(str0, array0, false, true, __LINE__); - doCompare(str0, array1, false, true, __LINE__); - doCompare(str0, obj0, false, true, __LINE__); - doCompare(str0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(str1, null0, false, false, __LINE__); - doCompare(str1, intNeg1, false, false, __LINE__); - doCompare(str1, int0, false, false, __LINE__); - doCompare(str1, intPos1, false, false, __LINE__); - doCompare(str1, uint0, false, false, __LINE__); - doCompare(str1, uint1, false, false, __LINE__); - doCompare(str1, realNeg1, false, false, __LINE__); - doCompare(str1, real0, false, false, __LINE__); - doCompare(str1, realPos1, false, false, __LINE__); - doCompare(str1, str0, false, false, __LINE__); - doCompare(str1, str1, true, false, __LINE__); - doCompare(str1, boolF, false, true, __LINE__); - doCompare(str1, boolT, false, true, __LINE__); - doCompare(str1, array0, false, true, __LINE__); - doCompare(str1, array1, false, true, __LINE__); - doCompare(str1, obj0, false, true, __LINE__); - doCompare(str1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(boolF, null0, false, false, __LINE__); - doCompare(boolF, intNeg1, false, false, __LINE__); - doCompare(boolF, int0, false, false, __LINE__); - doCompare(boolF, intPos1, false, false, __LINE__); - doCompare(boolF, uint0, false, false, __LINE__); - doCompare(boolF, uint1, false, false, __LINE__); - doCompare(boolF, realNeg1, false, false, __LINE__); - doCompare(boolF, real0, false, false, __LINE__); - doCompare(boolF, realPos1, false, false, __LINE__); - doCompare(boolF, str0, false, false, __LINE__); - doCompare(boolF, str1, false, false, __LINE__); - doCompare(boolF, boolF, true, false, __LINE__); - doCompare(boolF, boolT, false, true, __LINE__); - doCompare(boolF, array0, false, true, __LINE__); - doCompare(boolF, array1, false, true, __LINE__); - doCompare(boolF, obj0, false, true, __LINE__); - doCompare(boolF, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(boolT, null0, false, false, __LINE__); - doCompare(boolT, intNeg1, false, false, __LINE__); - doCompare(boolT, int0, false, false, __LINE__); - doCompare(boolT, intPos1, false, false, __LINE__); - doCompare(boolT, uint0, false, false, __LINE__); - doCompare(boolT, uint1, false, false, __LINE__); - doCompare(boolT, realNeg1, false, false, __LINE__); - doCompare(boolT, real0, false, false, __LINE__); - doCompare(boolT, realPos1, false, false, __LINE__); - doCompare(boolT, str0, false, false, __LINE__); - doCompare(boolT, str1, false, false, __LINE__); - doCompare(boolT, boolF, false, false, __LINE__); - doCompare(boolT, boolT, true, false, __LINE__); - doCompare(boolT, array0, false, true, __LINE__); - doCompare(boolT, array1, false, true, __LINE__); - doCompare(boolT, obj0, false, true, __LINE__); - doCompare(boolT, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(array0, null0, false, false, __LINE__); - doCompare(array0, intNeg1, false, false, __LINE__); - doCompare(array0, int0, false, false, __LINE__); - doCompare(array0, intPos1, false, false, __LINE__); - doCompare(array0, uint0, false, false, __LINE__); - doCompare(array0, uint1, false, false, __LINE__); - doCompare(array0, realNeg1, false, false, __LINE__); - doCompare(array0, real0, false, false, __LINE__); - doCompare(array0, realPos1, false, false, __LINE__); - doCompare(array0, str0, false, false, __LINE__); - doCompare(array0, str1, false, false, __LINE__); - doCompare(array0, boolF, false, false, __LINE__); - doCompare(array0, boolT, false, false, __LINE__); - doCompare(array0, array0, true, false, __LINE__); - doCompare(array0, array1, false, true, __LINE__); - doCompare(array0, obj0, false, true, __LINE__); - doCompare(array0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(array1, null0, false, false, __LINE__); - doCompare(array1, intNeg1, false, false, __LINE__); - doCompare(array1, int0, false, false, __LINE__); - doCompare(array1, intPos1, false, false, __LINE__); - doCompare(array1, uint0, false, false, __LINE__); - doCompare(array1, uint1, false, false, __LINE__); - doCompare(array1, realNeg1, false, false, __LINE__); - doCompare(array1, real0, false, false, __LINE__); - doCompare(array1, realPos1, false, false, __LINE__); - doCompare(array1, str0, false, false, __LINE__); - doCompare(array1, str1, false, false, __LINE__); - doCompare(array1, boolF, false, false, __LINE__); - doCompare(array1, boolT, false, false, __LINE__); - doCompare(array1, array0, false, false, __LINE__); - doCompare(array1, array1, true, false, __LINE__); - doCompare(array1, obj0, false, true, __LINE__); - doCompare(array1, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(obj0, null0, false, false, __LINE__); - doCompare(obj0, intNeg1, false, false, __LINE__); - doCompare(obj0, int0, false, false, __LINE__); - doCompare(obj0, intPos1, false, false, __LINE__); - doCompare(obj0, uint0, false, false, __LINE__); - doCompare(obj0, uint1, false, false, __LINE__); - doCompare(obj0, realNeg1, false, false, __LINE__); - doCompare(obj0, real0, false, false, __LINE__); - doCompare(obj0, realPos1, false, false, __LINE__); - doCompare(obj0, str0, false, false, __LINE__); - doCompare(obj0, str1, false, false, __LINE__); - doCompare(obj0, boolF, false, false, __LINE__); - doCompare(obj0, boolT, false, false, __LINE__); - doCompare(obj0, array0, false, false, __LINE__); - doCompare(obj0, array1, false, false, __LINE__); - doCompare(obj0, obj0, true, false, __LINE__); - doCompare(obj0, obj1, false, true, __LINE__); - // lhs == rhs lhs < rhs - doCompare(obj1, null0, false, false, __LINE__); - doCompare(obj1, intNeg1, false, false, __LINE__); - doCompare(obj1, int0, false, false, __LINE__); - doCompare(obj1, intPos1, false, false, __LINE__); - doCompare(obj1, uint0, false, false, __LINE__); - doCompare(obj1, uint1, false, false, __LINE__); - doCompare(obj1, realNeg1, false, false, __LINE__); - doCompare(obj1, real0, false, false, __LINE__); - doCompare(obj1, realPos1, false, false, __LINE__); - doCompare(obj1, str0, false, false, __LINE__); - doCompare(obj1, str1, false, false, __LINE__); - doCompare(obj1, boolF, false, false, __LINE__); - doCompare(obj1, boolT, false, false, __LINE__); - doCompare(obj1, array0, false, false, __LINE__); - doCompare(obj1, array1, false, false, __LINE__); - doCompare(obj1, obj0, false, false, __LINE__); - doCompare(obj1, obj1, true, false, __LINE__); - } - - void - test_bool() - { - BEAST_EXPECT(!Json::Value()); - - BEAST_EXPECT(!Json::Value("")); - - BEAST_EXPECT(bool(Json::Value("empty"))); - BEAST_EXPECT(bool(Json::Value(false))); - BEAST_EXPECT(bool(Json::Value(true))); - BEAST_EXPECT(bool(Json::Value(0))); - BEAST_EXPECT(bool(Json::Value(1))); - - Json::Value array(Json::arrayValue); - BEAST_EXPECT(!array); - array.append(0); - BEAST_EXPECT(bool(array)); - - Json::Value object(Json::objectValue); - BEAST_EXPECT(!object); - object[""] = false; - BEAST_EXPECT(bool(object)); - } - - void - test_bad_json() - { - char const* s( - "{\"method\":\"ledger\",\"params\":[{\"ledger_index\":1e300}]}"); - - Json::Value j; - Json::Reader r; - - r.parse(s, j); - pass(); - } - - void - test_edge_cases() - { - std::string json; - - std::uint32_t max_uint = std::numeric_limits::max(); - std::int32_t max_int = std::numeric_limits::max(); - std::int32_t min_int = std::numeric_limits::min(); - - std::uint32_t a_uint = max_uint - 1978; - std::int32_t a_large_int = max_int - 1978; - std::int32_t a_small_int = min_int + 1978; - - json = "{\"max_uint\":" + std::to_string(max_uint); - json += ",\"max_int\":" + std::to_string(max_int); - json += ",\"min_int\":" + std::to_string(min_int); - json += ",\"a_uint\":" + std::to_string(a_uint); - json += ",\"a_large_int\":" + std::to_string(a_large_int); - json += ",\"a_small_int\":" + std::to_string(a_small_int); - json += "}"; - - Json::Value j1; - Json::Reader r1; - - BEAST_EXPECT(r1.parse(json, j1)); - BEAST_EXPECT(j1["max_uint"].asUInt() == max_uint); - BEAST_EXPECT(j1["max_int"].asInt() == max_int); - BEAST_EXPECT(j1["min_int"].asInt() == min_int); - BEAST_EXPECT(j1["a_uint"].asUInt() == a_uint); - BEAST_EXPECT(j1["a_uint"] > a_large_int); - BEAST_EXPECT(j1["a_uint"] > a_small_int); - BEAST_EXPECT(j1["a_large_int"].asInt() == a_large_int); - BEAST_EXPECT(j1["a_large_int"].asUInt() == a_large_int); - BEAST_EXPECT(j1["a_large_int"] < a_uint); - BEAST_EXPECT(j1["a_small_int"].asInt() == a_small_int); - BEAST_EXPECT(j1["a_small_int"] < a_uint); - - json = "{\"overflow\":"; - json += std::to_string(std::uint64_t(max_uint) + 1); - json += "}"; - - Json::Value j2; - Json::Reader r2; - - BEAST_EXPECT(!r2.parse(json, j2)); - - json = "{\"underflow\":"; - json += std::to_string(std::int64_t(min_int) - 1); - json += "}"; - - Json::Value j3; - Json::Reader r3; - - BEAST_EXPECT(!r3.parse(json, j3)); - - Json::Value intString{"4294967296"}; - try - { - [[maybe_unused]] std::uint32_t const uTooBig{intString.asUInt()}; - fail("4294967296", __FILE__, __LINE__); - } - catch (beast::BadLexicalCast const&) - { - pass(); - } - - intString = "4294967295"; - BEAST_EXPECT(intString.asUInt() == 4294967295u); - - intString = "0"; - BEAST_EXPECT(intString.asUInt() == 0); - - intString = "-1"; - try - { - [[maybe_unused]] std::uint32_t const uTooSmall{intString.asUInt()}; - fail("-1", __FILE__, __LINE__); - } - catch (beast::BadLexicalCast const&) - { - pass(); - } - - intString = "2147483648"; - try - { - [[maybe_unused]] std::int32_t tooPos{intString.asInt()}; - fail("2147483648", __FILE__, __LINE__); - } - catch (beast::BadLexicalCast const&) - { - pass(); - } - - intString = "2147483647"; - BEAST_EXPECT(intString.asInt() == 2147483647); - - intString = "-2147483648"; - BEAST_EXPECT(intString.asInt() == -2147483648LL); // MSVC wants the LL - - intString = "-2147483649"; - try - { - [[maybe_unused]] std::int32_t tooNeg{intString.asInt()}; - fail("-2147483649", __FILE__, __LINE__); - } - catch (beast::BadLexicalCast const&) - { - pass(); - } - } - - void - test_copy() - { - Json::Value v1{2.5}; - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - - Json::Value v2 = v1; - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - BEAST_EXPECT(v2.isDouble()); - BEAST_EXPECT(v2.asDouble() == 2.5); - BEAST_EXPECT(v1 == v2); - - v1 = v2; - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - BEAST_EXPECT(v2.isDouble()); - BEAST_EXPECT(v2.asDouble() == 2.5); - BEAST_EXPECT(v1 == v2); - - pass(); - } - - void - test_move() - { - Json::Value v1{2.5}; - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - - Json::Value v2 = std::move(v1); - BEAST_EXPECT(!v1); - BEAST_EXPECT(v2.isDouble()); - BEAST_EXPECT(v2.asDouble() == 2.5); - BEAST_EXPECT(v1 != v2); - - v1 = std::move(v2); - BEAST_EXPECT(v1.isDouble()); - BEAST_EXPECT(v1.asDouble() == 2.5); - BEAST_EXPECT(!v2); - BEAST_EXPECT(v1 != v2); - - pass(); - } - - void - test_comparisons() - { - Json::Value a, b; - auto testEquals = [&](std::string const& name) { - BEAST_EXPECT(a == b); - BEAST_EXPECT(a <= b); - BEAST_EXPECT(a >= b); - - BEAST_EXPECT(!(a != b)); - BEAST_EXPECT(!(a < b)); - BEAST_EXPECT(!(a > b)); - - BEAST_EXPECT(b == a); - BEAST_EXPECT(b <= a); - BEAST_EXPECT(b >= a); - - BEAST_EXPECT(!(b != a)); - BEAST_EXPECT(!(b < a)); - BEAST_EXPECT(!(b > a)); - }; - - auto testGreaterThan = [&](std::string const& name) { - BEAST_EXPECT(!(a == b)); - BEAST_EXPECT(!(a <= b)); - BEAST_EXPECT(a >= b); - - BEAST_EXPECT(a != b); - BEAST_EXPECT(!(a < b)); - BEAST_EXPECT(a > b); - - BEAST_EXPECT(!(b == a)); - BEAST_EXPECT(b <= a); - BEAST_EXPECT(!(b >= a)); - - BEAST_EXPECT(b != a); - BEAST_EXPECT(b < a); - BEAST_EXPECT(!(b > a)); - }; - - a["a"] = Json::UInt(0); - b["a"] = Json::Int(0); - testEquals("zero"); - - b["a"] = Json::Int(-1); - testGreaterThan("negative"); - - Json::Int big = std::numeric_limits::max(); - Json::UInt bigger = big; - bigger++; - - a["a"] = bigger; - b["a"] = big; - testGreaterThan("big"); - } - - void - test_compact() - { - Json::Value j; - Json::Reader r; - char const* s("{\"array\":[{\"12\":23},{},null,false,0.5]}"); - - auto countLines = [](std::string const& str) { - return 1 + std::count_if(str.begin(), str.end(), [](char c) { - return c == '\n'; - }); - }; - - BEAST_EXPECT(r.parse(s, j)); - { - std::stringstream ss; - ss << j; - BEAST_EXPECT(countLines(ss.str()) > 1); - } - { - std::stringstream ss; - ss << Json::Compact(std::move(j)); - BEAST_EXPECT(countLines(ss.str()) == 1); - } - } - - void - test_conversions() - { - // We have Json::Int, but not Json::Double or Json::Real. - // We have Json::Int, Json::Value::Int, and Json::ValueType::intValue. - // We have Json::ValueType::realValue but Json::Value::asDouble. - // TODO: What's the thinking here? - { - // null - Json::Value val; - BEAST_EXPECT(val.isNull()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == ""); - BEAST_EXPECT(val.asInt() == 0); - BEAST_EXPECT(val.asUInt() == 0); - BEAST_EXPECT(val.asDouble() == 0.0); - BEAST_EXPECT(val.asBool() == false); - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::objectValue)); - } - { - // int - Json::Value val = -1234; - BEAST_EXPECT(val.isInt()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == "-1234"); - BEAST_EXPECT(val.asInt() == -1234); - // BEAST_EXPECT(val.asUInt() == ?); // - // asserts or throws - BEAST_EXPECT(val.asDouble() == -1234.0); - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // uint - Json::Value val = 1234U; - BEAST_EXPECT(val.isUInt()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == "1234"); - BEAST_EXPECT(val.asInt() == 1234); - BEAST_EXPECT(val.asUInt() == 1234u); - BEAST_EXPECT(val.asDouble() == 1234.0); - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // real - Json::Value val = 2.0; - BEAST_EXPECT(val.isDouble()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT( - std::regex_match(val.asString(), std::regex("^2\\.0*$"))); - BEAST_EXPECT(val.asInt() == 2); - BEAST_EXPECT(val.asUInt() == 2u); - BEAST_EXPECT(val.asDouble() == 2.0); - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // numeric string - Json::Value val = "54321"; - BEAST_EXPECT(val.isString()); - BEAST_EXPECT(strcmp(val.asCString(), "54321") == 0); - BEAST_EXPECT(val.asString() == "54321"); - BEAST_EXPECT(val.asInt() == 54321); - BEAST_EXPECT(val.asUInt() == 54321u); - // BEAST_EXPECT(val.asDouble() == 54321.0); // - // asserts or throws - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // non-numeric string - Json::Value val(Json::stringValue); - BEAST_EXPECT(val.isString()); - BEAST_EXPECT(val.asCString() == nullptr); - BEAST_EXPECT(val.asString() == ""); - try - { - BEAST_EXPECT(val.asInt() == 0); - fail("expected exception", __FILE__, __LINE__); - } - catch (std::exception const&) - { - pass(); - } - try - { - BEAST_EXPECT(val.asUInt() == 0); - fail("expected exception", __FILE__, __LINE__); - } - catch (std::exception const&) - { - pass(); - } - // BEAST_EXPECT(val.asDouble() == ?); // - // asserts or throws - BEAST_EXPECT(val.asBool() == false); - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // bool false - Json::Value val = false; - BEAST_EXPECT(val.isBool()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == "false"); - BEAST_EXPECT(val.asInt() == 0); - BEAST_EXPECT(val.asUInt() == 0); - BEAST_EXPECT(val.asDouble() == 0.0); - BEAST_EXPECT(val.asBool() == false); - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // bool true - Json::Value val = true; - BEAST_EXPECT(val.isBool()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts - BEAST_EXPECT(val.asString() == "true"); - BEAST_EXPECT(val.asInt() == 1); - BEAST_EXPECT(val.asUInt() == 1); - BEAST_EXPECT(val.asDouble() == 1.0); - BEAST_EXPECT(val.asBool() == true); - - BEAST_EXPECT(!val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // array type - Json::Value val(Json::arrayValue); - BEAST_EXPECT(val.isArray()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts BEAST_EXPECT(val.asString() == ?); // asserts or - // throws BEAST_EXPECT(val.asInt() == ?); // asserts or - // throws BEAST_EXPECT(val.asUInt() == ?); // asserts or - // throws BEAST_EXPECT(val.asDouble() == ?); // asserts or - // throws - BEAST_EXPECT(val.asBool() == false); // empty or not - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::objectValue)); - } - { - // object type - Json::Value val(Json::objectValue); - BEAST_EXPECT(val.isObject()); - // BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); // - // asserts BEAST_EXPECT(strcmp (val.asCString(), ?) == 0); - // // asserts BEAST_EXPECT(val.asString() == ?); // asserts - // or throws BEAST_EXPECT(val.asInt() == ?); // asserts or - // throws BEAST_EXPECT(val.asUInt() == ?); // asserts or - // throws - BEAST_EXPECT(val.asBool() == false); // empty or not - - BEAST_EXPECT(val.isConvertibleTo(Json::nullValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::intValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::uintValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::realValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::stringValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::booleanValue)); - BEAST_EXPECT(!val.isConvertibleTo(Json::arrayValue)); - BEAST_EXPECT(val.isConvertibleTo(Json::objectValue)); - } - } - - void - test_access() - { - Json::Value val; - BEAST_EXPECT(val.type() == Json::nullValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - { - Json::Value const constVal = val; - BEAST_EXPECT(constVal[7u].type() == Json::nullValue); - BEAST_EXPECT(!constVal.isMember("key")); - BEAST_EXPECT(constVal["key"].type() == Json::nullValue); - BEAST_EXPECT(constVal.getMemberNames().empty()); - BEAST_EXPECT(constVal.get(1u, "default0") == "default0"); - BEAST_EXPECT(constVal.get(std::string("not"), "oh") == "oh"); - BEAST_EXPECT(constVal.get("missing", "default2") == "default2"); - } - - val = -7; - BEAST_EXPECT(val.type() == Json::intValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = 42u; - BEAST_EXPECT(val.type() == Json::uintValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = 3.14159; - BEAST_EXPECT(val.type() == Json::realValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = true; - BEAST_EXPECT(val.type() == Json::booleanValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = "string"; - BEAST_EXPECT(val.type() == Json::stringValue); - BEAST_EXPECT(val.size() == 0); - BEAST_EXPECT(!val.isValidIndex(0)); - BEAST_EXPECT(!val.isMember("key")); - - val = Json::Value(Json::objectValue); - BEAST_EXPECT(val.type() == Json::objectValue); - BEAST_EXPECT(val.size() == 0); - static Json::StaticString const staticThree("three"); - val[staticThree] = 3; - val["two"] = 2; - BEAST_EXPECT(val.size() == 2); - BEAST_EXPECT(val.isValidIndex(1)); - BEAST_EXPECT(!val.isValidIndex(2)); - BEAST_EXPECT(val[staticThree] == 3); - BEAST_EXPECT(val.isMember("two")); - BEAST_EXPECT(val.isMember(staticThree)); - BEAST_EXPECT(!val.isMember("key")); - { - Json::Value const constVal = val; - BEAST_EXPECT(constVal["two"] == 2); - BEAST_EXPECT(constVal["four"].type() == Json::nullValue); - BEAST_EXPECT(constVal[staticThree] == 3); - BEAST_EXPECT(constVal.isMember("two")); - BEAST_EXPECT(constVal.isMember(staticThree)); - BEAST_EXPECT(!constVal.isMember("key")); - BEAST_EXPECT(val.get(std::string("two"), "backup") == 2); - BEAST_EXPECT(val.get("missing", "default2") == "default2"); - } - - val = Json::Value(Json::arrayValue); - BEAST_EXPECT(val.type() == Json::arrayValue); - BEAST_EXPECT(val.size() == 0); - val[0u] = "zero"; - val[1u] = "one"; - BEAST_EXPECT(val.size() == 2); - BEAST_EXPECT(val.isValidIndex(1)); - BEAST_EXPECT(!val.isValidIndex(2)); - BEAST_EXPECT(val[20u].type() == Json::nullValue); - BEAST_EXPECT(!val.isMember("key")); - { - Json::Value const constVal = val; - BEAST_EXPECT(constVal[0u] == "zero"); - BEAST_EXPECT(constVal[2u].type() == Json::nullValue); - BEAST_EXPECT(!constVal.isMember("key")); - BEAST_EXPECT(val.get(1u, "default0") == "one"); - BEAST_EXPECT(val.get(3u, "default1") == "default1"); - } - } - - void - test_removeMember() - { - Json::Value val; - BEAST_EXPECT( - val.removeMember(std::string("member")).type() == Json::nullValue); - - val = Json::Value(Json::objectValue); - static Json::StaticString const staticThree("three"); - val[staticThree] = 3; - val["two"] = 2; - BEAST_EXPECT(val.size() == 2); - - BEAST_EXPECT( - val.removeMember(std::string("six")).type() == Json::nullValue); - BEAST_EXPECT(val.size() == 2); - - BEAST_EXPECT(val.removeMember(staticThree) == 3); - BEAST_EXPECT(val.size() == 1); - - BEAST_EXPECT(val.removeMember(staticThree).type() == Json::nullValue); - BEAST_EXPECT(val.size() == 1); - - BEAST_EXPECT(val.removeMember(std::string("two")) == 2); - BEAST_EXPECT(val.size() == 0); - - BEAST_EXPECT( - val.removeMember(std::string("two")).type() == Json::nullValue); - BEAST_EXPECT(val.size() == 0); - } - - void - test_iterator() - { - { - // Iterating an array. - Json::Value arr{Json::arrayValue}; - arr[0u] = "zero"; - arr[1u] = "one"; - arr[2u] = "two"; - arr[3u] = "three"; - - Json::ValueIterator const b{arr.begin()}; - Json::ValueIterator const e{arr.end()}; - - Json::ValueIterator i1 = b; - Json::ValueIterator i2 = e; - --i2; - - // key(), index(), and memberName() on an object iterator. - BEAST_EXPECT(b != e); - BEAST_EXPECT(!(b == e)); - BEAST_EXPECT(i1.key() == 0); - BEAST_EXPECT(i2.key() == 3); - BEAST_EXPECT(i1.index() == 0); - BEAST_EXPECT(i2.index() == 3); - BEAST_EXPECT(std::strcmp(i1.memberName(), "") == 0); - BEAST_EXPECT(std::strcmp(i2.memberName(), "") == 0); - - // Pre and post increment and decrement. - *i1++ = "0"; - BEAST_EXPECT(*i1 == "one"); - *i1 = "1"; - ++i1; - - *i2-- = "3"; - BEAST_EXPECT(*i2 == "two"); - BEAST_EXPECT(i1 == i2); - *i2 = "2"; - BEAST_EXPECT(*i1 == "2"); - } - { - // Iterating a const object. - Json::Value const obj{[]() { - Json::Value obj{Json::objectValue}; - obj["0"] = 0; - obj["1"] = 1; - obj["2"] = 2; - obj["3"] = 3; - return obj; - }()}; - - Json::ValueConstIterator i1{obj.begin()}; - Json::ValueConstIterator i2{obj.end()}; - --i2; - - // key(), index(), and memberName() on an object iterator. - BEAST_EXPECT(i1 != i2); - BEAST_EXPECT(!(i1 == i2)); - BEAST_EXPECT(i1.key() == "0"); - BEAST_EXPECT(i2.key() == "3"); - BEAST_EXPECT(i1.index() == -1); - BEAST_EXPECT(i2.index() == -1); - BEAST_EXPECT(std::strcmp(i1.memberName(), "0") == 0); - BEAST_EXPECT(std::strcmp(i2.memberName(), "3") == 0); - - // Pre and post increment and decrement. - BEAST_EXPECT(*i1++ == 0); - BEAST_EXPECT(*i1 == 1); - ++i1; - - BEAST_EXPECT(*i2-- == 3); - BEAST_EXPECT(*i2 == 2); - BEAST_EXPECT(i1 == i2); - BEAST_EXPECT(*i1 == 2); - } - { - // Iterating a non-const null object. - Json::Value nul{}; - BEAST_EXPECT(nul.begin() == nul.end()); - } - { - // Iterating a const Int. - Json::Value const i{-3}; - BEAST_EXPECT(i.begin() == i.end()); - } - } - - void - test_nest_limits() - { - Json::Reader r; - { - auto nest = [](std::uint32_t depth) -> std::string { - std::string s = "{"; - for (std::uint32_t i{1}; i <= depth; ++i) - s += "\"obj\":{"; - for (std::uint32_t i{1}; i <= depth; ++i) - s += "}"; - s += "}"; - return s; - }; - - { - // Within object nest limit - auto json{nest(std::min(10u, Json::Reader::nest_limit))}; - Json::Value j; - BEAST_EXPECT(r.parse(json, j)); - } - - { - // Exceed object nest limit - auto json{nest(Json::Reader::nest_limit + 1)}; - Json::Value j; - BEAST_EXPECT(!r.parse(json, j)); - } - } - - auto nest = [](std::uint32_t depth) -> std::string { - std::string s = "{"; - for (std::uint32_t i{1}; i <= depth; ++i) - s += "\"array\":[{"; - for (std::uint32_t i{1}; i <= depth; ++i) - s += "]}"; - s += "}"; - return s; - }; - { - // Exceed array nest limit - auto json{nest(Json::Reader::nest_limit + 1)}; - Json::Value j; - BEAST_EXPECT(!r.parse(json, j)); - } - } - - void - test_leak() - { - // When run with the address sanitizer, this test confirms there is no - // memory leak with the scenarios below. - { - Json::Value a; - a[0u] = 1; - BEAST_EXPECT(a.type() == Json::arrayValue); - BEAST_EXPECT(a[0u].type() == Json::intValue); - a = std::move(a[0u]); - BEAST_EXPECT(a.type() == Json::intValue); - } - { - Json::Value b; - Json::Value temp; - temp["a"] = "Probably avoids the small string optimization"; - temp["b"] = "Also probably avoids the small string optimization"; - BEAST_EXPECT(temp.type() == Json::objectValue); - b.append(temp); - BEAST_EXPECT(temp.type() == Json::objectValue); - BEAST_EXPECT(b.size() == 1); - - b.append(std::move(temp)); - BEAST_EXPECT(b.size() == 2); - - // Note that the type() == nullValue check is implementation - // specific and not guaranteed to be valid in the future. - BEAST_EXPECT(temp.type() == Json::nullValue); - } - } - - void - run() override - { - test_StaticString(); - test_types(); - test_compare(); - test_bool(); - test_bad_json(); - test_edge_cases(); - test_copy(); - test_move(); - test_comparisons(); - test_compact(); - test_conversions(); - test_access(); - test_removeMember(); - test_iterator(); - test_nest_limits(); - test_leak(); - } -}; - -BEAST_DEFINE_TESTSUITE(json_value, json, ripple); - -} // namespace ripple diff --git a/src/test/rpc/AccountOffers_test.cpp b/src/test/rpc/AccountOffers_test.cpp index 2aa85ac9b9..59a6942ed2 100644 --- a/src/test/rpc/AccountOffers_test.cpp +++ b/src/test/rpc/AccountOffers_test.cpp @@ -190,12 +190,6 @@ public: } { - // now make a limit (= 0) query for the same data - // since we operate on the admin port, the limit - // value of 0 is not adjusted into tuned ranges for admin requests - // so we literally get 0 elements in that case. For non-admin - // requests, we get limit defaults applied thus all our results - // come back (we are below the min results limit) Json::Value jvParams; jvParams[jss::account] = bob.human(); jvParams[jss::limit] = 0u; @@ -203,18 +197,7 @@ public: "json", "account_offers", jvParams.toStyledString())[jss::result]; - auto const& jro = jrr[jss::offers]; - if (asAdmin) - { - // limit == 0 is invalid - BEAST_EXPECT(jrr.isMember(jss::error_message)); - } - else - { - // Call should enforce min limit of 10 - BEAST_EXPECT(checkArraySize(jro, 3u)); - BEAST_EXPECT(!jrr.isMember(jss::marker)); - } + BEAST_EXPECT(jrr.isMember(jss::error_message)); } } diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 82809b5c5b..cbe7cc8f4a 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -193,26 +193,26 @@ class AccountTx_test : public beast::unit_test::suite j[jss::result][jss::error] == RPC::get_error_info(code).token; }; - Json::Value jParms; - jParms[jss::api_version] = apiVersion; + Json::Value jParams; + jParams[jss::api_version] = apiVersion; BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(jParms)), + env.rpc("json", "account_tx", to_string(jParams)), rpcINVALID_PARAMS)); - jParms[jss::account] = "0xDEADBEEF"; + jParams[jss::account] = "0xDEADBEEF"; BEAST_EXPECT(isErr( - env.rpc("json", "account_tx", to_string(jParms)), + env.rpc("json", "account_tx", to_string(jParams)), rpcACT_MALFORMED)); - jParms[jss::account] = A1.human(); + jParams[jss::account] = A1.human(); BEAST_EXPECT(hasTxs( - env.rpc(apiVersion, "json", "account_tx", to_string(jParms)))); + env.rpc(apiVersion, "json", "account_tx", to_string(jParams)))); // Ledger min/max index { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index_min] = -1; p[jss::ledger_index_max] = -1; BEAST_EXPECT(hasTxs( @@ -247,7 +247,7 @@ class AccountTx_test : public beast::unit_test::suite } // Ledger index min only { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index_min] = -1; BEAST_EXPECT(hasTxs( env.rpc(apiVersion, "json", "account_tx", to_string(p)))); @@ -270,7 +270,7 @@ class AccountTx_test : public beast::unit_test::suite // Ledger index max only { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index_max] = -1; BEAST_EXPECT(hasTxs( env.rpc(apiVersion, "json", "account_tx", to_string(p)))); @@ -298,7 +298,7 @@ class AccountTx_test : public beast::unit_test::suite // Ledger Sequence { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index] = env.closed()->info().seq; BEAST_EXPECT(hasTxs( @@ -319,7 +319,7 @@ class AccountTx_test : public beast::unit_test::suite // Ledger Hash { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_hash] = to_string(env.closed()->info().hash); BEAST_EXPECT(hasTxs( @@ -332,9 +332,9 @@ class AccountTx_test : public beast::unit_test::suite // Ledger index max/min/index all specified // ERRORS out with invalid Parenthesis { - jParms[jss::account] = "0xDEADBEEF"; - jParms[jss::account] = A1.human(); - Json::Value p{jParms}; + jParams[jss::account] = "0xDEADBEEF"; + jParams[jss::account] = A1.human(); + Json::Value p{jParams}; p[jss::ledger_index_max] = -1; p[jss::ledger_index_min] = -1; @@ -351,7 +351,7 @@ class AccountTx_test : public beast::unit_test::suite // Ledger index max only { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::ledger_index_max] = env.current()->info().seq; if (apiVersion < 2u) BEAST_EXPECT(hasTxs( @@ -382,7 +382,7 @@ class AccountTx_test : public beast::unit_test::suite } // test binary and forward for bool/non bool values { - Json::Value p{jParms}; + Json::Value p{jParams}; p[jss::binary] = "asdf"; if (apiVersion < 2u) { @@ -410,6 +410,117 @@ class AccountTx_test : public beast::unit_test::suite result = env.rpc("json", "account_tx", to_string(p)); BEAST_EXPECT(result[jss::result][jss::status] == "success"); } + // test limit with malformed values + { + Json::Value p{jParams}; + + // Test case: limit = 0 should fail (below minimum) + p[jss::limit] = 0; + BEAST_EXPECT(isErr( + env.rpc("json", "account_tx", to_string(p)), + rpcINVALID_PARAMS)); + + // Test case: limit = 1.2 should fail (not an integer) + p[jss::limit] = 1.2; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = "10" should fail (string instead of integer) + p[jss::limit] = "10"; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = true should fail (boolean instead of integer) + p[jss::limit] = true; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = false should fail (boolean instead of integer) + p[jss::limit] = false; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = -1 should fail (negative number) + p[jss::limit] = -1; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = [] should fail (array instead of integer) + p[jss::limit] = Json::Value(Json::arrayValue); + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = {} should fail (object instead of integer) + p[jss::limit] = Json::Value(Json::objectValue); + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = "malformed" should fail (malformed string) + p[jss::limit] = "malformed"; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = ["limit"] should fail (array with string) + p[jss::limit] = Json::Value(Json::arrayValue); + p[jss::limit].append("limit"); + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = {"limit": 10} should fail (object with + // property) + p[jss::limit] = Json::Value(Json::objectValue); + p[jss::limit][jss::limit] = 10; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + + // Test case: limit = 10 should succeed (valid integer) + p[jss::limit] = 10; + BEAST_EXPECT( + env.rpc( + "json", + "account_tx", + to_string(p))[jss::result][jss::status] == "success"); + } } void diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index 7177ab847c..0d5366d3ef 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -1633,6 +1633,20 @@ public: "Invalid field 'limit', not unsigned integer."); } + { + Json::Value jvParams; + jvParams[jss::ledger_index] = "validated"; + jvParams[jss::taker] = env.master.human(); + jvParams[jss::limit] = 0; // must be > 0 + jvParams[jss::taker_pays][jss::currency] = "XRP"; + jvParams[jss::taker_gets][jss::currency] = "USD"; + jvParams[jss::taker_gets][jss::issuer] = gw.human(); + auto const jrr = env.rpc( + "json", "book_offers", to_string(jvParams))[jss::result]; + BEAST_EXPECT(jrr[jss::error] == "invalidParams"); + BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'limit'."); + } + { Json::Value jvParams; jvParams[jss::ledger_index] = "validated"; @@ -1710,11 +1724,6 @@ public: BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? 1u : 0u)); // NOTE - a marker field is not returned for this method - jvParams[jss::limit] = 0u; - jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result]; - BEAST_EXPECT(jrr[jss::offers].isArray()); - BEAST_EXPECT(jrr[jss::offers].size() == 0u); - jvParams[jss::limit] = RPC::Tuning::bookOffers.rmax + 1; jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result]; BEAST_EXPECT(jrr[jss::offers].isArray()); diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index f97283c955..caa308dd1f 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -12,5 +12,7 @@ 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) +xrpl_add_test(json) +target_link_libraries(xrpl.test.json PRIVATE xrpl.imports.test) xrpl_add_test(net) target_link_libraries(xrpl.test.net PRIVATE xrpl.imports.test) diff --git a/src/test/json/Output_test.cpp b/src/tests/libxrpl/json/Output.cpp similarity index 51% rename from src/test/json/Output_test.cpp rename to src/tests/libxrpl/json/Output.cpp index 6421682b01..3b542d81b0 100644 --- a/src/test/json/Output_test.cpp +++ b/src/tests/libxrpl/json/Output.cpp @@ -17,50 +17,43 @@ */ //============================================================================== -#include - +#include #include #include -namespace Json { +#include -struct Output_test : ripple::test::TestOutputSuite +#include + +using namespace ripple; +using namespace Json; + +TEST_SUITE_BEGIN("JsonOutput"); + +static void +checkOutput(std::string const& valueDesc) { - void - runTest(std::string const& name, std::string const& valueDesc) - { - setup(name); - Json::Value value; - BEAST_EXPECT(Json::Reader().parse(valueDesc, value)); - auto out = stringOutput(output_); - outputJson(value, out); + std::string output; + Json::Value value; + REQUIRE(Json::Reader().parse(valueDesc, value)); + auto out = stringOutput(output); + outputJson(value, out); - // Compare with the original version. - auto expected = Json::FastWriter().write(value); - expectResult(expected); - expectResult(valueDesc); - expectResult(jsonAsString(value)); - } + auto expected = Json::FastWriter().write(value); + CHECK(output == expected); + CHECK(output == valueDesc); + CHECK(output == jsonAsString(value)); +} - void - runTest(std::string const& name) - { - runTest(name, name); - } +TEST_CASE("output cases") +{ + checkOutput("{}"); + checkOutput("[]"); + checkOutput(R"([23,4.25,true,null,"string"])"); + checkOutput(R"({"hello":"world"})"); + checkOutput("[{}]"); + checkOutput("[[]]"); + checkOutput(R"({"array":[{"12":23},{},null,false,0.5]})"); +} - void - run() override - { - runTest("empty dict", "{}"); - runTest("empty array", "[]"); - runTest("array", "[23,4.25,true,null,\"string\"]"); - runTest("dict", "{\"hello\":\"world\"}"); - runTest("array dict", "[{}]"); - runTest("array array", "[[]]"); - runTest("more complex", "{\"array\":[{\"12\":23},{},null,false,0.5]}"); - } -}; - -BEAST_DEFINE_TESTSUITE(Output, json, ripple); - -} // namespace Json +TEST_SUITE_END(); diff --git a/src/tests/libxrpl/json/Value.cpp b/src/tests/libxrpl/json/Value.cpp new file mode 100644 index 0000000000..3da8e14ba4 --- /dev/null +++ b/src/tests/libxrpl/json/Value.cpp @@ -0,0 +1,1294 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace ripple { + +TEST_SUITE_BEGIN("json_value"); + +TEST_CASE("construct and compare Json::StaticString") +{ + static constexpr char sample[]{"Contents of a Json::StaticString"}; + + static constexpr Json::StaticString test1(sample); + char const* addrTest1{test1}; + + CHECK(addrTest1 == &sample[0]); + CHECK(test1.c_str() == &sample[0]); + + static constexpr Json::StaticString test2{ + "Contents of a Json::StaticString"}; + static constexpr Json::StaticString test3{"Another StaticString"}; + + CHECK(test1 == test2); + CHECK(test1 != test3); + + std::string str{sample}; + CHECK(str == test2); + CHECK(str != test3); + CHECK(test2 == str); + CHECK(test3 != str); +} + +TEST_CASE("different types") +{ + // Exercise ValueType constructor + static constexpr Json::StaticString staticStr{"staticStr"}; + + auto testCopy = [](Json::ValueType typ) { + Json::Value val{typ}; + Json::Value cpy{val}; + CHECK(val.type() == typ); + CHECK(cpy.type() == typ); + return val; + }; + { + Json::Value const nullV{testCopy(Json::nullValue)}; + CHECK(nullV.isNull()); + CHECK(!nullV.isBool()); + CHECK(!nullV.isInt()); + CHECK(!nullV.isUInt()); + CHECK(!nullV.isIntegral()); + CHECK(!nullV.isDouble()); + CHECK(!nullV.isNumeric()); + CHECK(!nullV.isString()); + CHECK(!nullV.isArray()); + CHECK(nullV.isArrayOrNull()); + CHECK(!nullV.isObject()); + CHECK(nullV.isObjectOrNull()); + } + { + Json::Value const intV{testCopy(Json::intValue)}; + CHECK(!intV.isNull()); + CHECK(!intV.isBool()); + CHECK(intV.isInt()); + CHECK(!intV.isUInt()); + CHECK(intV.isIntegral()); + CHECK(!intV.isDouble()); + CHECK(intV.isNumeric()); + CHECK(!intV.isString()); + CHECK(!intV.isArray()); + CHECK(!intV.isArrayOrNull()); + CHECK(!intV.isObject()); + CHECK(!intV.isObjectOrNull()); + } + { + Json::Value const uintV{testCopy(Json::uintValue)}; + CHECK(!uintV.isNull()); + CHECK(!uintV.isBool()); + CHECK(!uintV.isInt()); + CHECK(uintV.isUInt()); + CHECK(uintV.isIntegral()); + CHECK(!uintV.isDouble()); + CHECK(uintV.isNumeric()); + CHECK(!uintV.isString()); + CHECK(!uintV.isArray()); + CHECK(!uintV.isArrayOrNull()); + CHECK(!uintV.isObject()); + CHECK(!uintV.isObjectOrNull()); + } + { + Json::Value const realV{testCopy(Json::realValue)}; + CHECK(!realV.isNull()); + CHECK(!realV.isBool()); + CHECK(!realV.isInt()); + CHECK(!realV.isUInt()); + CHECK(!realV.isIntegral()); + CHECK(realV.isDouble()); + CHECK(realV.isNumeric()); + CHECK(!realV.isString()); + CHECK(!realV.isArray()); + CHECK(!realV.isArrayOrNull()); + CHECK(!realV.isObject()); + CHECK(!realV.isObjectOrNull()); + } + { + Json::Value const stringV{testCopy(Json::stringValue)}; + CHECK(!stringV.isNull()); + CHECK(!stringV.isBool()); + CHECK(!stringV.isInt()); + CHECK(!stringV.isUInt()); + CHECK(!stringV.isIntegral()); + CHECK(!stringV.isDouble()); + CHECK(!stringV.isNumeric()); + CHECK(stringV.isString()); + CHECK(!stringV.isArray()); + CHECK(!stringV.isArrayOrNull()); + CHECK(!stringV.isObject()); + CHECK(!stringV.isObjectOrNull()); + } + { + Json::Value const staticStrV{staticStr}; + { + Json::Value cpy{staticStrV}; + CHECK(staticStrV.type() == Json::stringValue); + CHECK(cpy.type() == Json::stringValue); + } + CHECK(!staticStrV.isNull()); + CHECK(!staticStrV.isBool()); + CHECK(!staticStrV.isInt()); + CHECK(!staticStrV.isUInt()); + CHECK(!staticStrV.isIntegral()); + CHECK(!staticStrV.isDouble()); + CHECK(!staticStrV.isNumeric()); + CHECK(staticStrV.isString()); + CHECK(!staticStrV.isArray()); + CHECK(!staticStrV.isArrayOrNull()); + CHECK(!staticStrV.isObject()); + CHECK(!staticStrV.isObjectOrNull()); + } + { + Json::Value const boolV{testCopy(Json::booleanValue)}; + CHECK(!boolV.isNull()); + CHECK(boolV.isBool()); + CHECK(!boolV.isInt()); + CHECK(!boolV.isUInt()); + CHECK(boolV.isIntegral()); + CHECK(!boolV.isDouble()); + CHECK(boolV.isNumeric()); + CHECK(!boolV.isString()); + CHECK(!boolV.isArray()); + CHECK(!boolV.isArrayOrNull()); + CHECK(!boolV.isObject()); + CHECK(!boolV.isObjectOrNull()); + } + { + Json::Value const arrayV{testCopy(Json::arrayValue)}; + CHECK(!arrayV.isNull()); + CHECK(!arrayV.isBool()); + CHECK(!arrayV.isInt()); + CHECK(!arrayV.isUInt()); + CHECK(!arrayV.isIntegral()); + CHECK(!arrayV.isDouble()); + CHECK(!arrayV.isNumeric()); + CHECK(!arrayV.isString()); + CHECK(arrayV.isArray()); + CHECK(arrayV.isArrayOrNull()); + CHECK(!arrayV.isObject()); + CHECK(!arrayV.isObjectOrNull()); + } + { + Json::Value const objectV{testCopy(Json::objectValue)}; + CHECK(!objectV.isNull()); + CHECK(!objectV.isBool()); + CHECK(!objectV.isInt()); + CHECK(!objectV.isUInt()); + CHECK(!objectV.isIntegral()); + CHECK(!objectV.isDouble()); + CHECK(!objectV.isNumeric()); + CHECK(!objectV.isString()); + CHECK(!objectV.isArray()); + CHECK(!objectV.isArrayOrNull()); + CHECK(objectV.isObject()); + CHECK(objectV.isObjectOrNull()); + } +} + +TEST_CASE("compare strings") +{ + auto doCompare = [&](Json::Value const& lhs, + Json::Value const& rhs, + bool lhsEqRhs, + bool lhsLtRhs, + int line) { + CAPTURE(line); + CHECK((lhs == rhs) == lhsEqRhs); + CHECK((lhs != rhs) != lhsEqRhs); + CHECK((lhs < rhs) == (!(lhsEqRhs || !lhsLtRhs))); + CHECK((lhs <= rhs) == (lhsEqRhs || lhsLtRhs)); + CHECK((lhs >= rhs) == (lhsEqRhs || !lhsLtRhs)); + CHECK((lhs > rhs) == (!(lhsEqRhs || lhsLtRhs))); + }; + + Json::Value const null0; + Json::Value const intNeg1{-1}; + Json::Value const int0{Json::intValue}; + Json::Value const intPos1{1}; + Json::Value const uint0{Json::uintValue}; + Json::Value const uint1{1u}; + Json::Value const realNeg1{-1.0}; + Json::Value const real0{Json::realValue}; + Json::Value const realPos1{1.0}; + Json::Value const str0{Json::stringValue}; + Json::Value const str1{"1"}; + Json::Value const boolF{false}; + Json::Value const boolT{true}; + Json::Value const array0{Json::arrayValue}; + Json::Value const array1{[]() { + Json::Value array1; + array1[0u] = 1; + return array1; + }()}; + Json::Value const obj0{Json::objectValue}; + Json::Value const obj1{[]() { + Json::Value obj1; + obj1["one"] = 1; + return obj1; + }()}; + +#pragma push_macro("DO_COMPARE") + // DO_COMPARE(lhs, rhs, lhsEqualsToRhs lhsLessThanRhs) +#define DO_COMPARE(lhs, rhs, eq, lt) doCompare(lhs, rhs, eq, lt, __LINE__) + DO_COMPARE(null0, Json::Value{}, true, false); + DO_COMPARE(null0, intNeg1, false, true); + DO_COMPARE(null0, int0, false, true); + DO_COMPARE(null0, intPos1, false, true); + DO_COMPARE(null0, uint0, false, true); + DO_COMPARE(null0, uint1, false, true); + DO_COMPARE(null0, realNeg1, false, true); + DO_COMPARE(null0, real0, false, true); + DO_COMPARE(null0, realPos1, false, true); + DO_COMPARE(null0, str0, false, true); + DO_COMPARE(null0, str1, false, true); + DO_COMPARE(null0, boolF, false, true); + DO_COMPARE(null0, boolT, false, true); + DO_COMPARE(null0, array0, false, true); + DO_COMPARE(null0, array1, false, true); + DO_COMPARE(null0, obj0, false, true); + DO_COMPARE(null0, obj1, false, true); + + DO_COMPARE(intNeg1, null0, false, false); + DO_COMPARE(intNeg1, intNeg1, true, false); + DO_COMPARE(intNeg1, int0, false, true); + DO_COMPARE(intNeg1, intPos1, false, true); + DO_COMPARE(intNeg1, uint0, false, true); + DO_COMPARE(intNeg1, uint1, false, true); + DO_COMPARE(intNeg1, realNeg1, false, true); + DO_COMPARE(intNeg1, real0, false, true); + DO_COMPARE(intNeg1, realPos1, false, true); + DO_COMPARE(intNeg1, str0, false, true); + DO_COMPARE(intNeg1, str1, false, true); + DO_COMPARE(intNeg1, boolF, false, true); + DO_COMPARE(intNeg1, boolT, false, true); + DO_COMPARE(intNeg1, array0, false, true); + DO_COMPARE(intNeg1, array1, false, true); + DO_COMPARE(intNeg1, obj0, false, true); + DO_COMPARE(intNeg1, obj1, false, true); + + DO_COMPARE(int0, null0, false, false); + DO_COMPARE(int0, intNeg1, false, false); + DO_COMPARE(int0, int0, true, false); + DO_COMPARE(int0, intPos1, false, true); + DO_COMPARE(int0, uint0, true, false); + DO_COMPARE(int0, uint1, false, true); + DO_COMPARE(int0, realNeg1, false, true); + DO_COMPARE(int0, real0, false, true); + DO_COMPARE(int0, realPos1, false, true); + DO_COMPARE(int0, str0, false, true); + DO_COMPARE(int0, str1, false, true); + DO_COMPARE(int0, boolF, false, true); + DO_COMPARE(int0, boolT, false, true); + DO_COMPARE(int0, array0, false, true); + DO_COMPARE(int0, array1, false, true); + DO_COMPARE(int0, obj0, false, true); + DO_COMPARE(int0, obj1, false, true); + + DO_COMPARE(intPos1, null0, false, false); + DO_COMPARE(intPos1, intNeg1, false, false); + DO_COMPARE(intPos1, int0, false, false); + DO_COMPARE(intPos1, intPos1, true, false); + DO_COMPARE(intPos1, uint0, false, false); + DO_COMPARE(intPos1, uint1, true, false); + DO_COMPARE(intPos1, realNeg1, false, true); + DO_COMPARE(intPos1, real0, false, true); + DO_COMPARE(intPos1, realPos1, false, true); + DO_COMPARE(intPos1, str0, false, true); + DO_COMPARE(intPos1, str1, false, true); + DO_COMPARE(intPos1, boolF, false, true); + DO_COMPARE(intPos1, boolT, false, true); + DO_COMPARE(intPos1, array0, false, true); + DO_COMPARE(intPos1, array1, false, true); + DO_COMPARE(intPos1, obj0, false, true); + DO_COMPARE(intPos1, obj1, false, true); + + DO_COMPARE(uint0, null0, false, false); + DO_COMPARE(uint0, intNeg1, false, false); + DO_COMPARE(uint0, int0, true, false); + DO_COMPARE(uint0, intPos1, false, true); + DO_COMPARE(uint0, uint0, true, false); + DO_COMPARE(uint0, uint1, false, true); + DO_COMPARE(uint0, realNeg1, false, true); + DO_COMPARE(uint0, real0, false, true); + DO_COMPARE(uint0, realPos1, false, true); + DO_COMPARE(uint0, str0, false, true); + DO_COMPARE(uint0, str1, false, true); + DO_COMPARE(uint0, boolF, false, true); + DO_COMPARE(uint0, boolT, false, true); + DO_COMPARE(uint0, array0, false, true); + DO_COMPARE(uint0, array1, false, true); + DO_COMPARE(uint0, obj0, false, true); + DO_COMPARE(uint0, obj1, false, true); + + DO_COMPARE(uint1, null0, false, false); + DO_COMPARE(uint1, intNeg1, false, false); + DO_COMPARE(uint1, int0, false, false); + DO_COMPARE(uint1, intPos1, true, false); + DO_COMPARE(uint1, uint0, false, false); + DO_COMPARE(uint1, uint1, true, false); + DO_COMPARE(uint1, realNeg1, false, true); + DO_COMPARE(uint1, real0, false, true); + DO_COMPARE(uint1, realPos1, false, true); + DO_COMPARE(uint1, str0, false, true); + DO_COMPARE(uint1, str1, false, true); + DO_COMPARE(uint1, boolF, false, true); + DO_COMPARE(uint1, boolT, false, true); + DO_COMPARE(uint1, array0, false, true); + DO_COMPARE(uint1, array1, false, true); + DO_COMPARE(uint1, obj0, false, true); + DO_COMPARE(uint1, obj1, false, true); + + DO_COMPARE(realNeg1, null0, false, false); + DO_COMPARE(realNeg1, intNeg1, false, false); + DO_COMPARE(realNeg1, int0, false, false); + DO_COMPARE(realNeg1, intPos1, false, false); + DO_COMPARE(realNeg1, uint0, false, false); + DO_COMPARE(realNeg1, uint1, false, false); + DO_COMPARE(realNeg1, realNeg1, true, false); + DO_COMPARE(realNeg1, real0, false, true); + DO_COMPARE(realNeg1, realPos1, false, true); + DO_COMPARE(realNeg1, str0, false, true); + DO_COMPARE(realNeg1, str1, false, true); + DO_COMPARE(realNeg1, boolF, false, true); + DO_COMPARE(realNeg1, boolT, false, true); + DO_COMPARE(realNeg1, array0, false, true); + DO_COMPARE(realNeg1, array1, false, true); + DO_COMPARE(realNeg1, obj0, false, true); + DO_COMPARE(realNeg1, obj1, false, true); + + DO_COMPARE(real0, null0, false, false); + DO_COMPARE(real0, intNeg1, false, false); + DO_COMPARE(real0, int0, false, false); + DO_COMPARE(real0, intPos1, false, false); + DO_COMPARE(real0, uint0, false, false); + DO_COMPARE(real0, uint1, false, false); + DO_COMPARE(real0, realNeg1, false, false); + DO_COMPARE(real0, real0, true, false); + DO_COMPARE(real0, realPos1, false, true); + DO_COMPARE(real0, str0, false, true); + DO_COMPARE(real0, str1, false, true); + DO_COMPARE(real0, boolF, false, true); + DO_COMPARE(real0, boolT, false, true); + DO_COMPARE(real0, array0, false, true); + DO_COMPARE(real0, array1, false, true); + DO_COMPARE(real0, obj0, false, true); + DO_COMPARE(real0, obj1, false, true); + + DO_COMPARE(realPos1, null0, false, false); + DO_COMPARE(realPos1, intNeg1, false, false); + DO_COMPARE(realPos1, int0, false, false); + DO_COMPARE(realPos1, intPos1, false, false); + DO_COMPARE(realPos1, uint0, false, false); + DO_COMPARE(realPos1, uint1, false, false); + DO_COMPARE(realPos1, realNeg1, false, false); + DO_COMPARE(realPos1, real0, false, false); + DO_COMPARE(realPos1, realPos1, true, false); + DO_COMPARE(realPos1, str0, false, true); + DO_COMPARE(realPos1, str1, false, true); + DO_COMPARE(realPos1, boolF, false, true); + DO_COMPARE(realPos1, boolT, false, true); + DO_COMPARE(realPos1, array0, false, true); + DO_COMPARE(realPos1, array1, false, true); + DO_COMPARE(realPos1, obj0, false, true); + DO_COMPARE(realPos1, obj1, false, true); + + DO_COMPARE(str0, null0, false, false); + DO_COMPARE(str0, intNeg1, false, false); + DO_COMPARE(str0, int0, false, false); + DO_COMPARE(str0, intPos1, false, false); + DO_COMPARE(str0, uint0, false, false); + DO_COMPARE(str0, uint1, false, false); + DO_COMPARE(str0, realNeg1, false, false); + DO_COMPARE(str0, real0, false, false); + DO_COMPARE(str0, realPos1, false, false); + DO_COMPARE(str0, str0, true, false); + DO_COMPARE(str0, str1, false, true); + DO_COMPARE(str0, boolF, false, true); + DO_COMPARE(str0, boolT, false, true); + DO_COMPARE(str0, array0, false, true); + DO_COMPARE(str0, array1, false, true); + DO_COMPARE(str0, obj0, false, true); + DO_COMPARE(str0, obj1, false, true); + + DO_COMPARE(str1, null0, false, false); + DO_COMPARE(str1, intNeg1, false, false); + DO_COMPARE(str1, int0, false, false); + DO_COMPARE(str1, intPos1, false, false); + DO_COMPARE(str1, uint0, false, false); + DO_COMPARE(str1, uint1, false, false); + DO_COMPARE(str1, realNeg1, false, false); + DO_COMPARE(str1, real0, false, false); + DO_COMPARE(str1, realPos1, false, false); + DO_COMPARE(str1, str0, false, false); + DO_COMPARE(str1, str1, true, false); + DO_COMPARE(str1, boolF, false, true); + DO_COMPARE(str1, boolT, false, true); + DO_COMPARE(str1, array0, false, true); + DO_COMPARE(str1, array1, false, true); + DO_COMPARE(str1, obj0, false, true); + DO_COMPARE(str1, obj1, false, true); + + DO_COMPARE(boolF, null0, false, false); + DO_COMPARE(boolF, intNeg1, false, false); + DO_COMPARE(boolF, int0, false, false); + DO_COMPARE(boolF, intPos1, false, false); + DO_COMPARE(boolF, uint0, false, false); + DO_COMPARE(boolF, uint1, false, false); + DO_COMPARE(boolF, realNeg1, false, false); + DO_COMPARE(boolF, real0, false, false); + DO_COMPARE(boolF, realPos1, false, false); + DO_COMPARE(boolF, str0, false, false); + DO_COMPARE(boolF, str1, false, false); + DO_COMPARE(boolF, boolF, true, false); + DO_COMPARE(boolF, boolT, false, true); + DO_COMPARE(boolF, array0, false, true); + DO_COMPARE(boolF, array1, false, true); + DO_COMPARE(boolF, obj0, false, true); + DO_COMPARE(boolF, obj1, false, true); + + DO_COMPARE(boolT, null0, false, false); + DO_COMPARE(boolT, intNeg1, false, false); + DO_COMPARE(boolT, int0, false, false); + DO_COMPARE(boolT, intPos1, false, false); + DO_COMPARE(boolT, uint0, false, false); + DO_COMPARE(boolT, uint1, false, false); + DO_COMPARE(boolT, realNeg1, false, false); + DO_COMPARE(boolT, real0, false, false); + DO_COMPARE(boolT, realPos1, false, false); + DO_COMPARE(boolT, str0, false, false); + DO_COMPARE(boolT, str1, false, false); + DO_COMPARE(boolT, boolF, false, false); + DO_COMPARE(boolT, boolT, true, false); + DO_COMPARE(boolT, array0, false, true); + DO_COMPARE(boolT, array1, false, true); + DO_COMPARE(boolT, obj0, false, true); + DO_COMPARE(boolT, obj1, false, true); + + DO_COMPARE(array0, null0, false, false); + DO_COMPARE(array0, intNeg1, false, false); + DO_COMPARE(array0, int0, false, false); + DO_COMPARE(array0, intPos1, false, false); + DO_COMPARE(array0, uint0, false, false); + DO_COMPARE(array0, uint1, false, false); + DO_COMPARE(array0, realNeg1, false, false); + DO_COMPARE(array0, real0, false, false); + DO_COMPARE(array0, realPos1, false, false); + DO_COMPARE(array0, str0, false, false); + DO_COMPARE(array0, str1, false, false); + DO_COMPARE(array0, boolF, false, false); + DO_COMPARE(array0, boolT, false, false); + DO_COMPARE(array0, array0, true, false); + DO_COMPARE(array0, array1, false, true); + DO_COMPARE(array0, obj0, false, true); + DO_COMPARE(array0, obj1, false, true); + + DO_COMPARE(array1, null0, false, false); + DO_COMPARE(array1, intNeg1, false, false); + DO_COMPARE(array1, int0, false, false); + DO_COMPARE(array1, intPos1, false, false); + DO_COMPARE(array1, uint0, false, false); + DO_COMPARE(array1, uint1, false, false); + DO_COMPARE(array1, realNeg1, false, false); + DO_COMPARE(array1, real0, false, false); + DO_COMPARE(array1, realPos1, false, false); + DO_COMPARE(array1, str0, false, false); + DO_COMPARE(array1, str1, false, false); + DO_COMPARE(array1, boolF, false, false); + DO_COMPARE(array1, boolT, false, false); + DO_COMPARE(array1, array0, false, false); + DO_COMPARE(array1, array1, true, false); + DO_COMPARE(array1, obj0, false, true); + DO_COMPARE(array1, obj1, false, true); + + DO_COMPARE(obj0, null0, false, false); + DO_COMPARE(obj0, intNeg1, false, false); + DO_COMPARE(obj0, int0, false, false); + DO_COMPARE(obj0, intPos1, false, false); + DO_COMPARE(obj0, uint0, false, false); + DO_COMPARE(obj0, uint1, false, false); + DO_COMPARE(obj0, realNeg1, false, false); + DO_COMPARE(obj0, real0, false, false); + DO_COMPARE(obj0, realPos1, false, false); + DO_COMPARE(obj0, str0, false, false); + DO_COMPARE(obj0, str1, false, false); + DO_COMPARE(obj0, boolF, false, false); + DO_COMPARE(obj0, boolT, false, false); + DO_COMPARE(obj0, array0, false, false); + DO_COMPARE(obj0, array1, false, false); + DO_COMPARE(obj0, obj0, true, false); + DO_COMPARE(obj0, obj1, false, true); + + DO_COMPARE(obj1, null0, false, false); + DO_COMPARE(obj1, intNeg1, false, false); + DO_COMPARE(obj1, int0, false, false); + DO_COMPARE(obj1, intPos1, false, false); + DO_COMPARE(obj1, uint0, false, false); + DO_COMPARE(obj1, uint1, false, false); + DO_COMPARE(obj1, realNeg1, false, false); + DO_COMPARE(obj1, real0, false, false); + DO_COMPARE(obj1, realPos1, false, false); + DO_COMPARE(obj1, str0, false, false); + DO_COMPARE(obj1, str1, false, false); + DO_COMPARE(obj1, boolF, false, false); + DO_COMPARE(obj1, boolT, false, false); + DO_COMPARE(obj1, array0, false, false); + DO_COMPARE(obj1, array1, false, false); + DO_COMPARE(obj1, obj0, false, false); + DO_COMPARE(obj1, obj1, true, false); +#undef DO_COMPARE +#pragma pop_macro("DO_COMPARE") +} + +TEST_CASE("bool") +{ + CHECK(!Json::Value()); + + CHECK(!Json::Value("")); + + CHECK(bool(Json::Value("empty"))); + CHECK(bool(Json::Value(false))); + CHECK(bool(Json::Value(true))); + CHECK(bool(Json::Value(0))); + CHECK(bool(Json::Value(1))); + + Json::Value array(Json::arrayValue); + CHECK(!array); + array.append(0); + CHECK(bool(array)); + + Json::Value object(Json::objectValue); + CHECK(!object); + object[""] = false; + CHECK(bool(object)); +} + +TEST_CASE("bad json") +{ + char const* s(R"({"method":"ledger","params":[{"ledger_index":1e300}]})"); + + Json::Value j; + Json::Reader r; + + CHECK(r.parse(s, j)); +} + +TEST_CASE("edge cases") +{ + std::string json; + + std::uint32_t max_uint = std::numeric_limits::max(); + std::int32_t max_int = std::numeric_limits::max(); + std::int32_t min_int = std::numeric_limits::min(); + + std::uint32_t a_uint = max_uint - 1978; + std::int32_t a_large_int = max_int - 1978; + std::int32_t a_small_int = min_int + 1978; + + json = "{\"max_uint\":" + std::to_string(max_uint); + json += ",\"max_int\":" + std::to_string(max_int); + json += ",\"min_int\":" + std::to_string(min_int); + json += ",\"a_uint\":" + std::to_string(a_uint); + json += ",\"a_large_int\":" + std::to_string(a_large_int); + json += ",\"a_small_int\":" + std::to_string(a_small_int); + json += "}"; + + Json::Value j1; + Json::Reader r1; + + CHECK(r1.parse(json, j1)); + CHECK(j1["max_uint"].asUInt() == max_uint); + CHECK(j1["max_int"].asInt() == max_int); + CHECK(j1["min_int"].asInt() == min_int); + CHECK(j1["a_uint"].asUInt() == a_uint); + CHECK(j1["a_uint"] > a_large_int); + CHECK(j1["a_uint"] > a_small_int); + CHECK(j1["a_large_int"].asInt() == a_large_int); + CHECK(j1["a_large_int"].asUInt() == a_large_int); + CHECK(j1["a_large_int"] < a_uint); + CHECK(j1["a_small_int"].asInt() == a_small_int); + CHECK(j1["a_small_int"] < a_uint); + + json = "{\"overflow\":"; + json += std::to_string(std::uint64_t(max_uint) + 1); + json += "}"; + + Json::Value j2; + Json::Reader r2; + + CHECK(!r2.parse(json, j2)); + + json = "{\"underflow\":"; + json += std::to_string(std::int64_t(min_int) - 1); + json += "}"; + + Json::Value j3; + Json::Reader r3; + + CHECK(!r3.parse(json, j3)); + + Json::Value intString{"4294967296"}; + CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); + + intString = "4294967295"; + CHECK(intString.asUInt() == 4294967295u); + + intString = "0"; + CHECK(intString.asUInt() == 0); + + intString = "-1"; + CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); + + intString = "2147483648"; + CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); + + intString = "2147483647"; + CHECK(intString.asInt() == 2147483647); + + intString = "-2147483648"; + CHECK(intString.asInt() == -2147483648LL); // MSVC wants the LL + + intString = "-2147483649"; + CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); +} + +TEST_CASE("copy") +{ + Json::Value v1{2.5}; + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + + Json::Value v2 = v1; + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + CHECK(v2.isDouble()); + CHECK(v2.asDouble() == 2.5); + CHECK(v1 == v2); + + v1 = v2; + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + CHECK(v2.isDouble()); + CHECK(v2.asDouble() == 2.5); + CHECK(v1 == v2); +} + +TEST_CASE("move") +{ + Json::Value v1{2.5}; + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + + Json::Value v2 = std::move(v1); + CHECK(!v1); + CHECK(v2.isDouble()); + CHECK(v2.asDouble() == 2.5); + CHECK(v1 != v2); + + v1 = std::move(v2); + CHECK(v1.isDouble()); + CHECK(v1.asDouble() == 2.5); + CHECK(!v2); + CHECK(v1 != v2); +} + +TEST_CASE("comparisons") +{ + Json::Value a, b; + auto testEquals = [&](std::string const& name) { + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + + CHECK(!(a != b)); + CHECK(!(a < b)); + CHECK(!(a > b)); + + CHECK(b == a); + CHECK(b <= a); + CHECK(b >= a); + + CHECK(!(b != a)); + CHECK(!(b < a)); + CHECK(!(b > a)); + }; + + auto testGreaterThan = [&](std::string const& name) { + CHECK(!(a == b)); + CHECK(!(a <= b)); + CHECK(a >= b); + + CHECK(a != b); + CHECK(!(a < b)); + CHECK(a > b); + + CHECK(!(b == a)); + CHECK(b <= a); + CHECK(!(b >= a)); + + CHECK(b != a); + CHECK(b < a); + CHECK(!(b > a)); + }; + + a["a"] = Json::UInt(0); + b["a"] = Json::Int(0); + testEquals("zero"); + + b["a"] = Json::Int(-1); + testGreaterThan("negative"); + + Json::Int big = std::numeric_limits::max(); + Json::UInt bigger = big; + bigger++; + + a["a"] = bigger; + b["a"] = big; + testGreaterThan("big"); +} + +TEST_CASE("compact") +{ + Json::Value j; + Json::Reader r; + char const* s("{\"array\":[{\"12\":23},{},null,false,0.5]}"); + + auto countLines = [](std::string const& str) { + return 1 + std::count_if(str.begin(), str.end(), [](char c) { + return c == '\n'; + }); + }; + + CHECK(r.parse(s, j)); + { + std::stringstream ss; + ss << j; + CHECK(countLines(ss.str()) > 1); + } + { + std::stringstream ss; + ss << Json::Compact(std::move(j)); + CHECK(countLines(ss.str()) == 1); + } +} + +TEST_CASE("conversions") +{ + // We have Json::Int, but not Json::Double or Json::Real. + // We have Json::Int, Json::Value::Int, and Json::ValueType::intValue. + // We have Json::ValueType::realValue but Json::Value::asDouble. + // TODO: What's the thinking here? + { + // null + Json::Value val; + CHECK(val.isNull()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == ""); + CHECK(val.asInt() == 0); + CHECK(val.asUInt() == 0); + CHECK(val.asDouble() == 0.0); + CHECK(val.asBool() == false); + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(val.isConvertibleTo(Json::arrayValue)); + CHECK(val.isConvertibleTo(Json::objectValue)); + } + { + // int + Json::Value val = -1234; + CHECK(val.isInt()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == "-1234"); + CHECK(val.asInt() == -1234); + CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK(val.asDouble() == -1234.0); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // uint + Json::Value val = 1234U; + CHECK(val.isUInt()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == "1234"); + CHECK(val.asInt() == 1234); + CHECK(val.asUInt() == 1234u); + CHECK(val.asDouble() == 1234.0); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // real + Json::Value val = 2.0; + CHECK(val.isDouble()); + // val.asCString() should trigger an assertion failure + CHECK(std::regex_match(val.asString(), std::regex("^2\\.0*$"))); + CHECK(val.asInt() == 2); + CHECK(val.asUInt() == 2u); + CHECK(val.asDouble() == 2.0); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // numeric string + Json::Value val = "54321"; + CHECK(val.isString()); + CHECK(strcmp(val.asCString(), "54321") == 0); + CHECK(val.asString() == "54321"); + CHECK(val.asInt() == 54321); + CHECK(val.asUInt() == 54321u); + CHECK_THROWS_AS(val.asDouble(), Json::error); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(!val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(!val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(!val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // non-numeric string + Json::Value val(Json::stringValue); + CHECK(val.isString()); + CHECK(val.asCString() == nullptr); + CHECK(val.asString() == ""); + CHECK_THROWS_AS(val.asInt(), std::exception); + CHECK_THROWS_AS(val.asUInt(), std::exception); + CHECK_THROWS_AS(val.asDouble(), std::exception); + CHECK(val.asBool() == false); + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(!val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(!val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(!val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // bool false + Json::Value val = false; + CHECK(val.isBool()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == "false"); + CHECK(val.asInt() == 0); + CHECK(val.asUInt() == 0); + CHECK(val.asDouble() == 0.0); + CHECK(val.asBool() == false); + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // bool true + Json::Value val = true; + CHECK(val.isBool()); + // val.asCString() should trigger an assertion failure + CHECK(val.asString() == "true"); + CHECK(val.asInt() == 1); + CHECK(val.asUInt() == 1); + CHECK(val.asDouble() == 1.0); + CHECK(val.asBool() == true); + + CHECK(!val.isConvertibleTo(Json::nullValue)); + CHECK(val.isConvertibleTo(Json::intValue)); + CHECK(val.isConvertibleTo(Json::uintValue)); + CHECK(val.isConvertibleTo(Json::realValue)); + CHECK(val.isConvertibleTo(Json::stringValue)); + CHECK(val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // array type + Json::Value val(Json::arrayValue); + CHECK(val.isArray()); + // val.asCString should trigger an assertion failure + CHECK_THROWS_AS(val.asString(), Json::error); + CHECK_THROWS_AS(val.asInt(), Json::error); + CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK_THROWS_AS(val.asDouble(), Json::error); + CHECK(val.asBool() == false); // empty or not + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(!val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(!val.isConvertibleTo(Json::realValue)); + CHECK(!val.isConvertibleTo(Json::stringValue)); + CHECK(!val.isConvertibleTo(Json::booleanValue)); + CHECK(val.isConvertibleTo(Json::arrayValue)); + CHECK(!val.isConvertibleTo(Json::objectValue)); + } + { + // object type + Json::Value val(Json::objectValue); + CHECK(val.isObject()); + // val.asCString should trigger an assertion failure + CHECK_THROWS_AS(val.asString(), Json::error); + CHECK_THROWS_AS(val.asInt(), Json::error); + CHECK_THROWS_AS(val.asUInt(), Json::error); + CHECK_THROWS_AS(val.asDouble(), Json::error); + CHECK(val.asBool() == false); // empty or not + + CHECK(val.isConvertibleTo(Json::nullValue)); + CHECK(!val.isConvertibleTo(Json::intValue)); + CHECK(!val.isConvertibleTo(Json::uintValue)); + CHECK(!val.isConvertibleTo(Json::realValue)); + CHECK(!val.isConvertibleTo(Json::stringValue)); + CHECK(!val.isConvertibleTo(Json::booleanValue)); + CHECK(!val.isConvertibleTo(Json::arrayValue)); + CHECK(val.isConvertibleTo(Json::objectValue)); + } +} + +TEST_CASE("access members") +{ + Json::Value val; + CHECK(val.type() == Json::nullValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + { + Json::Value const constVal = val; + CHECK(constVal[7u].type() == Json::nullValue); + CHECK(!constVal.isMember("key")); + CHECK(constVal["key"].type() == Json::nullValue); + CHECK(constVal.getMemberNames().empty()); + CHECK(constVal.get(1u, "default0") == "default0"); + CHECK(constVal.get(std::string("not"), "oh") == "oh"); + CHECK(constVal.get("missing", "default2") == "default2"); + } + + val = -7; + CHECK(val.type() == Json::intValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = 42u; + CHECK(val.type() == Json::uintValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = 3.14159; + CHECK(val.type() == Json::realValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = true; + CHECK(val.type() == Json::booleanValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = "string"; + CHECK(val.type() == Json::stringValue); + CHECK(val.size() == 0); + CHECK(!val.isValidIndex(0)); + CHECK(!val.isMember("key")); + + val = Json::Value(Json::objectValue); + CHECK(val.type() == Json::objectValue); + CHECK(val.size() == 0); + static Json::StaticString const staticThree("three"); + val[staticThree] = 3; + val["two"] = 2; + CHECK(val.size() == 2); + CHECK(val.isValidIndex(1)); + CHECK(!val.isValidIndex(2)); + CHECK(val[staticThree] == 3); + CHECK(val.isMember("two")); + CHECK(val.isMember(staticThree)); + CHECK(!val.isMember("key")); + { + Json::Value const constVal = val; + CHECK(constVal["two"] == 2); + CHECK(constVal["four"].type() == Json::nullValue); + CHECK(constVal[staticThree] == 3); + CHECK(constVal.isMember("two")); + CHECK(constVal.isMember(staticThree)); + CHECK(!constVal.isMember("key")); + CHECK(val.get(std::string("two"), "backup") == 2); + CHECK(val.get("missing", "default2") == "default2"); + } + + val = Json::Value(Json::arrayValue); + CHECK(val.type() == Json::arrayValue); + CHECK(val.size() == 0); + val[0u] = "zero"; + val[1u] = "one"; + CHECK(val.size() == 2); + CHECK(val.isValidIndex(1)); + CHECK(!val.isValidIndex(2)); + CHECK(val[20u].type() == Json::nullValue); + CHECK(!val.isMember("key")); + { + Json::Value const constVal = val; + CHECK(constVal[0u] == "zero"); + CHECK(constVal[2u].type() == Json::nullValue); + CHECK(!constVal.isMember("key")); + CHECK(val.get(1u, "default0") == "one"); + CHECK(val.get(3u, "default1") == "default1"); + } +} + +TEST_CASE("remove members") +{ + Json::Value val; + CHECK(val.removeMember(std::string("member")).type() == Json::nullValue); + + val = Json::Value(Json::objectValue); + static Json::StaticString const staticThree("three"); + val[staticThree] = 3; + val["two"] = 2; + CHECK(val.size() == 2); + + CHECK(val.removeMember(std::string("six")).type() == Json::nullValue); + CHECK(val.size() == 2); + + CHECK(val.removeMember(staticThree) == 3); + CHECK(val.size() == 1); + + CHECK(val.removeMember(staticThree).type() == Json::nullValue); + CHECK(val.size() == 1); + + CHECK(val.removeMember(std::string("two")) == 2); + CHECK(val.size() == 0); + + CHECK(val.removeMember(std::string("two")).type() == Json::nullValue); + CHECK(val.size() == 0); +} + +TEST_CASE("iterator") +{ + { + // Iterating an array. + Json::Value arr{Json::arrayValue}; + arr[0u] = "zero"; + arr[1u] = "one"; + arr[2u] = "two"; + arr[3u] = "three"; + + Json::ValueIterator const b{arr.begin()}; + Json::ValueIterator const e{arr.end()}; + + Json::ValueIterator i1 = b; + Json::ValueIterator i2 = e; + --i2; + + // key(), index(), and memberName() on an object iterator. + CHECK(b != e); + CHECK(!(b == e)); + CHECK(i1.key() == 0); + CHECK(i2.key() == 3); + CHECK(i1.index() == 0); + CHECK(i2.index() == 3); + CHECK(std::strcmp(i1.memberName(), "") == 0); + CHECK(std::strcmp(i2.memberName(), "") == 0); + + // Pre and post increment and decrement. + *i1++ = "0"; + CHECK(*i1 == "one"); + *i1 = "1"; + ++i1; + + *i2-- = "3"; + CHECK(*i2 == "two"); + CHECK(i1 == i2); + *i2 = "2"; + CHECK(*i1 == "2"); + } + { + // Iterating a const object. + Json::Value const obj{[]() { + Json::Value obj{Json::objectValue}; + obj["0"] = 0; + obj["1"] = 1; + obj["2"] = 2; + obj["3"] = 3; + return obj; + }()}; + + Json::ValueConstIterator i1{obj.begin()}; + Json::ValueConstIterator i2{obj.end()}; + --i2; + + // key(), index(), and memberName() on an object iterator. + CHECK(i1 != i2); + CHECK(!(i1 == i2)); + CHECK(i1.key() == "0"); + CHECK(i2.key() == "3"); + CHECK(i1.index() == -1); + CHECK(i2.index() == -1); + CHECK(std::strcmp(i1.memberName(), "0") == 0); + CHECK(std::strcmp(i2.memberName(), "3") == 0); + + // Pre and post increment and decrement. + CHECK(*i1++ == 0); + CHECK(*i1 == 1); + ++i1; + + CHECK(*i2-- == 3); + CHECK(*i2 == 2); + CHECK(i1 == i2); + CHECK(*i1 == 2); + } + { + // Iterating a non-const null object. + Json::Value nul{}; + CHECK(nul.begin() == nul.end()); + } + { + // Iterating a const Int. + Json::Value const i{-3}; + CHECK(i.begin() == i.end()); + } +} + +TEST_CASE("nest limits") +{ + Json::Reader r; + { + auto nest = [](std::uint32_t depth) -> std::string { + std::string s = "{"; + for (std::uint32_t i{1}; i <= depth; ++i) + s += "\"obj\":{"; + for (std::uint32_t i{1}; i <= depth; ++i) + s += "}"; + s += "}"; + return s; + }; + + { + // Within object nest limit + auto json{nest(std::min(10u, Json::Reader::nest_limit))}; + Json::Value j; + CHECK(r.parse(json, j)); + } + + { + // Exceed object nest limit + auto json{nest(Json::Reader::nest_limit + 1)}; + Json::Value j; + CHECK(!r.parse(json, j)); + } + } + + auto nest = [](std::uint32_t depth) -> std::string { + std::string s = "{"; + for (std::uint32_t i{1}; i <= depth; ++i) + s += "\"array\":[{"; + for (std::uint32_t i{1}; i <= depth; ++i) + s += "]}"; + s += "}"; + return s; + }; + { + // Exceed array nest limit + auto json{nest(Json::Reader::nest_limit + 1)}; + Json::Value j; + CHECK(!r.parse(json, j)); + } +} + +TEST_CASE("memory leak") +{ + // When run with the address sanitizer, this test confirms there is no + // memory leak with the scenarios below. + { + Json::Value a; + a[0u] = 1; + CHECK(a.type() == Json::arrayValue); + CHECK(a[0u].type() == Json::intValue); + a = std::move(a[0u]); + CHECK(a.type() == Json::intValue); + } + { + Json::Value b; + Json::Value temp; + temp["a"] = "Probably avoids the small string optimization"; + temp["b"] = "Also probably avoids the small string optimization"; + CHECK(temp.type() == Json::objectValue); + b.append(temp); + CHECK(temp.type() == Json::objectValue); + CHECK(b.size() == 1); + + b.append(std::move(temp)); + CHECK(b.size() == 2); + + // Note that the type() == nullValue check is implementation + // specific and not guaranteed to be valid in the future. + CHECK(temp.type() == Json::nullValue); + } +} + +TEST_SUITE_END(); + +} // namespace ripple diff --git a/src/tests/libxrpl/json/Writer.cpp b/src/tests/libxrpl/json/Writer.cpp new file mode 100644 index 0000000000..59251aaf60 --- /dev/null +++ b/src/tests/libxrpl/json/Writer.cpp @@ -0,0 +1,192 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include + +#include +#include + +using namespace ripple; +using namespace Json; + +TEST_SUITE_BEGIN("JsonWriter"); + +struct WriterFixture +{ + std::string output; + std::unique_ptr writer; + + WriterFixture() + { + writer = std::make_unique(stringOutput(output)); + } + + void + reset() + { + output.clear(); + writer = std::make_unique(stringOutput(output)); + } + + void + expectOutput(std::string const& expected) const + { + CHECK(output == expected); + } + + void + checkOutputAndReset(std::string const& expected) + { + expectOutput(expected); + reset(); + } +}; + +TEST_CASE_FIXTURE(WriterFixture, "trivial") +{ + CHECK(output.empty()); + checkOutputAndReset(""); +} + +TEST_CASE_FIXTURE(WriterFixture, "near trivial") +{ + CHECK(output.empty()); + writer->output(0); + checkOutputAndReset("0"); +} + +TEST_CASE_FIXTURE(WriterFixture, "primitives") +{ + writer->output(true); + checkOutputAndReset("true"); + + writer->output(false); + checkOutputAndReset("false"); + + writer->output(23); + checkOutputAndReset("23"); + + writer->output(23.0); + checkOutputAndReset("23.0"); + + writer->output(23.5); + checkOutputAndReset("23.5"); + + writer->output("a string"); + checkOutputAndReset(R"("a string")"); + + writer->output(nullptr); + checkOutputAndReset("null"); +} + +TEST_CASE_FIXTURE(WriterFixture, "empty") +{ + writer->startRoot(Writer::array); + writer->finish(); + checkOutputAndReset("[]"); + + writer->startRoot(Writer::object); + writer->finish(); + checkOutputAndReset("{}"); +} + +TEST_CASE_FIXTURE(WriterFixture, "escaping") +{ + writer->output("\\"); + checkOutputAndReset(R"("\\")"); + + writer->output("\""); + checkOutputAndReset(R"("\"")"); + + writer->output("\\\""); + checkOutputAndReset(R"("\\\"")"); + + writer->output("this contains a \\ in the middle of it."); + checkOutputAndReset(R"("this contains a \\ in the middle of it.")"); + + writer->output("\b\f\n\r\t"); + checkOutputAndReset(R"("\b\f\n\r\t")"); +} + +TEST_CASE_FIXTURE(WriterFixture, "array") +{ + writer->startRoot(Writer::array); + writer->append(12); + writer->finish(); + checkOutputAndReset("[12]"); +} + +TEST_CASE_FIXTURE(WriterFixture, "long array") +{ + writer->startRoot(Writer::array); + writer->append(12); + writer->append(true); + writer->append("hello"); + writer->finish(); + checkOutputAndReset(R"([12,true,"hello"])"); +} + +TEST_CASE_FIXTURE(WriterFixture, "embedded array simple") +{ + writer->startRoot(Writer::array); + writer->startAppend(Writer::array); + writer->finish(); + writer->finish(); + checkOutputAndReset("[[]]"); +} + +TEST_CASE_FIXTURE(WriterFixture, "object") +{ + writer->startRoot(Writer::object); + writer->set("hello", "world"); + writer->finish(); + checkOutputAndReset(R"({"hello":"world"})"); +} + +TEST_CASE_FIXTURE(WriterFixture, "complex object") +{ + writer->startRoot(Writer::object); + writer->set("hello", "world"); + writer->startSet(Writer::array, "array"); + writer->append(true); + writer->append(12); + writer->startAppend(Writer::array); + writer->startAppend(Writer::object); + writer->set("goodbye", "cruel world."); + writer->startSet(Writer::array, "subarray"); + writer->append(23.5); + writer->finishAll(); + checkOutputAndReset( + R"({"hello":"world","array":[true,12,[{"goodbye":"cruel world.","subarray":[23.5]}]]})"); +} + +TEST_CASE_FIXTURE(WriterFixture, "json value") +{ + Json::Value value(Json::objectValue); + value["foo"] = 23; + writer->startRoot(Writer::object); + writer->set("hello", value); + writer->finish(); + checkOutputAndReset(R"({"hello":{"foo":23}})"); +} + +TEST_SUITE_END(); diff --git a/src/tests/libxrpl/json/main.cpp b/src/tests/libxrpl/json/main.cpp new file mode 100644 index 0000000000..0a3f254ea8 --- /dev/null +++ b/src/tests/libxrpl/json/main.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include diff --git a/src/xrpld/app/tx/detail/Escrow.cpp b/src/xrpld/app/tx/detail/Escrow.cpp index 42b7c8e458..a5c44b1e2e 100644 --- a/src/xrpld/app/tx/detail/Escrow.cpp +++ b/src/xrpld/app/tx/detail/Escrow.cpp @@ -118,13 +118,6 @@ escrowCreatePreflightHelper(PreflightContext const& ctx) return tesSUCCESS; } -std::uint32_t -EscrowCreate::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC EscrowCreate::preflight(PreflightContext const& ctx) { @@ -639,13 +632,6 @@ EscrowFinish::checkExtraFeatures(PreflightContext const& ctx) ctx.rules.enabled(featureCredentials); } -std::uint32_t -EscrowFinish::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC EscrowFinish::preflight(PreflightContext const& ctx) { @@ -1225,13 +1211,6 @@ EscrowFinish::doApply() //------------------------------------------------------------------------------ -std::uint32_t -EscrowCancel::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC EscrowCancel::preflight(PreflightContext const& ctx) { diff --git a/src/xrpld/app/tx/detail/Escrow.h b/src/xrpld/app/tx/detail/Escrow.h index 8956be2939..55816734eb 100644 --- a/src/xrpld/app/tx/detail/Escrow.h +++ b/src/xrpld/app/tx/detail/Escrow.h @@ -36,9 +36,6 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); @@ -63,9 +60,6 @@ public: static bool checkExtraFeatures(PreflightContext const& ctx); - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); @@ -93,9 +87,6 @@ public: { } - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 2a44eac007..b6ca25e300 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -2582,13 +2582,12 @@ ValidVault::visitEntry( after != nullptr && (before != nullptr || !isDelete), "ripple::ValidVault::visitEntry : some object is available"); - // `Number balance` will capture the difference (delta) between "before" + // Number balanceDelta will capture the difference (delta) between "before" // state (zero if created) and "after" state (zero if destroyed), so the // invariants can validate that the change in account balances matches the // change in vault balances, stored to deltas_ at the end of this function. - Number balance{}; + Number balanceDelta{}; - // By default do not add anything to deltas std::int8_t sign = 0; if (before) { @@ -2601,18 +2600,18 @@ ValidVault::visitEntry( // At this moment we have no way of telling if this object holds // vault shares or something else. Save it for finalize. beforeMPTs_.push_back(Shares::make(*before)); - balance = static_cast( + balanceDelta = static_cast( before->getFieldU64(sfOutstandingAmount)); sign = 1; break; case ltMPTOKEN: - balance = + balanceDelta = static_cast(before->getFieldU64(sfMPTAmount)); sign = -1; break; case ltACCOUNT_ROOT: case ltRIPPLE_STATE: - balance = before->getFieldAmount(sfBalance); + balanceDelta = before->getFieldAmount(sfBalance); sign = -1; break; default:; @@ -2630,18 +2629,18 @@ ValidVault::visitEntry( // At this moment we have no way of telling if this object holds // vault shares or something else. Save it for finalize. afterMPTs_.push_back(Shares::make(*after)); - balance -= Number(static_cast( + balanceDelta -= Number(static_cast( after->getFieldU64(sfOutstandingAmount))); sign = 1; break; case ltMPTOKEN: - balance -= Number( + balanceDelta -= Number( static_cast(after->getFieldU64(sfMPTAmount))); sign = -1; break; case ltACCOUNT_ROOT: case ltRIPPLE_STATE: - balance -= Number(after->getFieldAmount(sfBalance)); + balanceDelta -= Number(after->getFieldAmount(sfBalance)); sign = -1; break; default:; @@ -2649,8 +2648,13 @@ ValidVault::visitEntry( } uint256 const key = (before ? before->key() : after->key()); - if (sign && balance != zero) - deltas_[key] = balance * sign; + // Append to deltas if sign is non-zero, i.e. an object of an interesting + // type has been updated. A transaction may update an object even when + // its balance has not changed, e.g. transaction fee equals the amount + // transferred to the account. We intentionally do not compare balanceDelta + // against zero, to avoid missing such updates. + if (sign != 0) + deltas_[key] = balanceDelta * sign; } bool @@ -2958,6 +2962,23 @@ ValidVault::finalize( }, vaultAsset.value()); }; + auto const deltaAssetsTxAccount = [&]() -> std::optional { + auto ret = deltaAssets(tx[sfAccount]); + // Nothing returned or not XRP transaction + if (!ret.has_value() || !vaultAsset.native()) + return ret; + + // Delegated transaction; no need to compensate for fees + if (auto const delegate = tx[~sfDelegate]; + delegate.has_value() && *delegate != tx[sfAccount]) + return ret; + + *ret += fee.drops(); + if (*ret == zero) + return std::nullopt; + + return ret; + }; auto const deltaShares = [&](AccountID const& id) -> std::optional { auto const it = [&]() { if (id == afterVault.pseudoId) @@ -3128,20 +3149,7 @@ ValidVault::finalize( if (!issuerDeposit) { - auto const accountDeltaAssets = - [&]() -> std::optional { - if (auto ret = deltaAssets(tx[sfAccount]); ret) - { - // Compensate for transaction fee deduced from - // sfAccount - if (vaultAsset.native()) - *ret += fee.drops(); - if (*ret != zero) - return ret; - } - return std::nullopt; - }(); - + auto const accountDeltaAssets = deltaAssetsTxAccount(); if (!accountDeltaAssets) { JLOG(j.fatal()) << // @@ -3194,7 +3202,7 @@ ValidVault::finalize( } auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares) + if (!vaultDeltaShares || *vaultDeltaShares == zero) { JLOG(j.fatal()) << // "Invariant failed: deposit must change vault shares"; @@ -3263,20 +3271,7 @@ ValidVault::finalize( if (!issuerWithdrawal) { - auto const accountDeltaAssets = - [&]() -> std::optional { - if (auto ret = deltaAssets(tx[sfAccount]); ret) - { - // Compensate for transaction fee deduced from - // sfAccount - if (vaultAsset.native()) - *ret += fee.drops(); - if (*ret != zero) - return ret; - } - return std::nullopt; - }(); - + auto const accountDeltaAssets = deltaAssetsTxAccount(); auto const otherAccountDelta = [&]() -> std::optional { if (auto const destination = tx[~sfDestination]; @@ -3333,7 +3328,7 @@ ValidVault::finalize( } auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares) + if (!vaultDeltaShares || *vaultDeltaShares == zero) { JLOG(j.fatal()) << // "Invariant failed: withdrawal must change vault shares"; @@ -3418,7 +3413,7 @@ ValidVault::finalize( } auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares) + if (!vaultDeltaShares || *vaultDeltaShares == zero) { JLOG(j.fatal()) << // "Invariant failed: clawback must change vault shares"; diff --git a/src/xrpld/app/tx/detail/PayChan.cpp b/src/xrpld/app/tx/detail/PayChan.cpp index b495e00b3f..b2353e3078 100644 --- a/src/xrpld/app/tx/detail/PayChan.cpp +++ b/src/xrpld/app/tx/detail/PayChan.cpp @@ -175,13 +175,6 @@ PayChanCreate::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; } -std::uint32_t -PayChanCreate::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC PayChanCreate::preflight(PreflightContext const& ctx) { @@ -335,13 +328,6 @@ PayChanFund::makeTxConsequences(PreflightContext const& ctx) return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()}; } -std::uint32_t -PayChanFund::getFlagsMask(PreflightContext const& ctx) -{ - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfUniversalMask : 0; -} - NotTEC PayChanFund::preflight(PreflightContext const& ctx) { @@ -434,10 +420,9 @@ PayChanClaim::checkExtraFeatures(PreflightContext const& ctx) } std::uint32_t -PayChanClaim::getFlagsMask(PreflightContext const& ctx) +PayChanClaim::getFlagsMask(PreflightContext const&) { - // 0 means "Allow any flags" - return ctx.rules.enabled(fix1543) ? tfPayChanClaimMask : 0; + return tfPayChanClaimMask; } NotTEC diff --git a/src/xrpld/app/tx/detail/PayChan.h b/src/xrpld/app/tx/detail/PayChan.h index b25a4529be..0042f5c7c9 100644 --- a/src/xrpld/app/tx/detail/PayChan.h +++ b/src/xrpld/app/tx/detail/PayChan.h @@ -36,9 +36,6 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); @@ -65,9 +62,6 @@ public: static TxConsequences makeTxConsequences(PreflightContext const& ctx); - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index edb91611be..9c49751bcd 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -704,15 +704,21 @@ readLimitField( JsonContext const& context) { limit = range.rdefault; - if (auto const& jvLimit = context.params[jss::limit]) - { - if (!(jvLimit.isUInt() || (jvLimit.isInt() && jvLimit.asInt() >= 0))) - return RPC::expected_field_error(jss::limit, "unsigned integer"); + if (!context.params.isMember(jss::limit) || + context.params[jss::limit].isNull()) + return std::nullopt; + + auto const& jvLimit = context.params[jss::limit]; + if (!(jvLimit.isUInt() || (jvLimit.isInt() && jvLimit.asInt() >= 0))) + return RPC::expected_field_error(jss::limit, "unsigned integer"); + + limit = jvLimit.asUInt(); + if (limit == 0) + return RPC::invalid_field_error(jss::limit); + + if (!isUnlimited(context.role)) + limit = std::max(range.rmin, std::min(range.rmax, limit)); - limit = jvLimit.asUInt(); - if (!isUnlimited(context.role)) - limit = std::max(range.rmin, std::min(range.rmax, limit)); - } return std::nullopt; } diff --git a/src/xrpld/rpc/detail/Tuning.h b/src/xrpld/rpc/detail/Tuning.h index 4f4a8be1bf..f3218a3fb1 100644 --- a/src/xrpld/rpc/detail/Tuning.h +++ b/src/xrpld/rpc/detail/Tuning.h @@ -45,6 +45,9 @@ static LimitRange constexpr accountObjects = {10, 200, 400}; /** Limits for the account_offers command. */ static LimitRange constexpr accountOffers = {10, 200, 400}; +/** Limits for the account_tx command. */ +static LimitRange constexpr accountTx = {10, 200, 400}; + /** Limits for the book_offers command. */ static LimitRange constexpr bookOffers = {0, 60, 100}; diff --git a/src/xrpld/rpc/handlers/AccountChannels.cpp b/src/xrpld/rpc/handlers/AccountChannels.cpp index 17e46f052f..1a21f67860 100644 --- a/src/xrpld/rpc/handlers/AccountChannels.cpp +++ b/src/xrpld/rpc/handlers/AccountChannels.cpp @@ -103,9 +103,6 @@ doAccountChannels(RPC::JsonContext& context) if (auto err = readLimitField(limit, RPC::Tuning::accountChannels, context)) return *err; - if (limit == 0u) - return rpcError(rpcINVALID_PARAMS); - Json::Value jsonChannels{Json::arrayValue}; struct VisitData { diff --git a/src/xrpld/rpc/handlers/AccountLines.cpp b/src/xrpld/rpc/handlers/AccountLines.cpp index 146a9527a9..9fb076445b 100644 --- a/src/xrpld/rpc/handlers/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/AccountLines.cpp @@ -120,9 +120,6 @@ doAccountLines(RPC::JsonContext& context) if (auto err = readLimitField(limit, RPC::Tuning::accountLines, context)) return *err; - if (limit == 0) - return rpcError(rpcINVALID_PARAMS); - // this flag allows the requester to ask incoming trustlines in default // state be omitted bool ignoreDefault = params.isMember(jss::ignore_default) && diff --git a/src/xrpld/rpc/handlers/AccountOffers.cpp b/src/xrpld/rpc/handlers/AccountOffers.cpp index 1f2b76efe4..737f39cc13 100644 --- a/src/xrpld/rpc/handlers/AccountOffers.cpp +++ b/src/xrpld/rpc/handlers/AccountOffers.cpp @@ -86,9 +86,6 @@ doAccountOffers(RPC::JsonContext& context) if (auto err = readLimitField(limit, RPC::Tuning::accountOffers, context)) return *err; - if (limit == 0) - return RPC::invalid_field_error(jss::limit); - Json::Value& jsonOffers(result[jss::offers] = Json::arrayValue); std::vector> offers; uint256 startAfter = beast::zero; diff --git a/src/xrpld/rpc/handlers/AccountTx.cpp b/src/xrpld/rpc/handlers/AccountTx.cpp index e053c2adc0..64247c732e 100644 --- a/src/xrpld/rpc/handlers/AccountTx.cpp +++ b/src/xrpld/rpc/handlers/AccountTx.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -429,7 +431,10 @@ doAccountTxJson(RPC::JsonContext& context) return RPC::invalid_field_error(jss::forward); } - args.limit = params.isMember(jss::limit) ? params[jss::limit].asUInt() : 0; + if (auto const err = + RPC::readLimitField(args.limit, RPC::Tuning::accountTx, context)) + return *err; + args.binary = params.isMember(jss::binary) && params[jss::binary].asBool(); args.forward = params.isMember(jss::forward) && params[jss::forward].asBool();