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