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#include <xrpld/ledger/ApplyViewImpl.h>
22#include <xrpld/ledger/PaymentSandbox.h>
23#include <xrpld/ledger/View.h>
24#include <xrpl/protocol/AmountConversions.h>
25#include <xrpl/protocol/Feature.h>
26
27namespace ripple {
28namespace test {
29
31{
32 /*
33 Create paths so one path funds another path.
34
35 Two accounts: sender and receiver.
36 Two gateways: gw1 and gw2.
37 Sender and receiver both have trust lines to the gateways.
38 Sender has 2 gw1/USD and 4 gw2/USD.
39 Sender has offer to exchange 2 gw1 for gw2 and gw2 for gw1 1-for-1.
40 Paths are:
41 1) GW1 -> [OB GW1/USD->GW2/USD] -> GW2
42 2) GW2 -> [OB GW2/USD->GW1/USD] -> GW1
43
44 sender pays receiver 4 USD.
45 Path 1:
46 1) Sender exchanges 2 GW1/USD for 2 GW2/USD
47 2) Old code: the 2 GW1/USD is available to sender
48 New code: the 2 GW1/USD is not available until the
49 end of the transaction.
50 3) Receiver gets 2 GW2/USD
51 Path 2:
52 1) Old code: Sender exchanges 2 GW2/USD for 2 GW1/USD
53 2) Old code: Receiver get 2 GW1
54 2) New code: Path is dry because sender does not have any
55 GW1 to spend until the end of the transaction.
56 */
57 void
59 {
60 testcase("selfFunding");
61
62 using namespace jtx;
63 Env env(*this, features);
64 Account const gw1("gw1");
65 Account const gw2("gw2");
66 Account const snd("snd");
67 Account const rcv("rcv");
68
69 env.fund(XRP(10000), snd, rcv, gw1, gw2);
70
71 auto const USD_gw1 = gw1["USD"];
72 auto const USD_gw2 = gw2["USD"];
73
74 env.trust(USD_gw1(10), snd);
75 env.trust(USD_gw2(10), snd);
76 env.trust(USD_gw1(100), rcv);
77 env.trust(USD_gw2(100), rcv);
78
79 env(pay(gw1, snd, USD_gw1(2)));
80 env(pay(gw2, snd, USD_gw2(4)));
81
82 env(offer(snd, USD_gw1(2), USD_gw2(2)), txflags(tfPassive));
83 env(offer(snd, USD_gw2(2), USD_gw1(2)), txflags(tfPassive));
84
85 PathSet paths(Path(gw1, USD_gw2, gw2), Path(gw2, USD_gw1, gw1));
86
87 env(pay(snd, rcv, any(USD_gw1(4))),
88 json(paths.json()),
90
91 env.require(balance("rcv", USD_gw1(0)));
92 env.require(balance("rcv", USD_gw2(2)));
93 }
94
95 void
97 {
98 testcase("subtractCredits");
99
100 using namespace jtx;
101 Env env(*this, features);
102 Account const gw1("gw1");
103 Account const gw2("gw2");
104 Account const alice("alice");
105
106 env.fund(XRP(10000), alice, gw1, gw2);
107
108 auto j = env.app().journal("View");
109
110 auto const USD_gw1 = gw1["USD"];
111 auto const USD_gw2 = gw2["USD"];
112
113 env.trust(USD_gw1(100), alice);
114 env.trust(USD_gw2(100), alice);
115
116 env(pay(gw1, alice, USD_gw1(50)));
117 env(pay(gw2, alice, USD_gw2(50)));
118
119 STAmount const toCredit(USD_gw1(30));
120 STAmount const toDebit(USD_gw1(20));
121 {
122 // accountSend, no deferredCredits
123 ApplyViewImpl av(&*env.current(), tapNONE);
124
125 auto const iss = USD_gw1.issue();
126 auto const startingAmount = accountHolds(
127 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
128 {
129 auto r = accountSend(av, gw1, alice, toCredit, j);
130 BEAST_EXPECT(r == tesSUCCESS);
131 }
132 BEAST_EXPECT(
134 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
135 startingAmount + toCredit);
136 {
137 auto r = accountSend(av, alice, gw1, toDebit, j);
138 BEAST_EXPECT(r == tesSUCCESS);
139 }
140 BEAST_EXPECT(
142 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
143 startingAmount + toCredit - toDebit);
144 }
145
146 {
147 // rippleCredit, no deferredCredits
148 ApplyViewImpl av(&*env.current(), tapNONE);
149
150 auto const iss = USD_gw1.issue();
151 auto const startingAmount = accountHolds(
152 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
153
154 rippleCredit(av, gw1, alice, toCredit, true, j);
155 BEAST_EXPECT(
157 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
158 startingAmount + toCredit);
159
160 rippleCredit(av, alice, gw1, toDebit, true, j);
161 BEAST_EXPECT(
163 av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
164 startingAmount + toCredit - toDebit);
165 }
166
167 {
168 // accountSend, w/ deferredCredits
169 ApplyViewImpl av(&*env.current(), tapNONE);
170 PaymentSandbox pv(&av);
171
172 auto const iss = USD_gw1.issue();
173 auto const startingAmount = accountHolds(
174 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
175
176 {
177 auto r = accountSend(pv, gw1, alice, toCredit, j);
178 BEAST_EXPECT(r == tesSUCCESS);
179 }
180 BEAST_EXPECT(
182 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
183 startingAmount);
184
185 {
186 auto r = accountSend(pv, alice, gw1, toDebit, j);
187 BEAST_EXPECT(r == tesSUCCESS);
188 }
189 BEAST_EXPECT(
191 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
192 startingAmount - toDebit);
193 }
194
195 {
196 // rippleCredit, w/ deferredCredits
197 ApplyViewImpl av(&*env.current(), tapNONE);
198 PaymentSandbox pv(&av);
199
200 auto const iss = USD_gw1.issue();
201 auto const startingAmount = accountHolds(
202 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
203
204 rippleCredit(pv, gw1, alice, toCredit, true, j);
205 BEAST_EXPECT(
207 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
208 startingAmount);
209 }
210
211 {
212 // redeemIOU, w/ deferredCredits
213 ApplyViewImpl av(&*env.current(), tapNONE);
214 PaymentSandbox pv(&av);
215
216 auto const iss = USD_gw1.issue();
217 auto const startingAmount = accountHolds(
218 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
219
220 BEAST_EXPECT(redeemIOU(pv, alice, toDebit, iss, j) == tesSUCCESS);
221 BEAST_EXPECT(
223 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
224 startingAmount - toDebit);
225 }
226
227 {
228 // issueIOU, w/ deferredCredits
229 ApplyViewImpl av(&*env.current(), tapNONE);
230 PaymentSandbox pv(&av);
231
232 auto const iss = USD_gw1.issue();
233 auto const startingAmount = accountHolds(
234 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
235
236 BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS);
237 BEAST_EXPECT(
239 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
240 startingAmount);
241 }
242
243 {
244 // accountSend, w/ deferredCredits and stacked views
245 ApplyViewImpl av(&*env.current(), tapNONE);
246 PaymentSandbox pv(&av);
247
248 auto const iss = USD_gw1.issue();
249 auto const startingAmount = accountHolds(
250 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
251
252 {
253 auto r = accountSend(pv, gw1, alice, toCredit, j);
254 BEAST_EXPECT(r == tesSUCCESS);
255 }
256 BEAST_EXPECT(
258 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
259 startingAmount);
260
261 {
262 PaymentSandbox pv2(&pv);
263 BEAST_EXPECT(
265 pv2,
266 alice,
267 iss.currency,
268 iss.account,
270 j) == startingAmount);
271 {
272 auto r = accountSend(pv2, gw1, alice, toCredit, j);
273 BEAST_EXPECT(r == tesSUCCESS);
274 }
275 BEAST_EXPECT(
277 pv2,
278 alice,
279 iss.currency,
280 iss.account,
282 j) == startingAmount);
283 }
284
285 {
286 auto r = accountSend(pv, alice, gw1, toDebit, j);
287 BEAST_EXPECT(r == tesSUCCESS);
288 }
289 BEAST_EXPECT(
291 pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
292 startingAmount - toDebit);
293 }
294 }
295
296 void
298 {
299 testcase("Tiny balance");
300
301 // Add and subtract a huge credit from a tiny balance, expect the tiny
302 // balance back. Numerical stability problems could cause the balance to
303 // be zero.
304
305 using namespace jtx;
306
307 Env env(*this, features);
308
309 Account const gw("gw");
310 Account const alice("alice");
311 auto const USD = gw["USD"];
312
313 auto const issue = USD.issue();
314 STAmount tinyAmt(
315 issue,
318 false,
320 STAmount hugeAmt(
321 issue,
324 false,
326
327 ApplyViewImpl av(&*env.current(), tapNONE);
328 PaymentSandbox pv(&av);
329 pv.creditHook(gw, alice, hugeAmt, -tinyAmt);
330 BEAST_EXPECT(pv.balanceHook(alice, gw, hugeAmt) == tinyAmt);
331 }
332
333 void
335 {
336 testcase("Reserve");
337 using namespace jtx;
338
339 auto accountFundsXRP = [](ReadView const& view,
340 AccountID const& id,
343 view, id, xrpCurrency(), xrpAccount(), fhZERO_IF_FROZEN, j));
344 };
345
346 auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount {
347 return env.current()->fees().accountReserve(count);
348 };
349
350 Env env(*this, features);
351
352 Account const alice("alice");
353 env.fund(reserve(env, 1), alice);
354
355 env.close();
356 ApplyViewImpl av(&*env.current(), tapNONE);
357 PaymentSandbox sb(&av);
358 {
359 // Send alice an amount and spend it. The deferredCredits will cause
360 // her balance to drop below the reserve. Make sure her funds are
361 // zero (there was a bug that caused her funds to become negative).
362
363 {
364 auto r =
365 accountSend(sb, xrpAccount(), alice, XRP(100), env.journal);
366 BEAST_EXPECT(r == tesSUCCESS);
367 }
368 {
369 auto r =
370 accountSend(sb, alice, xrpAccount(), XRP(100), env.journal);
371 BEAST_EXPECT(r == tesSUCCESS);
372 }
373 BEAST_EXPECT(
374 accountFundsXRP(sb, alice, env.journal) == beast::zero);
375 }
376 }
377
378 void
380 {
381 // Make sure the Issue::Account returned by PAymentSandbox::balanceHook
382 // is correct.
383 testcase("balanceHook");
384
385 using namespace jtx;
386 Env env(*this, features);
387
388 Account const gw("gw");
389 auto const USD = gw["USD"];
390 Account const alice("alice");
391
392 ApplyViewImpl av(&*env.current(), tapNONE);
393 PaymentSandbox sb(&av);
394
395 // The currency we pass for the last argument mimics the currency that
396 // is typically passed to creditHook, since it comes from a trust line.
397 Issue tlIssue = noIssue();
398 tlIssue.currency = USD.issue().currency;
399
400 sb.creditHook(gw.id(), alice.id(), {USD, 400}, {tlIssue, 600});
401 sb.creditHook(gw.id(), alice.id(), {USD, 100}, {tlIssue, 600});
402
403 // Expect that the STAmount issuer returned by balanceHook() is correct.
404 STAmount const balance =
405 sb.balanceHook(gw.id(), alice.id(), {USD, 600});
406 BEAST_EXPECT(balance.getIssuer() == USD.issue().account);
407 }
408
409public:
410 void
411 run() override
412 {
413 auto testAll = [this](FeatureBitset features) {
414 testSelfFunding(features);
415 testSubtractCredits(features);
416 testTinyBalance(features);
417 testReserve(features);
418 testBalanceHook(features);
419 };
420 using namespace jtx;
421 auto const sa = supported_amendments();
422 testAll(sa - featureFlowCross);
423 testAll(sa);
424 }
425};
426
427BEAST_DEFINE_TESTSUITE(PaymentSandbox, ledger, ripple);
428
429} // namespace test
430} // 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.
Definition: ApplyViewImpl.h:37
A currency issued by an account.
Definition: Issue.h:36
Currency currency
Definition: Issue.h:38
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 const int cMaxOffset
Definition: STAmount.h:66
static const std::uint64_t cMinValue
Definition: STAmount.h:69
static const int cMinOffset
Definition: STAmount.h:65
static const std::uint64_t 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:107
A transaction testing environment.
Definition: Env.h:118
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:530
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:326
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:115
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:262
Application & app()
Definition: Env.h:256
beast::Journal const journal
Definition: Env.h:159
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:231
A balance matches.
Definition: balance.h:39
Inject raw JSON.
Definition: jtx_json.h:32
Set Paths, SendMax on a JTx.
Definition: paths.h:34
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:29
any_t const any
Returns an amount representing "any issuer".
Definition: amount.cpp:126
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:28
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:104
FeatureBitset supported_amendments()
Definition: Env.h:71
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:26
@ fhZERO_IF_FROZEN
Definition: View.h:75
@ fhIGNORE_FREEZE
Definition: View.h:75
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:178
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1782
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:96
constexpr std::uint32_t tfPartialPayment
Definition: TxFlags.h:105
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:119
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1682
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition: Issue.h:126
constexpr std::uint32_t tfNoRippleDirect
Definition: TxFlags.h:104
@ tesSUCCESS
Definition: TER.h:242
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:308
@ tapNONE
Definition: ApplyView.h:31
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:2098
XRPAmount toAmount< XRPAmount >(STAmount const &amt)
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition: View.cpp:1608