xrpld
Loading...
Searching...
No Matches
PaymentSandbox_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/PathSet.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/balance.h>
6#include <test/jtx/jtx_json.h>
7#include <test/jtx/offer.h>
8#include <test/jtx/pay.h>
9#include <test/jtx/txflags.h>
10
11#include <xrpl/beast/unit_test/suite.h>
12#include <xrpl/beast/utility/Zero.h>
13#include <xrpl/ledger/ApplyView.h>
14#include <xrpl/ledger/ApplyViewImpl.h>
15#include <xrpl/ledger/PaymentSandbox.h>
16#include <xrpl/ledger/ReadView.h>
17#include <xrpl/ledger/helpers/RippleStateHelpers.h>
18#include <xrpl/ledger/helpers/TokenHelpers.h>
19#include <xrpl/protocol/AccountID.h>
20#include <xrpl/protocol/AmountConversions.h>
21#include <xrpl/protocol/Feature.h>
22#include <xrpl/protocol/Issue.h>
23#include <xrpl/protocol/STAmount.h>
24#include <xrpl/protocol/TER.h>
25#include <xrpl/protocol/TxFlags.h>
26#include <xrpl/protocol/UintTypes.h>
27#include <xrpl/protocol/XRPAmount.h>
28
29#include <cstdint>
30
31namespace xrpl::test {
32
34{
35 /*
36 Create paths so one path funds another path.
37
38 Two accounts: sender and receiver.
39 Two gateways: gw1 and gw2.
40 Sender and receiver both have trust lines to the gateways.
41 Sender has 2 gw1/USD and 4 gw2/USD.
42 Sender has offer to exchange 2 gw1 for gw2 and gw2 for gw1 1-for-1.
43 Paths are:
44 1) GW1 -> [OB GW1/USD->GW2/USD] -> GW2
45 2) GW2 -> [OB GW2/USD->GW1/USD] -> GW1
46
47 sender pays receiver 4 USD.
48 Path 1:
49 1) Sender exchanges 2 GW1/USD for 2 GW2/USD
50 2) Old code: the 2 GW1/USD is available to sender
51 New code: the 2 GW1/USD is not available until the
52 end of the transaction.
53 3) Receiver gets 2 GW2/USD
54 Path 2:
55 1) Old code: Sender exchanges 2 GW2/USD for 2 GW1/USD
56 2) Old code: Receiver get 2 GW1
57 2) New code: Path is dry because sender does not have any
58 GW1 to spend until the end of the transaction.
59 */
60 void
62 {
63 testcase("selfFunding");
64
65 using namespace jtx;
66 Env env(*this, features);
67 Account const gw1("gw1");
68 Account const gw2("gw2");
69 Account const snd("snd");
70 Account const rcv("rcv");
71
72 env.fund(XRP(10000), snd, rcv, gw1, gw2);
73
74 auto const usdGw1 = gw1["USD"];
75 auto const usdGw2 = gw2["USD"];
76
77 env.trust(usdGw1(10), snd);
78 env.trust(usdGw2(10), snd);
79 env.trust(usdGw1(100), rcv);
80 env.trust(usdGw2(100), rcv);
81
82 env(pay(gw1, snd, usdGw1(2)));
83 env(pay(gw2, snd, usdGw2(4)));
84
85 env(offer(snd, usdGw1(2), usdGw2(2)), Txflags(tfPassive));
86 env(offer(snd, usdGw2(2), usdGw1(2)), Txflags(tfPassive));
87
88 PathSet const paths(TestPath(gw1, usdGw2, gw2), TestPath(gw2, usdGw1, gw1));
89
90 env(pay(snd, rcv, kAny(usdGw1(4))),
91 Json(paths.json()),
92 Txflags(tfNoRippleDirect | tfPartialPayment));
93
94 env.require(Balance("rcv", usdGw1(0)));
95 env.require(Balance("rcv", usdGw2(2)));
96 }
97
98 void
100 {
101 testcase("subtractCredits");
102
103 using namespace jtx;
104 Env env(*this, features);
105 Account const gw1("gw1");
106 Account const gw2("gw2");
107 Account const alice("alice");
108
109 env.fund(XRP(10000), alice, gw1, gw2);
110
111 auto j = env.app().getJournal("View");
112
113 auto const usdGw1 = gw1["USD"];
114 auto const usdGw2 = gw2["USD"];
115
116 env.trust(usdGw1(100), alice);
117 env.trust(usdGw2(100), alice);
118
119 env(pay(gw1, alice, usdGw1(50)));
120 env(pay(gw2, alice, usdGw2(50)));
121
122 STAmount const toCredit(usdGw1(30));
123 STAmount const toDebit(usdGw1(20));
124 {
125 // accountSend, no deferredCredits
126 ApplyViewImpl av(&*env.current(), TapNone);
127
128 auto const iss = usdGw1;
129 auto const startingAmount =
130 accountHolds(av, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j);
131 {
132 auto r = accountSend(av, gw1, alice, toCredit, j);
133 BEAST_EXPECT(isTesSuccess(r));
134 }
135 BEAST_EXPECT(
137 av, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
138 startingAmount + toCredit);
139 {
140 auto r = accountSend(av, alice, gw1, toDebit, j);
141 BEAST_EXPECT(isTesSuccess(r));
142 }
143 BEAST_EXPECT(
145 av, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
146 startingAmount + toCredit - toDebit);
147 }
148
149 {
150 // directSendNoFee, no deferredCredits
151 ApplyViewImpl av(&*env.current(), TapNone);
152
153 auto const iss = usdGw1;
154 auto const startingAmount =
155 accountHolds(av, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j);
156
157 directSendNoFee(av, gw1, alice, toCredit, true, j);
158 BEAST_EXPECT(
160 av, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
161 startingAmount + toCredit);
162
163 directSendNoFee(av, alice, gw1, toDebit, true, j);
164 BEAST_EXPECT(
166 av, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
167 startingAmount + toCredit - toDebit);
168 }
169
170 {
171 // accountSend, w/ deferredCredits
172 ApplyViewImpl av(&*env.current(), TapNone);
173 PaymentSandbox pv(&av);
174
175 auto const iss = usdGw1;
176 auto const startingAmount =
177 accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j);
178
179 {
180 auto r = accountSend(pv, gw1, alice, toCredit, j);
181 BEAST_EXPECT(isTesSuccess(r));
182 }
183 BEAST_EXPECT(
185 pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
186 startingAmount);
187
188 {
189 auto r = accountSend(pv, alice, gw1, toDebit, j);
190 BEAST_EXPECT(isTesSuccess(r));
191 }
192 BEAST_EXPECT(
194 pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
195 startingAmount - toDebit);
196 }
197
198 {
199 // directSendNoFee, w/ deferredCredits
200 ApplyViewImpl av(&*env.current(), TapNone);
201 PaymentSandbox pv(&av);
202
203 auto const iss = usdGw1;
204 auto const startingAmount =
205 accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j);
206
207 directSendNoFee(pv, gw1, alice, toCredit, true, j);
208 BEAST_EXPECT(
210 pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
211 startingAmount);
212 }
213
214 {
215 // redeemIOU, w/ deferredCredits
216 ApplyViewImpl av(&*env.current(), TapNone);
217 PaymentSandbox pv(&av);
218
219 auto const iss = usdGw1;
220 auto const startingAmount =
221 accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j);
222
223 BEAST_EXPECT(redeemIOU(pv, alice, toDebit, iss, j) == tesSUCCESS);
224 BEAST_EXPECT(
226 pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
227 startingAmount - toDebit);
228 }
229
230 {
231 // issueIOU, w/ deferredCredits
232 ApplyViewImpl av(&*env.current(), TapNone);
233 PaymentSandbox pv(&av);
234
235 auto const iss = usdGw1;
236 auto const startingAmount =
237 accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j);
238
239 BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS);
240 BEAST_EXPECT(
242 pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
243 startingAmount);
244 }
245
246 {
247 // accountSend, w/ deferredCredits and stacked views
248 ApplyViewImpl av(&*env.current(), TapNone);
249 PaymentSandbox pv(&av);
250
251 auto const iss = usdGw1;
252 auto const startingAmount =
253 accountHolds(pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j);
254
255 {
256 auto r = accountSend(pv, gw1, alice, toCredit, j);
257 BEAST_EXPECT(isTesSuccess(r));
258 }
259 BEAST_EXPECT(
261 pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
262 startingAmount);
263
264 {
265 PaymentSandbox pv2(&pv);
266 BEAST_EXPECT(
268 pv2, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
269 startingAmount);
270 {
271 auto r = accountSend(pv2, gw1, alice, toCredit, j);
272 BEAST_EXPECT(isTesSuccess(r));
273 }
274 BEAST_EXPECT(
276 pv2, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
277 startingAmount);
278 }
279
280 {
281 auto r = accountSend(pv, alice, gw1, toDebit, j);
282 BEAST_EXPECT(isTesSuccess(r));
283 }
284 BEAST_EXPECT(
286 pv, alice, iss.currency, iss.account, FreezeHandling::IgnoreFreeze, j) ==
287 startingAmount - toDebit);
288 }
289 }
290
291 void
293 {
294 testcase("Tiny balance");
295
296 // Add and subtract a huge credit from a tiny balance, expect the tiny
297 // balance back. Numerical stability problems could cause the balance to
298 // be zero.
299
300 using namespace jtx;
301
302 Env const env(*this, features);
303
304 Account const gw("gw");
305 Account const alice("alice");
306 auto const usd = gw["USD"];
307
308 auto const issue = usd;
309 STAmount const tinyAmt(
311 STAmount const hugeAmt(
313
314 ApplyViewImpl av(&*env.current(), TapNone);
315 PaymentSandbox pv(&av);
316 pv.creditHookIOU(gw, alice, hugeAmt, -tinyAmt);
317 BEAST_EXPECT(pv.balanceHookIOU(alice, gw, hugeAmt) == tinyAmt);
318 }
319
320 void
322 {
323 testcase("Reserve");
324 using namespace jtx;
325
326 auto accountFundsXRP =
327 [](ReadView const& view, AccountID const& id, beast::Journal j) -> XRPAmount {
330 };
331
332 auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount {
333 return env.current()->fees().accountReserve(count);
334 };
335
336 Env env(*this, features);
337
338 Account const alice("alice");
339 env.fund(reserve(env, 1), alice);
340
341 env.close();
342 ApplyViewImpl av(&*env.current(), TapNone);
343 PaymentSandbox sb(&av);
344 {
345 // Send alice an amount and spend it. The deferredCredits will cause
346 // her balance to drop below the reserve. Make sure her funds are
347 // zero (there was a bug that caused her funds to become negative).
348
349 {
350 auto r = accountSend(sb, xrpAccount(), alice, XRP(100), env.journal);
351 BEAST_EXPECT(isTesSuccess(r));
352 }
353 {
354 auto r = accountSend(sb, alice, xrpAccount(), XRP(100), env.journal);
355 BEAST_EXPECT(isTesSuccess(r));
356 }
357 BEAST_EXPECT(accountFundsXRP(sb, alice, env.journal) == beast::kZero);
358 }
359 }
360
361 void
363 {
364 // Make sure the Issue::Account returned by
365 // PaymentSandbox::balanceHookIOU is correct.
366 testcase("balanceHook");
367
368 using namespace jtx;
369 Env const env(*this, features);
370
371 Account const gw("gw");
372 auto const usd = gw["USD"];
373 Account const alice("alice");
374
375 ApplyViewImpl av(&*env.current(), TapNone);
376 PaymentSandbox sb(&av);
377
378 // The currency we pass for the last argument mimics the currency that
379 // is typically passed to creditHookIOU, since it comes from a trust
380 // line.
381 Issue tlIssue = noIssue();
382 tlIssue.currency = usd.currency;
383
384 sb.creditHookIOU(gw.id(), alice.id(), {usd, 400}, {tlIssue, 600});
385 sb.creditHookIOU(gw.id(), alice.id(), {usd, 100}, {tlIssue, 600});
386
387 // Expect that the STAmount issuer returned by balanceHookIOU() is correct.
388 STAmount const balance = sb.balanceHookIOU(gw.id(), alice.id(), {usd, 600});
389 BEAST_EXPECT(balance.getIssuer() == usd.account.id());
390 }
391
392public:
393 void
394 run() override
395 {
396 auto testAll = [this](FeatureBitset features) {
397 testSelfFunding(features);
398 testSubtractCredits(features);
399 testTinyBalance(features);
400 testReserve(features);
401 testBalanceHook(features);
402 };
403 using namespace jtx;
404 auto const sa = testableAmendments();
405 testAll(sa - featurePermissionedDEX);
406 testAll(sa);
407 }
408};
409
411
412} // namespace xrpl::test
A generic endpoint for log messages.
Definition Journal.h:38
A testsuite class.
Definition suite.h:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Editable, discardable view that can build metadata for one tx.
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
A wrapper which makes credits unavailable to balances.
STAmount balanceHookIOU(AccountID const &account, AccountID const &issuer, STAmount const &amount) const override
void creditHookIOU(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance) override
A view into a ledger.
Definition ReadView.h:31
static constexpr int kMinOffset
Definition STAmount.h:47
AccountID const & getIssuer() const
Definition STAmount.h:498
static constexpr std::uint64_t kMinValue
Definition STAmount.h:51
static constexpr std::uint64_t kMaxValue
Definition STAmount.h:53
static constexpr int kMaxOffset
Definition STAmount.h:48
virtual beast::Journal getJournal(std::string const &name)=0
json::Value json() const
Definition PathSet.h:170
void testBalanceHook(FeatureBitset features)
void run() override
Runs the suite.
void testSubtractCredits(FeatureBitset features)
void testReserve(FeatureBitset features)
void testTinyBalance(FeatureBitset features)
void testSelfFunding(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
AccountID id() const
Returns the Account ID.
Definition jtx/Account.h:85
A transaction testing environment.
Definition Env.h:143
Application & app()
Definition Env.h:280
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:296
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:327
beast::Journal const journal
Definition Env.h:184
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:605
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:353
Inject raw JSON.
Definition jtx_json.h:11
Set the flags on a JTx.
Definition txflags.h:9
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
FeatureBitset testableAmendments()
Definition Env.h:76
AnyT const kAny
Returns an amount representing "any issuer".
Definition amount.cpp:120
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:48
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
XRPAmount toAmount< XRPAmount >(STAmount const &amt)
TER directSendNoFee(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static directSendNoFeeIOU if saAmount represents Issue.
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No, AllowMPTOverflow allowOverflow=AllowMPTOverflow::No)
Calls static accountSendIOU if saAmount represents Issue.
@ TapNone
Definition ApplyView.h:13
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition Issue.h:105
AccountID const & xrpAccount()
Compute AccountID from public key.
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
@ tesSUCCESS
Definition TER.h:240