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>
16#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
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>
52 int const expectedSequence,
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));
64 txJson = result[jss::tx_json];
68 auto const unHexed =
strUnHex(result[jss::tx_blob].asString());
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));
84 int const expectedSequence,
96 bool testSerialized =
true)
101 params[jss::tx_json] = tx;
102 validate(env.
rpc(
"json",
"simulate",
to_string(params)), tx);
104 params[jss::binary] =
true;
105 validate(env.
rpc(
"json",
"simulate",
to_string(params)), tx);
107 validate(env.
rpc(
"simulate",
to_string(tx),
"binary"), tx);
116 auto const txBlob =
strHex(parsed.
object->getSerializer().peekData());
117 if (BEAST_EXPECT(parsed.
object.has_value()))
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);
125 validate(env.
rpc(
"simulate", txBlob), tx);
126 validate(env.
rpc(
"simulate", txBlob,
"binary"), tx);
147 params[jss::tx_json] = tx;
152 expectedMetadataValue);
154 env.
rpc(
"simulate",
to_string(tx)), tx, expectedMetadataKey, expectedMetadataValue);
162 if (txResult.
isMember(jss::meta_blob))
164 auto unHexed =
strUnHex(txResult[jss::meta_blob].asString());
169 return txResult[jss::meta];
184 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
186 resp[jss::result][jss::error_message] ==
187 "Neither `tx_blob` nor `tx_json` included.");
193 params[jss::tx_blob] =
"1200";
195 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
197 resp[jss::result][jss::error_message] ==
198 "Can only include one of `tx_blob` and `tx_json`.");
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'.");
211 params[jss::tx_blob] =
"12";
213 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
214 BEAST_EXPECT(resp[jss::result][jss::error_message] ==
"Invalid field 'tx_blob'.");
221 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
223 resp[jss::result][jss::error_message] ==
"Missing field 'tx.TransactionType'.");
229 txJson[jss::TransactionType] = jss::Payment;
230 params[jss::tx_json] = txJson;
232 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
233 BEAST_EXPECT(resp[jss::result][jss::error_message] ==
"Missing field 'tx.Account'.");
238 params[jss::tx_blob] =
"";
240 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
241 BEAST_EXPECT(resp[jss::result][jss::error_message] ==
"Invalid field 'tx_blob'.");
246 params[jss::tx_blob] = 1.1;
248 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
249 BEAST_EXPECT(resp[jss::result][jss::error_message] ==
"Invalid field 'tx_blob'.");
254 params[jss::tx_json] =
"";
256 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
258 resp[jss::result][jss::error_message] ==
"Invalid field 'tx_json', not object.");
263 params[jss::seed] =
"random_data";
265 txJson[jss::TransactionType] = jss::AccountSet;
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'.");
274 params[jss::secret] =
"random_data";
276 txJson[jss::TransactionType] = jss::AccountSet;
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'.");
285 params[jss::seed_hex] =
"random_data";
287 txJson[jss::TransactionType] = jss::AccountSet;
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'.");
296 params[jss::passphrase] =
"random_data";
298 txJson[jss::TransactionType] = jss::AccountSet;
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'.");
308 txJson[jss::TransactionType] = jss::Payment;
310 params[jss::tx_json] = txJson;
312 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
314 resp[jss::result][jss::error_exception] ==
315 "Field 'Destination' is required but missing.");
321 txJson[jss::TransactionType] = jss::AccountSet;
322 txJson[jss::Account] =
"badAccount";
323 params[jss::tx_json] = txJson;
325 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
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'.");
335 txJson[jss::TransactionType] = jss::AccountSet;
336 txJson[jss::Account] = alice.
human();
337 params[jss::tx_json] = txJson;
339 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
340 BEAST_EXPECT(resp[jss::result][jss::error_message] ==
"Source account not found.");
346 txJson[jss::TransactionType] = jss::AccountSet;
348 txJson[sfSigners] =
"1";
349 params[jss::tx_json] = txJson;
351 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
352 BEAST_EXPECT(resp[jss::result][jss::error_message] ==
"Invalid field 'tx.Signers'.");
358 txJson[jss::TransactionType] = jss::AccountSet;
361 txJson[sfSigners].
append(
"1");
362 params[jss::tx_json] = txJson;
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]'.");
371 txJson[jss::TransactionType] = jss::AccountSet;
373 txJson[sfSponsorSignature] =
"";
374 params[jss::tx_json] = txJson;
376 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
378 resp[jss::result][jss::error_message] ==
379 "Invalid field 'SponsorSignature', not object.");
385 txJson[jss::TransactionType] = jss::AccountSet;
388 sponsorSignature[sfSigners] =
"1";
389 txJson[sfSponsorSignature] = sponsorSignature;
390 params[jss::tx_json] = txJson;
392 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
394 resp[jss::result][jss::error_message] ==
395 "Invalid field 'tx.SponsorSignature.Signers'.",
396 resp.toStyledString());
402 txJson[jss::TransactionType] = jss::AccountSet;
404 txJson[
"foo"] =
"bar";
405 params[jss::tx_json] = txJson;
407 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
409 resp[jss::result][jss::error_message] ==
"Field 'tx_json.foo' is unknown.");
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.");
423 txJson[jss::TransactionType] = jss::AccountSet;
425 txJson[jss::TxnSignature] =
"1200ABCD";
426 params[jss::tx_json] = txJson;
428 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
430 resp[jss::result][jss::error_message] ==
"Transaction should not be signed.");
436 txJson[jss::TransactionType] = jss::AccountSet;
441 signer[jss::Account] = alice.
human();
442 signer[jss::SigningPubKey] = alice.
human();
443 signer[jss::TxnSignature] =
"1200ABCD";
445 signerOuter[sfSigner] = signer;
446 txJson[sfSigners].
append(signerOuter);
448 params[jss::tx_json] = txJson;
450 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
452 resp[jss::result][jss::error_message] ==
"Transaction should not be signed.");
458 txJson[jss::TransactionType] = jss::AccountSet;
461 params[jss::tx_json] = txJson;
463 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
465 resp[jss::result][jss::error_message] ==
466 "tfInnerBatchTxn flag is not allowed on top-level transactions.");
493 params[jss::tx_json] =
noop(alice);
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)))
499 BEAST_EXPECT(result[jss::error] ==
"highFee");
500 BEAST_EXPECT(result[jss::error_code] ==
RpcHighFee);
508 testcase(
"Invalid transaction type");
516 env.
fund(
XRP(1000000), alice, bob);
520 auto const seq = env.
seq(alice);
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.");
544 static auto const kNewDomain =
"123ABC";
548 auto result = resp[jss::result];
551 BEAST_EXPECT(result[jss::engine_result] ==
"tesSUCCESS");
552 BEAST_EXPECT(result[jss::engine_result_code] == 0);
554 result[jss::engine_result_message] ==
555 "The simulated transaction would have been applied.");
557 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
561 if (BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName)))
563 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
size() == 1);
564 auto node = metadata[sfAffectedNodes.jsonName][0u];
565 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
567 auto modifiedNode = node[sfModifiedNode];
568 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] ==
"AccountRoot");
569 auto finalFields = modifiedNode[sfFinalFields];
570 BEAST_EXPECT(finalFields[sfDomain] == kNewDomain);
573 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
574 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] ==
"tesSUCCESS");
581 tx[jss::TransactionType] = jss::AccountSet;
582 tx[sfDomain] = kNewDomain;
585 testTx(env, tx, validateOutput);
587 tx[sfSigningPubKey] =
"";
588 tx[sfTxnSignature] =
"";
590 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
593 testTx(env, tx, validateOutput);
600 auto result = resp[jss::result];
604 BEAST_EXPECT(result[jss::engine_result] ==
"tesSUCCESS");
605 BEAST_EXPECT(result[jss::engine_result_code] == 0);
607 result[jss::engine_result_message] ==
608 "The simulated transaction would have been applied.");
610 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
614 if (BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName)))
616 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
size() == 2);
618 auto node = metadata[sfAffectedNodes.jsonName][0u];
619 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
621 auto modifiedNode = node[sfModifiedNode];
622 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] ==
"AccountRoot");
623 auto previousFields = modifiedNode[sfPreviousFields];
624 BEAST_EXPECT(!previousFields.isMember(sfBalance.jsonName));
627 auto node2 = metadata[sfAffectedNodes.jsonName][1u];
628 if (BEAST_EXPECT(node2.isMember(sfModifiedNode.jsonName)))
630 auto modifiedNode = node2[sfModifiedNode];
631 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] ==
"AccountRoot");
633 auto previousFields = modifiedNode[sfPreviousFields];
634 BEAST_EXPECT(previousFields.isMember(sfBalance.jsonName));
637 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
638 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] ==
"tesSUCCESS");
649 tx[jss::TransactionType] = jss::AccountSet;
650 tx[sfDomain.jsonName] = kNewDomain;
651 tx[sfSponsor.jsonName] =
sponsor.human();
656 testTx(env, tx, validateOutput);
658 tx[sfSponsorSignature.jsonName][sfTxnSignature.jsonName] =
"";
659 tx[sfSponsorSignature.jsonName][sfSigningPubKey.jsonName] =
"";
662 testTx(env, tx, validateOutput);
669 testcase(
"Transaction non-tec failure");
678 auto result = resp[jss::result];
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.");
685 BEAST_EXPECT(!result.isMember(jss::meta) && !result.isMember(jss::meta_blob));
691 tx[jss::TransactionType] = jss::Payment;
692 tx[sfDestination] = alice.
human();
696 testTx(env, tx, testSimulation);
698 tx[sfSigningPubKey] =
"";
699 tx[sfTxnSignature] =
"";
701 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
704 testTx(env, tx, testSimulation);
711 testcase(
"Transaction tec failure");
720 auto result = resp[jss::result];
723 BEAST_EXPECT(result[jss::engine_result] ==
"tecNO_DST_INSUF_XRP");
724 BEAST_EXPECT(result[jss::engine_result_code] == 125);
726 result[jss::engine_result_message] ==
727 "Destination does not exist. Too little XRP sent to "
731 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
735 if (BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName)))
737 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
size() == 1);
738 auto node = metadata[sfAffectedNodes.jsonName][0u];
739 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
741 auto modifiedNode = node[sfModifiedNode];
742 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] ==
"AccountRoot");
743 auto finalFields = modifiedNode[sfFinalFields];
745 finalFields[sfBalance] ==
747 100'000'000'000'000'000 -
748 env.
current()->fees().base.drops()));
751 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
753 metadata[sfTransactionResult.jsonName] ==
"tecNO_DST_INSUF_XRP");
760 tx[jss::TransactionType] = jss::Payment;
761 tx[sfDestination] = alice.
human();
765 testTx(env, tx, testSimulation);
767 tx[sfSigningPubKey] =
"";
768 tx[sfTxnSignature] =
"";
770 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
773 testTx(env, tx, testSimulation);
780 testcase(
"Successful multi-signed transaction");
784 static auto const kNewDomain =
"123ABC";
792 env(
signers(alice, 1, {{becky, 1}, {carol, 1}}));
797 auto result = resp[jss::result];
805 BEAST_EXPECT(result[jss::engine_result] ==
"tesSUCCESS");
806 BEAST_EXPECT(result[jss::engine_result_code] == 0);
808 result[jss::engine_result_message] ==
809 "The simulated transaction would have been applied.");
811 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
815 if (BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName)))
817 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
size() == 1);
818 auto node = metadata[sfAffectedNodes.jsonName][0u];
819 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
821 auto modifiedNode = node[sfModifiedNode];
822 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] ==
"AccountRoot");
823 auto finalFields = modifiedNode[sfFinalFields];
824 BEAST_EXPECT(finalFields[sfDomain] == kNewDomain);
827 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
828 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] ==
"tesSUCCESS");
834 tx[jss::Account] = alice.
human();
835 tx[jss::TransactionType] = jss::AccountSet;
836 tx[sfDomain] = kNewDomain;
839 testTx(env, tx, validateOutput,
false);
844 signer[jss::Account] = becky.
human();
846 signerOuter[sfSigner] = signer;
847 tx[sfSigners].
append(signerOuter);
851 testTx(env, tx, validateOutput,
false);
853 tx[sfSigningPubKey] =
"";
854 tx[sfTxnSignature] =
"";
855 tx[sfSequence] = env.
seq(alice);
858 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] =
"";
859 tx[sfSigners][0u][sfSigner][jss::TxnSignature] =
"";
862 testTx(env, tx, validateOutput);
869 testcase(
"Successful sponsored multi-signed transaction");
874 Account const signer(
"signer");
882 auto const result = resp[jss::result];
884 auto const expectedFee = env.
current()->fees().base * 2;
887 BEAST_EXPECT(result[jss::engine_result] ==
"tesSUCCESS");
888 BEAST_EXPECT(result[jss::engine_result_code] == 0);
890 result[jss::engine_result_message] ==
891 "The simulated transaction would have been applied.");
893 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
896 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] ==
"tesSUCCESS");
902 tx[jss::TransactionType] = jss::AccountSet;
903 tx[sfDomain] =
"123ABC";
904 tx[sfSponsor.jsonName] =
sponsor.human();
910 signerObj[sfSigner][jss::Account] = signer.
human();
911 tx[sfSponsorSignature.jsonName][sfSigners.jsonName].
append(signerObj);
914 BEAST_EXPECT(!tx.
isMember(jss::Fee));
915 testTx(env, tx, validateOutput,
false);
921 testcase(
"Transaction with a key-related failure");
925 static auto const kNewDomain =
"123ABC";
934 auto result = resp[jss::result];
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.");
942 BEAST_EXPECT(!result.isMember(jss::meta) && !result.isMember(jss::meta_blob));
948 tx[jss::TransactionType] = jss::AccountSet;
949 tx[sfDomain] = kNewDomain;
954 testTx(env, tx, testSimulation);
956 tx[sfTxnSignature] =
"";
958 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
961 testTx(env, tx, testSimulation);
969 "Transaction with both single-signing SigningPubKey and "
970 "multi-signing Signers");
974 static auto const kNewDomain =
"123ABC";
982 env(
signers(alice, 1, {{becky, 1}, {carol, 1}}));
988 auto result = resp[jss::result];
992 BEAST_EXPECT(result[jss::engine_result] ==
"temINVALID");
993 BEAST_EXPECT(result[jss::engine_result_code] == -277);
995 result[jss::engine_result_message] ==
"The transaction is ill-formed.");
997 BEAST_EXPECT(!result.isMember(jss::meta) && !result.isMember(jss::meta_blob));
1003 tx[jss::TransactionType] = jss::AccountSet;
1004 tx[sfDomain] = kNewDomain;
1010 signer[jss::Account] = becky.
human();
1012 signerOuter[sfSigner] = signer;
1013 tx[sfSigners].
append(signerOuter);
1017 testTx(env, tx, testSimulation,
false);
1019 tx[sfTxnSignature] =
"";
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] =
"";
1026 testTx(env, tx, testSimulation);
1033 testcase(
"Multi-signed transaction with a bad public key");
1035 using namespace jtx;
1037 static auto const kNewDomain =
"123ABC";
1046 env(
signers(alice, 1, {{becky, 1}, {carol, 1}}));
1050 auto result = resp[jss::result];
1052 result, tx, env.
seq(alice), env.
current()->fees().base * 2);
1055 result[jss::engine_result] ==
"tefBAD_SIGNATURE",
1056 result[jss::engine_result].toStyledString());
1057 BEAST_EXPECT(result[jss::engine_result_code] == -186);
1059 result[jss::engine_result_message] ==
1060 "A signature is provided for a non-signer.");
1062 BEAST_EXPECT(!result.isMember(jss::meta) && !result.isMember(jss::meta_blob));
1067 tx[jss::Account] = alice.
human();
1068 tx[jss::TransactionType] = jss::AccountSet;
1069 tx[sfDomain] = kNewDomain;
1073 signer[jss::Account] = becky.
human();
1076 signerOuter[sfSigner] = signer;
1077 tx[sfSigners].
append(signerOuter);
1081 testTx(env, tx, validateOutput,
false);
1083 tx[sfSigningPubKey] =
"";
1084 tx[sfTxnSignature] =
"";
1085 tx[sfSequence] = env.
seq(alice);
1087 tx[sfFee] = (env.
current()->fees().base * 2).jsonClipped().
asString();
1088 tx[sfSigners][0u][sfSigner][jss::TxnSignature] =
"";
1091 testTx(env, tx, validateOutput);
1098 testcase(
"Credentials aren't actually deleted on `tecEXPIRED`");
1102 using namespace jtx;
1105 Account const subject{
"subject"};
1106 Account const issuer{
"issuer"};
1108 env.
fund(
XRP(10000), subject, issuer);
1111 auto const credType =
"123ABC";
1114 uint32_t
const t = env.
current()->header().parentCloseTime.time_since_epoch().count();
1115 jv[sfExpiration.jsonName] = t;
1121 auto result = resp[jss::result];
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.");
1128 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
1132 if (BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName)))
1134 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
size() == 5);
1139 for (
auto const& node : metadata[sfAffectedNodes.jsonName])
1141 if (node.isMember(sfDeletedNode.jsonName) &&
1142 node[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName]
1143 .asString() ==
"Credential")
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"] ==
1154 BEAST_EXPECT(found);
1161 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1162 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] ==
"tecEXPIRED");
1169 testTx(env, tx, validateOutput);
1171 tx[sfSigningPubKey] =
"";
1172 tx[sfTxnSignature] =
"";
1173 tx[sfSequence] = env.
seq(subject);
1174 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
1177 testTx(env, tx, validateOutput);
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() &&
1198 testcase(
"Successful transaction with a custom network ID");
1200 using namespace jtx;
1202 cfg->networkId = 1025;
1205 static auto const kNewDomain =
"123ABC";
1209 auto result = resp[jss::result];
1212 BEAST_EXPECT(result[jss::engine_result] ==
"tesSUCCESS");
1213 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1215 result[jss::engine_result_message] ==
1216 "The simulated transaction would have been applied.");
1218 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
1222 if (BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName)))
1224 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
size() == 1);
1225 auto node = metadata[sfAffectedNodes.jsonName][0u];
1226 if (BEAST_EXPECT(node.isMember(sfModifiedNode.jsonName)))
1228 auto modifiedNode = node[sfModifiedNode];
1229 BEAST_EXPECT(modifiedNode[sfLedgerEntryType] ==
"AccountRoot");
1230 auto finalFields = modifiedNode[sfFinalFields];
1231 BEAST_EXPECT(finalFields[sfDomain] == kNewDomain);
1234 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1235 BEAST_EXPECT(metadata[sfTransactionResult.jsonName] ==
"tesSUCCESS");
1242 tx[jss::TransactionType] = jss::AccountSet;
1243 tx[sfDomain] = kNewDomain;
1246 testTx(env, tx, validateOutput);
1248 tx[sfSigningPubKey] =
"";
1249 tx[sfTxnSignature] =
"";
1251 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
1252 tx[sfNetworkID] = 1025;
1255 testTx(env, tx, validateOutput);
1262 testcase(
"Successful transaction with additional metadata");
1264 using namespace jtx;
1265 using namespace std::chrono_literals;
1267 cfg->networkId = 1025;
1274 env.
fund(
XRP(10000), alice, bob);
1281 auto validateOutput = [&](
json::Value const& resp,
1285 auto result = resp[jss::result];
1287 BEAST_EXPECT(result[jss::engine_result] ==
"tesSUCCESS");
1288 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1290 result[jss::engine_result_message] ==
1291 "The simulated transaction would have been applied.");
1293 if (BEAST_EXPECT(result.isMember(jss::meta) || result.isMember(jss::meta_blob)))
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);
1306 tx[jss::Account] = alice.
human();
1307 tx[jss::TransactionType] = jss::Payment;
1308 tx[sfDestination] = bob.
human();
1309 tx[sfAmount] =
"100";
1317 tx[jss::Account] = alice.
human();
1318 tx[jss::TransactionType] = jss::NFTokenMint;
1319 tx[sfNFTokenTaxon] = 1;
1328 tx[jss::Account] = alice.
human();
1329 tx[jss::TransactionType] = jss::MPTokenIssuanceCreate;
1334 env, tx, validateOutput, jss::mpt_issuance_id, mptIssuanceId);
void fail(String const &reason, char const *file, int line)
Record a failure.
TestcaseT testcase
Memberspace for declaring test cases.
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
Slice slice() const noexcept
json::Value getJson(JsonOptions=JsonOptions::Values::None) const override
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
json::Value jsonClipped() const
void testTransactionTecFailure()
void testTransactionSigningFailure()
void testMultisignedBadPubKey()
void testSuccessfulSponsoredTransactionMultisigned()
void testDeleteExpiredCredentials()
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 testSuccessfulTransactionNetworkID()
void testSuccessfulTransactionMultisigned()
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 testTransactionNonTecFailure()
void testSuccessfulTransaction()
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)
void testSuccessfulTransactionAdditionalMetadata()
void testInvalidTransactionType()
Immutable cryptographic account descriptor.
std::string const & human() const
Returns the human readable public key.
PublicKey const & pk() const
Return the public key.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
JTx jtnofill(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Set the regular signature on a JTx.
Adds an inner Batch transaction to a JTx and autofills it.
@ Array
array value (ordered list)
@ Object
object value (collection of name/value pairs).
API version numbers used in later API versions.
json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Build an outer Batch transaction JSON object.
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate the expected outer Batch transaction fee.
json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
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.
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
json::Value regkey(Account const &account, DisabledT)
Disable the regular key.
XrpT const XRP
Converts to XRP Issue or STAmount.
json::Value noop(Account const &account)
The null transaction.
std::uint32_t ownerCount(Env const &env, Account const &account)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
json::Value signers(Account const &account, std::uint32_t quorum, std::vector< Signer > const &v)
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
constexpr XRPAmount
Convert XRP to drops (integral types).
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
constexpr FlagValue spfSponsorFee
constexpr FlagValue tfInnerBatchTxn
std::string strHex(FwdIt begin, FwdIt end)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Slice makeSlice(std::array< T, N > const &a)
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
static constexpr auto kMinimumTxnInLedgerStandalone
static constexpr auto kTransactionQueue