rippled
Loading...
Searching...
No Matches
DeliveredAmount_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2019 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 <test/jtx.h>
21#include <test/jtx/WSClient.h>
22
23#include <xrpl/beast/unit_test.h>
24#include <xrpl/beast/unit_test/suite.h>
25#include <xrpl/protocol/jss.h>
26
27namespace ripple {
28namespace test {
29
30// Helper class to track the expected number `delivered_amount` results.
32{
33 // If the test occurs before or after the switch time
35 // number of payments expected 'delivered_amount' available
37 // Number of payments with field with `delivered_amount` set to the
38 // string "unavailable"
40 // Number of payments with no `delivered_amount` field
42
43 // Increment one of the expected numExpected{Available_, Unavailable_,
44 // NotSet_} values. Which value to increment depends on: 1) If the ledger is
45 // before or after the switch time 2) If the tx is a partial payment 3) If
46 // the payment is successful or not
47 void
48 adjCounters(bool success, bool partial)
49 {
50 if (!success)
51 {
53 return;
54 }
56 {
57 if (partial)
59 else
61 return;
62 }
63 // normal case: after switch time & successful transaction
65 }
66
67public:
68 explicit CheckDeliveredAmount(bool afterSwitchTime)
69 : afterSwitchTime_(afterSwitchTime)
70 {
71 }
72
73 void
75 {
76 adjCounters(true, false);
77 }
78
79 void
81 {
82 adjCounters(false, false);
83 }
84 void
86 {
87 adjCounters(true, true);
88 }
89
90 // After all the txns are checked, all the `numExpected` variables should be
91 // zero. The `checkTxn` function decrements these variables.
92 bool
98
99 // Check if the transaction has `delivered_amount` in the metaData as
100 // expected from our rules. Decrements the appropriate `numExpected`
101 // variable. After all the txns are checked, all the `numExpected` variables
102 // should be zero.
103 bool
104 checkTxn(Json::Value const& t, Json::Value const& metaData)
105 {
106 if (t[jss::TransactionType].asString() != jss::Payment)
107 return true;
108
109 bool isSet = metaData.isMember(jss::delivered_amount);
110 bool isSetUnavailable = false;
111 bool isSetAvailable = false;
112 if (isSet)
113 {
114 if (metaData[jss::delivered_amount] != "unavailable")
115 isSetAvailable = true;
116 else
117 isSetUnavailable = true;
118 }
119 if (isSetAvailable)
121 else if (isSetUnavailable)
123 else if (!isSet)
125
126 if (isSet)
127 {
128 if (metaData.isMember(sfDeliveredAmount.jsonName))
129 {
130 if (metaData[jss::delivered_amount] !=
131 metaData[sfDeliveredAmount.jsonName])
132 return false;
133 }
134 else
135 {
137 {
138 if (metaData[jss::delivered_amount] != t[jss::Amount])
139 return false;
140 }
141 else
142 {
143 if (metaData[jss::delivered_amount] != "unavailable")
144 return false;
145 }
146 }
147 }
148
149 if (metaData[sfTransactionResult.jsonName] != "tesSUCCESS")
150 {
151 if (isSet)
152 return false;
153 }
154 else
155 {
157 {
158 if (!isSetAvailable)
159 return false;
160 }
161 else
162 {
163 if (metaData.isMember(sfDeliveredAmount.jsonName))
164 {
165 if (!isSetAvailable)
166 return false;
167 }
168 else
169 {
170 if (!isSetUnavailable)
171 return false;
172 }
173 }
174 }
175 return true;
176 }
177};
178
180{
181 void
183 {
184 testcase("Ledger Request Subscribe DeliveredAmount");
185
186 using namespace test::jtx;
187 using namespace std::chrono_literals;
188
189 Account const alice("alice");
190 Account const bob("bob");
191 Account const carol("carol");
192 auto const gw = Account("gateway");
193 auto const USD = gw["USD"];
194
195 for (bool const afterSwitchTime : {true, false})
196 {
197 auto cfg = envconfig();
198 cfg->FEES.reference_fee = 10;
199 Env env(*this, std::move(cfg));
200 env.fund(XRP(10000), alice, bob, carol, gw);
201 env.trust(USD(1000), alice, bob, carol);
202 if (afterSwitchTime)
203 env.close(NetClock::time_point{446000000s});
204 else
205 env.close();
206
207 CheckDeliveredAmount checkDeliveredAmount{afterSwitchTime};
208 {
209 // add payments, but do no close until subscribed
210
211 // normal payments
212 env(pay(gw, alice, USD(50)));
213 checkDeliveredAmount.adjCountersSuccess();
214 env(pay(gw, alice, XRP(50)));
215 checkDeliveredAmount.adjCountersSuccess();
216
217 // partial payment
218 env(pay(gw, bob, USD(9999999)), txflags(tfPartialPayment));
219 checkDeliveredAmount.adjCountersPartialPayment();
220 env.require(balance(bob, USD(1000)));
221
222 // failed payment
223 env(pay(bob, carol, USD(9999999)), ter(tecPATH_PARTIAL));
224 checkDeliveredAmount.adjCountersFail();
225 env.require(balance(carol, USD(0)));
226 }
227
228 auto wsc = makeWSClient(env.app().config());
229
230 {
231 Json::Value stream;
232 // RPC subscribe to ledger stream
233 stream[jss::streams] = Json::arrayValue;
234 stream[jss::streams].append("ledger");
235 stream[jss::accounts] = Json::arrayValue;
236 stream[jss::accounts].append(toBase58(alice.id()));
237 stream[jss::accounts].append(toBase58(bob.id()));
238 stream[jss::accounts].append(toBase58(carol.id()));
239 auto jv = wsc->invoke("subscribe", stream);
240 if (wsc->version() == 2)
241 {
242 BEAST_EXPECT(
243 jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
244 BEAST_EXPECT(
245 jv.isMember(jss::ripplerpc) &&
246 jv[jss::ripplerpc] == "2.0");
247 BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
248 }
249 BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 3);
250 }
251 {
252 env.close();
253 // Check stream update
254 while (true)
255 {
256 auto const r = wsc->findMsg(1s, [&](auto const& jv) {
257 return jv[jss::ledger_index] == 4;
258 });
259 if (!r)
260 break;
261
262 if (!r->isMember(jss::transaction))
263 continue;
264
265 BEAST_EXPECT(checkDeliveredAmount.checkTxn(
266 (*r)[jss::transaction], (*r)[jss::meta]));
267 }
268 }
269 BEAST_EXPECT(checkDeliveredAmount.checkExpectedCounters());
270 }
271 }
272 void
274 {
275 testcase("Ledger Request RPC DeliveredAmount");
276
277 using namespace test::jtx;
278 using namespace std::chrono_literals;
279
280 Account const alice("alice");
281 Account const bob("bob");
282 Account const carol("carol");
283 auto const gw = Account("gateway");
284 auto const USD = gw["USD"];
285
286 for (bool const afterSwitchTime : {true, false})
287 {
288 auto cfg = envconfig();
289 cfg->FEES.reference_fee = 10;
290 Env env(*this, std::move(cfg));
291 env.fund(XRP(10000), alice, bob, carol, gw);
292 env.trust(USD(1000), alice, bob, carol);
293 if (afterSwitchTime)
294 env.close(NetClock::time_point{446000000s});
295 else
296 env.close();
297
298 CheckDeliveredAmount checkDeliveredAmount{afterSwitchTime};
299 // normal payments
300 env(pay(gw, alice, USD(50)));
301 checkDeliveredAmount.adjCountersSuccess();
302 env(pay(gw, alice, XRP(50)));
303 checkDeliveredAmount.adjCountersSuccess();
304
305 // partial payment
306 env(pay(gw, bob, USD(9999999)), txflags(tfPartialPayment));
307 checkDeliveredAmount.adjCountersPartialPayment();
308 env.require(balance(bob, USD(1000)));
309
310 // failed payment
311 env(pay(gw, carol, USD(9999999)), ter(tecPATH_PARTIAL));
312 checkDeliveredAmount.adjCountersFail();
313 env.require(balance(carol, USD(0)));
314
315 env.close();
316 std::string index;
317 Json::Value jvParams;
318 jvParams[jss::ledger_index] = 4u;
319 jvParams[jss::transactions] = true;
320 jvParams[jss::expand] = true;
321 auto const jtxn = env.rpc(
322 "json",
323 "ledger",
324 to_string(
325 jvParams))[jss::result][jss::ledger][jss::transactions];
326 for (auto const& t : jtxn)
327 BEAST_EXPECT(
328 checkDeliveredAmount.checkTxn(t, t[jss::metaData]));
329 BEAST_EXPECT(checkDeliveredAmount.checkExpectedCounters());
330 }
331 }
332
333 void
335 {
336 testcase("MPT DeliveredAmount");
337
338 using namespace jtx;
339 Account const alice("alice");
340 Account const carol("carol");
341 Account const bob("bob");
342 Env env{*this, features};
343
344 MPTTester mptAlice(
345 env, alice, {.holders = {bob, carol}, .close = false});
346
347 mptAlice.create(
348 {.transferFee = 25000,
349 .ownerCount = 1,
350 .holderCount = 0,
351 .flags = tfMPTCanTransfer});
352 auto const MPT = mptAlice["MPT"];
353
354 mptAlice.authorize({.account = bob});
355 mptAlice.authorize({.account = carol});
356
357 // issuer to holder
358 mptAlice.pay(alice, bob, 10000);
359
360 // holder to holder
361 env(pay(bob, carol, mptAlice.mpt(1000)), txflags(tfPartialPayment));
362 env.close();
363
364 // Get the hash for the most recent transaction.
365 std::string txHash{
366 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
367 Json::Value meta = env.rpc("tx", txHash)[jss::result][jss::meta];
368
369 if (features[fixMPTDeliveredAmount])
370 {
371 BEAST_EXPECT(
372 meta[sfDeliveredAmount.jsonName] ==
374 BEAST_EXPECT(
375 meta[jss::delivered_amount] ==
376 STAmount{MPT(800)}.getJson(JsonOptions::none));
377 }
378 else
379 {
380 BEAST_EXPECT(!meta.isMember(sfDeliveredAmount.jsonName));
381 BEAST_EXPECT(
382 meta[jss::delivered_amount] = Json::Value("unavailable"));
383 }
384
385 env(pay(bob, carol, MPT(1000)),
386 sendmax(MPT(1200)),
388 env.close();
389
390 txHash = env.tx()->getJson(JsonOptions::none)[jss::hash].asString();
391 meta = env.rpc("tx", txHash)[jss::result][jss::meta];
392
393 if (features[fixMPTDeliveredAmount])
394 {
395 BEAST_EXPECT(
396 meta[sfDeliveredAmount.jsonName] ==
398 BEAST_EXPECT(
399 meta[jss::delivered_amount] ==
400 STAmount{MPT(960)}.getJson(JsonOptions::none));
401 }
402 else
403 {
404 BEAST_EXPECT(!meta.isMember(sfDeliveredAmount.jsonName));
405 BEAST_EXPECT(
406 meta[jss::delivered_amount] = Json::Value("unavailable"));
407 }
408 }
409
410public:
411 void
412 run() override
413 {
414 using namespace test::jtx;
416
419
420 testMPTDeliveredAmountRPC(all - fixMPTDeliveredAmount);
422 }
423};
424
425BEAST_DEFINE_TESTSUITE(DeliveredAmount, rpc, ripple);
426
427} // namespace test
428} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
virtual Config & config()=0
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:795
void adjCounters(bool success, bool partial)
bool checkTxn(Json::Value const &t, Json::Value const &metaData)
void testMPTDeliveredAmountRPC(FeatureBitset features)
void run() override
Runs the suite.
Immutable cryptographic account descriptor.
Definition Account.h:39
AccountID id() const
Returns the Account ID.
Definition Account.h:111
A transaction testing environment.
Definition Env.h:121
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:547
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:121
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:320
Application & app()
Definition Env.h:261
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:791
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:87
Converts to MPT Issue or STAmount.
A balance matches.
Definition balance.h:39
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:35
Sets the SendMax on a JTx.
Definition sendmax.h:33
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Set the flags on a JTx.
Definition txflags.h:31
@ arrayValue
array value (ordered list)
Definition json_value.h:44
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:54
FeatureBitset testable_amendments()
Definition Env.h:74
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
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:323
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:152
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:108
@ tecPATH_PARTIAL
Definition TER.h:282
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630