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