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