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