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