xrpld
Loading...
Searching...
No Matches
Simulate_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/TestHelpers.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/batch.h>
6#include <test/jtx/credentials.h>
7#include <test/jtx/envconfig.h>
8#include <test/jtx/flags.h>
9#include <test/jtx/multisign.h>
10#include <test/jtx/noop.h>
11#include <test/jtx/pay.h>
12#include <test/jtx/regkey.h>
13#include <test/jtx/sig.h>
14#include <test/jtx/token.h>
15
16#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
17
18#include <xrpl/basics/Slice.h>
19#include <xrpl/basics/StringUtilities.h>
20#include <xrpl/basics/base_uint.h>
21#include <xrpl/basics/chrono.h>
22#include <xrpl/basics/strHex.h>
23#include <xrpl/beast/unit_test/suite.h>
24#include <xrpl/config/Constants.h>
25#include <xrpl/json/json_value.h>
26#include <xrpl/json/to_string.h>
27#include <xrpl/protocol/ErrorCodes.h>
28#include <xrpl/protocol/Indexes.h>
29#include <xrpl/protocol/SField.h>
30#include <xrpl/protocol/STBase.h>
31#include <xrpl/protocol/STParsedJSON.h>
32#include <xrpl/protocol/Serializer.h>
33#include <xrpl/protocol/TxFlags.h>
34#include <xrpl/protocol/XRPAmount.h>
35#include <xrpl/protocol/jss.h>
36
37#include <chrono>
38#include <cstdint>
39#include <functional>
40#include <memory>
41#include <optional>
42#include <string>
43
44namespace xrpl::test {
45
47{
48 void
50 json::Value const& result,
51 json::Value const& tx,
52 int const expectedSequence,
53 std::string const& expectedFee)
54 {
55 BEAST_EXPECT(result[jss::applied] == false);
56 BEAST_EXPECT(result.isMember(jss::engine_result));
57 BEAST_EXPECT(result.isMember(jss::engine_result_code));
58 BEAST_EXPECT(result.isMember(jss::engine_result_message));
59 BEAST_EXPECT(result.isMember(jss::tx_json) || result.isMember(jss::tx_blob));
60
61 json::Value txJson;
62 if (result.isMember(jss::tx_json))
63 {
64 txJson = result[jss::tx_json];
65 }
66 else
67 {
68 auto const unHexed = strUnHex(result[jss::tx_blob].asString());
69 SerialIter sitTrans(makeSlice(*unHexed)); // NOLINT(bugprone-unchecked-optional-access)
71 }
72 BEAST_EXPECT(txJson[jss::TransactionType] == tx[jss::TransactionType]);
73 BEAST_EXPECT(txJson[jss::Account] == tx[jss::Account]);
74 BEAST_EXPECT(txJson[jss::SigningPubKey] == tx.get(jss::SigningPubKey, ""));
75 BEAST_EXPECT(txJson[jss::TxnSignature] == tx.get(jss::TxnSignature, ""));
76 BEAST_EXPECT(txJson[jss::Fee] == tx.get(jss::Fee, expectedFee));
77 BEAST_EXPECT(txJson[jss::Sequence] == tx.get(jss::Sequence, expectedSequence));
78 }
79
80 void
82 json::Value const& result,
83 json::Value const& tx,
84 int const expectedSequence,
85 XRPAmount const& expectedFee)
86 {
88 result, tx, expectedSequence, expectedFee.jsonClipped().asString());
89 }
90
91 void
93 jtx::Env& env,
94 json::Value const& tx,
95 std::function<void(json::Value const&, json::Value const&)> const& validate,
96 bool testSerialized = true)
97 {
98 env.close();
99
100 json::Value params;
101 params[jss::tx_json] = tx;
102 validate(env.rpc("json", "simulate", to_string(params)), tx);
103
104 params[jss::binary] = true;
105 validate(env.rpc("json", "simulate", to_string(params)), tx);
106 validate(env.rpc("simulate", to_string(tx)), tx);
107 validate(env.rpc("simulate", to_string(tx), "binary"), tx);
108
109 if (testSerialized)
110 {
111 // This cannot be tested in the multisign autofill scenario
112 // It is technically not a valid STObject, so the following line
113 // will crash
114 STParsedJSONObject const parsed(std::string(jss::tx_json), tx);
115 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
116 auto const txBlob = strHex(parsed.object->getSerializer().peekData());
117 if (BEAST_EXPECT(parsed.object.has_value()))
118 {
119 json::Value params;
120 params[jss::tx_blob] = txBlob;
121 validate(env.rpc("json", "simulate", to_string(params)), tx);
122 params[jss::binary] = true;
123 validate(env.rpc("json", "simulate", to_string(params)), tx);
124 }
125 validate(env.rpc("simulate", txBlob), tx);
126 validate(env.rpc("simulate", txBlob, "binary"), tx);
127 }
128
129 BEAST_EXPECTS(env.current()->txCount() == 0, std::to_string(env.current()->txCount()));
130 }
131
132 void
134 jtx::Env& env,
135 json::Value const& tx,
136 std::function<void(
137 json::Value const&,
138 json::Value const&,
139 json::Value const&,
140 json::Value const&)> const& validate,
141 json::Value const& expectedMetadataKey,
142 json::Value const& expectedMetadataValue)
143 {
144 env.close();
145
146 json::Value params;
147 params[jss::tx_json] = tx;
148 validate(
149 env.rpc("json", "simulate", to_string(params)),
150 tx,
151 expectedMetadataKey,
152 expectedMetadataValue);
153 validate(
154 env.rpc("simulate", to_string(tx)), tx, expectedMetadataKey, expectedMetadataValue);
155
156 BEAST_EXPECTS(env.current()->txCount() == 0, std::to_string(env.current()->txCount()));
157 }
158
159 static json::Value
161 {
162 if (txResult.isMember(jss::meta_blob))
163 {
164 auto unHexed = strUnHex(txResult[jss::meta_blob].asString());
165 SerialIter sitTrans(makeSlice(*unHexed)); // NOLINT(bugprone-unchecked-optional-access)
167 }
168
169 return txResult[jss::meta];
170 }
171
172 void
174 {
175 testcase("Test parameter errors");
176
177 using namespace jtx;
178 Env env(*this);
179 Account const alice("alice");
180
181 {
182 // No params
184 auto const resp = env.rpc("json", "simulate", to_string(params));
185 BEAST_EXPECT(
186 resp[jss::result][jss::error_message] ==
187 "Neither `tx_blob` nor `tx_json` included.");
188 }
189 {
190 // Providing both `tx_json` and `tx_blob`
192 params[jss::tx_json] = json::ValueType::Object;
193 params[jss::tx_blob] = "1200";
194
195 auto const resp = env.rpc("json", "simulate", to_string(params));
196 BEAST_EXPECT(
197 resp[jss::result][jss::error_message] ==
198 "Can only include one of `tx_blob` and `tx_json`.");
199 }
200 {
201 // `binary` isn't a boolean
203 params[jss::tx_blob] = "1200";
204 params[jss::binary] = "100";
205 auto const resp = env.rpc("json", "simulate", to_string(params));
206 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'binary'.");
207 }
208 {
209 // Invalid `tx_blob`
211 params[jss::tx_blob] = "12";
212
213 auto const resp = env.rpc("json", "simulate", to_string(params));
214 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx_blob'.");
215 }
216 {
217 // Empty `tx_json`
219 params[jss::tx_json] = json::ValueType::Object;
220
221 auto const resp = env.rpc("json", "simulate", to_string(params));
222 BEAST_EXPECT(
223 resp[jss::result][jss::error_message] == "Missing field 'tx.TransactionType'.");
224 }
225 {
226 // No tx.Account
229 txJson[jss::TransactionType] = jss::Payment;
230 params[jss::tx_json] = txJson;
231
232 auto const resp = env.rpc("json", "simulate", to_string(params));
233 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Missing field 'tx.Account'.");
234 }
235 {
236 // Empty `tx_blob`
238 params[jss::tx_blob] = "";
239
240 auto const resp = env.rpc("json", "simulate", to_string(params));
241 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx_blob'.");
242 }
243 {
244 // Non-string `tx_blob`
245 json::Value params;
246 params[jss::tx_blob] = 1.1;
247
248 auto const resp = env.rpc("json", "simulate", to_string(params));
249 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx_blob'.");
250 }
251 {
252 // Non-object `tx_json`
254 params[jss::tx_json] = "";
255
256 auto const resp = env.rpc("json", "simulate", to_string(params));
257 BEAST_EXPECT(
258 resp[jss::result][jss::error_message] == "Invalid field 'tx_json', not object.");
259 }
260 {
261 // `seed` field included
263 params[jss::seed] = "random_data";
265 txJson[jss::TransactionType] = jss::AccountSet;
266 txJson[jss::Account] = env.master.human();
267 params[jss::tx_json] = txJson;
268 auto const resp = env.rpc("json", "simulate", to_string(params));
269 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'seed'.");
270 }
271 {
272 // `secret` field included
274 params[jss::secret] = "random_data";
276 txJson[jss::TransactionType] = jss::AccountSet;
277 txJson[jss::Account] = env.master.human();
278 params[jss::tx_json] = txJson;
279 auto const resp = env.rpc("json", "simulate", to_string(params));
280 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'secret'.");
281 }
282 {
283 // `seed_hex` field included
285 params[jss::seed_hex] = "random_data";
287 txJson[jss::TransactionType] = jss::AccountSet;
288 txJson[jss::Account] = env.master.human();
289 params[jss::tx_json] = txJson;
290 auto const resp = env.rpc("json", "simulate", to_string(params));
291 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'seed_hex'.");
292 }
293 {
294 // `passphrase` field included
296 params[jss::passphrase] = "random_data";
298 txJson[jss::TransactionType] = jss::AccountSet;
299 txJson[jss::Account] = env.master.human();
300 params[jss::tx_json] = txJson;
301 auto const resp = env.rpc("json", "simulate", to_string(params));
302 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'passphrase'.");
303 }
304 {
305 // Invalid transaction
308 txJson[jss::TransactionType] = jss::Payment;
309 txJson[jss::Account] = env.master.human();
310 params[jss::tx_json] = txJson;
311
312 auto const resp = env.rpc("json", "simulate", to_string(params));
313 BEAST_EXPECT(
314 resp[jss::result][jss::error_exception] ==
315 "Field 'Destination' is required but missing.");
316 }
317 {
318 // Bad account
319 json::Value params;
321 txJson[jss::TransactionType] = jss::AccountSet;
322 txJson[jss::Account] = "badAccount";
323 params[jss::tx_json] = txJson;
324
325 auto const resp = env.rpc("json", "simulate", to_string(params));
326 BEAST_EXPECTS(
327 resp[jss::result][jss::error] == "srcActMalformed",
328 resp[jss::result][jss::error].toStyledString());
329 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Account'.");
330 }
331 {
332 // Account doesn't exist for Sequence autofill
333 json::Value params;
335 txJson[jss::TransactionType] = jss::AccountSet;
336 txJson[jss::Account] = alice.human();
337 params[jss::tx_json] = txJson;
338
339 auto const resp = env.rpc("json", "simulate", to_string(params));
340 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Source account not found.");
341 }
342 {
343 // Invalid Signers field
344 json::Value params;
346 txJson[jss::TransactionType] = jss::AccountSet;
347 txJson[jss::Account] = env.master.human();
348 txJson[sfSigners] = "1";
349 params[jss::tx_json] = txJson;
350
351 auto const resp = env.rpc("json", "simulate", to_string(params));
352 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Signers'.");
353 }
354 {
355 // Invalid Signers field
356 json::Value params;
358 txJson[jss::TransactionType] = jss::AccountSet;
359 txJson[jss::Account] = env.master.human();
360 txJson[sfSigners] = json::ValueType::Array;
361 txJson[sfSigners].append("1");
362 params[jss::tx_json] = txJson;
363
364 auto const resp = env.rpc("json", "simulate", to_string(params));
365 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'tx.Signers[0]'.");
366 }
367 {
368 // Non-object SponsorSignature field
369 json::Value params;
371 txJson[jss::TransactionType] = jss::AccountSet;
372 txJson[jss::Account] = env.master.human();
373 txJson[sfSponsorSignature] = "";
374 params[jss::tx_json] = txJson;
375
376 auto const resp = env.rpc("json", "simulate", to_string(params));
377 BEAST_EXPECT(
378 resp[jss::result][jss::error_message] ==
379 "Invalid field 'SponsorSignature', not object.");
380 }
381 {
382 // Invalid SponsorSignature.Signers field
383 json::Value params;
385 txJson[jss::TransactionType] = jss::AccountSet;
386 txJson[jss::Account] = env.master.human();
387 json::Value sponsorSignature = json::ValueType::Object;
388 sponsorSignature[sfSigners] = "1";
389 txJson[sfSponsorSignature] = sponsorSignature;
390 params[jss::tx_json] = txJson;
391
392 auto const resp = env.rpc("json", "simulate", to_string(params));
393 BEAST_EXPECTS(
394 resp[jss::result][jss::error_message] ==
395 "Invalid field 'tx.SponsorSignature.Signers'.",
396 resp.toStyledString());
397 }
398 {
399 // Invalid transaction
400 json::Value params;
402 txJson[jss::TransactionType] = jss::AccountSet;
403 txJson[jss::Account] = env.master.human();
404 txJson["foo"] = "bar";
405 params[jss::tx_json] = txJson;
406
407 auto const resp = env.rpc("json", "simulate", to_string(params));
408 BEAST_EXPECT(
409 resp[jss::result][jss::error_message] == "Field 'tx_json.foo' is unknown.");
410 }
411 {
412 // non-`"binary"` second param for CLI
414 txJson[jss::TransactionType] = jss::AccountSet;
415 txJson[jss::Account] = alice.human();
416 auto const resp = env.rpc("simulate", to_string(txJson), "1");
417 BEAST_EXPECT(resp[jss::error_message] == "Invalid parameters.");
418 }
419 {
420 // Signed transaction
421 json::Value params;
423 txJson[jss::TransactionType] = jss::AccountSet;
424 txJson[jss::Account] = env.master.human();
425 txJson[jss::TxnSignature] = "1200ABCD";
426 params[jss::tx_json] = txJson;
427
428 auto const resp = env.rpc("json", "simulate", to_string(params));
429 BEAST_EXPECT(
430 resp[jss::result][jss::error_message] == "Transaction should not be signed.");
431 }
432 {
433 // Signed multisig transaction
434 json::Value params;
436 txJson[jss::TransactionType] = jss::AccountSet;
437 txJson[jss::Account] = env.master.human();
438 txJson[sfSigners] = json::ValueType::Array;
439 {
440 json::Value signer;
441 signer[jss::Account] = alice.human();
442 signer[jss::SigningPubKey] = alice.human();
443 signer[jss::TxnSignature] = "1200ABCD";
444 json::Value signerOuter;
445 signerOuter[sfSigner] = signer;
446 txJson[sfSigners].append(signerOuter);
447 }
448 params[jss::tx_json] = txJson;
449
450 auto const resp = env.rpc("json", "simulate", to_string(params));
451 BEAST_EXPECT(
452 resp[jss::result][jss::error_message] == "Transaction should not be signed.");
453 }
454 {
455 // tfInnerBatchTxn flag on top-level transaction
456 json::Value params;
458 txJson[jss::TransactionType] = jss::AccountSet;
459 txJson[jss::Account] = env.master.human();
460 txJson[jss::Flags] = tfInnerBatchTxn;
461 params[jss::tx_json] = txJson;
462
463 auto const resp = env.rpc("json", "simulate", to_string(params));
464 BEAST_EXPECT(
465 resp[jss::result][jss::error_message] ==
466 "tfInnerBatchTxn flag is not allowed on top-level transactions.");
467 }
468 }
469
470 void
472 {
473 testcase("Fee failure");
474
475 using namespace jtx;
476
477 Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
479 return cfg;
480 }));
481
482 Account const alice{"alice"};
483 env.fund(XRP(1000000), alice);
484 env.close();
485
486 // fill queue
487 auto metrics = env.app().getTxQ().getMetrics(*env.current());
488 for (int i = metrics.txInLedger; i <= metrics.txPerLedger; ++i)
489 env(noop(alice));
490
491 {
492 json::Value params;
493 params[jss::tx_json] = noop(alice);
494
495 auto const resp = env.rpc("json", "simulate", to_string(params));
496 auto const result = resp[jss::result];
497 if (BEAST_EXPECT(result.isMember(jss::error)))
498 {
499 BEAST_EXPECT(result[jss::error] == "highFee");
500 BEAST_EXPECT(result[jss::error_code] == RpcHighFee);
501 }
502 }
503 }
504
505 void
507 {
508 testcase("Invalid transaction type");
509
510 using namespace jtx;
511
512 Env env(*this);
513
514 Account const alice{"alice"};
515 Account const bob{"bob"};
516 env.fund(XRP(1000000), alice, bob);
517 env.close();
518
519 auto const batchFee = batch::calcBatchFee(env, 0, 2);
520 auto const seq = env.seq(alice);
521 auto jt = env.jtnofill(
522 batch::outer(alice, env.seq(alice), batchFee, tfAllOrNothing),
523 batch::Inner(pay(alice, bob, XRP(10)), seq + 1),
524 batch::Inner(pay(alice, bob, XRP(10)), seq + 2));
525
526 jt.jv.removeMember(jss::TxnSignature);
527 json::Value params;
528 params[jss::tx_json] = jt.jv;
529 auto const resp = env.rpc("json", "simulate", to_string(params));
530 BEAST_EXPECT(resp[jss::result][jss::error] == "notImpl");
531 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Not implemented.");
532 }
533
534 void
536 {
537 testcase("Successful transaction");
538
539 using namespace jtx;
540 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
541 cfg->networkId = 0;
542 return cfg;
543 })};
544 static auto const kNewDomain = "123ABC";
545
546 {
547 auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
548 auto result = resp[jss::result];
549 checkBasicReturnValidity(result, tx, 1, env.current()->fees().base);
550
551 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
552 BEAST_EXPECT(result[jss::engine_result_code] == 0);
553 BEAST_EXPECT(
554 result[jss::engine_result_message] ==
555 "The simulated transaction would have been applied.");
556
557 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
558 {
559 json::Value const metadata = getJsonMetadata(result);
560
561 if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName)))
562 {
563 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 1);
564 auto node = metadata[sfAffectedNodes.jsonName][0u];
565 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
566 {
567 auto modifiedNode = node[sfModifiedNode];
568 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
569 auto finalFields = modifiedNode[sfFinalFields];
570 BEAST_EXPECT(finalFields[sfDomain] == kNewDomain);
571 }
572 }
573 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
574 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
575 }
576 };
577
578 json::Value tx;
579
580 tx[jss::Account] = env.master.human();
581 tx[jss::TransactionType] = jss::AccountSet;
582 tx[sfDomain] = kNewDomain;
583
584 // test with autofill
585 testTx(env, tx, validateOutput);
586
587 tx[sfSigningPubKey] = "";
588 tx[sfTxnSignature] = "";
589 tx[sfSequence] = 1;
590 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
591
592 // test without autofill
593 testTx(env, tx, validateOutput);
594 }
595
596 {
597 // autofill sponsor signature
598
599 auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
600 auto result = resp[jss::result];
602 result, tx, env.seq(env.master), env.current()->fees().base);
603
604 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
605 BEAST_EXPECT(result[jss::engine_result_code] == 0);
606 BEAST_EXPECT(
607 result[jss::engine_result_message] ==
608 "The simulated transaction would have been applied.");
609
610 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
611 {
612 json::Value const metadata = getJsonMetadata(result);
613
614 if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName)))
615 {
616 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 2);
617
618 auto node = metadata[sfAffectedNodes.jsonName][0u];
619 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
620 {
621 auto modifiedNode = node[sfModifiedNode];
622 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
623 auto previousFields = modifiedNode[sfPreviousFields];
624 BEAST_EXPECT(!previousFields.isMember(sfBalance.jsonName));
625 }
626
627 auto node2 = metadata[sfAffectedNodes.jsonName][1u];
628 if (BEAST_EXPECT(node2.isMember(sfModifiedNode.jsonName)))
629 {
630 auto modifiedNode = node2[sfModifiedNode];
631 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
632
633 auto previousFields = modifiedNode[sfPreviousFields];
634 BEAST_EXPECT(previousFields.isMember(sfBalance.jsonName));
635 }
636 }
637 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
638 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
639 }
640 };
641
642 Account const sponsor("sponsor");
643 env.fund(XRP(10000), sponsor);
644 env.close();
645
646 json::Value tx;
647
648 tx[jss::Account] = env.master.human();
649 tx[jss::TransactionType] = jss::AccountSet;
650 tx[sfDomain.jsonName] = kNewDomain;
651 tx[sfSponsor.jsonName] = sponsor.human();
652 tx[sfSponsorFlags.jsonName] = spfSponsorFee;
653 tx[sfSponsorSignature.jsonName] = json::ValueType::Object;
654
655 // test with autofill
656 testTx(env, tx, validateOutput);
657
658 tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] = "";
659 tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] = "";
660
661 // test without autofill
662 testTx(env, tx, validateOutput);
663 }
664 }
665
666 void
668 {
669 testcase("Transaction non-tec failure");
670
671 using namespace jtx;
672 Env env(*this);
673 Account const alice("alice");
674
675 {
676 std::function<void(json::Value const&, json::Value const&)> const& testSimulation =
677 [&](json::Value const& resp, json::Value const& tx) {
678 auto result = resp[jss::result];
679 checkBasicReturnValidity(result, tx, 1, env.current()->fees().base);
680
681 BEAST_EXPECT(result[jss::engine_result] == "temBAD_AMOUNT");
682 BEAST_EXPECT(result[jss::engine_result_code] == -298);
683 BEAST_EXPECT(result[jss::engine_result_message] == "Malformed: Bad amount.");
684
685 BEAST_EXPECT(!result.isMember(jss::meta) && !result.isMember(jss::meta_blob));
686 };
687
688 json::Value tx;
689
690 tx[jss::Account] = env.master.human();
691 tx[jss::TransactionType] = jss::Payment;
692 tx[sfDestination] = alice.human();
693 tx[sfAmount] = "0"; // invalid amount
694
695 // test with autofill
696 testTx(env, tx, testSimulation);
697
698 tx[sfSigningPubKey] = "";
699 tx[sfTxnSignature] = "";
700 tx[sfSequence] = 1;
701 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
702
703 // test without autofill
704 testTx(env, tx, testSimulation);
705 }
706 }
707
708 void
710 {
711 testcase("Transaction tec failure");
712
713 using namespace jtx;
714 Env env(*this);
715 Account const alice("alice");
716
717 {
718 std::function<void(json::Value const&, json::Value const&)> const& testSimulation =
719 [&](json::Value const& resp, json::Value const& tx) {
720 auto result = resp[jss::result];
721 checkBasicReturnValidity(result, tx, 1, env.current()->fees().base);
722
723 BEAST_EXPECT(result[jss::engine_result] == "tecNO_DST_INSUF_XRP");
724 BEAST_EXPECT(result[jss::engine_result_code] == 125);
725 BEAST_EXPECT(
726 result[jss::engine_result_message] ==
727 "Destination does not exist. Too little XRP sent to "
728 "create "
729 "it.");
730
731 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
732 {
733 json::Value const metadata = getJsonMetadata(result);
734
735 if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName)))
736 {
737 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 1);
738 auto node = metadata[sfAffectedNodes.jsonName][0u];
739 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
740 {
741 auto modifiedNode = node[sfModifiedNode];
742 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
743 auto finalFields = modifiedNode[sfFinalFields];
744 BEAST_EXPECT(
745 finalFields[sfBalance] ==
747 100'000'000'000'000'000 -
748 env.current()->fees().base.drops()));
749 }
750 }
751 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
752 BEAST_EXPECT(
753 metadata[sfTransactionResult.jsonName] == "tecNO_DST_INSUF_XRP");
754 }
755 };
756
757 json::Value tx;
758
759 tx[jss::Account] = env.master.human();
760 tx[jss::TransactionType] = jss::Payment;
761 tx[sfDestination] = alice.human();
762 tx[sfAmount] = "1"; // not enough to create an account
763
764 // test with autofill
765 testTx(env, tx, testSimulation);
766
767 tx[sfSigningPubKey] = "";
768 tx[sfTxnSignature] = "";
769 tx[sfSequence] = 1;
770 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
771
772 // test without autofill
773 testTx(env, tx, testSimulation);
774 }
775 }
776
777 void
779 {
780 testcase("Successful multi-signed transaction");
781
782 using namespace jtx;
783 Env env(*this);
784 static auto const kNewDomain = "123ABC";
785 Account const alice("alice");
786 Account const becky("becky");
787 Account const carol("carol");
788 env.fund(XRP(10000), alice);
789 env.close();
790
791 // set up valid multisign
792 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
793 env.close();
794
795 {
796 auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
797 auto result = resp[jss::result];
799 result,
800 tx,
801 env.seq(alice),
802 tx.isMember(jss::Signers) ? env.current()->fees().base * 2
803 : env.current()->fees().base);
804
805 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
806 BEAST_EXPECT(result[jss::engine_result_code] == 0);
807 BEAST_EXPECT(
808 result[jss::engine_result_message] ==
809 "The simulated transaction would have been applied.");
810
811 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
812 {
813 json::Value const metadata = getJsonMetadata(result);
814
815 if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName)))
816 {
817 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 1);
818 auto node = metadata[sfAffectedNodes.jsonName][0u];
819 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
820 {
821 auto modifiedNode = node[sfModifiedNode];
822 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
823 auto finalFields = modifiedNode[sfFinalFields];
824 BEAST_EXPECT(finalFields[sfDomain] == kNewDomain);
825 }
826 }
827 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
828 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
829 }
830 };
831
832 json::Value tx;
833
834 tx[jss::Account] = alice.human();
835 tx[jss::TransactionType] = jss::AccountSet;
836 tx[sfDomain] = kNewDomain;
837
838 // test with autofill
839 testTx(env, tx, validateOutput, false);
840
841 tx[sfSigners] = json::ValueType::Array;
842 {
843 json::Value signer;
844 signer[jss::Account] = becky.human();
845 json::Value signerOuter;
846 signerOuter[sfSigner] = signer;
847 tx[sfSigners].append(signerOuter);
848 }
849
850 // test with just signer accounts
851 testTx(env, tx, validateOutput, false);
852
853 tx[sfSigningPubKey] = "";
854 tx[sfTxnSignature] = "";
855 tx[sfSequence] = env.seq(alice);
856 // transaction requires a non-base fee
857 tx[sfFee] = (env.current()->fees().base * 2).jsonClipped().asString();
858 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] = "";
859 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
860
861 // test without autofill
862 testTx(env, tx, validateOutput);
863 }
864 }
865
866 void
868 {
869 testcase("Successful sponsored multi-signed transaction");
870
871 using namespace jtx;
872 Env env(*this);
873 Account const sponsor("sponsor");
874 Account const signer("signer");
875 env.fund(XRP(10000), sponsor, signer);
876 env.close();
877
878 env(signers(sponsor, 1, {{signer, 1}}));
879 env.close();
880
881 auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
882 auto const result = resp[jss::result];
883 // Verifies Fee autofill counts nested sponsor-signature signers.
884 auto const expectedFee = env.current()->fees().base * 2;
885 checkBasicReturnValidity(result, tx, env.seq(env.master), expectedFee);
886
887 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
888 BEAST_EXPECT(result[jss::engine_result_code] == 0);
889 BEAST_EXPECT(
890 result[jss::engine_result_message] ==
891 "The simulated transaction would have been applied.");
892
893 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
894 {
895 json::Value const metadata = getJsonMetadata(result);
896 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
897 }
898 };
899
900 json::Value tx;
901 tx[jss::Account] = env.master.human();
902 tx[jss::TransactionType] = jss::AccountSet;
903 tx[sfDomain] = "123ABC";
904 tx[sfSponsor.jsonName] = sponsor.human();
905 tx[sfSponsorFlags.jsonName] = spfSponsorFee;
906 tx[sfSponsorSignature.jsonName] = json::ValueType::Object;
907 tx[sfSponsorSignature.jsonName][sfSigners.jsonName] = json::ValueType::Array;
908
909 json::Value signerObj;
910 signerObj[sfSigner][jss::Account] = signer.human();
911 tx[sfSponsorSignature.jsonName][sfSigners.jsonName].append(signerObj);
912
913 // Leave Fee unset so simulate must autofill it after sponsor signer normalization.
914 BEAST_EXPECT(!tx.isMember(jss::Fee));
915 testTx(env, tx, validateOutput, false);
916 }
917
918 void
920 {
921 testcase("Transaction with a key-related failure");
922
923 using namespace jtx;
924 Env env(*this);
925 static auto const kNewDomain = "123ABC";
926 Account const alice{"alice"};
927 env(regkey(env.master, alice));
928 env(fset(env.master, asfDisableMaster), Sig(env.master));
929 env.close();
930
931 {
932 std::function<void(json::Value const&, json::Value const&)> const& testSimulation =
933 [&](json::Value const& resp, json::Value const& tx) {
934 auto result = resp[jss::result];
936 result, tx, env.seq(env.master), env.current()->fees().base);
937
938 BEAST_EXPECT(result[jss::engine_result] == "tefMASTER_DISABLED");
939 BEAST_EXPECT(result[jss::engine_result_code] == -188);
940 BEAST_EXPECT(result[jss::engine_result_message] == "Master key is disabled.");
941
942 BEAST_EXPECT(!result.isMember(jss::meta) && !result.isMember(jss::meta_blob));
943 };
944
945 json::Value tx;
946
947 tx[jss::Account] = env.master.human();
948 tx[jss::TransactionType] = jss::AccountSet;
949 tx[sfDomain] = kNewDomain;
950 // master key is disabled, so this is invalid
951 tx[jss::SigningPubKey] = strHex(env.master.pk().slice());
952
953 // test with autofill
954 testTx(env, tx, testSimulation);
955
956 tx[sfTxnSignature] = "";
957 tx[sfSequence] = env.seq(env.master);
958 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
959
960 // test without autofill
961 testTx(env, tx, testSimulation);
962 }
963 }
964
965 void
967 {
968 testcase(
969 "Transaction with both single-signing SigningPubKey and "
970 "multi-signing Signers");
971
972 using namespace jtx;
973 Env env(*this);
974 static auto const kNewDomain = "123ABC";
975 Account const alice("alice");
976 Account const becky("becky");
977 Account const carol("carol");
978 env.fund(XRP(10000), alice);
979 env.close();
980
981 // set up valid multisign
982 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
983 env.close();
984
985 {
986 std::function<void(json::Value const&, json::Value const&)> const& testSimulation =
987 [&](json::Value const& resp, json::Value const& tx) {
988 auto result = resp[jss::result];
990 result, tx, env.seq(env.master), env.current()->fees().base * 2);
991
992 BEAST_EXPECT(result[jss::engine_result] == "temINVALID");
993 BEAST_EXPECT(result[jss::engine_result_code] == -277);
994 BEAST_EXPECT(
995 result[jss::engine_result_message] == "The transaction is ill-formed.");
996
997 BEAST_EXPECT(!result.isMember(jss::meta) && !result.isMember(jss::meta_blob));
998 };
999
1000 json::Value tx;
1001
1002 tx[jss::Account] = env.master.human();
1003 tx[jss::TransactionType] = jss::AccountSet;
1004 tx[sfDomain] = kNewDomain;
1005 // master key is disabled, so this is invalid
1006 tx[jss::SigningPubKey] = strHex(env.master.pk().slice());
1007 tx[sfSigners] = json::ValueType::Array;
1008 {
1009 json::Value signer;
1010 signer[jss::Account] = becky.human();
1011 json::Value signerOuter;
1012 signerOuter[sfSigner] = signer;
1013 tx[sfSigners].append(signerOuter);
1014 }
1015
1016 // test with autofill
1017 testTx(env, tx, testSimulation, false);
1018
1019 tx[sfTxnSignature] = "";
1020 tx[sfSequence] = env.seq(env.master);
1021 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1022 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] = strHex(becky.pk().slice());
1023 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
1024
1025 // test without autofill
1026 testTx(env, tx, testSimulation);
1027 }
1028 }
1029
1030 void
1032 {
1033 testcase("Multi-signed transaction with a bad public key");
1034
1035 using namespace jtx;
1036 Env env(*this);
1037 static auto const kNewDomain = "123ABC";
1038 Account const alice("alice");
1039 Account const becky("becky");
1040 Account const carol("carol");
1041 Account const dylan("dylan");
1042 env.fund(XRP(10000), alice);
1043 env.close();
1044
1045 // set up valid multisign
1046 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
1047
1048 {
1049 auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
1050 auto result = resp[jss::result];
1052 result, tx, env.seq(alice), env.current()->fees().base * 2);
1053
1054 BEAST_EXPECTS(
1055 result[jss::engine_result] == "tefBAD_SIGNATURE",
1056 result[jss::engine_result].toStyledString());
1057 BEAST_EXPECT(result[jss::engine_result_code] == -186);
1058 BEAST_EXPECT(
1059 result[jss::engine_result_message] ==
1060 "A signature is provided for a non-signer.");
1061
1062 BEAST_EXPECT(!result.isMember(jss::meta) && !result.isMember(jss::meta_blob));
1063 };
1064
1065 json::Value tx;
1066
1067 tx[jss::Account] = alice.human();
1068 tx[jss::TransactionType] = jss::AccountSet;
1069 tx[sfDomain] = kNewDomain;
1070 tx[sfSigners] = json::ValueType::Array;
1071 {
1072 json::Value signer;
1073 signer[jss::Account] = becky.human();
1074 signer[jss::SigningPubKey] = strHex(dylan.pk().slice());
1075 json::Value signerOuter;
1076 signerOuter[sfSigner] = signer;
1077 tx[sfSigners].append(signerOuter);
1078 }
1079
1080 // test with autofill
1081 testTx(env, tx, validateOutput, false);
1082
1083 tx[sfSigningPubKey] = "";
1084 tx[sfTxnSignature] = "";
1085 tx[sfSequence] = env.seq(alice);
1086 // transaction requires a non-base fee
1087 tx[sfFee] = (env.current()->fees().base * 2).jsonClipped().asString();
1088 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
1089
1090 // test without autofill
1091 testTx(env, tx, validateOutput);
1092 }
1093 }
1094
1095 void
1097 {
1098 testcase("Credentials aren't actually deleted on `tecEXPIRED`");
1099
1100 // scenario setup
1101
1102 using namespace jtx;
1103 Env env(*this);
1104
1105 Account const subject{"subject"};
1106 Account const issuer{"issuer"};
1107
1108 env.fund(XRP(10000), subject, issuer);
1109 env.close();
1110
1111 auto const credType = "123ABC";
1112
1113 auto jv = credentials::create(subject, issuer, credType);
1114 uint32_t const t = env.current()->header().parentCloseTime.time_since_epoch().count();
1115 jv[sfExpiration.jsonName] = t;
1116 env(jv);
1117 env.close();
1118
1119 {
1120 auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
1121 auto result = resp[jss::result];
1122 checkBasicReturnValidity(result, tx, env.seq(subject), env.current()->fees().base);
1123
1124 BEAST_EXPECT(result[jss::engine_result] == "tecEXPIRED");
1125 BEAST_EXPECT(result[jss::engine_result_code] == 148);
1126 BEAST_EXPECT(result[jss::engine_result_message] == "Expiration time is passed.");
1127
1128 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
1129 {
1130 json::Value const metadata = getJsonMetadata(result);
1131
1132 if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName)))
1133 {
1134 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 5);
1135
1136 try
1137 {
1138 bool found = false;
1139 for (auto const& node : metadata[sfAffectedNodes.jsonName])
1140 {
1141 if (node.isMember(sfDeletedNode.jsonName) &&
1142 node[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName]
1143 .asString() == "Credential")
1144 {
1145 auto const deleted =
1146 node[sfDeletedNode.jsonName][sfFinalFields.jsonName];
1147 found = deleted[jss::Issuer] == issuer.human() &&
1148 deleted[jss::Subject] == subject.human() &&
1149 deleted["CredentialType"] ==
1150 strHex(std::string_view(credType));
1151 break;
1152 }
1153 }
1154 BEAST_EXPECT(found);
1155 }
1156 catch (...)
1157 {
1158 fail();
1159 }
1160 }
1161 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1162 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tecEXPIRED");
1163 }
1164 };
1165
1166 json::Value tx = credentials::accept(subject, issuer, credType);
1167
1168 // test with autofill
1169 testTx(env, tx, validateOutput);
1170
1171 tx[sfSigningPubKey] = "";
1172 tx[sfTxnSignature] = "";
1173 tx[sfSequence] = env.seq(subject);
1174 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1175
1176 // test without autofill
1177 testTx(env, tx, validateOutput);
1178 }
1179
1180 // check that expired credentials weren't deleted
1181 auto const jle = credentials::ledgerEntry(env, subject, issuer, credType);
1182 BEAST_EXPECT(
1183 jle.isObject() && jle.isMember(jss::result) && !jle[jss::result].isMember(jss::error) &&
1184 jle[jss::result].isMember(jss::node) &&
1185 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
1186 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
1187 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
1188 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
1189 jle[jss::result][jss::node]["CredentialType"] == strHex(std::string_view(credType)));
1190
1191 BEAST_EXPECT(ownerCount(env, issuer) == 1);
1192 BEAST_EXPECT(ownerCount(env, subject) == 0);
1193 }
1194
1195 void
1197 {
1198 testcase("Successful transaction with a custom network ID");
1199
1200 using namespace jtx;
1201 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
1202 cfg->networkId = 1025;
1203 return cfg;
1204 })};
1205 static auto const kNewDomain = "123ABC";
1206
1207 {
1208 auto validateOutput = [&](json::Value const& resp, json::Value const& tx) {
1209 auto result = resp[jss::result];
1210 checkBasicReturnValidity(result, tx, 1, env.current()->fees().base);
1211
1212 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
1213 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1214 BEAST_EXPECT(
1215 result[jss::engine_result_message] ==
1216 "The simulated transaction would have been applied.");
1217
1218 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
1219 {
1220 json::Value const metadata = getJsonMetadata(result);
1221
1222 if (BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName)))
1223 {
1224 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].size() == 1);
1225 auto node = metadata[sfAffectedNodes.jsonName][0u];
1226 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
1227 {
1228 auto modifiedNode = node[sfModifiedNode];
1229 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] == "AccountRoot");
1230 auto finalFields = modifiedNode[sfFinalFields];
1231 BEAST_EXPECT(finalFields[sfDomain] == kNewDomain);
1232 }
1233 }
1234 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1235 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
1236 }
1237 };
1238
1239 json::Value tx;
1240
1241 tx[jss::Account] = env.master.human();
1242 tx[jss::TransactionType] = jss::AccountSet;
1243 tx[sfDomain] = kNewDomain;
1244
1245 // test with autofill
1246 testTx(env, tx, validateOutput);
1247
1248 tx[sfSigningPubKey] = "";
1249 tx[sfTxnSignature] = "";
1250 tx[sfSequence] = 1;
1251 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1252 tx[sfNetworkID] = 1025;
1253
1254 // test without autofill
1255 testTx(env, tx, validateOutput);
1256 }
1257 }
1258
1259 void
1261 {
1262 testcase("Successful transaction with additional metadata");
1263
1264 using namespace jtx;
1265 using namespace std::chrono_literals;
1266 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
1267 cfg->networkId = 1025;
1268 return cfg;
1269 })};
1270
1271 Account const alice("alice");
1272 Account const bob("bob");
1273
1274 env.fund(XRP(10000), alice, bob);
1275 env.close();
1276 // deliver_amount is unavailable in the metadata before 2014-02-01
1277 // so proceed to 2014-02-01
1278 env.close(NetClock::time_point{446000000s});
1279
1280 {
1281 auto validateOutput = [&](json::Value const& resp,
1282 json::Value const& tx,
1283 json::Value const& expectedMetadataKey,
1284 json::Value const& expectedMetadataValue) {
1285 auto result = resp[jss::result];
1286
1287 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
1288 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1289 BEAST_EXPECT(
1290 result[jss::engine_result_message] ==
1291 "The simulated transaction would have been applied.");
1292
1293 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
1294 {
1295 json::Value const metadata = getJsonMetadata(result);
1296
1297 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1298 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
1299 BEAST_EXPECT(metadata.isMember(expectedMetadataKey.asString()));
1300 BEAST_EXPECT(metadata[expectedMetadataKey.asString()] == expectedMetadataValue);
1301 }
1302 };
1303
1304 {
1305 json::Value tx;
1306 tx[jss::Account] = alice.human();
1307 tx[jss::TransactionType] = jss::Payment;
1308 tx[sfDestination] = bob.human();
1309 tx[sfAmount] = "100";
1310
1311 // test delivered amount
1312 testTxJsonMetadataField(env, tx, validateOutput, jss::delivered_amount, "100");
1313 }
1314
1315 {
1316 json::Value tx;
1317 tx[jss::Account] = alice.human();
1318 tx[jss::TransactionType] = jss::NFTokenMint;
1319 tx[sfNFTokenTaxon] = 1;
1320
1321 json::Value const nftokenId = to_string(token::getNextID(env, alice, 1));
1322 // test nft synthetic
1323 testTxJsonMetadataField(env, tx, validateOutput, jss::nftoken_id, nftokenId);
1324 }
1325
1326 {
1327 json::Value tx;
1328 tx[jss::Account] = alice.human();
1329 tx[jss::TransactionType] = jss::MPTokenIssuanceCreate;
1330
1331 json::Value const mptIssuanceId = to_string(makeMptID(env.seq(alice), alice));
1332 // test mpt issuance id
1334 env, tx, validateOutput, jss::mpt_issuance_id, mptIssuanceId);
1335 }
1336 }
1337 }
1338
1339public:
1340 void
1358};
1359
1361
1362} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:554
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Represents a JSON value.
Definition json_value.h:117
Value removeMember(char const *key)
Remove and return the named member.
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
std::chrono::time_point< NetClock > time_point
Definition chrono.h:48
Slice slice() const noexcept
Definition PublicKey.h:115
json::Value getJson(JsonOptions=JsonOptions::Values::None) const override
Definition STObject.cpp:845
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
virtual TxQ & getTxQ()=0
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition TxQ.cpp:1750
json::Value jsonClipped() const
Definition XRPAmount.h:210
void testSuccessfulSponsoredTransactionMultisigned()
void checkBasicReturnValidity(json::Value const &result, json::Value const &tx, int const expectedSequence, std::string const &expectedFee)
static json::Value getJsonMetadata(json::Value txResult)
void run() override
Runs the suite.
void testTx(jtx::Env &env, json::Value const &tx, std::function< void(json::Value const &, json::Value const &)> const &validate, bool testSerialized=true)
void checkBasicReturnValidity(json::Value const &result, json::Value const &tx, int const expectedSequence, XRPAmount const &expectedFee)
void testInvalidSingleAndMultiSigningTransaction()
void testTxJsonMetadataField(jtx::Env &env, json::Value const &tx, std::function< void(json::Value const &, json::Value const &, json::Value const &, json::Value const &)> const &validate, json::Value const &expectedMetadataKey, json::Value const &expectedMetadataValue)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
std::string const & human() const
Returns the human readable public key.
PublicKey const & pk() const
Return the public key.
Definition jtx/Account.h:84
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
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:323
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:302
Account const & master
Definition Env.h:165
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:1056
JTx jtnofill(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:735
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
Set the regular signature on a JTx.
Definition sig.h:19
Adds an inner Batch transaction to a JTx and autofills it.
Definition batch.h:66
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
API version numbers used in later API versions.
Definition ApiVersion.h:36
json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Build an outer Batch transaction JSON object.
Definition batch.cpp:53
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate the expected outer Batch transaction fee.
Definition batch.cpp:35
json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:29
json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:16
json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:56
uint256 getNextID(jtx::Env const &env, jtx::Account const &issuer, std::uint32_t nfTokenTaxon, std::uint16_t flags, std::uint16_t xferFee)
Get the next NFTokenID that will be issued.
Definition token.cpp:57
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
json::Value regkey(Account const &account, DisabledT)
Disable the regular key.
Definition regkey.cpp:13
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
json::Value noop(Account const &account)
The null transaction.
Definition noop.h:14
std::uint32_t ownerCount(Env const &env, Account const &account)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:37
json::Value signers(Account const &account, std::uint32_t quorum, std::vector< Signer > const &v)
Definition multisign.cpp:31
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr FlagValue spfSponsorFee
Definition TxFlags.h:459
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:44
@ RpcHighFee
Definition ErrorCodes.h:41
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
SField const sfGeneric
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:184
T ref(T... args)
static constexpr auto kMinimumTxnInLedgerStandalone
Definition Constants.h:133
static constexpr auto kTransactionQueue
Definition Constants.h:66
json::Value jv
Definition JTx.h:28
T to_string(T... args)