rippled
Loading...
Searching...
No Matches
TrustAndBalance_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/WSClient.h>
3
4#include <xrpl/beast/unit_test.h>
5#include <xrpl/protocol/Feature.h>
6#include <xrpl/protocol/SField.h>
7#include <xrpl/protocol/jss.h>
8
9namespace xrpl {
10
12{
13 void
15 {
16 testcase("Payment to Nonexistent Account");
17 using namespace test::jtx;
18
19 Env env{*this, features};
20 env(pay(env.master, "alice", XRP(1)), ter(tecNO_DST_INSUF_XRP));
21 env.close();
22 }
23
24 void
26 {
27 testcase("Trust Nonexistent Account");
28 using namespace test::jtx;
29
30 Env env{*this};
31 Account const alice{"alice"};
32
33 env(trust(env.master, alice["USD"](100)), ter(tecNO_DST));
34 }
35
36 void
38 {
39 testcase("Credit Limit");
40 using namespace test::jtx;
41
42 Env env{*this};
43 Account const gw{"gateway"};
44 Account const alice{"alice"};
45 Account const bob{"bob"};
46
47 env.fund(XRP(10000), gw, alice, bob);
48 env.close();
49
50 // credit limit doesn't exist yet - verify ledger_entry
51 // reflects this
52 auto jrr = ledgerEntryState(env, gw, alice, "USD");
53 BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
54
55 // now create a credit limit
56 env(trust(alice, gw["USD"](800)));
57
58 jrr = ledgerEntryState(env, gw, alice, "USD");
59 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
60 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::value] == "800");
61 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::issuer] == alice.human());
62 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
63 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "0");
64 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == gw.human());
65 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
66
67 // modify the credit limit
68 env(trust(alice, gw["USD"](700)));
69
70 jrr = ledgerEntryState(env, gw, alice, "USD");
71 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
72 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::value] == "700");
73 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::issuer] == alice.human());
74 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
75 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "0");
76 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == gw.human());
77 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
78
79 // set negative limit - expect failure
80 env(trust(alice, gw["USD"](-1)), ter(temBAD_LIMIT));
81
82 // set zero limit
83 env(trust(alice, gw["USD"](0)));
84
85 // ensure line is deleted
86 jrr = ledgerEntryState(env, gw, alice, "USD");
87 BEAST_EXPECT(jrr[jss::error] == "entryNotFound");
88
89 // TODO Check in both owner books.
90
91 // set another credit limit
92 env(trust(alice, bob["USD"](600)));
93
94 // set limit on other side
95 env(trust(bob, alice["USD"](500)));
96
97 // check the ledger state for the trust line
98 jrr = ledgerEntryState(env, alice, bob, "USD");
99 BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "0");
100 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::value] == "500");
101 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::issuer] == bob.human());
102 BEAST_EXPECT(jrr[jss::node][sfHighLimit.fieldName][jss::currency] == "USD");
103 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::value] == "600");
104 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::issuer] == alice.human());
105 BEAST_EXPECT(jrr[jss::node][sfLowLimit.fieldName][jss::currency] == "USD");
106 }
107
108 void
110 {
111 testcase("Direct Payment, Ripple");
112 using namespace test::jtx;
113
114 Env env{*this, features};
115 Account const alice{"alice"};
116 Account const bob{"bob"};
117
118 env.fund(XRP(10000), alice, bob);
119 env.close();
120
121 env(trust(alice, bob["USD"](600)));
122 env(trust(bob, alice["USD"](700)));
123
124 // alice sends bob partial with alice as issuer
125 env(pay(alice, bob, alice["USD"](24)));
126 env.require(balance(bob, alice["USD"](24)));
127
128 // alice sends bob more with bob as issuer
129 env(pay(alice, bob, bob["USD"](33)));
130 env.require(balance(bob, alice["USD"](57)));
131
132 // bob sends back more than sent
133 env(pay(bob, alice, bob["USD"](90)));
134 env.require(balance(bob, alice["USD"](-33)));
135
136 // alice sends to her limit
137 env(pay(alice, bob, bob["USD"](733)));
138 env.require(balance(bob, alice["USD"](700)));
139
140 // bob sends to his limit
141 env(pay(bob, alice, bob["USD"](1300)));
142 env.require(balance(bob, alice["USD"](-600)));
143
144 // bob sends past limit
145 env(pay(bob, alice, bob["USD"](1)), ter(tecPATH_DRY));
146 env.require(balance(bob, alice["USD"](-600)));
147 }
148
149 void
150 testWithTransferFee(bool subscribe, bool with_rate, FeatureBitset features)
151 {
152 testcase(
153 std::string("Direct Payment: ") + (with_rate ? "With " : "Without ") + " Xfer Fee, " +
154 (subscribe ? "With " : "Without ") + " Subscribe");
155 using namespace test::jtx;
156
157 Env env{*this, features};
158 auto wsc = test::makeWSClient(env.app().config());
159 Account const gw{"gateway"};
160 Account const alice{"alice"};
161 Account const bob{"bob"};
162
163 env.fund(XRP(10000), gw, alice, bob);
164 env.close();
165
166 env(trust(alice, gw["AUD"](100)));
167 env(trust(bob, gw["AUD"](100)));
168
169 env(pay(gw, alice, alice["AUD"](1)));
170 env.close();
171
172 env.require(balance(alice, gw["AUD"](1)));
173
174 // alice sends bob 1 AUD
175 env(pay(alice, bob, gw["AUD"](1)));
176 env.close();
177
178 env.require(balance(alice, gw["AUD"](0)));
179 env.require(balance(bob, gw["AUD"](1)));
180 env.require(balance(gw, bob["AUD"](-1)));
181
182 if (with_rate)
183 {
184 // set a transfer rate
185 env(rate(gw, 1.1));
186 env.close();
187 // bob sends alice 0.5 AUD with a max to spend
188 env(pay(bob, alice, gw["AUD"](0.5)), sendmax(gw["AUD"](0.55)));
189 }
190 else
191 {
192 // bob sends alice 0.5 AUD
193 env(pay(bob, alice, gw["AUD"](0.5)));
194 }
195
196 env.require(balance(alice, gw["AUD"](0.5)));
197 env.require(balance(bob, gw["AUD"](with_rate ? 0.45 : 0.5)));
198 env.require(balance(gw, bob["AUD"](with_rate ? -0.45 : -0.5)));
199
200 if (subscribe)
201 {
202 Json::Value jvs;
203 jvs[jss::accounts] = Json::arrayValue;
204 jvs[jss::accounts].append(gw.human());
205 jvs[jss::streams] = Json::arrayValue;
206 jvs[jss::streams].append("transactions");
207 jvs[jss::streams].append("ledger");
208 auto jv = wsc->invoke("subscribe", jvs);
209 BEAST_EXPECT(jv[jss::status] == "success");
210
211 env.close();
212
213 using namespace std::chrono_literals;
214 BEAST_EXPECT(wsc->findMsg(5s, [](auto const& jval) {
215 auto const& t = jval[jss::transaction];
216 return t[jss::TransactionType] == jss::Payment;
217 }));
218 BEAST_EXPECT(wsc->findMsg(
219 5s, [](auto const& jval) { return jval[jss::type] == "ledgerClosed"; }));
220
221 BEAST_EXPECT(wsc->invoke("unsubscribe", jv)[jss::status] == "success");
222 }
223 }
224
225 void
227 {
228 testcase("Payments With Paths and Fees");
229 using namespace test::jtx;
230
231 Env env{*this, features};
232 Account const gw{"gateway"};
233 Account const alice{"alice"};
234 Account const bob{"bob"};
235
236 env.fund(XRP(10000), gw, alice, bob);
237 env.close();
238
239 // set a transfer rate
240 env(rate(gw, 1.1));
241
242 env(trust(alice, gw["AUD"](100)));
243 env(trust(bob, gw["AUD"](100)));
244
245 env(pay(gw, alice, alice["AUD"](4.4)));
246 env.require(balance(alice, gw["AUD"](4.4)));
247
248 // alice sends gw issues to bob with a max spend that allows for the
249 // xfer rate
250 env(pay(alice, bob, gw["AUD"](1)), sendmax(gw["AUD"](1.1)));
251 env.require(balance(alice, gw["AUD"](3.3)));
252 env.require(balance(bob, gw["AUD"](1)));
253
254 // alice sends bob issues to bob with a max spend
255 env(pay(alice, bob, bob["AUD"](1)), sendmax(gw["AUD"](1.1)));
256 env.require(balance(alice, gw["AUD"](2.2)));
257 env.require(balance(bob, gw["AUD"](2)));
258
259 // alice sends gw issues to bob with a max spend
260 env(pay(alice, bob, gw["AUD"](1)), sendmax(alice["AUD"](1.1)));
261 env.require(balance(alice, gw["AUD"](1.1)));
262 env.require(balance(bob, gw["AUD"](3)));
263
264 // alice sends bob issues to bob with a max spend in alice issues.
265 // expect fail since gw is not involved
266 env(pay(alice, bob, bob["AUD"](1)), sendmax(alice["AUD"](1.1)), ter(tecPATH_DRY));
267
268 env.require(balance(alice, gw["AUD"](1.1)));
269 env.require(balance(bob, gw["AUD"](3)));
270 }
271
272 void
274 {
275 testcase("Indirect Payment");
276 using namespace test::jtx;
277
278 Env env{*this, features};
279 Account const gw{"gateway"};
280 Account const alice{"alice"};
281 Account const bob{"bob"};
282
283 env.fund(XRP(10000), gw, alice, bob);
284 env.close();
285
286 env(trust(alice, gw["USD"](600)));
287 env(trust(bob, gw["USD"](700)));
288
289 env(pay(gw, alice, alice["USD"](70)));
290 env(pay(gw, bob, bob["USD"](50)));
291
292 env.require(balance(alice, gw["USD"](70)));
293 env.require(balance(bob, gw["USD"](50)));
294
295 // alice sends more than has to issuer: 100 out of 70
296 env(pay(alice, gw, gw["USD"](100)), ter(tecPATH_PARTIAL));
297
298 // alice sends more than has to bob: 100 out of 70
299 env(pay(alice, bob, gw["USD"](100)), ter(tecPATH_PARTIAL));
300
301 env.close();
302
303 env.require(balance(alice, gw["USD"](70)));
304 env.require(balance(bob, gw["USD"](50)));
305
306 // send with an account path
307 env(pay(alice, bob, gw["USD"](5)), test::jtx::path(gw));
308
309 env.require(balance(alice, gw["USD"](65)));
310 env.require(balance(bob, gw["USD"](55)));
311 }
312
313 void
314 testIndirectMultiPath(bool with_rate, FeatureBitset features)
315 {
316 testcase(
317 std::string("Indirect Payment, Multi Path, ") + (with_rate ? "With " : "Without ") +
318 " Xfer Fee, ");
319 using namespace test::jtx;
320
321 Env env{*this, features};
322 Account const gw{"gateway"};
323 Account const amazon{"amazon"};
324 Account const alice{"alice"};
325 Account const bob{"bob"};
326 Account const carol{"carol"};
327
328 env.fund(XRP(10000), gw, amazon, alice, bob, carol);
329 env.close();
330
331 env(trust(amazon, gw["USD"](2000)));
332 env(trust(bob, alice["USD"](600)));
333 env(trust(bob, gw["USD"](1000)));
334 env(trust(carol, alice["USD"](700)));
335 env(trust(carol, gw["USD"](1000)));
336
337 if (with_rate)
338 env(rate(gw, 1.1));
339
340 env(pay(gw, bob, bob["USD"](100)));
341 env(pay(gw, carol, carol["USD"](100)));
342 env.close();
343
344 // alice pays amazon via multiple paths
345 if (with_rate)
346 {
347 env(pay(alice, amazon, gw["USD"](150)),
348 sendmax(alice["USD"](200)),
349 test::jtx::path(bob),
350 test::jtx::path(carol));
351 }
352 else
353 {
354 env(pay(alice, amazon, gw["USD"](150)), test::jtx::path(bob), test::jtx::path(carol));
355 }
356
357 if (with_rate)
358 {
359 env.require(balance(
360 alice,
361 STAmount(
362 carol["USD"].issue(), 6500000000000000ull, -14, true, STAmount::unchecked{})));
363 env.require(balance(carol, gw["USD"](35)));
364 }
365 else
366 {
367 env.require(balance(alice, carol["USD"](-50)));
368 env.require(balance(carol, gw["USD"](50)));
369 }
370 env.require(balance(alice, bob["USD"](-100)));
371 env.require(balance(amazon, gw["USD"](150)));
372 env.require(balance(bob, gw["USD"](0)));
373 }
374
375 void
377 {
378 testcase("Set Invoice ID on Payment");
379 using namespace test::jtx;
380
381 Env env{*this, features};
382 Account const alice{"alice"};
383 auto wsc = test::makeWSClient(env.app().config());
384
385 env.fund(XRP(10000), alice);
386 env.close();
387
388 Json::Value jvs;
389 jvs[jss::accounts] = Json::arrayValue;
390 jvs[jss::accounts].append(env.master.human());
391 jvs[jss::streams] = Json::arrayValue;
392 jvs[jss::streams].append("transactions");
393 BEAST_EXPECT(wsc->invoke("subscribe", jvs)[jss::status] == "success");
394
395 char const* invoiceId = "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89";
396
397 Json::Value jv;
398 auto tx =
399 env.jt(pay(env.master, alice, XRP(10000)), json(sfInvoiceID.fieldName, invoiceId));
400 jv[jss::tx_blob] = strHex(tx.stx->getSerializer().slice());
401 auto jrr = wsc->invoke("submit", jv)[jss::result];
402 BEAST_EXPECT(jrr[jss::status] == "success");
403 BEAST_EXPECT(jrr[jss::tx_json][sfInvoiceID.fieldName] == invoiceId);
404 env.close();
405
406 using namespace std::chrono_literals;
407 BEAST_EXPECT(wsc->findMsg(2s, [invoiceId](auto const& jval) {
408 auto const& t = jval[jss::transaction];
409 return t[jss::TransactionType] == jss::Payment && t[sfInvoiceID.fieldName] == invoiceId;
410 }));
411
412 BEAST_EXPECT(wsc->invoke("unsubscribe", jv)[jss::status] == "success");
413 }
414
415public:
416 void
417 run() override
418 {
421
422 auto testWithFeatures = [this](FeatureBitset features) {
423 testPayNonexistent(features);
424 testDirectRipple(features);
425 testWithTransferFee(false, false, features);
426 testWithTransferFee(false, true, features);
427 testWithTransferFee(true, false, features);
428 testWithTransferFee(true, true, features);
429 testWithPath(features);
430 testIndirect(features);
431 testIndirectMultiPath(true, features);
432 testIndirectMultiPath(false, features);
433 testInvoiceID(features);
434 };
435
436 using namespace test::jtx;
437 auto const sa = testable_amendments();
438 testWithFeatures(sa - featurePermissionedDEX);
439 testWithFeatures(sa);
440 }
441};
442
443BEAST_DEFINE_TESTSUITE(TrustAndBalance, app, xrpl);
444
445} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
Value & append(Value const &value)
Append value to array at the end.
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
void testInvoiceID(FeatureBitset features)
void testWithTransferFee(bool subscribe, bool with_rate, FeatureBitset features)
void testPayNonexistent(FeatureBitset features)
void run() override
Runs the suite.
void testWithPath(FeatureBitset features)
void testDirectRipple(FeatureBitset features)
void testIndirectMultiPath(bool with_rate, FeatureBitset features)
void testIndirect(FeatureBitset features)
Add a path.
Definition paths.h:38
@ arrayValue
array value (ordered list)
Definition json_value.h:25
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition WSClient.cpp:296
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
@ temBAD_LIMIT
Definition TER.h:74
@ tecPATH_PARTIAL
Definition TER.h:263
@ tecPATH_DRY
Definition TER.h:275
@ tecNO_DST_INSUF_XRP
Definition TER.h:272
@ tecNO_DST
Definition TER.h:271