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 xrpl {
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))), json(paths.json()), txflags(tfNoRippleDirect | tfPartialPayment));
70
71 env.require(balance("rcv", USD_gw1(0)));
72 env.require(balance("rcv", USD_gw2(2)));
73 }
74
75 void
77 {
78 testcase("subtractCredits");
79
80 using namespace jtx;
81 Env env(*this, features);
82 Account const gw1("gw1");
83 Account const gw2("gw2");
84 Account const alice("alice");
85
86 env.fund(XRP(10000), alice, gw1, gw2);
87
88 auto j = env.app().journal("View");
89
90 auto const USD_gw1 = gw1["USD"];
91 auto const USD_gw2 = gw2["USD"];
92
93 env.trust(USD_gw1(100), alice);
94 env.trust(USD_gw2(100), alice);
95
96 env(pay(gw1, alice, USD_gw1(50)));
97 env(pay(gw2, alice, USD_gw2(50)));
98
99 STAmount const toCredit(USD_gw1(30));
100 STAmount const toDebit(USD_gw1(20));
101 {
102 // accountSend, no deferredCredits
103 ApplyViewImpl av(&*env.current(), tapNONE);
104
105 auto const iss = USD_gw1.issue();
106 auto const startingAmount = accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
107 {
108 auto r = accountSend(av, gw1, alice, toCredit, j);
109 BEAST_EXPECT(r == tesSUCCESS);
110 }
111 BEAST_EXPECT(
112 accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount + toCredit);
113 {
114 auto r = accountSend(av, alice, gw1, toDebit, j);
115 BEAST_EXPECT(r == tesSUCCESS);
116 }
117 BEAST_EXPECT(
118 accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
119 startingAmount + toCredit - toDebit);
120 }
121
122 {
123 // rippleCredit, no deferredCredits
124 ApplyViewImpl av(&*env.current(), tapNONE);
125
126 auto const iss = USD_gw1.issue();
127 auto const startingAmount = accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
128
129 rippleCredit(av, gw1, alice, toCredit, true, j);
130 BEAST_EXPECT(
131 accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount + toCredit);
132
133 rippleCredit(av, alice, gw1, toDebit, true, j);
134 BEAST_EXPECT(
135 accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
136 startingAmount + toCredit - toDebit);
137 }
138
139 {
140 // accountSend, w/ deferredCredits
141 ApplyViewImpl av(&*env.current(), tapNONE);
142 PaymentSandbox pv(&av);
143
144 auto const iss = USD_gw1.issue();
145 auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
146
147 {
148 auto r = accountSend(pv, gw1, alice, toCredit, j);
149 BEAST_EXPECT(r == tesSUCCESS);
150 }
151 BEAST_EXPECT(accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount);
152
153 {
154 auto r = accountSend(pv, alice, gw1, toDebit, j);
155 BEAST_EXPECT(r == tesSUCCESS);
156 }
157 BEAST_EXPECT(
158 accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount - toDebit);
159 }
160
161 {
162 // rippleCredit, w/ deferredCredits
163 ApplyViewImpl av(&*env.current(), tapNONE);
164 PaymentSandbox pv(&av);
165
166 auto const iss = USD_gw1.issue();
167 auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
168
169 rippleCredit(pv, gw1, alice, toCredit, true, j);
170 BEAST_EXPECT(accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount);
171 }
172
173 {
174 // redeemIOU, w/ deferredCredits
175 ApplyViewImpl av(&*env.current(), tapNONE);
176 PaymentSandbox pv(&av);
177
178 auto const iss = USD_gw1.issue();
179 auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
180
181 BEAST_EXPECT(redeemIOU(pv, alice, toDebit, iss, j) == tesSUCCESS);
182 BEAST_EXPECT(
183 accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount - toDebit);
184 }
185
186 {
187 // issueIOU, w/ deferredCredits
188 ApplyViewImpl av(&*env.current(), tapNONE);
189 PaymentSandbox pv(&av);
190
191 auto const iss = USD_gw1.issue();
192 auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
193
194 BEAST_EXPECT(issueIOU(pv, alice, toCredit, iss, j) == tesSUCCESS);
195 BEAST_EXPECT(accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount);
196 }
197
198 {
199 // accountSend, w/ deferredCredits and stacked views
200 ApplyViewImpl av(&*env.current(), tapNONE);
201 PaymentSandbox pv(&av);
202
203 auto const iss = USD_gw1.issue();
204 auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
205
206 {
207 auto r = accountSend(pv, gw1, alice, toCredit, j);
208 BEAST_EXPECT(r == tesSUCCESS);
209 }
210 BEAST_EXPECT(accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount);
211
212 {
213 PaymentSandbox pv2(&pv);
214 BEAST_EXPECT(accountHolds(pv2, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount);
215 {
216 auto r = accountSend(pv2, gw1, alice, toCredit, j);
217 BEAST_EXPECT(r == tesSUCCESS);
218 }
219 BEAST_EXPECT(accountHolds(pv2, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount);
220 }
221
222 {
223 auto r = accountSend(pv, alice, gw1, toDebit, j);
224 BEAST_EXPECT(r == tesSUCCESS);
225 }
226 BEAST_EXPECT(
227 accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount - toDebit);
228 }
229 }
230
231 void
233 {
234 testcase("Tiny balance");
235
236 // Add and subtract a huge credit from a tiny balance, expect the tiny
237 // balance back. Numerical stability problems could cause the balance to
238 // be zero.
239
240 using namespace jtx;
241
242 Env env(*this, features);
243
244 Account const gw("gw");
245 Account const alice("alice");
246 auto const USD = gw["USD"];
247
248 auto const issue = USD.issue();
251
252 ApplyViewImpl av(&*env.current(), tapNONE);
253 PaymentSandbox pv(&av);
254 pv.creditHook(gw, alice, hugeAmt, -tinyAmt);
255 BEAST_EXPECT(pv.balanceHook(alice, gw, hugeAmt) == tinyAmt);
256 }
257
258 void
260 {
261 testcase("Reserve");
262 using namespace jtx;
263
264 auto accountFundsXRP = [](ReadView const& view, AccountID const& id, beast::Journal j) -> XRPAmount {
266 };
267
268 auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount {
269 return env.current()->fees().accountReserve(count);
270 };
271
272 Env env(*this, features);
273
274 Account const alice("alice");
275 env.fund(reserve(env, 1), alice);
276
277 env.close();
278 ApplyViewImpl av(&*env.current(), tapNONE);
279 PaymentSandbox sb(&av);
280 {
281 // Send alice an amount and spend it. The deferredCredits will cause
282 // her balance to drop below the reserve. Make sure her funds are
283 // zero (there was a bug that caused her funds to become negative).
284
285 {
286 auto r = accountSend(sb, xrpAccount(), alice, XRP(100), env.journal);
287 BEAST_EXPECT(r == tesSUCCESS);
288 }
289 {
290 auto r = accountSend(sb, alice, xrpAccount(), XRP(100), env.journal);
291 BEAST_EXPECT(r == tesSUCCESS);
292 }
293 BEAST_EXPECT(accountFundsXRP(sb, alice, env.journal) == beast::zero);
294 }
295 }
296
297 void
299 {
300 // Make sure the Issue::Account returned by PAymentSandbox::balanceHook
301 // is correct.
302 testcase("balanceHook");
303
304 using namespace jtx;
305 Env env(*this, features);
306
307 Account const gw("gw");
308 auto const USD = gw["USD"];
309 Account const alice("alice");
310
311 ApplyViewImpl av(&*env.current(), tapNONE);
312 PaymentSandbox sb(&av);
313
314 // The currency we pass for the last argument mimics the currency that
315 // is typically passed to creditHook, since it comes from a trust line.
316 Issue tlIssue = noIssue();
317 tlIssue.currency = USD.issue().currency;
318
319 sb.creditHook(gw.id(), alice.id(), {USD, 400}, {tlIssue, 600});
320 sb.creditHook(gw.id(), alice.id(), {USD, 100}, {tlIssue, 600});
321
322 // Expect that the STAmount issuer returned by balanceHook() is correct.
323 STAmount const balance = sb.balanceHook(gw.id(), alice.id(), {USD, 600});
324 BEAST_EXPECT(balance.getIssuer() == USD.issue().account);
325 }
326
327public:
328 void
329 run() override
330 {
331 auto testAll = [this](FeatureBitset features) {
332 testSelfFunding(features);
333 testSubtractCredits(features);
334 testTinyBalance(features);
335 testReserve(features);
336 testBalanceHook(features);
337 };
338 using namespace jtx;
339 auto const sa = testable_amendments();
340 testAll(sa - featurePermissionedDEX);
341 testAll(sa);
342 }
343};
344
345BEAST_DEFINE_TESTSUITE(PaymentSandbox, ledger, xrpl);
346
347} // namespace test
348} // 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:147
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: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
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:119
Application & app()
Definition Env.h:251
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:98
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:261
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:284
beast::Journal const journal
Definition Env.h:160
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:533
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:319
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:111
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
FeatureBitset testable_amendments()
Definition Env.h:76
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
constexpr std::uint32_t tfPassive
Definition TxFlags.h:78
@ fhZERO_IF_FROZEN
Definition View.h:58
@ fhIGNORE_FREEZE
Definition View.h:58
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2535
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:392
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:96
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:2446
XRPAmount toAmount< XRPAmount >(STAmount const &amt)
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:87
@ tapNONE
Definition ApplyView.h:11
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2616
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.
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:88
@ 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.
Definition View.cpp:3083