rippled
Loading...
Searching...
No Matches
PaymentSandbox_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2015 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx/PathSet.h>
21
22#include <xrpl/ledger/ApplyViewImpl.h>
23#include <xrpl/ledger/PaymentSandbox.h>
24#include <xrpl/ledger/View.h>
25#include <xrpl/protocol/AmountConversions.h>
26#include <xrpl/protocol/Feature.h>
27
28namespace ripple {
29namespace test {
30
32{
33 /*
34 Create paths so one path funds another path.
35
36 Two accounts: sender and receiver.
37 Two gateways: gw1 and gw2.
38 Sender and receiver both have trust lines to the gateways.
39 Sender has 2 gw1/USD and 4 gw2/USD.
40 Sender has offer to exchange 2 gw1 for gw2 and gw2 for gw1 1-for-1.
41 Paths are:
42 1) GW1 -> [OB GW1/USD->GW2/USD] -> GW2
43 2) GW2 -> [OB GW2/USD->GW1/USD] -> GW1
44
45 sender pays receiver 4 USD.
46 Path 1:
47 1) Sender exchanges 2 GW1/USD for 2 GW2/USD
48 2) Old code: the 2 GW1/USD is available to sender
49 New code: the 2 GW1/USD is not available until the
50 end of the transaction.
51 3) Receiver gets 2 GW2/USD
52 Path 2:
53 1) Old code: Sender exchanges 2 GW2/USD for 2 GW1/USD
54 2) Old code: Receiver get 2 GW1
55 2) New code: Path is dry because sender does not have any
56 GW1 to spend until the end of the transaction.
57 */
58 void
60 {
61 testcase("selfFunding");
62
63 using namespace jtx;
64 Env env(*this, features);
65 Account const gw1("gw1");
66 Account const gw2("gw2");
67 Account const snd("snd");
68 Account const rcv("rcv");
69
70 env.fund(XRP(10000), snd, rcv, gw1, gw2);
71
72 auto const USD_gw1 = gw1["USD"];
73 auto const USD_gw2 = gw2["USD"];
74
75 env.trust(USD_gw1(10), snd);
76 env.trust(USD_gw2(10), snd);
77 env.trust(USD_gw1(100), rcv);
78 env.trust(USD_gw2(100), rcv);
79
80 env(pay(gw1, snd, USD_gw1(2)));
81 env(pay(gw2, snd, USD_gw2(4)));
82
83 env(offer(snd, USD_gw1(2), USD_gw2(2)), txflags(tfPassive));
84 env(offer(snd, USD_gw2(2), USD_gw1(2)), txflags(tfPassive));
85
86 PathSet paths(Path(gw1, USD_gw2, gw2), Path(gw2, USD_gw1, gw1));
87
88 env(pay(snd, rcv, any(USD_gw1(4))),
89 json(paths.json()),
91
92 env.require(balance("rcv", USD_gw1(0)));
93 env.require(balance("rcv", USD_gw2(2)));
94 }
95
96 void
98 {
99 testcase("subtractCredits");
100
101 using namespace jtx;
102 Env env(*this, features);
103 Account const gw1("gw1");
104 Account const gw2("gw2");
105 Account const alice("alice");
106
107 env.fund(XRP(10000), alice, gw1, gw2);
108
109 auto j = env.app().journal("View");
110
111 auto const USD_gw1 = gw1["USD"];
112 auto const USD_gw2 = gw2["USD"];
113
114 env.trust(USD_gw1(100), alice);
115 env.trust(USD_gw2(100), alice);
116
117 env(pay(gw1, alice, USD_gw1(50)));
118 env(pay(gw2, alice, USD_gw2(50)));
119
120 STAmount const toCredit(USD_gw1(30));
121 STAmount const toDebit(USD_gw1(20));
122 {
123 // accountSend, no deferredCredits
124 ApplyViewImpl av(&*env.current(), tapNONE);
125
126 auto const iss = USD_gw1.issue();
127 auto const startingAmount = accountHolds(
128 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
129 {
130 auto r = accountSend(av, gw1, alice, toCredit, j);
131 BEAST_EXPECT(r == tesSUCCESS);
132 }
133 BEAST_EXPECT(
135 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
136 startingAmount + toCredit);
137 {
138 auto r = accountSend(av, alice, gw1, toDebit, j);
139 BEAST_EXPECT(r == tesSUCCESS);
140 }
141 BEAST_EXPECT(
143 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
144 startingAmount + toCredit - toDebit);
145 }
146
147 {
148 // rippleCredit, no deferredCredits
149 ApplyViewImpl av(&*env.current(), tapNONE);
150
151 auto const iss = USD_gw1.issue();
152 auto const startingAmount = accountHolds(
153 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
154
155 rippleCredit(av, gw1, alice, toCredit, true, j);
156 BEAST_EXPECT(
158 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
159 startingAmount + toCredit);
160
161 rippleCredit(av, alice, gw1, toDebit, true, j);
162 BEAST_EXPECT(
164 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
165 startingAmount + toCredit - toDebit);
166 }
167
168 {
169 // accountSend, w/ deferredCredits
170 ApplyViewImpl av(&*env.current(), tapNONE);
171 PaymentSandbox pv(&av);
172
173 auto const iss = USD_gw1.issue();
174 auto const startingAmount = accountHolds(
175 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
176
177 {
178 auto r = accountSend(pv, gw1, alice, toCredit, j);
179 BEAST_EXPECT(r == tesSUCCESS);
180 }
181 BEAST_EXPECT(
183 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
184 startingAmount);
185
186 {
187 auto r = accountSend(pv, alice, gw1, toDebit, j);
188 BEAST_EXPECT(r == tesSUCCESS);
189 }
190 BEAST_EXPECT(
192 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
193 startingAmount - toDebit);
194 }
195
196 {
197 // rippleCredit, w/ deferredCredits
198 ApplyViewImpl av(&*env.current(), tapNONE);
199 PaymentSandbox pv(&av);
200
201 auto const iss = USD_gw1.issue();
202 auto const startingAmount = accountHolds(
203 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
204
205 rippleCredit(pv, gw1, alice, toCredit, true, j);
206 BEAST_EXPECT(
208 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
209 startingAmount);
210 }
211
212 {
213 // redeemIOU, w/ deferredCredits
214 ApplyViewImpl av(&*env.current(), tapNONE);
215 PaymentSandbox pv(&av);
216
217 auto const iss = USD_gw1.issue();
218 auto const startingAmount = accountHolds(
219 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
220
221 BEAST_EXPECT(redeemIOU(pv, alice, toDebit, iss, j) == tesSUCCESS);
222 BEAST_EXPECT(
224 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
225 startingAmount - toDebit);
226 }
227
228 {
229 // issueIOU, w/ deferredCredits
230 ApplyViewImpl av(&*env.current(), tapNONE);
231 PaymentSandbox pv(&av);
232
233 auto const iss = USD_gw1.issue();
234 auto const startingAmount = accountHolds(
235 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
236
237 BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS);
238 BEAST_EXPECT(
240 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
241 startingAmount);
242 }
243
244 {
245 // accountSend, w/ deferredCredits and stacked views
246 ApplyViewImpl av(&*env.current(), tapNONE);
247 PaymentSandbox pv(&av);
248
249 auto const iss = USD_gw1.issue();
250 auto const startingAmount = accountHolds(
251 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
252
253 {
254 auto r = accountSend(pv, gw1, alice, toCredit, j);
255 BEAST_EXPECT(r == tesSUCCESS);
256 }
257 BEAST_EXPECT(
259 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
260 startingAmount);
261
262 {
263 PaymentSandbox pv2(&pv);
264 BEAST_EXPECT(
266 pv2,
267 alice,
268 iss.currency,
269 iss.account,
271 j) == startingAmount);
272 {
273 auto r = accountSend(pv2, gw1, alice, toCredit, j);
274 BEAST_EXPECT(r == tesSUCCESS);
275 }
276 BEAST_EXPECT(
278 pv2,
279 alice,
280 iss.currency,
281 iss.account,
283 j) == startingAmount);
284 }
285
286 {
287 auto r = accountSend(pv, alice, gw1, toDebit, j);
288 BEAST_EXPECT(r == tesSUCCESS);
289 }
290 BEAST_EXPECT(
292 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
293 startingAmount - toDebit);
294 }
295 }
296
297 void
299 {
300 testcase("Tiny balance");
301
302 // Add and subtract a huge credit from a tiny balance, expect the tiny
303 // balance back. Numerical stability problems could cause the balance to
304 // be zero.
305
306 using namespace jtx;
307
308 Env env(*this, features);
309
310 Account const gw("gw");
311 Account const alice("alice");
312 auto const USD = gw["USD"];
313
314 auto const issue = USD.issue();
315 STAmount tinyAmt(
316 issue,
319 false,
321 STAmount hugeAmt(
322 issue,
325 false,
327
328 ApplyViewImpl av(&*env.current(), tapNONE);
329 PaymentSandbox pv(&av);
330 pv.creditHook(gw, alice, hugeAmt, -tinyAmt);
331 BEAST_EXPECT(pv.balanceHook(alice, gw, hugeAmt) == tinyAmt);
332 }
333
334 void
336 {
337 testcase("Reserve");
338 using namespace jtx;
339
340 auto accountFundsXRP = [](ReadView const& view,
341 AccountID const& id,
344 view, id, xrpCurrency(), xrpAccount(), fhZERO_IF_FROZEN, j));
345 };
346
347 auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount {
348 return env.current()->fees().accountReserve(count);
349 };
350
351 Env env(*this, features);
352
353 Account const alice("alice");
354 env.fund(reserve(env, 1), alice);
355
356 env.close();
357 ApplyViewImpl av(&*env.current(), tapNONE);
358 PaymentSandbox sb(&av);
359 {
360 // Send alice an amount and spend it. The deferredCredits will cause
361 // her balance to drop below the reserve. Make sure her funds are
362 // zero (there was a bug that caused her funds to become negative).
363
364 {
365 auto r =
366 accountSend(sb, xrpAccount(), alice, XRP(100), env.journal);
367 BEAST_EXPECT(r == tesSUCCESS);
368 }
369 {
370 auto r =
371 accountSend(sb, alice, xrpAccount(), XRP(100), env.journal);
372 BEAST_EXPECT(r == tesSUCCESS);
373 }
374 BEAST_EXPECT(
375 accountFundsXRP(sb, alice, env.journal) == beast::zero);
376 }
377 }
378
379 void
381 {
382 // Make sure the Issue::Account returned by PAymentSandbox::balanceHook
383 // is correct.
384 testcase("balanceHook");
385
386 using namespace jtx;
387 Env env(*this, features);
388
389 Account const gw("gw");
390 auto const USD = gw["USD"];
391 Account const alice("alice");
392
393 ApplyViewImpl av(&*env.current(), tapNONE);
394 PaymentSandbox sb(&av);
395
396 // The currency we pass for the last argument mimics the currency that
397 // is typically passed to creditHook, since it comes from a trust line.
398 Issue tlIssue = noIssue();
399 tlIssue.currency = USD.issue().currency;
400
401 sb.creditHook(gw.id(), alice.id(), {USD, 400}, {tlIssue, 600});
402 sb.creditHook(gw.id(), alice.id(), {USD, 100}, {tlIssue, 600});
403
404 // Expect that the STAmount issuer returned by balanceHook() is correct.
405 STAmount const balance =
406 sb.balanceHook(gw.id(), alice.id(), {USD, 600});
407 BEAST_EXPECT(balance.getIssuer() == USD.issue().account);
408 }
409
410public:
411 void
412 run() override
413 {
414 auto testAll = [this](FeatureBitset features) {
415 testSelfFunding(features);
416 testSubtractCredits(features);
417 testTinyBalance(features);
418 testReserve(features);
419 testBalanceHook(features);
420 };
421 using namespace jtx;
422 auto const sa = testable_amendments();
423 testAll(sa - featurePermissionedDEX);
424 testAll(sa);
425 }
426};
427
428BEAST_DEFINE_TESTSUITE(PaymentSandbox, ledger, ripple);
429
430} // namespace test
431} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:60
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
virtual beast::Journal journal(std::string const &name)=0
Editable, discardable view that can build metadata for one tx.
A currency issued by an account.
Definition Issue.h:33
Currency currency
Definition Issue.h:35
A wrapper which makes credits unavailable to balances.
void creditHook(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance) override
STAmount balanceHook(AccountID const &account, AccountID const &issuer, STAmount const &amount) const override
A view into a ledger.
Definition ReadView.h:51
static int const cMaxOffset
Definition STAmount.h:66
static int const cMinOffset
Definition STAmount.h:65
static std::uint64_t const cMinValue
Definition STAmount.h:69
static std::uint64_t const cMaxValue
Definition STAmount.h:70
void testTinyBalance(FeatureBitset features)
void testSelfFunding(FeatureBitset features)
void testSubtractCredits(FeatureBitset features)
void testBalanceHook(FeatureBitset features)
void testReserve(FeatureBitset features)
void run() override
Runs the suite.
Immutable cryptographic account descriptor.
Definition Account.h:39
AccountID id() const
Returns the Account ID.
Definition Account.h:111
A transaction testing environment.
Definition Env.h:121
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:547
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:121
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:320
Application & app()
Definition Env.h:261
beast::Journal const journal
Definition Env.h:162
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
A balance matches.
Definition balance.h:39
Inject raw JSON.
Definition jtx_json.h:33
Set Paths, SendMax on a JTx.
Definition paths.h:35
Set the flags on a JTx.
Definition txflags.h:31
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
any_t const any
Returns an amount representing "any issuer".
Definition amount.cpp:133
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
@ fhZERO_IF_FROZEN
Definition View.h:77
@ fhIGNORE_FREEZE
Definition View.h:77
AccountID const & xrpAccount()
Compute AccountID from public key.
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2348
constexpr std::uint32_t tfPassive
Definition TxFlags.h:98
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2174
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:108
Currency const & xrpCurrency()
XRP currency.
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2248
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition Issue.h:123
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static rippleCreditIOU if saAmount represents Issue.
Definition View.cpp:2829
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:107
@ tesSUCCESS
Definition TER.h:244
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:384
@ tapNONE
Definition ApplyView.h:31
XRPAmount toAmount< XRPAmount >(STAmount const &amt)