rippled
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 <ripple/ledger/ApplyViewImpl.h>
21 #include <ripple/ledger/PaymentSandbox.h>
22 #include <ripple/ledger/View.h>
23 #include <ripple/protocol/AmountConversions.h>
24 #include <ripple/protocol/Feature.h>
25 #include <test/jtx/PathSet.h>
26 
27 namespace ripple {
28 namespace test {
29 
30 class PaymentSandbox_test : public beast::unit_test::suite
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(
133  accountHolds(
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(
141  accountHolds(
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(
156  accountHolds(
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(
162  accountHolds(
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(
181  accountHolds(
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(
190  accountHolds(
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(
206  accountHolds(
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(
222  accountHolds(
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(
238  accountHolds(
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(
257  accountHolds(
258  pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) ==
259  startingAmount);
260 
261  {
262  PaymentSandbox pv2(&pv);
263  BEAST_EXPECT(
264  accountHolds(
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(
276  accountHolds(
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(
290  accountHolds(
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,
319  false,
321  STAmount hugeAmt(
322  issue,
325  false,
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,
343  beast::Journal j) -> XRPAmount {
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 
411 public:
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 - featureFlowCross);
425  testAll(sa);
426  }
427 };
428 
430 
431 } // namespace test
432 } // namespace ripple
ripple::rippleCredit
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Definition: View.cpp:933
ripple::test::jtx::json
Inject raw JSON.
Definition: jtx_json.h:31
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::Issue
A currency issued by an account.
Definition: Issue.h:34
ripple::STAmount::cMinValue
static const std::uint64_t cMinValue
Definition: STAmount.h:66
ripple::fhZERO_IF_FROZEN
@ fhZERO_IF_FROZEN
Definition: View.h:76
ripple::PaymentSandbox
A wrapper which makes credits unavailable to balances.
Definition: PaymentSandbox.h:112
ripple::test::jtx::Env::require
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:466
ripple::test::jtx::balance
A balance matches.
Definition: balance.h:38
ripple::accountHolds
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:223
ripple::test::PaymentSandbox_test::testReserve
void testReserve(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:336
ripple::Issue::currency
Currency currency
Definition: Issue.h:37
ripple::test::Path
Definition: PathSet.h:92
ripple::test::jtx::Env::journal
const beast::Journal journal
Definition: Env.h:144
ripple::STAmount::cMinOffset
static const int cMinOffset
Definition: STAmount.h:62
ripple::noIssue
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition: Issue.h:103
ripple::tfPassive
constexpr std::uint32_t tfPassive
Definition: TxFlags.h:93
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:241
ripple::ApplyViewImpl
Editable, discardable view that can build metadata for one tx.
Definition: ApplyViewImpl.h:36
ripple::tapNONE
@ tapNONE
Definition: ApplyView.h:30
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:259
ripple::test::PaymentSandbox_test::run
void run() override
Definition: PaymentSandbox_test.cpp:413
ripple::test::jtx::Account::id
AccountID id() const
Returns the Account ID.
Definition: Account.h:106
ripple::base_uint< 160, detail::AccountIDTag >
ripple::toAmount< XRPAmount >
XRPAmount toAmount< XRPAmount >(STAmount const &amt)
Definition: AmountConversions.h:90
ripple::tfPartialPayment
constexpr std::uint32_t tfPartialPayment
Definition: TxFlags.h:102
ripple::test::jtx::any
const any_t any
Returns an amount representing "any issuer".
Definition: amount.cpp:126
ripple::test::PathSet
Definition: PathSet.h:165
ripple::test::reserve
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
Definition: DepositAuth_test.cpp:29
ripple::test::jtx::txflags
Set the flags on a JTx.
Definition: txflags.h:30
ripple::test::jtx::paths
Set Paths, SendMax on a JTx.
Definition: paths.h:32
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::accountSend
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
Definition: View.cpp:1122
ripple::STAmount
Definition: STAmount.h:45
ripple::test::PaymentSandbox_test::testSubtractCredits
void testSubtractCredits(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:96
ripple::xrpAccount
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:168
ripple::test::PaymentSandbox_test
Definition: PaymentSandbox_test.cpp:30
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
ripple::test::PaymentSandbox_test::testTinyBalance
void testTinyBalance(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:297
ripple::test::jtx::supported_amendments
FeatureBitset supported_amendments()
Definition: Env.h:70
std::uint32_t
ripple::issueIOU
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1281
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:125
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::Application::journal
virtual beast::Journal journal(std::string const &name)=0
ripple::STAmount::unchecked
Definition: STAmount.h:80
ripple::test::jtx::pay
Json::Value pay(Account const &account, Account const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
ripple::test::PaymentSandbox_test::testSelfFunding
void testSelfFunding(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:58
ripple::test::PaymentSandbox_test::testBalanceHook
void testBalanceHook(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:381
ripple::redeemIOU
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1377
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:228
ripple::FeatureBitset
Definition: Feature.h:113
ripple::fhIGNORE_FREEZE
@ fhIGNORE_FREEZE
Definition: View.h:76
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::STAmount::cMaxOffset
static const int cMaxOffset
Definition: STAmount.h:63
ripple::featureFlowCross
const uint256 featureFlowCross
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:222
ripple::PaymentSandbox::creditHook
void creditHook(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance) override
Definition: PaymentSandbox.cpp:235
ripple::test::jtx::Env::current
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:300
ripple::xrpCurrency
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:121
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::PaymentSandbox::balanceHook
STAmount balanceHook(AccountID const &account, AccountID const &issuer, STAmount const &amount) const override
Definition: PaymentSandbox.cpp:172
ripple::STAmount::cMaxValue
static const std::uint64_t cMaxValue
Definition: STAmount.h:67
ripple::tfNoRippleDirect
constexpr std::uint32_t tfNoRippleDirect
Definition: TxFlags.h:101
ripple::XRPAmount
Definition: XRPAmount.h:46
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(DeliverMin, app, ripple)