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