mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-05 19:28:15 +00:00
Merge remote-tracking branch 'XRPLF/develop' into ximinez/lending-XLS-66
* XRPLF/develop: ci: Use commit hash so workflows are not canceled when merging multiple PRs (5950) ci: Only upload codecov reports in the original repo, not in forks (5953) ci: Only log into Conan when uploading packages (5952) fix: invariant error in fee-sized `VaultWithdraw` (5876) fix: account_tx limit parameter validation for malformed values (5891) refactor: Retire fix1543 amendment (5926) ci: Only run .exe files during test phase on Windows (5947) refactor: Migrate json unit tests to use doctest (5533) Change `fixMPTDeliveredAmount` to `Supported::yes` (5833) fix: Upload all test binaries (5932) chore: Better pre-commit failure message (5940)
This commit is contained in:
@@ -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
|
||||
|
||||
7
.github/workflows/on-trigger.yml
vendored
7
.github/workflows/on-trigger.yml
vendored
@@ -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:
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -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" }'
|
||||
|
||||
20
.github/workflows/reusable-build.yml
vendored
20
.github/workflows/reusable-build.yml
vendored
@@ -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
|
||||
|
||||
23
.github/workflows/reusable-test.yml
vendored
23
.github/workflows/reusable-test.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/upload-conan-deps.yml
vendored
4
.github/workflows/upload-conan-deps.yml
vendored
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2172,7 +2172,7 @@ class Invariants_test : public beast::unit_test::suite
|
||||
AccountID account;
|
||||
int amount;
|
||||
};
|
||||
struct Adjustements
|
||||
struct Adjustments
|
||||
{
|
||||
std::optional<int> assetsTotal = {};
|
||||
std::optional<int> 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;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/AMMTest.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/amount.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
@@ -43,6 +44,8 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
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<long> {
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 <test/json/TestOutputSuite.h>
|
||||
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/json/Writer.h>
|
||||
|
||||
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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -17,50 +17,43 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/json/TestOutputSuite.h>
|
||||
|
||||
#include <xrpl/json/Output.h>
|
||||
#include <xrpl/json/json_reader.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
|
||||
namespace Json {
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
struct Output_test : ripple::test::TestOutputSuite
|
||||
#include <string>
|
||||
|
||||
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();
|
||||
1294
src/tests/libxrpl/json/Value.cpp
Normal file
1294
src/tests/libxrpl/json/Value.cpp
Normal file
File diff suppressed because it is too large
Load Diff
192
src/tests/libxrpl/json/Writer.cpp
Normal file
192
src/tests/libxrpl/json/Writer.cpp
Normal file
@@ -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 <xrpl/json/Writer.h>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
#include <google/protobuf/stubs/port.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using namespace ripple;
|
||||
using namespace Json;
|
||||
|
||||
TEST_SUITE_BEGIN("JsonWriter");
|
||||
|
||||
struct WriterFixture
|
||||
{
|
||||
std::string output;
|
||||
std::unique_ptr<Writer> writer;
|
||||
|
||||
WriterFixture()
|
||||
{
|
||||
writer = std::make_unique<Writer>(stringOutput(output));
|
||||
}
|
||||
|
||||
void
|
||||
reset()
|
||||
{
|
||||
output.clear();
|
||||
writer = std::make_unique<Writer>(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();
|
||||
2
src/tests/libxrpl/json/main.cpp
Normal file
2
src/tests/libxrpl/json/main.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
@@ -118,13 +118,6 @@ escrowCreatePreflightHelper<MPTIssue>(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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<std::int64_t>(
|
||||
balanceDelta = static_cast<std::int64_t>(
|
||||
before->getFieldU64(sfOutstandingAmount));
|
||||
sign = 1;
|
||||
break;
|
||||
case ltMPTOKEN:
|
||||
balance =
|
||||
balanceDelta =
|
||||
static_cast<std::int64_t>(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<std::int64_t>(
|
||||
balanceDelta -= Number(static_cast<std::int64_t>(
|
||||
after->getFieldU64(sfOutstandingAmount)));
|
||||
sign = 1;
|
||||
break;
|
||||
case ltMPTOKEN:
|
||||
balance -= Number(
|
||||
balanceDelta -= Number(
|
||||
static_cast<std::int64_t>(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<Number> {
|
||||
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<Number> {
|
||||
auto const it = [&]() {
|
||||
if (id == afterVault.pseudoId)
|
||||
@@ -3128,20 +3149,7 @@ ValidVault::finalize(
|
||||
|
||||
if (!issuerDeposit)
|
||||
{
|
||||
auto const accountDeltaAssets =
|
||||
[&]() -> std::optional<Number> {
|
||||
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<Number> {
|
||||
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<Number> {
|
||||
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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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<std::shared_ptr<SLE const>> offers;
|
||||
uint256 startAfter = beast::zero;
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <xrpld/rpc/DeliveredAmount.h>
|
||||
#include <xrpld/rpc/MPTokenIssuanceID.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
#include <xrpld/rpc/detail/Tuning.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user