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 <test/jtx/PathSet.h>
23 #include <ripple/ledger/View.h>
24 #include <ripple/protocol/AmountConversions.h>
25 #include <ripple/protocol/Feature.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  */
58  {
59  testcase ("selfFunding");
60 
61  using namespace jtx;
62  Env env (*this, features);
63  Account const gw1 ("gw1");
64  Account const gw2 ("gw2");
65  Account const snd ("snd");
66  Account const rcv ("rcv");
67 
68  env.fund (XRP (10000), snd, rcv, gw1, gw2);
69 
70  auto const USD_gw1 = gw1["USD"];
71  auto const USD_gw2 = gw2["USD"];
72 
73  env.trust (USD_gw1 (10), snd);
74  env.trust (USD_gw2 (10), snd);
75  env.trust (USD_gw1 (100), rcv);
76  env.trust (USD_gw2 (100), rcv);
77 
78  env (pay (gw1, snd, USD_gw1 (2)));
79  env (pay (gw2, snd, USD_gw2 (4)));
80 
81  env (offer (snd, USD_gw1 (2), USD_gw2 (2)),
82  txflags (tfPassive));
83  env (offer (snd, USD_gw2 (2), USD_gw1 (2)),
84  txflags (tfPassive));
85 
86  PathSet paths (
87  Path (gw1, USD_gw2, gw2),
88  Path (gw2, USD_gw1, gw1));
89 
90  env (pay (snd, rcv, any (USD_gw1 (4))),
91  json (paths.json ()),
93 
94  env.require (balance ("rcv", USD_gw1 (0)));
95  env.require (balance ("rcv", USD_gw2 (2)));
96  }
97 
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(accountHolds (av, alice, iss.currency, iss.account,
135  fhIGNORE_FREEZE, j) ==
136  startingAmount + toCredit);
137  {
138  auto r = accountSend(av, alice, gw1, toDebit, j);
139  BEAST_EXPECT(r == tesSUCCESS);
140  }
141  BEAST_EXPECT(accountHolds (av, alice, iss.currency, iss.account,
142  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(accountHolds (av, alice, iss.currency, iss.account,
156  fhIGNORE_FREEZE, j) ==
157  startingAmount + toCredit);
158 
159  rippleCredit (av, alice, gw1, toDebit, true, j);
160  BEAST_EXPECT(accountHolds (av, alice, iss.currency, iss.account,
161  fhIGNORE_FREEZE, j) ==
162  startingAmount + toCredit - toDebit);
163  }
164 
165  {
166  // accountSend, w/ deferredCredits
167  ApplyViewImpl av (&*env.current(), tapNONE);
168  PaymentSandbox pv (&av);
169 
170  auto const iss = USD_gw1.issue ();
171  auto const startingAmount = accountHolds (
172  pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
173 
174  {
175  auto r = accountSend (pv, gw1, alice, toCredit, j);
176  BEAST_EXPECT(r == tesSUCCESS);
177  }
178  BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
179  fhIGNORE_FREEZE, j) ==
180  startingAmount);
181 
182  {
183  auto r = accountSend (pv, alice, gw1, toDebit, j);
184  BEAST_EXPECT(r == tesSUCCESS);
185  }
186  BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
187  fhIGNORE_FREEZE, j) ==
188  startingAmount - toDebit);
189  }
190 
191  {
192  // rippleCredit, w/ deferredCredits
193  ApplyViewImpl av (&*env.current(), tapNONE);
194  PaymentSandbox pv (&av);
195 
196  auto const iss = USD_gw1.issue ();
197  auto const startingAmount = accountHolds (
198  pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
199 
200  rippleCredit (pv, gw1, alice, toCredit, true, j);
201  BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
202  fhIGNORE_FREEZE, j) ==
203  startingAmount);
204  }
205 
206  {
207  // redeemIOU, w/ deferredCredits
208  ApplyViewImpl av (&*env.current(), tapNONE);
209  PaymentSandbox pv (&av);
210 
211  auto const iss = USD_gw1.issue ();
212  auto const startingAmount = accountHolds (
213  pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
214 
215  BEAST_EXPECT(redeemIOU (pv, alice, toDebit, iss, j) == tesSUCCESS);
216  BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
217  fhIGNORE_FREEZE, j) ==
218  startingAmount - toDebit);
219  }
220 
221  {
222  // issueIOU, w/ deferredCredits
223  ApplyViewImpl av (&*env.current(), tapNONE);
224  PaymentSandbox pv (&av);
225 
226  auto const iss = USD_gw1.issue ();
227  auto const startingAmount = accountHolds (
228  pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
229 
230  BEAST_EXPECT(issueIOU (pv, alice, toCredit, iss, j) == tesSUCCESS);
231  BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
232  fhIGNORE_FREEZE, j) ==
233  startingAmount);
234  }
235 
236  {
237  // accountSend, w/ deferredCredits and stacked views
238  ApplyViewImpl av (&*env.current(), tapNONE);
239  PaymentSandbox pv (&av);
240 
241  auto const iss = USD_gw1.issue ();
242  auto const startingAmount = accountHolds (
243  pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j);
244 
245  {
246  auto r = accountSend (pv, gw1, alice, toCredit, j);
247  BEAST_EXPECT(r == tesSUCCESS);
248  }
249  BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
250  fhIGNORE_FREEZE, j) ==
251  startingAmount);
252 
253  {
254  PaymentSandbox pv2(&pv);
255  BEAST_EXPECT(accountHolds (pv2, alice, iss.currency, iss.account,
256  fhIGNORE_FREEZE, j) ==
257  startingAmount);
258  {
259  auto r = accountSend (pv2, gw1, alice, toCredit, j);
260  BEAST_EXPECT(r == tesSUCCESS);
261  }
262  BEAST_EXPECT(accountHolds (pv2, alice, iss.currency, iss.account,
263  fhIGNORE_FREEZE, j) ==
264  startingAmount);
265  }
266 
267  {
268  auto r = accountSend (pv, alice, gw1, toDebit, j);
269  BEAST_EXPECT(r == tesSUCCESS);
270  }
271  BEAST_EXPECT(accountHolds (pv, alice, iss.currency, iss.account,
272  fhIGNORE_FREEZE, j) ==
273  startingAmount - toDebit);
274  }
275  }
276 
278  {
279  testcase ("Tiny balance");
280 
281  // Add and subtract a huge credit from a tiny balance, expect the tiny
282  // balance back. Numerical stability problems could cause the balance to
283  // be zero.
284 
285  using namespace jtx;
286 
287  Env env (*this, features);
288 
289  Account const gw ("gw");
290  Account const alice ("alice");
291  auto const USD = gw["USD"];
292 
293  auto const issue = USD.issue ();
295  false, false, STAmount::unchecked{});
297  false, false, STAmount::unchecked{});
298 
299  ApplyViewImpl av (&*env.current (), tapNONE);
300  PaymentSandbox pv (&av);
301  pv.creditHook (gw, alice, hugeAmt, -tinyAmt);
302  BEAST_EXPECT(pv.balanceHook (alice, gw, hugeAmt) == tinyAmt);
303  }
304 
305  void testReserve(FeatureBitset features)
306  {
307  testcase ("Reserve");
308  using namespace jtx;
309 
310  auto accountFundsXRP = [](ReadView const& view,
311  AccountID const& id, beast::Journal j) -> XRPAmount
312  {
314  view, id, xrpCurrency (), xrpAccount (), fhZERO_IF_FROZEN, j));
315  };
316 
317  auto reserve = [](jtx::Env& env, std::uint32_t count) -> XRPAmount
318  {
319  return env.current ()->fees ().accountReserve (count);
320  };
321 
322  Env env (*this, features);
323 
324  Account const alice ("alice");
325  env.fund (reserve(env, 1), alice);
326 
327  env.close();
328  ApplyViewImpl av (&*env.current (), tapNONE);
329  PaymentSandbox sb (&av);
330  {
331  // Send alice an amount and spend it. The deferredCredits will cause her balance
332  // to drop below the reserve. Make sure her funds are zero (there was a bug that
333  // caused her funds to become negative).
334 
335  {
336  auto r = accountSend (sb, xrpAccount (), alice, XRP(100), env.journal);
337  BEAST_EXPECT(r == tesSUCCESS);
338  }
339  {
340  auto r = accountSend (sb, alice, xrpAccount (), XRP(100), env.journal);
341  BEAST_EXPECT(r == tesSUCCESS);
342  }
343  BEAST_EXPECT(
344  accountFundsXRP (sb, alice, env.journal) == beast::zero);
345  }
346  }
347 
349  {
350  // Make sure the Issue::Account returned by PAymentSandbox::balanceHook
351  // is correct.
352  testcase ("balanceHook");
353 
354  using namespace jtx;
355  Env env (*this, features);
356 
357  Account const gw ("gw");
358  auto const USD = gw["USD"];
359  Account const alice ("alice");
360 
361  ApplyViewImpl av (&*env.current (), tapNONE);
362  PaymentSandbox sb (&av);
363 
364  // The currency we pass for the last argument mimics the currency that
365  // is typically passed to creditHook, since it comes from a trust line.
366  Issue tlIssue = noIssue();
367  tlIssue.currency = USD.issue().currency;
368 
369  sb.creditHook (gw.id(), alice.id(), {USD, 400}, {tlIssue, 600});
370  sb.creditHook (gw.id(), alice.id(), {USD, 100}, {tlIssue, 600});
371 
372  // Expect that the STAmount issuer returned by balanceHook() is correct.
373  STAmount const balance =
374  sb.balanceHook (gw.id(), alice.id(), {USD, 600});
375  BEAST_EXPECT (balance.getIssuer() == USD.issue().account);
376  }
377 
378 public:
379  void run () override
380  {
381  auto testAll = [this](FeatureBitset features) {
382  testSelfFunding(features);
383  testSubtractCredits(features);
384  testTinyBalance(features);
385  testReserve(features);
386  testBalanceHook(features);
387  };
388  using namespace jtx;
389  auto const sa = supported_amendments();
390  testAll(sa - featureFlowCross);
391  testAll(sa);
392  }
393 };
394 
396 
397 } // test
398 } // ripple
ripple::rippleCredit
TER rippleCredit(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Definition: View.cpp:914
ripple::test::jtx::json
Inject raw JSON.
Definition: jtx_json.h:31
ripple::tfNoRippleDirect
const std::uint32_t tfNoRippleDirect
Definition: TxFlags.h:83
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:109
ripple::Issue
A currency issued by an account.
Definition: Issue.h:34
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple)
ripple::STAmount::cMinValue
static const std::uint64_t cMinValue
Definition: STAmount.h:64
ripple::fhZERO_IF_FROZEN
@ fhZERO_IF_FROZEN
Definition: View.h:56
ripple::tfPartialPayment
const std::uint32_t tfPartialPayment
Definition: TxFlags.h:84
ripple::PaymentSandbox
A wrapper which makes credits unavailable to balances.
Definition: PaymentSandbox.h:110
ripple::test::jtx::Env::require
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:461
ripple::test::jtx::balance
A balance matches.
Definition: balance.h:38
ripple::tfPassive
const std::uint32_t tfPassive
Definition: TxFlags.h:76
ripple::accountHolds
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:91
ripple::test::PaymentSandbox_test::testReserve
void testReserve(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:305
ripple::Issue::currency
Currency currency
Definition: Issue.h:37
ripple::test::Path
Definition: PathSet.h:51
ripple::test::jtx::Env::journal
const beast::Journal journal
Definition: Env.h:143
ripple::STAmount::cMinOffset
static const int cMinOffset
Definition: STAmount.h:60
ripple::noIssue
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition: Issue.h:104
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:237
ripple::ApplyViewImpl
Editable, discardable view that can build metadata for one tx.
Definition: ApplyViewImpl.h:37
ripple::test::jtx::offer
Json::Value offer(Account const &account, STAmount const &in, STAmount const &out, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:28
ripple::tapNONE
@ tapNONE
Definition: ApplyView.h:33
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:245
ripple::test::PaymentSandbox_test::run
void run() override
Definition: PaymentSandbox_test.cpp:379
ripple::test::jtx::Account::id
AccountID id() const
Returns the Account ID.
Definition: Account.h:97
ripple::base_uint< 160, detail::AccountIDTag >
ripple::toAmount< XRPAmount >
XRPAmount toAmount< XRPAmount >(STAmount const &amt)
Definition: AmountConversions.h:96
ripple::test::jtx::any
const any_t any
Returns an amount representing "any issuer".
Definition: amount.cpp:136
ripple::test::PathSet
Definition: PathSet.h:110
ripple::test::reserve
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
Definition: DepositAuth_test.cpp:28
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::accountSend
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j)
Definition: View.cpp:1101
ripple::STAmount
Definition: STAmount.h:42
ripple::test::PaymentSandbox_test::testSubtractCredits
void testSubtractCredits(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:98
ripple::xrpAccount
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:149
ripple::test::PaymentSandbox_test
Definition: PaymentSandbox_test.cpp:30
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:60
ripple::test::PaymentSandbox_test::testTinyBalance
void testTinyBalance(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:277
ripple::test::jtx::supported_amendments
FeatureBitset supported_amendments()
Definition: Env.h:71
std::uint32_t
ripple::issueIOU
TER issueIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1262
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:186
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:78
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:57
ripple::test::PaymentSandbox_test::testBalanceHook
void testBalanceHook(FeatureBitset features)
Definition: PaymentSandbox_test.cpp:348
ripple::redeemIOU
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1335
ripple::test::jtx::Env::close
void close(NetClock::time_point closeTime, boost::optional< std::chrono::milliseconds > consensusDelay=boost::none)
Close and advance the ledger.
Definition: Env.cpp:114
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:214
ripple::FeatureBitset
Definition: Feature.h:153
ripple::fhIGNORE_FREEZE
@ fhIGNORE_FREEZE
Definition: View.h:55
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::STAmount::cMaxOffset
static const int cMaxOffset
Definition: STAmount.h:61
ripple::featureFlowCross
const uint256 featureFlowCross
Definition: Feature.cpp:161
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:219
ripple::PaymentSandbox::creditHook
void creditHook(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance) override
Definition: PaymentSandbox.cpp:228
ripple::test::jtx::Env::current
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:296
ripple::xrpCurrency
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:111
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:117
ripple::PaymentSandbox::balanceHook
STAmount balanceHook(AccountID const &account, AccountID const &issuer, STAmount const &amount) const override
Definition: PaymentSandbox.cpp:166
ripple::STAmount::cMaxValue
static const std::uint64_t cMaxValue
Definition: STAmount.h:65
ripple::XRPAmount
Definition: XRPAmount.h:46