mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-23 05:30:13 +00:00
Compare commits
1 Commits
develop
...
tialymov/F
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6a5157891 |
@@ -53,11 +53,6 @@ repos:
|
||||
entry: ./bin/pre-commit/check_doxygen_style.py
|
||||
language: python
|
||||
types_or: [c++, c]
|
||||
- id: fix-gtest-names
|
||||
name: "fix gtest names: CamelCase suite, snake_case test case"
|
||||
entry: ./bin/pre-commit/fix_gtest_names.py
|
||||
language: python
|
||||
types_or: [c++, c]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: f4d7745e17a28aad7eed2f4874ca8d1568c11c4c # frozen: v22.1.8
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Rewrites gtest names to the required style in this project: the suite name is
|
||||
CamelCase, the test-case name is snake_case.
|
||||
|
||||
TEST(SuiteName, test_case_name)
|
||||
|
||||
The gtest `DISABLED_` prefix is kept verbatim on either name.
|
||||
|
||||
Both conversions fold acronyms the way a reader expects:
|
||||
`SetAndResetAccountTxnID` -> `set_and_reset_account_txn_id`, not
|
||||
`set_and_reset_account_txn_i_d`.
|
||||
|
||||
The first argument of `TEST_F`, `TEST_P`, `TYPED_TEST` and `TYPED_TEST_P` is a
|
||||
fixture class rather than a free identifier, so rewriting it here would leave
|
||||
the class it names behind. Those are reported for a human to rename (clang-tidy
|
||||
checks the class declaration itself, via readability-identifier-naming).
|
||||
|
||||
Usage: ./bin/pre-commit/fix_gtest_names.py <file1> <file2> ...
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
# A test-case definition, `MACRO(SuiteOrFixture, TestName)`, anchored at the
|
||||
# start of a line so that commented-out definitions and project macros that
|
||||
# merely look similar (`TEST_EXPECT(...)`) are left alone. The `\s*` between
|
||||
# arguments allows for a definition clang-format wrapped over several lines.
|
||||
PATTERN = re.compile(
|
||||
r"(?P<head>^[ \t]*(?P<macro>TYPED_TEST_P|TYPED_TEST|TEST_F|TEST_P|TEST)\s*\(\s*)"
|
||||
r"(?P<suite>\w+)(?P<mid>\s*,\s*)(?P<name>\w+)(?P<tail>\s*\))",
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
# The macros whose first argument names a fixture class, not a free identifier.
|
||||
FIXTURE_MACROS = ("TEST_F", "TEST_P", "TYPED_TEST", "TYPED_TEST_P")
|
||||
|
||||
DISABLED = "DISABLED_"
|
||||
|
||||
ACRONYM_BOUNDARY = re.compile(r"([A-Z]+)([A-Z][a-z])")
|
||||
WORD_BOUNDARY = re.compile(r"([a-z\d])([A-Z])")
|
||||
|
||||
|
||||
def _split_disabled(name: str) -> tuple[str, str]:
|
||||
"""Splits off gtest's `DISABLED_` prefix, which is kept verbatim."""
|
||||
if name.startswith(DISABLED):
|
||||
return DISABLED, name[len(DISABLED) :]
|
||||
return "", name
|
||||
|
||||
|
||||
def snake_case(name: str) -> str:
|
||||
"""Returns the name in snake_case, leaving acronyms whole.
|
||||
|
||||
`SetAndResetAccountTxnID` -> `set_and_reset_account_txn_id`,
|
||||
`parseStatRSSkB` -> `parse_stat_rs_sk_b`.
|
||||
"""
|
||||
prefix, core = _split_disabled(name)
|
||||
core = ACRONYM_BOUNDARY.sub(r"\1_\2", core)
|
||||
return prefix + WORD_BOUNDARY.sub(r"\1_\2", core).lower()
|
||||
|
||||
|
||||
def camel_case(name: str) -> str:
|
||||
"""Returns the name in CamelCase, capitalizing each underscored word.
|
||||
|
||||
Only the letters that have to change are touched, so acronyms survive: a
|
||||
conversion that went via snake_case would turn `SHAMapTest` into
|
||||
`ShaMapTest`, whereas here it is already CamelCase and stays put.
|
||||
`json_value` -> `JsonValue`, `parseStatRSSkB` -> `ParseStatRSSkB`.
|
||||
"""
|
||||
prefix, core = _split_disabled(name)
|
||||
return prefix + "".join(w[:1].upper() + w[1:] for w in core.split("_") if w)
|
||||
|
||||
|
||||
def _corrected(match: re.Match) -> tuple[str, str]:
|
||||
"""Returns the suite and test-case names this definition should end up with."""
|
||||
suite = match["suite"]
|
||||
return (
|
||||
suite if match["macro"] in FIXTURE_MACROS else camel_case(suite),
|
||||
snake_case(match["name"]),
|
||||
)
|
||||
|
||||
|
||||
def fix_source(text: str) -> tuple[str, list[str]]:
|
||||
"""Returns the corrected text and one `line: message` report per bad name."""
|
||||
# gtest joins the suite and test names into one class name, so two test
|
||||
# cases whose joined names agree cannot coexist: `TEST(a, b_c)` and
|
||||
# `TEST(a_b, c)` both define `a_b_c_Test`. A rename that would introduce
|
||||
# such a clash is reported for a human instead of applied.
|
||||
joined = Counter("_".join(_corrected(m)) for m in PATTERN.finditer(text))
|
||||
reports = []
|
||||
|
||||
def rewrite(match: re.Match) -> str:
|
||||
suite, name = match["suite"], match["name"]
|
||||
new_suite, new_name = _corrected(match)
|
||||
line = text.count("\n", 0, match.start()) + 1
|
||||
|
||||
if match["macro"] in FIXTURE_MACROS and camel_case(suite) != suite:
|
||||
reports.append(
|
||||
f"{line}: fixture '{suite}' is not CamelCase: rename the class "
|
||||
f"to '{camel_case(suite)}' by hand"
|
||||
)
|
||||
if (new_suite, new_name) == (suite, name):
|
||||
return match[0]
|
||||
if joined[f"{new_suite}_{new_name}"] > 1:
|
||||
reports.append(
|
||||
f"{line}: cannot rename '{suite}, {name}' to '{new_suite}, "
|
||||
f"{new_name}': another test case already generates that name"
|
||||
)
|
||||
return match[0]
|
||||
if new_suite != suite:
|
||||
reports.append(f"{line}: renamed suite '{suite}' to '{new_suite}'")
|
||||
if new_name != name:
|
||||
reports.append(f"{line}: renamed test case '{name}' to '{new_name}'")
|
||||
return match["head"] + new_suite + match["mid"] + new_name + match["tail"]
|
||||
|
||||
return PATTERN.sub(rewrite, text), reports
|
||||
|
||||
|
||||
def fix_names(path: Path) -> bool:
|
||||
"""Corrects one file's gtest names, reporting each on stdout."""
|
||||
original = path.read_text(encoding="utf-8")
|
||||
fixed, reports = fix_source(original)
|
||||
for report in reports:
|
||||
print(f"{path}:{report}")
|
||||
if fixed != original:
|
||||
path.write_text(fixed, encoding="utf-8")
|
||||
return not reports
|
||||
|
||||
|
||||
def main() -> int:
|
||||
files = [Path(f) for f in sys.argv[1:]]
|
||||
success = True
|
||||
|
||||
for path in files:
|
||||
success &= fix_names(path)
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,259 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tests for fix_gtest_names.py.
|
||||
|
||||
Run directly (no test framework needed):
|
||||
./bin/pre-commit/test_fix_gtest_names.py
|
||||
or under pytest:
|
||||
pytest bin/pre-commit/test_fix_gtest_names.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from fix_gtest_names import camel_case, fix_source, snake_case
|
||||
|
||||
|
||||
def dedent(text: str) -> str:
|
||||
"""Removes a fixture's common indentation and its leading newline.
|
||||
|
||||
Lets fixtures be written as indented triple-quoted here-docs while keeping
|
||||
honest 1-based line numbers.
|
||||
"""
|
||||
return textwrap.dedent(text).lstrip("\n")
|
||||
|
||||
|
||||
def fixed(text: str) -> str:
|
||||
return fix_source(dedent(text))[0]
|
||||
|
||||
|
||||
def reports(text: str) -> list[str]:
|
||||
return fix_source(dedent(text))[1]
|
||||
|
||||
|
||||
# --- conversion --------------------------------------------------------------
|
||||
|
||||
|
||||
def test_snake_case_conversion() -> None:
|
||||
assert snake_case("BadInputs") == "bad_inputs"
|
||||
assert snake_case("mulDiv") == "mul_div"
|
||||
assert snake_case("already_snake") == "already_snake"
|
||||
assert snake_case("base64") == "base64"
|
||||
|
||||
|
||||
def test_snake_case_keeps_acronyms_whole() -> None:
|
||||
assert snake_case("SetAndResetAccountTxnID") == "set_and_reset_account_txn_id"
|
||||
assert snake_case("XRPToIOU") == "xrp_to_iou"
|
||||
assert snake_case("STAmountMath") == "st_amount_math"
|
||||
|
||||
|
||||
def test_camel_case_conversion() -> None:
|
||||
assert camel_case("json_value") == "JsonValue"
|
||||
assert camel_case("mulDiv") == "MulDiv"
|
||||
assert camel_case("scope") == "Scope"
|
||||
assert camel_case("base64") == "Base64"
|
||||
|
||||
|
||||
def test_camel_case_leaves_acronyms_alone() -> None:
|
||||
# A snake_case round-trip would give `ShaMapTest` / `ParseStatmRsSkB` here.
|
||||
assert camel_case("SHAMapTest") == "SHAMapTest"
|
||||
assert camel_case("parseStatmRSSkB") == "ParseStatmRSSkB"
|
||||
assert camel_case("XRPAmount") == "XRPAmount"
|
||||
assert camel_case("CSPRNG") == "CSPRNG"
|
||||
|
||||
|
||||
def test_disabled_prefix_preserved() -> None:
|
||||
assert snake_case("DISABLED_FooBar") == "DISABLED_foo_bar"
|
||||
assert snake_case("DISABLED_foo_bar") == "DISABLED_foo_bar"
|
||||
assert snake_case("DISABLED_") == "DISABLED_"
|
||||
assert camel_case("DISABLED_foo_bar") == "DISABLED_FooBar"
|
||||
assert camel_case("DISABLED_") == "DISABLED_"
|
||||
|
||||
|
||||
# --- what counts as a test definition ---------------------------------------
|
||||
|
||||
|
||||
def test_all_macros_recognized() -> None:
|
||||
code = """
|
||||
TEST(Suite, oneName)
|
||||
TEST_F(Fixture, twoName)
|
||||
TEST_P(Fixture, threeName)
|
||||
TYPED_TEST(Fixture, fourName)
|
||||
TYPED_TEST_P(Fixture, fiveName)
|
||||
"""
|
||||
assert fixed(code) == dedent("""
|
||||
TEST(Suite, one_name)
|
||||
TEST_F(Fixture, two_name)
|
||||
TEST_P(Fixture, three_name)
|
||||
TYPED_TEST(Fixture, four_name)
|
||||
TYPED_TEST_P(Fixture, five_name)
|
||||
""")
|
||||
|
||||
|
||||
def test_conforming_definitions_untouched() -> None:
|
||||
code = """
|
||||
TEST(AccountSet, bad_inputs)
|
||||
TEST_F(MutexMakeTest, default_constructor)
|
||||
TEST(SHAMap, DISABLED_slow_path)
|
||||
"""
|
||||
assert reports(code) == []
|
||||
assert fixed(code) == dedent(code)
|
||||
|
||||
|
||||
def test_lookalikes_ignored() -> None:
|
||||
code = """
|
||||
// TEST(Suite, notATest)
|
||||
TEST_EXPECT(someCall())
|
||||
TEST_EXPECTS(amount == value, amount.getText())
|
||||
INSTANTIATE_TEST_SUITE_P(Prefix, Fixture, testValues());
|
||||
auto x = TEST(Suite, notATest);
|
||||
TYPED_TEST_SUITE(Fixture, MyTypes);
|
||||
"""
|
||||
assert reports(code) == []
|
||||
assert fixed(code) == dedent(code)
|
||||
|
||||
|
||||
def test_indented_and_wrapped_definitions() -> None:
|
||||
code = """
|
||||
namespace ripple {
|
||||
TEST(Suite, indentedName)
|
||||
}
|
||||
TEST_F(
|
||||
SomeVeryLongFixtureName,
|
||||
wrappedName)
|
||||
"""
|
||||
assert fixed(code) == dedent("""
|
||||
namespace ripple {
|
||||
TEST(Suite, indented_name)
|
||||
}
|
||||
TEST_F(
|
||||
SomeVeryLongFixtureName,
|
||||
wrapped_name)
|
||||
""")
|
||||
|
||||
|
||||
# --- rewriting --------------------------------------------------------------
|
||||
|
||||
|
||||
def test_only_the_two_names_are_rewritten() -> None:
|
||||
code = """
|
||||
TEST(mulDiv, mulDiv)
|
||||
{
|
||||
auto const mulDiv = 1; // mulDiv stays
|
||||
}
|
||||
"""
|
||||
assert fixed(code) == dedent("""
|
||||
TEST(MulDiv, mul_div)
|
||||
{
|
||||
auto const mulDiv = 1; // mulDiv stays
|
||||
}
|
||||
""")
|
||||
|
||||
|
||||
def test_suite_name_camel_cased() -> None:
|
||||
code = """
|
||||
TEST(json_value, limits)
|
||||
TEST(scope, ScopeExit)
|
||||
"""
|
||||
assert reports(code) == [
|
||||
"1: renamed suite 'json_value' to 'JsonValue'",
|
||||
"2: renamed suite 'scope' to 'Scope'",
|
||||
"2: renamed test case 'ScopeExit' to 'scope_exit'",
|
||||
]
|
||||
assert fixed(code) == dedent("""
|
||||
TEST(JsonValue, limits)
|
||||
TEST(Scope, scope_exit)
|
||||
""")
|
||||
|
||||
|
||||
def test_fixture_reported_but_not_renamed() -> None:
|
||||
# The first argument names a class, so only a human (or clang-tidy) can
|
||||
# rename it; the test-case name is still fixed.
|
||||
code = """
|
||||
TEST_F(my_fixture, someTest)
|
||||
"""
|
||||
assert reports(code) == [
|
||||
"1: fixture 'my_fixture' is not CamelCase: rename the class to "
|
||||
"'MyFixture' by hand",
|
||||
"1: renamed test case 'someTest' to 'some_test'",
|
||||
]
|
||||
assert fixed(code) == dedent("""
|
||||
TEST_F(my_fixture, some_test)
|
||||
""")
|
||||
|
||||
|
||||
def test_reports_carry_line_numbers() -> None:
|
||||
code = """
|
||||
#include <foo.h>
|
||||
|
||||
TEST(Suite, firstName)
|
||||
|
||||
TEST(Suite, secondName)
|
||||
"""
|
||||
assert reports(code) == [
|
||||
"3: renamed test case 'firstName' to 'first_name'",
|
||||
"5: renamed test case 'secondName' to 'second_name'",
|
||||
]
|
||||
|
||||
|
||||
# --- collisions -------------------------------------------------------------
|
||||
|
||||
|
||||
def test_collision_reported_and_not_applied() -> None:
|
||||
# Both would define `Suite_mul_div_Test`.
|
||||
code = """
|
||||
TEST(Suite, mulDiv)
|
||||
TEST(Suite, mul_div)
|
||||
"""
|
||||
assert reports(code) == [
|
||||
"1: cannot rename 'Suite, mulDiv' to 'Suite, mul_div': another test "
|
||||
"case already generates that name"
|
||||
]
|
||||
assert fixed(code) == dedent(code)
|
||||
|
||||
|
||||
def test_collision_between_converging_suites() -> None:
|
||||
# Both suites camel-case to `SuiteA`, so both would define
|
||||
# `SuiteA_one_test_Test`.
|
||||
code = """
|
||||
TEST(SuiteA, oneTest)
|
||||
TEST(Suite_a, one_test)
|
||||
"""
|
||||
assert [r.split(":")[1].strip() for r in reports(code)] == [
|
||||
"cannot rename 'SuiteA, oneTest' to 'SuiteA, one_test'",
|
||||
"cannot rename 'Suite_a, one_test' to 'SuiteA, one_test'",
|
||||
]
|
||||
assert fixed(code) == dedent(code)
|
||||
|
||||
|
||||
def test_same_name_in_different_suites_is_not_a_collision() -> None:
|
||||
code = """
|
||||
TEST(SuiteOne, mulDiv)
|
||||
TEST(SuiteTwo, mulDiv)
|
||||
"""
|
||||
assert fixed(code) == dedent("""
|
||||
TEST(SuiteOne, mul_div)
|
||||
TEST(SuiteTwo, mul_div)
|
||||
""")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
tests = sorted(
|
||||
(name, fn)
|
||||
for name, fn in globals().items()
|
||||
if name.startswith("test_") and callable(fn)
|
||||
)
|
||||
failed = 0
|
||||
for name, fn in tests:
|
||||
try:
|
||||
fn()
|
||||
print(f"PASS {name}")
|
||||
except AssertionError as exc:
|
||||
failed += 1
|
||||
print(f"FAIL {name}: {exc!r}")
|
||||
print(f"\n{len(tests) - failed}/{len(tests)} passed")
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -265,6 +265,13 @@ canWithdraw(
|
||||
[[nodiscard]] TER
|
||||
canWithdraw(ReadView const& view, STTx const& tx);
|
||||
|
||||
/**
|
||||
* Pays out a withdrawal from a vault or loan broker pseudo-account.
|
||||
*
|
||||
* `sourceAmount` leaves `sourceAcct` and `destinationAmount` reaches `dstAcct`.
|
||||
* The two differ only when the withdrawal carries a transfer fee; the
|
||||
* difference is the fee and is settled through the issuer.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
doWithdraw(
|
||||
ApplyViewContext ctx,
|
||||
@@ -272,7 +279,8 @@ doWithdraw(
|
||||
AccountID const& dstAcct,
|
||||
AccountID const& sourceAcct,
|
||||
XRPAmount priorBalance,
|
||||
STAmount const& amount,
|
||||
STAmount const& destinationAmount,
|
||||
STAmount const& sourceAmount,
|
||||
beast::Journal j);
|
||||
|
||||
/**
|
||||
|
||||
@@ -316,6 +316,13 @@ transferRate(ReadView const& view, Asset const& asset);
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount);
|
||||
|
||||
/**
|
||||
* Returns the amount delivered when the transfer fee is deducted from a fixed
|
||||
* source amount.
|
||||
*/
|
||||
[[nodiscard]] STAmount
|
||||
subtractTransferFee(STAmount const& sourceAmount, Rate const& rate);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding operations (Asset-based dispatchers)
|
||||
|
||||
@@ -538,9 +538,14 @@ doWithdraw(
|
||||
AccountID const& dstAcct,
|
||||
AccountID const& sourceAcct,
|
||||
XRPAmount priorBalance,
|
||||
STAmount const& amount,
|
||||
STAmount const& destinationAmount,
|
||||
STAmount const& sourceAmount,
|
||||
beast::Journal j)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
destinationAmount.asset() == sourceAmount.asset(),
|
||||
"xrpl::doWithdraw : delivered and source amounts use the same asset");
|
||||
|
||||
auto const dstSle = ctx.view.read(keylet::account(dstAcct));
|
||||
|
||||
// Create a trust line or MPToken for a self-destination only when there
|
||||
@@ -550,9 +555,10 @@ doWithdraw(
|
||||
// create+delete MPTokens in the same transaction.
|
||||
if (dstAcct == senderAcct)
|
||||
{
|
||||
if (amount > beast::kZero || !ctx.view.rules().enabled(fixCleanup3_4_0))
|
||||
if (destinationAmount > beast::kZero || !ctx.view.rules().enabled(fixCleanup3_4_0))
|
||||
{
|
||||
if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j);
|
||||
if (auto const ter =
|
||||
addEmptyHolding(ctx, senderAcct, priorBalance, destinationAmount.asset(), j);
|
||||
!isTesSuccess(ter) && ter != tecDUPLICATE)
|
||||
return ter;
|
||||
}
|
||||
@@ -567,17 +573,33 @@ doWithdraw(
|
||||
if (accountHolds(
|
||||
ctx.view,
|
||||
sourceAcct,
|
||||
amount.asset(),
|
||||
destinationAmount.asset(),
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j) < amount)
|
||||
j) < sourceAmount)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "doWithdraw: negative balance of broker cover assets.";
|
||||
JLOG(j.error()) << "doWithdraw: source account holds fewer assets than the withdrawal.";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// A transfer fee applies. Redeem the gross amount with the issuer, then
|
||||
// issue the net amount to the destination.
|
||||
if (sourceAmount != destinationAmount)
|
||||
{
|
||||
auto const issuer = destinationAmount.getIssuer();
|
||||
XRPL_ASSERT(
|
||||
!destinationAmount.native() && sourceAcct != issuer && dstAcct != issuer,
|
||||
"xrpl::doWithdraw : transfer fee applies between token holders");
|
||||
|
||||
if (auto const ter = directSendNoFee(ctx.view, sourceAcct, issuer, sourceAmount, false, j);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return directSendNoFee(ctx.view, issuer, dstAcct, destinationAmount, false, j);
|
||||
}
|
||||
|
||||
// A reserve sponsor only covers tx.Account's own objects, so resolve the
|
||||
// sponsor against the destination. accountSend can auto-create a holding
|
||||
// for dstAcct; keying on the destination ensures a third-party destination's
|
||||
@@ -589,7 +611,7 @@ doWithdraw(
|
||||
// Move the funds directly from the broker's pseudo-account to the
|
||||
// dstAcct
|
||||
return accountSend(
|
||||
ctx.view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes);
|
||||
ctx.view, sourceAcct, dstAcct, destinationAmount, j, *sponsorSle, WaiveTransferFee::Yes);
|
||||
}
|
||||
|
||||
TER
|
||||
|
||||
@@ -548,6 +548,12 @@ transferRate(ReadView const& view, STAmount const& amount)
|
||||
return transferRate(view, amount.asset());
|
||||
}
|
||||
|
||||
STAmount
|
||||
subtractTransferFee(STAmount const& sourceAmount, Rate const& rate)
|
||||
{
|
||||
return divideRound(sourceAmount, rate, sourceAmount.asset(), false);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding operations
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -418,6 +419,7 @@ ValidVault::finalize(
|
||||
{
|
||||
bool const enforce = view.rules().enabled(featureSingleAssetVault);
|
||||
bool const fix340Enabled = view.rules().enabled(fixCleanup3_4_0);
|
||||
bool const fix350Enabled = view.rules().enabled(fixCleanup3_5_0);
|
||||
|
||||
if (!isTesSuccess(ret))
|
||||
return true; // Do not perform checks
|
||||
@@ -1144,6 +1146,17 @@ ValidVault::finalize(
|
||||
|
||||
auto const localPseudoDeltaAssets =
|
||||
roundToAsset(vaultAsset, vaultPseudoDeltaAssets, localMinScale);
|
||||
bool const feeAdjustedWithdrawal =
|
||||
fix350Enabled && distinctDestination && !vaultAsset.native();
|
||||
auto expectedDestinationDelta = localPseudoDeltaAssets * -1;
|
||||
if (feeAdjustedWithdrawal)
|
||||
{
|
||||
expectedDestinationDelta =
|
||||
subtractTransferFee(
|
||||
STAmount{vaultAsset, expectedDestinationDelta},
|
||||
transferRate(view, vaultAsset))
|
||||
.value();
|
||||
}
|
||||
// For IOU assets near a precision boundary the destination's STAmount
|
||||
// exponent can shift, making part of the sent value unrepresentable at
|
||||
// the receiver's new scale — that portion is irreversibly absorbed by the
|
||||
@@ -1152,7 +1165,8 @@ ValidVault::finalize(
|
||||
// the destination's scale. Floor rounding is used so that values exactly
|
||||
// at the step boundary are not mistakenly dismissed. Any representable
|
||||
// discrepancy indicates a real accounting bug and must be caught.
|
||||
auto const destroyedIsSubUlp = tolerateZeroDelta &&
|
||||
auto const destroyedIsSubUlp = !feeAdjustedWithdrawal &&
|
||||
tolerateZeroDelta &&
|
||||
roundToAsset(
|
||||
vaultAsset,
|
||||
vaultDeltaAssets.delta * -1 - destinationDelta.delta,
|
||||
@@ -1160,11 +1174,11 @@ ValidVault::finalize(
|
||||
Number::RoundingMode::Downward) == kZero;
|
||||
bool const withdrawAddsUp = fix340Enabled
|
||||
? agreesWithinOneUnit(
|
||||
localPseudoDeltaAssets * -1,
|
||||
expectedDestinationDelta,
|
||||
roundedDestinationDelta,
|
||||
vaultAsset,
|
||||
localMinScale)
|
||||
: localPseudoDeltaAssets * -1 == roundedDestinationDelta;
|
||||
: expectedDestinationDelta == roundedDestinationDelta;
|
||||
if (!destroyedIsSubUlp && !withdrawAddsUp)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: " << //
|
||||
|
||||
@@ -239,6 +239,7 @@ LoanBrokerCoverWithdraw::doApply()
|
||||
brokerPseudoID,
|
||||
preFeeBalance_,
|
||||
amount,
|
||||
amount,
|
||||
j_);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
||||
@@ -49,6 +51,24 @@ shouldWaiveWithdrawal(ReadView const& view, AccountID const& account, SLE::const
|
||||
: WaiveUnrealizedLoss::No;
|
||||
}
|
||||
|
||||
static Rate
|
||||
withdrawalTransferRate(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& destination,
|
||||
Asset const& asset)
|
||||
{
|
||||
// Pre-fixCleanup3_5_0: every vault withdrawal waives the transfer fee.
|
||||
// Post-fixCleanup3_5_0: the fee applies only when another token holder receives the assets.
|
||||
if (!view.rules().enabled(fixCleanup3_5_0))
|
||||
return kParityRate;
|
||||
|
||||
if (asset.native() || destination == account || destination == asset.getIssuer())
|
||||
return kParityRate;
|
||||
|
||||
return transferRate(view, asset);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
VaultWithdraw::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -170,11 +190,13 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
if (!maybeAssets)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const rate = withdrawalTransferRate(ctx.view, account, dstAcct, vaultAsset);
|
||||
auto const amountToReceive = subtractTransferFee(*maybeAssets, rate);
|
||||
if (auto const ret = canWithdraw(
|
||||
ctx.view,
|
||||
account,
|
||||
dstAcct,
|
||||
*maybeAssets,
|
||||
amountToReceive,
|
||||
ctx.tx.isFieldPresent(sfDestinationTag),
|
||||
ctx.tx[~sfCredentialIDs]))
|
||||
return ret;
|
||||
@@ -302,6 +324,8 @@ VaultWithdraw::doApply()
|
||||
|
||||
auto const amount = ctx_.tx[sfAmount];
|
||||
Asset const vaultAsset = vault->at(sfAsset);
|
||||
auto const dstAcct = ctx_.tx[~sfDestination].value_or(accountID_);
|
||||
auto const rate = withdrawalTransferRate(view(), accountID_, dstAcct, vaultAsset);
|
||||
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
STAmount sharesRedeemed = {share};
|
||||
@@ -329,8 +353,10 @@ VaultWithdraw::doApply()
|
||||
auto const truncate =
|
||||
view().rules().enabled(fixCleanup3_4_0) ? TruncateShares::Yes : TruncateShares::No;
|
||||
{
|
||||
auto const sourceAmount =
|
||||
rate == kParityRate ? amount : multiplyRound(amount, rate, vaultAsset, true);
|
||||
auto const maybeShares = assetsToSharesWithdraw(
|
||||
vault, sleIssuance, amount, truncate, waiveUnrealizedLoss);
|
||||
vault, sleIssuance, sourceAmount, truncate, waiveUnrealizedLoss);
|
||||
if (!maybeShares)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
sharesRedeemed = *maybeShares;
|
||||
@@ -531,7 +557,17 @@ VaultWithdraw::doApply()
|
||||
<< " assetsAvailable=" << allAvailable.getText();
|
||||
}
|
||||
assetsWithdrawn = allAvailable;
|
||||
}
|
||||
|
||||
auto const assetsDelivered = subtractTransferFee(assetsWithdrawn, rate);
|
||||
if (assetsWithdrawn > beast::kZero && assetsDelivered == beast::kZero)
|
||||
{
|
||||
JLOG(j_.debug()) << "VaultWithdraw: transfer fee reduces the payout to zero";
|
||||
return tecPRECISION_LOSS;
|
||||
}
|
||||
|
||||
if (isFinalWithdrawal)
|
||||
{
|
||||
// Do not let dust accumulate in the Vault.
|
||||
assetsTotal = 0;
|
||||
assetsAvailable = 0;
|
||||
@@ -583,9 +619,15 @@ VaultWithdraw::doApply()
|
||||
|
||||
associateAsset(*vault, vaultAsset);
|
||||
|
||||
auto const dstAcct = ctx_.tx[~sfDestination].value_or(accountID_);
|
||||
return doWithdraw(
|
||||
applyViewContext, accountID_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_);
|
||||
applyViewContext,
|
||||
accountID_,
|
||||
dstAcct,
|
||||
vaultAccount,
|
||||
preFeeBalance_,
|
||||
assetsDelivered,
|
||||
assetsWithdrawn,
|
||||
j_);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -50,6 +50,38 @@ namespace xrpl {
|
||||
class VaultLifecycle_test : public VaultTestBase
|
||||
{
|
||||
private:
|
||||
struct FundedVault
|
||||
{
|
||||
Keylet keylet;
|
||||
test::jtx::Account account;
|
||||
test::jtx::PrettyAsset shares;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a vault owned by `owner` and deposits `amount` into it from `depositor`.
|
||||
*/
|
||||
static FundedVault
|
||||
createFundedVault(
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Vault& vault,
|
||||
test::jtx::Account const& owner,
|
||||
test::jtx::Account const& depositor,
|
||||
STAmount const& amount)
|
||||
{
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = amount.asset()});
|
||||
env(tx);
|
||||
env.close();
|
||||
env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = amount}));
|
||||
env.close();
|
||||
|
||||
auto const sle = env.le(keylet);
|
||||
test::jtx::Account const account{"vault", sle->at(sfAccount)};
|
||||
env.memoize(account);
|
||||
return {
|
||||
.keylet = keylet,
|
||||
.account = account,
|
||||
.shares = test::jtx::PrettyAsset{MPTIssue{sle->at(sfShareMPTID)}}};
|
||||
}
|
||||
void
|
||||
testSequences()
|
||||
{
|
||||
@@ -569,6 +601,7 @@ private:
|
||||
bool enableClawback = true;
|
||||
bool requireAuth = true;
|
||||
int initialXRP = 1000;
|
||||
std::uint16_t transferFee = 0;
|
||||
FeatureBitset features = testableAmendments();
|
||||
};
|
||||
|
||||
@@ -593,7 +626,8 @@ private:
|
||||
MPTTester mptt{env, issuer, kMptInitNoFund};
|
||||
auto const kNone = LedgerSpecificFlags(0);
|
||||
mptt.create(
|
||||
{.flags = tfMPTCanTransfer | tfMPTCanLock |
|
||||
{.transferFee = args.transferFee,
|
||||
.flags = tfMPTCanTransfer | tfMPTCanLock |
|
||||
(args.enableClawback ? tfMPTCanClawback : kNone) |
|
||||
(args.requireAuth ? tfMPTRequireAuth : kNone)});
|
||||
PrettyAsset const asset = mptt.issuanceID();
|
||||
@@ -611,6 +645,230 @@ private:
|
||||
test(env, issuer, owner, depositor, asset, vault, mptt);
|
||||
};
|
||||
|
||||
// The MPT charges a 25% transfer fee, so 100 gross delivers 80 net.
|
||||
auto const feeArgs = CaseArgs{.transferFee = 25'000};
|
||||
|
||||
auto const testTransferFeeGate = [&](FeatureBitset const& features) {
|
||||
testCase(
|
||||
[this, features](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester& mptt) {
|
||||
bool const feeCharged = features[fixCleanup3_5_0];
|
||||
testcase(
|
||||
feeCharged ? "MPT transfer fee on third-party withdrawal"
|
||||
: "MPT transfer fee waived pre-fixCleanup3_5_0");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, depositor, asset(100));
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = depositor,
|
||||
.id = funded.keylet.key,
|
||||
.amount = funded.shares(100)});
|
||||
tx[sfDestination] = owner.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
auto const expected = feeCharged ? 80 : 100;
|
||||
BEAST_EXPECT(mptt.checkMPTokenAmount(depositor, 900));
|
||||
BEAST_EXPECT(mptt.checkMPTokenAmount(owner, expected));
|
||||
BEAST_EXPECT(mptt.checkMPTokenOutstandingAmount(900 + expected));
|
||||
BEAST_EXPECT(
|
||||
env.balance(funded.account, asset.raw().get<MPTIssue>()) == asset(0));
|
||||
|
||||
env(vault.del({.owner = owner, .id = funded.keylet.key}));
|
||||
env.close();
|
||||
},
|
||||
CaseArgs{.transferFee = 25'000, .features = features});
|
||||
};
|
||||
testTransferFeeGate(testableAmendments() - fixCleanup3_5_0);
|
||||
testTransferFeeGate(testableAmendments());
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester& mptt) {
|
||||
testcase("MPT transfer fee on fixed-asset third-party withdrawal");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, depositor, asset(100));
|
||||
auto const mptIssue = asset.raw().get<MPTIssue>();
|
||||
auto const shareIssue = funded.shares.raw().get<MPTIssue>();
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = depositor, .id = funded.keylet.key, .amount = asset(40)});
|
||||
tx[sfDestination] = owner.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// The destination receives the requested 40; the gross 50 leaves the vault.
|
||||
BEAST_EXPECT(mptt.checkMPTokenAmount(owner, 40));
|
||||
BEAST_EXPECT(env.balance(funded.account, mptIssue) == asset(50));
|
||||
BEAST_EXPECT(env.balance(depositor, shareIssue) == funded.shares(50));
|
||||
BEAST_EXPECT(mptt.checkMPTokenOutstandingAmount(990));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester& mptt) {
|
||||
testcase("MPT transfer fee on fixed-share third-party withdrawal");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, depositor, asset(100));
|
||||
auto const mptIssue = asset.raw().get<MPTIssue>();
|
||||
auto const shareIssue = funded.shares.raw().get<MPTIssue>();
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = depositor, .id = funded.keylet.key, .amount = funded.shares(25)});
|
||||
tx[sfDestination] = owner.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// The 25 shares redeem 25 assets; the destination receives 20 net.
|
||||
BEAST_EXPECT(mptt.checkMPTokenAmount(owner, 20));
|
||||
BEAST_EXPECT(env.balance(funded.account, mptIssue) == asset(75));
|
||||
BEAST_EXPECT(env.balance(depositor, shareIssue) == funded.shares(75));
|
||||
BEAST_EXPECT(mptt.checkMPTokenOutstandingAmount(995));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const& issuer,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester& mptt) {
|
||||
testcase("MPT no transfer fee on withdrawal to issuer");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, depositor, asset(100));
|
||||
auto const mptIssue = asset.raw().get<MPTIssue>();
|
||||
auto const shareIssue = funded.shares.raw().get<MPTIssue>();
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = depositor, .id = funded.keylet.key, .amount = asset(10)});
|
||||
tx[sfDestination] = issuer.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(funded.account, mptIssue) == asset(90));
|
||||
BEAST_EXPECT(env.balance(depositor, shareIssue) == funded.shares(90));
|
||||
BEAST_EXPECT(mptt.checkMPTokenOutstandingAmount(990));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester& mptt) {
|
||||
testcase("MPT no transfer fee on self-withdrawal");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, depositor, asset(100));
|
||||
auto const mptIssue = asset.raw().get<MPTIssue>();
|
||||
|
||||
env(vault.withdraw(
|
||||
{.depositor = depositor, .id = funded.keylet.key, .amount = asset(10)}));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(mptt.checkMPTokenAmount(depositor, 910));
|
||||
BEAST_EXPECT(env.balance(funded.account, mptIssue) == asset(90));
|
||||
BEAST_EXPECT(mptt.checkMPTokenOutstandingAmount(1000));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester& mptt) {
|
||||
testcase("MPT transfer fee must fit in the vault position");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, depositor, asset(100));
|
||||
auto const mptIssue = asset.raw().get<MPTIssue>();
|
||||
auto const shareIssue = funded.shares.raw().get<MPTIssue>();
|
||||
|
||||
// Delivering 81 costs 102 gross, more than the 100 shares held.
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = depositor, .id = funded.keylet.key, .amount = asset(81)});
|
||||
tx[sfDestination] = owner.human();
|
||||
env(tx, Ter{tecINSUFFICIENT_FUNDS});
|
||||
env.close();
|
||||
|
||||
// Delivering 80 costs exactly the 100 shares held.
|
||||
tx = vault.withdraw(
|
||||
{.depositor = depositor, .id = funded.keylet.key, .amount = asset(80)});
|
||||
tx[sfDestination] = owner.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(mptt.checkMPTokenAmount(owner, 80));
|
||||
BEAST_EXPECT(env.balance(funded.account, mptIssue) == asset(0));
|
||||
BEAST_EXPECT(env.balance(depositor, shareIssue) == funded.shares(0));
|
||||
BEAST_EXPECT(mptt.checkMPTokenOutstandingAmount(980));
|
||||
|
||||
env(vault.del({.owner = owner, .id = funded.keylet.key}));
|
||||
env.close();
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester& mptt) {
|
||||
testcase("MPT transfer fee rounds the payout down to zero");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, depositor, asset(1));
|
||||
auto const mptIssue = asset.raw().get<MPTIssue>();
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = depositor, .id = funded.keylet.key, .amount = funded.shares(1)});
|
||||
tx[sfDestination] = owner.human();
|
||||
env(tx, Ter{tecPRECISION_LOSS});
|
||||
env.close();
|
||||
|
||||
// A self-withdrawal pays no fee, so the same share still redeems.
|
||||
env(vault.withdraw(
|
||||
{.depositor = depositor, .id = funded.keylet.key, .amount = funded.shares(1)}));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(mptt.checkMPTokenAmount(depositor, 1000));
|
||||
BEAST_EXPECT(env.balance(funded.account, mptIssue) == asset(0));
|
||||
|
||||
env(vault.del({.owner = owner, .id = funded.keylet.key}));
|
||||
env.close();
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase([this](
|
||||
Env& env,
|
||||
Account const& issuer,
|
||||
@@ -1487,66 +1745,251 @@ private:
|
||||
env.close();
|
||||
});
|
||||
|
||||
// The issuer charges a 25% transfer fee, so 100 gross delivers 80 net.
|
||||
auto const feeArgs = CaseArgs{.transferRate = 1.25};
|
||||
|
||||
auto const testTransferFeeGate = [&](FeatureBitset const& features) {
|
||||
testCase(
|
||||
[this, features](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const& issuer,
|
||||
Account const& charlie,
|
||||
auto,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto&&...) {
|
||||
bool const feeCharged = features[fixCleanup3_5_0];
|
||||
testcase(
|
||||
feeCharged ? "IOU transfer fee on third-party withdrawal"
|
||||
: "IOU transfer fee waived pre-fixCleanup3_5_0");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, owner, asset(100));
|
||||
auto const issue = asset.raw().get<Issue>();
|
||||
|
||||
// Deposits are fee-free.
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(100));
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(100));
|
||||
|
||||
env(vault.clawback(
|
||||
{.issuer = issuer,
|
||||
.id = funded.keylet.key,
|
||||
.holder = owner,
|
||||
.amount = asset(50)}));
|
||||
env.close();
|
||||
|
||||
// Clawbacks are fee-free.
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(100));
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(50));
|
||||
|
||||
env(vault.withdraw(
|
||||
{.depositor = owner,
|
||||
.id = funded.keylet.key,
|
||||
.amount = funded.shares(20'000'000)}));
|
||||
env.close();
|
||||
|
||||
// Self-withdrawals are fee-free.
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(120));
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(30));
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = owner,
|
||||
.id = funded.keylet.key,
|
||||
.amount = funded.shares(30'000'000)});
|
||||
tx[sfDestination] = charlie.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
auto const expected = feeCharged ? asset(24) : asset(30);
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(120));
|
||||
BEAST_EXPECT(env.balance(charlie, issue) == expected);
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(0));
|
||||
|
||||
env(vault.del({.owner = owner, .id = funded.keylet.key}));
|
||||
env.close();
|
||||
},
|
||||
CaseArgs{.transferRate = 1.25, .features = features});
|
||||
};
|
||||
testTransferFeeGate(testableAmendments() - fixCleanup3_5_0);
|
||||
testTransferFeeGate(testableAmendments());
|
||||
|
||||
testCase(
|
||||
[&, this](
|
||||
[this](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const& issuer,
|
||||
Account const&,
|
||||
Account const& charlie,
|
||||
auto vaultAccount,
|
||||
auto,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto issuanceId) {
|
||||
testcase("IOU transfer fees not applied");
|
||||
auto&&...) {
|
||||
testcase("IOU transfer fee on fixed-asset third-party withdrawal");
|
||||
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
auto const funded = createFundedVault(env, vault, owner, owner, asset(100));
|
||||
auto const issue = asset.raw().get<Issue>();
|
||||
auto const shareIssue = funded.shares.raw().get<MPTIssue>();
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = owner, .id = funded.keylet.key, .amount = asset(40)});
|
||||
tx[sfDestination] = charlie.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = owner, .id = keylet.key, .amount = asset(100)}));
|
||||
// The destination receives the requested 40; the gross 50 leaves the vault.
|
||||
BEAST_EXPECT(env.balance(charlie, issue) == asset(40));
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(50));
|
||||
BEAST_EXPECT(env.balance(owner, shareIssue) == funded.shares(50'000'000));
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(100));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const&,
|
||||
Account const& charlie,
|
||||
auto,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto&&...) {
|
||||
testcase("IOU transfer fee on fixed-share third-party withdrawal");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, owner, asset(100));
|
||||
auto const issue = asset.raw().get<Issue>();
|
||||
auto const shareIssue = funded.shares.raw().get<MPTIssue>();
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = owner,
|
||||
.id = funded.keylet.key,
|
||||
.amount = funded.shares(25'000'000)});
|
||||
tx[sfDestination] = charlie.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// The shares redeem 25 assets; the destination receives 20 net.
|
||||
BEAST_EXPECT(env.balance(charlie, issue) == asset(20));
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(75));
|
||||
BEAST_EXPECT(env.balance(owner, shareIssue) == funded.shares(75'000'000));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const& issuer,
|
||||
Account const&,
|
||||
auto,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto&&...) {
|
||||
testcase("IOU no transfer fee on withdrawal to issuer");
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, owner, asset(100));
|
||||
auto const issue = asset.raw().get<Issue>();
|
||||
Asset const share = Asset(issuanceId(keylet));
|
||||
|
||||
// transfer fees ignored on deposit
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = owner, .id = funded.keylet.key, .amount = asset(10)});
|
||||
tx[sfDestination] = issuer.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(90));
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(100));
|
||||
BEAST_EXPECT(env.balance(vaultAccount(keylet), issue) == asset(100));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
{
|
||||
auto tx = vault.clawback(
|
||||
{.issuer = issuer, .id = keylet.key, .holder = owner, .amount = asset(50)});
|
||||
env(tx);
|
||||
env.close();
|
||||
}
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const&,
|
||||
Account const&,
|
||||
auto,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto&&...) {
|
||||
testcase("IOU no transfer fee on self-withdrawal");
|
||||
|
||||
// transfer fees ignored on clawback
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(100));
|
||||
BEAST_EXPECT(env.balance(vaultAccount(keylet), issue) == asset(50));
|
||||
auto const funded = createFundedVault(env, vault, owner, owner, asset(100));
|
||||
auto const issue = asset.raw().get<Issue>();
|
||||
|
||||
env(vault.withdraw(
|
||||
{.depositor = owner, .id = keylet.key, .amount = share(20'000'000)}));
|
||||
{.depositor = owner, .id = funded.keylet.key, .amount = asset(10)}));
|
||||
env.close();
|
||||
|
||||
// transfer fees ignored on withdraw
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(120));
|
||||
BEAST_EXPECT(env.balance(vaultAccount(keylet), issue) == asset(30));
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(110));
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(90));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
{
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = owner, .id = keylet.key, .amount = share(30'000'000)});
|
||||
tx[sfDestination] = charlie.human();
|
||||
env(tx);
|
||||
}
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const&,
|
||||
Account const& charlie,
|
||||
auto,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto&&...) {
|
||||
testcase("IOU transfer fee must fit in the vault position");
|
||||
|
||||
// transfer fees ignored on withdraw to 3rd party
|
||||
BEAST_EXPECT(env.balance(owner, issue) == asset(120));
|
||||
BEAST_EXPECT(env.balance(charlie, issue) == asset(30));
|
||||
BEAST_EXPECT(env.balance(vaultAccount(keylet), issue) == asset(0));
|
||||
auto const funded = createFundedVault(env, vault, owner, owner, asset(100));
|
||||
auto const issue = asset.raw().get<Issue>();
|
||||
|
||||
env(vault.del({.owner = owner, .id = keylet.key}));
|
||||
// Delivering 81 costs 101.25 gross, more than the vault position.
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = owner, .id = funded.keylet.key, .amount = asset(81)});
|
||||
tx[sfDestination] = charlie.human();
|
||||
env(tx, Ter{tecINSUFFICIENT_FUNDS});
|
||||
env.close();
|
||||
|
||||
// Delivering 80 costs exactly the 100 held.
|
||||
tx = vault.withdraw(
|
||||
{.depositor = owner, .id = funded.keylet.key, .amount = asset(80)});
|
||||
tx[sfDestination] = charlie.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(charlie, issue) == asset(80));
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(0));
|
||||
|
||||
env(vault.del({.owner = owner, .id = funded.keylet.key}));
|
||||
env.close();
|
||||
},
|
||||
CaseArgs{.transferRate = 1.25});
|
||||
feeArgs);
|
||||
|
||||
testCase(
|
||||
[this](
|
||||
Env& env,
|
||||
Account const& owner,
|
||||
Account const&,
|
||||
Account const& charlie,
|
||||
auto,
|
||||
Vault& vault,
|
||||
PrettyAsset const& asset,
|
||||
auto&&...) {
|
||||
testcase("IOU receiver limit applies to the net amount");
|
||||
|
||||
// The limit fits the 40 delivered but not the 50 gross.
|
||||
env(trust(charlie, asset(40)));
|
||||
env.close();
|
||||
|
||||
auto const funded = createFundedVault(env, vault, owner, owner, asset(100));
|
||||
auto const issue = asset.raw().get<Issue>();
|
||||
|
||||
auto tx = vault.withdraw(
|
||||
{.depositor = owner, .id = funded.keylet.key, .amount = asset(40)});
|
||||
tx[sfDestination] = charlie.human();
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(charlie, issue) == asset(40));
|
||||
BEAST_EXPECT(env.balance(funded.account, issue) == asset(50));
|
||||
},
|
||||
feeArgs);
|
||||
|
||||
testCase([&, this](
|
||||
Env& env,
|
||||
|
||||
@@ -49,7 +49,7 @@ TEST(MallocTrimReport, structure)
|
||||
}
|
||||
|
||||
#if defined(__GLIBC__) && BOOST_OS_LINUX
|
||||
TEST(ParseStatmRSSkB, standard_format)
|
||||
TEST(parseStatmRSSkB, standard_format)
|
||||
{
|
||||
using xrpl::detail::parseStatmRSSkB;
|
||||
|
||||
@@ -121,7 +121,7 @@ TEST(ParseStatmRSSkB, standard_format)
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(MallocTrim, without_debug_logging)
|
||||
TEST(mallocTrim, without_debug_logging)
|
||||
{
|
||||
beast::Journal const journal{beast::Journal::getNullSink()};
|
||||
|
||||
@@ -144,7 +144,7 @@ TEST(MallocTrim, without_debug_logging)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(MallocTrim, empty_tag)
|
||||
TEST(mallocTrim, empty_tag)
|
||||
{
|
||||
beast::Journal const journal{beast::Journal::getNullSink()};
|
||||
MallocTrimReport const report = mallocTrim("", journal);
|
||||
@@ -157,7 +157,7 @@ TEST(MallocTrim, empty_tag)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(MallocTrim, with_debug_logging)
|
||||
TEST(mallocTrim, with_debug_logging)
|
||||
{
|
||||
struct DebugSink : public beast::Journal::Sink
|
||||
{
|
||||
@@ -194,7 +194,7 @@ TEST(MallocTrim, with_debug_logging)
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(MallocTrim, repeated_calls)
|
||||
TEST(mallocTrim, repeated_calls)
|
||||
{
|
||||
beast::Journal const journal{beast::Journal::getNullSink()};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(RangeSet, prev_missing)
|
||||
TEST(RangeSet, prevMissing)
|
||||
{
|
||||
// Set will include:
|
||||
// [ 0, 5]
|
||||
@@ -36,7 +36,7 @@ TEST(RangeSet, prev_missing)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RangeSet, to_string)
|
||||
TEST(RangeSet, toString)
|
||||
{
|
||||
RangeSet<std::uint32_t> set;
|
||||
EXPECT_EQ(to_string(set), "empty");
|
||||
@@ -54,7 +54,7 @@ TEST(RangeSet, to_string)
|
||||
EXPECT_EQ(to_string(set), "1-2,6");
|
||||
}
|
||||
|
||||
TEST(RangeSet, from_string)
|
||||
TEST(RangeSet, fromString)
|
||||
{
|
||||
RangeSet<std::uint32_t> set;
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ TEST_F(StringUtilitiesTest, to_string)
|
||||
EXPECT_EQ(result, "hello");
|
||||
}
|
||||
|
||||
TEST_F(StringUtilitiesTest, trim_whitespace)
|
||||
TEST_F(StringUtilitiesTest, trimWhitespace)
|
||||
{
|
||||
EXPECT_EQ(trimWhitespace(""), "");
|
||||
EXPECT_EQ(trimWhitespace(" "), "");
|
||||
@@ -303,7 +303,7 @@ TEST_F(StringUtilitiesTest, trim_whitespace)
|
||||
EXPECT_EQ(trimWhitespace(" a b\tc "), "a b\tc");
|
||||
}
|
||||
|
||||
TEST_F(StringUtilitiesTest, to_lower)
|
||||
TEST_F(StringUtilitiesTest, toLower)
|
||||
{
|
||||
EXPECT_EQ(toLower(""), "");
|
||||
EXPECT_EQ(toLower("ABC"), "abc");
|
||||
@@ -318,7 +318,7 @@ TEST_F(StringUtilitiesTest, to_lower)
|
||||
// Both helpers are documented as depending only on their input. Guard that by
|
||||
// checking the bytes just outside ASCII, which a locale-aware isspace/tolower
|
||||
// could classify differently.
|
||||
TEST_F(StringUtilitiesTest, trim_and_lower_ignore_locale)
|
||||
TEST_F(StringUtilitiesTest, trimAndLowerIgnoreLocale)
|
||||
{
|
||||
// 0xA0 is NO-BREAK SPACE in Latin-1 and is whitespace to some locales.
|
||||
std::string const nbsp("\xA0", 1);
|
||||
|
||||
@@ -14,7 +14,7 @@ check(std::string const& in, std::string const& out)
|
||||
EXPECT_EQ(base64Decode(encoded), in);
|
||||
}
|
||||
|
||||
TEST(Base64, base64)
|
||||
TEST(base64, base64)
|
||||
{
|
||||
// cspell: disable
|
||||
check("", "");
|
||||
|
||||
@@ -128,7 +128,7 @@ struct BaseUintTest : public ::testing::Test
|
||||
|
||||
using BaseUintDeathTest = BaseUintTest;
|
||||
|
||||
TEST_F(BaseUintDeathTest, from_raw_size_mismatch)
|
||||
TEST_F(BaseUintDeathTest, fromRaw_size_mismatch)
|
||||
{
|
||||
// ENABLE_VOIDSTAR is a debug build, but does not crash on failed asserts. Rather than twist
|
||||
// these tests into knots to make them work, just skip them.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(Contract, contract)
|
||||
TEST(contract, contract)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(MulDiv, mul_div)
|
||||
TEST(mulDiv, mulDiv)
|
||||
{
|
||||
auto const max = std::numeric_limits<std::uint64_t>::max();
|
||||
std::uint64_t const max32 = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(Scope, scope_exit)
|
||||
TEST(scope, ScopeExit)
|
||||
{
|
||||
// ScopeExit always executes the functor on destruction,
|
||||
// unless release() is called
|
||||
@@ -56,7 +56,7 @@ TEST(Scope, scope_exit)
|
||||
EXPECT_EQ(i, 5);
|
||||
}
|
||||
|
||||
TEST(Scope, scope_fail)
|
||||
TEST(scope, ScopeFail)
|
||||
{
|
||||
// ScopeFail executes the functor on destruction only
|
||||
// if an exception is unwinding, unless release() is called
|
||||
@@ -106,7 +106,7 @@ TEST(Scope, scope_fail)
|
||||
EXPECT_EQ(i, 5);
|
||||
}
|
||||
|
||||
TEST(Scope, scope_success)
|
||||
TEST(scope, ScopeSuccess)
|
||||
{
|
||||
// ScopeSuccess executes the functor on destruction only
|
||||
// if an exception is not unwinding, unless release() is called
|
||||
|
||||
@@ -105,7 +105,7 @@ static_assert(
|
||||
|
||||
using TagInt = TaggedInteger<std::int32_t, Tag1>;
|
||||
|
||||
TEST(TaggedInteger, comparison_operators)
|
||||
TEST(tagged_integer, comparison_operators)
|
||||
{
|
||||
TagInt const zero(0);
|
||||
TagInt const one(1);
|
||||
@@ -131,7 +131,7 @@ TEST(TaggedInteger, comparison_operators)
|
||||
EXPECT_FALSE(one <= zero);
|
||||
}
|
||||
|
||||
TEST(TaggedInteger, increment_decrement_operators)
|
||||
TEST(tagged_integer, increment_decrement_operators)
|
||||
{
|
||||
TagInt const zero(0);
|
||||
TagInt const one(1);
|
||||
@@ -146,7 +146,7 @@ TEST(TaggedInteger, increment_decrement_operators)
|
||||
EXPECT_EQ(a, zero);
|
||||
}
|
||||
|
||||
TEST(TaggedInteger, arithmetic_operators)
|
||||
TEST(tagged_integer, arithmetic_operators)
|
||||
{
|
||||
TagInt const a{-2};
|
||||
EXPECT_EQ(+a, TagInt{-2});
|
||||
@@ -166,7 +166,7 @@ TEST(TaggedInteger, arithmetic_operators)
|
||||
EXPECT_EQ((TagInt{16} >> TagInt{2}), TagInt{4});
|
||||
}
|
||||
|
||||
TEST(TaggedInteger, assignment_operators)
|
||||
TEST(tagged_integer, assignment_operators)
|
||||
{
|
||||
TagInt a{-2};
|
||||
TagInt b{0};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
using namespace xrpl;
|
||||
|
||||
TEST(Csprng, get_values)
|
||||
TEST(csprng, get_values)
|
||||
{
|
||||
auto& engine = cryptoPrng();
|
||||
auto randVal = engine();
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TEST(JsonValue, limits)
|
||||
TEST(json_value, limits)
|
||||
{
|
||||
using namespace json;
|
||||
static_assert(Value::kMinInt == Int(~(UInt(-1) / 2)));
|
||||
@@ -29,7 +29,7 @@ TEST(JsonValue, limits)
|
||||
static_assert(Value::kMaxUInt == UInt(-1));
|
||||
}
|
||||
|
||||
TEST(JsonValue, construct_and_compare_json_static_string)
|
||||
TEST(json_value, construct_and_compare_Json_StaticString)
|
||||
{
|
||||
static constexpr char kSample[]{"Contents of a json::StaticString"};
|
||||
|
||||
@@ -52,7 +52,7 @@ TEST(JsonValue, construct_and_compare_json_static_string)
|
||||
EXPECT_NE(kTest3, str);
|
||||
}
|
||||
|
||||
TEST(JsonValue, different_types)
|
||||
TEST(json_value, different_types)
|
||||
{
|
||||
// Exercise ValueType constructor
|
||||
static constexpr json::StaticString kStaticStr{"staticStr"};
|
||||
@@ -206,7 +206,7 @@ TEST(JsonValue, different_types)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, compare_strings)
|
||||
TEST(json_value, compare_strings)
|
||||
{
|
||||
auto doCompare = [&](json::Value const& lhs,
|
||||
json::Value const& rhs,
|
||||
@@ -560,7 +560,7 @@ TEST(JsonValue, compare_strings)
|
||||
#pragma pop_macro("DO_COMPARE")
|
||||
}
|
||||
|
||||
TEST(JsonValue, bool)
|
||||
TEST(json_value, bool)
|
||||
{
|
||||
EXPECT_FALSE(json::Value());
|
||||
|
||||
@@ -583,7 +583,7 @@ TEST(JsonValue, bool)
|
||||
EXPECT_TRUE(bool(object));
|
||||
}
|
||||
|
||||
TEST(JsonValue, bad_json)
|
||||
TEST(json_value, bad_json)
|
||||
{
|
||||
char const* s(R"({"method":"ledger","params":[{"ledger_index":1e300}]})");
|
||||
|
||||
@@ -607,7 +607,7 @@ parseValue(std::string const& doc)
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(JsonValue, parse_double_valid)
|
||||
TEST(json_value, parse_double_valid)
|
||||
{
|
||||
// 1e300 is large but still representable, so it parses (unlike the out-of-range cases below).
|
||||
for (auto const& [text, expected] :
|
||||
@@ -627,14 +627,14 @@ TEST(JsonValue, parse_double_valid)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, parse_double_out_of_range)
|
||||
TEST(json_value, parse_double_out_of_range)
|
||||
{
|
||||
// Magnitudes with no finite double representation are rejected.
|
||||
for (char const* oor : {"1e400", "-1e400", "0.001e500", "1e-400", "-1e-400", "123e-500"})
|
||||
EXPECT_FALSE(parseValue(oor).has_value()) << oor;
|
||||
}
|
||||
|
||||
TEST(JsonValue, parse_double_malformed)
|
||||
TEST(json_value, parse_double_malformed)
|
||||
{
|
||||
// readNumber() collects any run of digits and '.eE+-' into a single Double
|
||||
// token, so these malformed tokens reach decodeDouble. Each has a valid
|
||||
@@ -644,7 +644,7 @@ TEST(JsonValue, parse_double_malformed)
|
||||
EXPECT_FALSE(parseValue(bad).has_value()) << bad;
|
||||
}
|
||||
|
||||
TEST(JsonValue, edge_cases)
|
||||
TEST(json_value, edge_cases)
|
||||
{
|
||||
std::uint32_t const maxUInt = std::numeric_limits<std::uint32_t>::max();
|
||||
std::int32_t const maxInt = std::numeric_limits<std::int32_t>::max();
|
||||
@@ -791,7 +791,7 @@ TEST(JsonValue, edge_cases)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, copy)
|
||||
TEST(json_value, copy)
|
||||
{
|
||||
json::Value v1{2.5};
|
||||
EXPECT_TRUE(v1.isDouble());
|
||||
@@ -812,7 +812,7 @@ TEST(JsonValue, copy)
|
||||
EXPECT_EQ(v1, v2);
|
||||
}
|
||||
|
||||
TEST(JsonValue, move)
|
||||
TEST(json_value, move)
|
||||
{
|
||||
json::Value v1{2.5};
|
||||
EXPECT_TRUE(v1.isDouble());
|
||||
@@ -831,7 +831,7 @@ TEST(JsonValue, move)
|
||||
EXPECT_NE(v1, v2); // NOLINT(bugprone-use-after-move)
|
||||
}
|
||||
|
||||
TEST(JsonValue, comparisons)
|
||||
TEST(json_value, comparisons)
|
||||
{
|
||||
json::Value a, b;
|
||||
auto testEquals = [&](std::string const& name) {
|
||||
@@ -886,7 +886,7 @@ TEST(JsonValue, comparisons)
|
||||
testGreaterThan("big");
|
||||
}
|
||||
|
||||
TEST(JsonValue, compact)
|
||||
TEST(json_value, compact)
|
||||
{
|
||||
json::Value j;
|
||||
json::Reader r;
|
||||
@@ -909,7 +909,7 @@ TEST(JsonValue, compact)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, conversions)
|
||||
TEST(json_value, conversions)
|
||||
{
|
||||
// We have json::ValueType::Real but json::Value::asDouble.
|
||||
// TODO: What's the thinking here?
|
||||
@@ -1125,7 +1125,7 @@ TEST(JsonValue, conversions)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, access_members)
|
||||
TEST(json_value, access_members)
|
||||
{
|
||||
json::Value val;
|
||||
EXPECT_EQ(val.type(), json::ValueType::Null);
|
||||
@@ -1218,7 +1218,7 @@ TEST(JsonValue, access_members)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, remove_members)
|
||||
TEST(json_value, remove_members)
|
||||
{
|
||||
json::Value val;
|
||||
EXPECT_EQ(val.removeMember(std::string("member")).type(), json::ValueType::Null);
|
||||
@@ -1245,7 +1245,7 @@ TEST(JsonValue, remove_members)
|
||||
EXPECT_EQ(val.size(), 0);
|
||||
}
|
||||
|
||||
TEST(JsonValue, iterator)
|
||||
TEST(json_value, iterator)
|
||||
{
|
||||
{
|
||||
// Iterating an array.
|
||||
@@ -1331,7 +1331,7 @@ TEST(JsonValue, iterator)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, nest_limits)
|
||||
TEST(json_value, nest_limits)
|
||||
{
|
||||
json::Reader r;
|
||||
{
|
||||
@@ -1377,7 +1377,7 @@ TEST(JsonValue, nest_limits)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JsonValue, memory_leak)
|
||||
TEST(json_value, memory_leak)
|
||||
{
|
||||
// When run with the address sanitizer, this test confirms there is no
|
||||
// memory leak with the scenarios below.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(AMMEntryTests, constructors)
|
||||
TEST(AMMEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(AccountRootEntryTests, constructors)
|
||||
TEST(AccountRootEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(AmendmentsEntryTests, constructors)
|
||||
TEST(AmendmentsEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(BridgeEntryTests, constructors)
|
||||
TEST(BridgeEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(CheckEntryTests, constructors)
|
||||
TEST(CheckEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(CredentialEntryTests, constructors)
|
||||
TEST(CredentialEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(DIDEntryTests, constructors)
|
||||
TEST(DIDEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(DelegateEntryTests, constructors)
|
||||
TEST(DelegateEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(DepositPreauthEntryTests, constructors)
|
||||
TEST(DepositPreauthEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(DirectoryNodeEntryTests, constructors)
|
||||
TEST(DirectoryNodeEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(EscrowEntryTests, constructors)
|
||||
TEST(EscrowEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(FeeSettingsEntryTests, constructors)
|
||||
TEST(FeeSettingsEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(LedgerHashesEntryTests, constructors)
|
||||
TEST(LedgerHashesEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(LoanBrokerEntryTests, constructors)
|
||||
TEST(LoanBrokerEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(LoanEntryTests, constructors)
|
||||
TEST(LoanEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(MPTokenEntryTests, constructors)
|
||||
TEST(MPTokenEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(MPTokenIssuanceEntryTests, constructors)
|
||||
TEST(MPTokenIssuanceEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(NFTokenOfferEntryTests, constructors)
|
||||
TEST(NFTokenOfferEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(NFTokenPageEntryTests, constructors)
|
||||
TEST(NFTokenPageEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(NegativeUNLEntryTests, constructors)
|
||||
TEST(NegativeUNLEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(OfferEntryTests, constructors)
|
||||
TEST(OfferEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(OracleEntryTests, constructors)
|
||||
TEST(OracleEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(PayChannelEntryTests, constructors)
|
||||
TEST(PayChannelEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(PermissionedDomainEntryTests, constructors)
|
||||
TEST(PermissionedDomainEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(RippleStateEntryTests, constructors)
|
||||
TEST(RippleStateEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SLEBaseTests, read_only)
|
||||
TEST_F(SLEBaseTests, ReadOnly)
|
||||
{
|
||||
AccountRootEntryR const absent(bob_.id(), env_.getClosedLedger());
|
||||
EXPECT_FALSE(absent.exists());
|
||||
@@ -168,7 +168,7 @@ TEST_F(SLEBaseTests, read_only)
|
||||
EXPECT_EQ(&present.readView(), &env_.getClosedLedger());
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, adopt_sle)
|
||||
TEST_F(SLEBaseTests, AdoptSLE)
|
||||
{
|
||||
auto const sle = env_.getClosedLedger().read(keylet::account(alice_.id()));
|
||||
ASSERT_NE(sle, nullptr);
|
||||
@@ -201,7 +201,7 @@ TEST_F(SLEBaseTests, adopt_sle)
|
||||
"writable entries must not be constructible from a bare SLE");
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, writable_accessors)
|
||||
TEST_F(SLEBaseTests, WritableAccessors)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
beast::Journal const j{beast::Journal::getNullSink()};
|
||||
@@ -236,7 +236,7 @@ TEST_F(SLEBaseTests, writable_accessors)
|
||||
!HasApplyView<AccountRootEntryR>, "applyView() must not exist on a read-only entry");
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, apply_view_context_ctor)
|
||||
TEST_F(SLEBaseTests, ApplyViewContextCtor)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
beast::Journal const j{beast::Journal::getNullSink()};
|
||||
@@ -260,7 +260,7 @@ TEST_F(SLEBaseTests, apply_view_context_ctor)
|
||||
EXPECT_EQ(fromCtx.rawSle(), fromView.rawSle());
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, writable_lifecycle)
|
||||
TEST_F(SLEBaseTests, WritableLifecycle)
|
||||
{
|
||||
// A view we never apply, so nothing here reaches the ledger.
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
@@ -318,7 +318,7 @@ TEST_F(SLEBaseTests, writable_lifecycle)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, conversion)
|
||||
TEST_F(SLEBaseTests, Conversion)
|
||||
{
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
|
||||
@@ -336,7 +336,7 @@ TEST_F(SLEBaseTests, conversion)
|
||||
EXPECT_EQ(generic.type(), ltACCOUNT_ROOT);
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, resolve_entry_peeks)
|
||||
TEST_F(SLEBaseTests, ResolveEntryPeeks)
|
||||
{
|
||||
// getOpenLedger() is an OpenView, which derives from ReadView but not
|
||||
// from ApplyView, so resolveEntry's dynamic_cast fails and this takes
|
||||
@@ -368,7 +368,7 @@ TEST_F(SLEBaseTests, resolve_entry_peeks)
|
||||
EXPECT_EQ(readOnly->getFieldU32(sfSequence), bumped);
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, throws_on_missing_entry)
|
||||
TEST_F(SLEBaseTests, ThrowsOnMissingEntry)
|
||||
{
|
||||
// A generic read-only entry has no static type to fall back on, so
|
||||
// type() must read it off the (absent) SLE and throw.
|
||||
@@ -389,7 +389,7 @@ TEST_F(SLEBaseTests, throws_on_missing_entry)
|
||||
EXPECT_THROW(std::ignore = (*missing).getType(), std::logic_error);
|
||||
}
|
||||
|
||||
TEST_F(SLEBaseTests, throws_on_missing_writable_entry)
|
||||
TEST_F(SLEBaseTests, ThrowsOnMissingWritableEntry)
|
||||
{
|
||||
// A view we never apply, so nothing here reaches the ledger.
|
||||
ApplyViewImpl av(&env_.getClosedLedger(), TapNone);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(SignerListEntryTests, constructors)
|
||||
TEST(SignerListEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(SponsorshipEntryTests, constructors)
|
||||
TEST(SponsorshipEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(TicketEntryTests, constructors)
|
||||
TEST(TicketEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(VaultEntryTests, constructors)
|
||||
TEST(VaultEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(XChainOwnedClaimIDEntryTests, constructors)
|
||||
TEST(XChainOwnedClaimIDEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(XChainOwnedCreateAccountClaimIDEntryTests, constructors)
|
||||
TEST(XChainOwnedCreateAccountClaimIDEntryTests, Constructors)
|
||||
{
|
||||
EntryTestEnv e;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ account(std::string_view hex)
|
||||
// getText() builds its string from eight substitutions of the same type, so a
|
||||
// transposed pair would still compile and still type check. Pin the output so
|
||||
// the field/value pairing is actually verified.
|
||||
TEST(STXChainBridge, get_text_pairs_each_field_with_its_value)
|
||||
TEST(STXChainBridge, getTextPairsEachFieldWithItsValue)
|
||||
{
|
||||
auto const lockingDoor = account("0102030405060708090A0B0C0D0E0F1011121314");
|
||||
auto const issuingDoor = account("14131211100F0E0D0C0B0A090807060504030201");
|
||||
@@ -46,7 +46,7 @@ TEST(STXChainBridge, get_text_pairs_each_field_with_its_value)
|
||||
EXPECT_EQ(bridge.getText(), expected);
|
||||
}
|
||||
|
||||
TEST(STXChainBridge, get_text_on_a_default_bridge)
|
||||
TEST(STXChainBridge, getTextOnADefaultBridge)
|
||||
{
|
||||
STXChainBridge const bridge;
|
||||
auto const text = bridge.getText();
|
||||
|
||||
@@ -12,7 +12,7 @@ using namespace xrpl;
|
||||
// by subscribing the real cap through a WebSocket, which would exceed the frame
|
||||
// limit and drop the connection before the check runs) lets the boundary be
|
||||
// asserted exactly.
|
||||
TEST(InfoSubSubscriptionCap, boundary)
|
||||
TEST(InfoSubSubscriptionCap, Boundary)
|
||||
{
|
||||
constexpr std::size_t cap = kMaxSubscriptionsPerConnection;
|
||||
|
||||
@@ -30,7 +30,7 @@ TEST(InfoSubSubscriptionCap, boundary)
|
||||
EXPECT_TRUE(exceedsSubscriptionCap(cap - 1, 2));
|
||||
}
|
||||
|
||||
TEST(InfoSubSubscriptionCap, no_overflow)
|
||||
TEST(InfoSubSubscriptionCap, NoOverflow)
|
||||
{
|
||||
constexpr std::size_t cap = kMaxSubscriptionsPerConnection;
|
||||
constexpr std::size_t max = std::numeric_limits<std::size_t>::max();
|
||||
@@ -41,7 +41,7 @@ TEST(InfoSubSubscriptionCap, no_overflow)
|
||||
EXPECT_TRUE(exceedsSubscriptionCap(cap, max));
|
||||
}
|
||||
|
||||
TEST(InfoSubSubscriptionCap, explicit_cap)
|
||||
TEST(InfoSubSubscriptionCap, ExplicitCap)
|
||||
{
|
||||
// A configured override is honored: the boundary tracks the passed cap, not
|
||||
// the built-in default. This is the seam doSubscribe uses to enforce a
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
TEST(AccountSet, null_account_set)
|
||||
TEST(AccountSet, NullAccountSet)
|
||||
{
|
||||
TxTest env;
|
||||
|
||||
@@ -60,7 +60,7 @@ TEST(AccountSet, null_account_set)
|
||||
EXPECT_EQ(accountRoot.getFlags(), 0);
|
||||
}
|
||||
|
||||
TEST(AccountSet, most_flags)
|
||||
TEST(AccountSet, MostFlags)
|
||||
{
|
||||
Account const alice("alice");
|
||||
|
||||
@@ -175,7 +175,7 @@ TEST(AccountSet, most_flags)
|
||||
});
|
||||
}
|
||||
|
||||
TEST(AccountSet, set_and_reset_account_txn_id)
|
||||
TEST(AccountSet, SetAndResetAccountTxnID)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -206,7 +206,7 @@ TEST(AccountSet, set_and_reset_account_txn_id)
|
||||
EXPECT_EQ(nowFlags, origFlags);
|
||||
}
|
||||
|
||||
TEST(AccountSet, set_no_freeze)
|
||||
TEST(AccountSet, SetNoFreeze)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -249,7 +249,7 @@ TEST(AccountSet, set_no_freeze)
|
||||
EXPECT_TRUE(env.getAccountRoot(alice).isFlag(lsfNoFreeze));
|
||||
}
|
||||
|
||||
TEST(AccountSet, domain)
|
||||
TEST(AccountSet, Domain)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -317,7 +317,7 @@ TEST(AccountSet, domain)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AccountSet, message_key)
|
||||
TEST(AccountSet, MessageKey)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -358,7 +358,7 @@ TEST(AccountSet, message_key)
|
||||
telBAD_PUBLIC_KEY);
|
||||
}
|
||||
|
||||
TEST(AccountSet, wallet_id)
|
||||
TEST(AccountSet, WalletID)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -391,7 +391,7 @@ TEST(AccountSet, wallet_id)
|
||||
EXPECT_FALSE(env.getAccountRoot(alice).hasWalletLocator());
|
||||
}
|
||||
|
||||
TEST(AccountSet, email_hash)
|
||||
TEST(AccountSet, EmailHash)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -422,7 +422,7 @@ TEST(AccountSet, email_hash)
|
||||
EXPECT_FALSE(env.getAccountRoot(alice).hasEmailHash());
|
||||
}
|
||||
|
||||
TEST(AccountSet, transfer_rate)
|
||||
TEST(AccountSet, TransferRate)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
@@ -473,7 +473,7 @@ TEST(AccountSet, transfer_rate)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(AccountSet, bad_inputs)
|
||||
TEST(AccountSet, BadInputs)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -553,7 +553,7 @@ TEST(AccountSet, bad_inputs)
|
||||
tecNO_ALTERNATIVE_KEY);
|
||||
}
|
||||
|
||||
TEST(AccountSet, require_auth_with_dir)
|
||||
TEST(AccountSet, RequireAuthWithDir)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -601,7 +601,7 @@ TEST(AccountSet, require_auth_with_dir)
|
||||
tesSUCCESS);
|
||||
}
|
||||
|
||||
TEST(AccountSet, ticket)
|
||||
TEST(AccountSet, Ticket)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -660,7 +660,7 @@ TEST(AccountSet, ticket)
|
||||
tefNO_TICKET);
|
||||
}
|
||||
|
||||
TEST(AccountSet, bad_signing_key)
|
||||
TEST(AccountSet, BadSigningKey)
|
||||
{
|
||||
TxTest env;
|
||||
Account const alice("alice");
|
||||
@@ -684,7 +684,7 @@ TEST(AccountSet, bad_signing_key)
|
||||
EXPECT_FALSE(result.applied);
|
||||
}
|
||||
|
||||
TEST(AccountSet, gateway)
|
||||
TEST(AccountSet, Gateway)
|
||||
{
|
||||
Account const alice("alice");
|
||||
Account const bob("bob");
|
||||
|
||||
Reference in New Issue
Block a user