1#include <test/app/vault/VaultPrecisionFixture.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/amount.h>
4#include <test/jtx/envconfig.h>
5#include <test/jtx/ter.h>
6#include <test/jtx/vault.h>
8#include <xrpl/basics/Number.h>
9#include <xrpl/beast/unit_test/suite.h>
10#include <xrpl/beast/utility/Journal.h>
11#include <xrpl/protocol/Feature.h>
12#include <xrpl/protocol/MPTIssue.h>
13#include <xrpl/protocol/STAmount.h>
14#include <xrpl/protocol/TER.h>
48 BEAST_EXPECTS(tDelta == aDelta, tag +
" tDelta != aDelta");
49 BEAST_EXPECTS(tDelta == pDelta, tag +
" tDelta != pDelta");
57 testcase(
"deposit clamp does not over-credit");
61 for (
auto const amount : kAmounts)
71 auto const before =
read(env, f);
75 {.depositor = f.depositor,
76 .id = f.vaultKeylet.key,
77 .amount = asset(amount).value()}),
85 Number const tDelta =
after.assetsTotal - before.assetsTotal;
86 Number const requested = asset(amount).number();
91 Number const sharesMinted =
after.sharesTotal - before.sharesTotal;
92 if (before.sharesTotal ==
Number{0})
94 Number const shareValue = (before.assetsTotal * sharesMinted) / before.sharesTotal;
102 "amount=" +
std::to_string(amount) +
" discount is not below one unit");
116 {.depositor = f.depositor,
117 .id = f.vaultKeylet.key,
118 .amount = asset(99'000'000).value()}),
122 auto const before =
read(env, f);
123 Number const kLowerBound{1, 6};
124 BEAST_EXPECT(before.assetsTotal > kLowerBound);
126 auto const tinyAmount = asset(
Number{1, -10}).value();
128 {.depositor = f.depositor, .id = f.vaultKeylet.key, .amount = tinyAmount}),
137 BEAST_EXPECT(
after.assetsTotal == before.assetsTotal);
138 BEAST_EXPECT(
after.assetsAvailable == before.assetsAvailable);
139 BEAST_EXPECT(
after.sharesTotal == before.sharesTotal);
148 testcase(
"withdraw deltas are equal");
160 {.depositor = f.depositor,
161 .id = f.vaultKeylet.key,
162 .amount = asset(1'000'000).value()}),
167 auto const before =
read(env, f);
168 env(v.
withdraw({.depositor = f.depositor, .id = f.vaultKeylet.key, .amount = amount}),
177 Number const sharesBurned = before.sharesTotal -
after.sharesTotal;
178 if (before.sharesTotal ==
Number{0})
180 Number const shareValue = (before.assetsTotal * sharesBurned) / before.sharesTotal;
181 Number const tDelta = before.assetsTotal -
after.assetsTotal;
182 BEAST_EXPECTS(tDelta <= shareValue, tag + " payout > shareValue
");
185 std::array<std::uint64_t, 3> const kShareCounts{99'999u, 333'333u, 1'234'567u};
186 for (auto const count : kShareCounts)
188 auto const before = read(env, f);
189 if (before.sharesTotal < count)
191 STAmount const shareAmount{MPTIssue{f.share}, Number{static_cast<std::int64_t>(count)}};
192 checkSuccess(shareAmount, "shares=
" + std::to_string(count));
195 std::array<int, 3> const kAssetAmounts{1, 7, 99};
196 for (auto const amount : kAssetAmounts)
197 checkSuccess(asset(amount).value(), "assets=
" + std::to_string(amount));
200 // Withdraw more than sfAssetsAvailable must return tecINSUFFICIENT_FUNDS,
201 // not tecPRECISION_LOSS.
203 testWithdrawInsufficientFundsPrecedence()
207 testcase("withdraw over available returns insufficient funds, not precision loss
");
210 auto f = setupSingleLoanVault(env, /*impairAndPaySibling=*/false);
213 // ready() above guarantees f.asset is engaged; the guard is opaque to clang-tidy.
214 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
215 jtx::PrettyAsset const& asset = f.asset.value();
219 {.depositor = f.depositor,
220 .id = f.vaultKeylet.key,
221 .amount = asset(1'000'000).value()}),
225 auto const before = read(env, f);
226 if (!BEAST_EXPECT(before.assetsAvailable > Number{0}))
229 STAmount const request = asset(before.assetsAvailable + Number{1}).value();
230 env(v.withdraw({.depositor = f.depositor, .id = f.vaultKeylet.key, .amount = request}),
235 env.ter() == tecINSUFFICIENT_FUNDS,
244 testcase("clawback deltas are
equal");
247 auto f = setupSingleLoanVault(
249 /*impairAndPaySibling=*/false,
250 /*allowClawback=*/true);
253 // ready() above guarantees f.asset is engaged; the guard is opaque to clang-tidy.
254 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
255 jtx::PrettyAsset const& asset = f.asset.value();
259 {.depositor = f.depositor,
260 .id = f.vaultKeylet.key,
261 .amount = asset(2'000).value()}),
265 auto checkSuccess = [&](std::optional<STAmount> const& amount, std::string const& tag) {
266 auto const before = read(env, f);
267 if (before.sharesTotal == Number{0})
272 .id = f.vaultKeylet.key,
273 .holder = f.depositor,
277 if (env.ter() != tesSUCCESS)
280 assertEqualDeltas(before, read(env, f), tag);
283 std::array<int, 3> const kAmounts{1, 7, 99};
284 for (auto const amount : kAmounts)
285 checkSuccess(asset(amount).value(), "amount=
" + std::to_string(amount));
287 checkSuccess(std::nullopt, "sfAmount absent
");
295 testcase("impaired vault loss stays within assetsTotal - assetsAvailable
");
298 auto f = setupSingleLoanVault(env, /*impairAndPaySibling=*/true);
301 // ready() above guarantees f.asset is engaged; the guard is opaque to clang-tidy.
302 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
303 jtx::PrettyAsset const& asset = f.asset.value();
307 {.depositor = f.depositor,
308 .id = f.vaultKeylet.key,
309 .amount = asset(5'000).value()}),
313 auto checkInvariant = [&](std::string const& tag) {
314 TER const actual = env.ter();
315 BEAST_EXPECTS(actual != tecINVARIANT_FAILED, tag + " unexpected invariant failure
");
316 if (actual != tesSUCCESS)
318 auto const after = read(env, f);
320 after.lossUnrealized <= after.assetsTotal - after.assetsAvailable,
321 tag + " lossUnrealized exceeds assetsTotal - assetsAvailable
");
324 std::array<int, 4> const kAmounts{1, 7, 51, 137};
325 for (std::size_t i = 0; i + 1 < kAmounts.size(); i += 2)
327 int const depositAmount = kAmounts[i];
328 int const withdrawAmount = kAmounts[i + 1];
331 {.depositor = f.depositor,
332 .id = f.vaultKeylet.key,
333 .amount = asset(depositAmount).value()}),
336 checkInvariant("deposit=
" + std::to_string(depositAmount));
339 {.depositor = f.depositor,
340 .id = f.vaultKeylet.key,
341 .amount = asset(withdrawAmount).value()}),
344 checkInvariant("withdraw=
" + std::to_string(withdrawAmount));
354 testWithdrawInsufficientFundsPrecedence();
360BEAST_DEFINE_TESTSUITE(VaultTransactorPrecision, app, xrpl);
362} // namespace xrpl::test
bool unexpected(Condition shouldBeFalse, String const &reason)
DEPRECATED.
TestcaseT testcase
Memberspace for declaring test cases.
Number is a floating point type that can represent a wide range of values.
static Number oneUnit(Asset const &asset, Number const &assetsTotalAfter)
static Numbers read(jtx::Env const &env, Fixture const &f)
static Fixture setupSingleLoanVault(jtx::Env &env, bool impairAndPaySibling, bool allowClawback=false)
bool ready(Fixture const &f)
void assertEqualDeltas(Numbers const &before, Numbers const &after, std::string const &tag)
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
TER ter() const
Return the TER for the last JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Deposit preauthorize operations.
bool equal(STAmount const &sa1, STAmount const &sa2)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
std::string transToken(TER code)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
std::optional< BrokerInfo > broker
std::optional< jtx::PrettyAsset > asset
Asset const & raw() const
static json::Value withdraw(WithdrawArgs const &args)
static json::Value deposit(DepositArgs const &args)