rippled
Loading...
Searching...
No Matches
Batch_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/TestHelpers.h>
3#include <test/jtx/utility.h>
4
5#include <xrpld/app/misc/HashRouter.h>
6#include <xrpld/app/misc/NetworkOPs.h>
7#include <xrpld/app/misc/Transaction.h>
8#include <xrpld/app/tx/apply.h>
9#include <xrpld/app/tx/detail/Batch.h>
10
11#include <xrpl/protocol/Batch.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/STParsedJSON.h>
14#include <xrpl/protocol/Sign.h>
15#include <xrpl/protocol/TxFlags.h>
16#include <xrpl/protocol/jss.h>
17
18namespace xrpl {
19namespace test {
20
22{
31
37
39 getTxByIndex(Json::Value const& jrr, int const index)
40 {
41 for (auto const& txn : jrr[jss::result][jss::ledger][jss::transactions])
42 {
43 if (txn[jss::metaData][sfTransactionIndex.jsonName] == index)
44 return txn;
45 }
46 return {};
47 }
48
51 {
52 Json::Value params;
53 params[jss::ledger_index] = env.closed()->seq();
54 params[jss::transactions] = true;
55 params[jss::expand] = true;
56 return env.rpc("json", "ledger", to_string(params));
57 }
58
59 void
61 jtx::Env& env,
62 std::string const& batchID,
63 TestLedgerData const& ledgerResult)
64 {
65 Json::Value const jrr = env.rpc("tx", ledgerResult.txHash)[jss::result];
66 BEAST_EXPECT(jrr[sfTransactionType.jsonName] == ledgerResult.txType);
67 BEAST_EXPECT(
68 jrr[jss::meta][sfTransactionResult.jsonName] ==
69 ledgerResult.result);
70 BEAST_EXPECT(jrr[jss::meta][sfParentBatchID.jsonName] == batchID);
71 }
72
73 void
75 jtx::Env& env,
76 std::vector<TestLedgerData> const& ledgerResults)
77 {
78 auto const jrr = getLastLedger(env);
79 auto const transactions =
80 jrr[jss::result][jss::ledger][jss::transactions];
81 BEAST_EXPECT(transactions.size() == ledgerResults.size());
82 for (TestLedgerData const& ledgerResult : ledgerResults)
83 {
84 auto const txn = getTxByIndex(jrr, ledgerResult.index);
85 BEAST_EXPECT(txn[jss::hash].asString() == ledgerResult.txHash);
86 BEAST_EXPECT(txn.isMember(jss::metaData));
87 Json::Value const meta = txn[jss::metaData];
88 BEAST_EXPECT(
89 txn[sfTransactionType.jsonName] == ledgerResult.txType);
90 BEAST_EXPECT(
91 meta[sfTransactionResult.jsonName] == ledgerResult.result);
92 if (ledgerResult.batchID)
93 validateInnerTxn(env, *ledgerResult.batchID, ledgerResult);
94 }
95 }
96
97 template <typename... Args>
99 submitBatch(jtx::Env& env, TER const& result, Args&&... args)
100 {
101 auto batchTxn = env.jt(std::forward<Args>(args)...);
102 env(batchTxn, jtx::ter(result));
103
104 auto const ids = batchTxn.stx->getBatchTransactionIDs();
106 for (auto const& id : ids)
107 txIDs.push_back(strHex(id));
108 TxID const batchID = batchTxn.stx->getTransactionID();
109 return std::make_pair(txIDs, strHex(batchID));
110 }
111
112 static uint256
113 getCheckIndex(AccountID const& account, std::uint32_t uSequence)
114 {
115 return keylet::check(account, uSequence).key;
116 }
117
121 std::map<std::string, std::string> extraVoting = {})
122 {
123 auto p = test::jtx::envconfig();
124 auto& section = p->section("transaction_queue");
125 section.set("ledgers_in_queue", "2");
126 section.set("minimum_queue_size", "2");
127 section.set("min_ledgers_to_compute_size_limit", "3");
128 section.set("max_ledger_counts_to_store", "100");
129 section.set("retry_sequence_percent", "25");
130 section.set("normal_consensus_increase_percent", "0");
131
132 for (auto const& [k, v] : extraTxQ)
133 section.set(k, v);
134
135 return p;
136 }
137
138 auto
139 openLedgerFee(jtx::Env& env, XRPAmount const& batchFee)
140 {
141 using namespace jtx;
142
143 auto const& view = *env.current();
144 auto metrics = env.app().getTxQ().getMetrics(view);
145 return toDrops(metrics.openLedgerFeeLevel, batchFee) + 1;
146 }
147
148 void
150 {
151 using namespace test::jtx;
152 using namespace std::literals;
153
154 bool const withInnerSigFix = features[fixBatchInnerSigs];
155
156 for (bool const withBatch : {true, false})
157 {
158 testcase << "enabled: Batch "
159 << (withBatch ? "enabled" : "disabled")
160 << ", Inner Sig Fix: "
161 << (withInnerSigFix ? "enabled" : "disabled");
162
163 auto const amend = withBatch ? features : features - featureBatch;
164
165 test::jtx::Env env{*this, amend};
166
167 auto const alice = Account("alice");
168 auto const bob = Account("bob");
169 auto const carol = Account("carol");
170 env.fund(XRP(10000), alice, bob, carol);
171 env.close();
172
173 // ttBatch
174 {
175 auto const seq = env.seq(alice);
176 auto const batchFee = batch::calcBatchFee(env, 0, 2);
177 auto const txResult =
178 withBatch ? ter(tesSUCCESS) : ter(temDISABLED);
179 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
180 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
181 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
182 txResult);
183 env.close();
184 }
185
186 // tfInnerBatchTxn
187 // If the feature is disabled, the transaction fails with
188 // temINVALID_FLAG. If the feature is enabled, the transaction fails
189 // early in checkValidity()
190 {
191 auto const txResult =
193 env(pay(alice, bob, XRP(1)),
195 txResult);
196 env.close();
197 }
198
199 env.close();
200 }
201 }
202
203 void
205 {
206 testcase("preflight");
207
208 using namespace test::jtx;
209 using namespace std::literals;
210
211 //----------------------------------------------------------------------
212 // preflight
213
214 test::jtx::Env env{*this, features};
215
216 auto const alice = Account("alice");
217 auto const bob = Account("bob");
218 auto const carol = Account("carol");
219 env.fund(XRP(10000), alice, bob, carol);
220 env.close();
221
222 // temBAD_FEE: preflight1
223 {
224 env(batch::outer(alice, env.seq(alice), XRP(-1), tfAllOrNothing),
225 ter(temBAD_FEE));
226 env.close();
227 }
228
229 // DEFENSIVE: temINVALID_FLAG: Batch: inner batch flag.
230 // ACTUAL: telENV_RPC_FAILED: checkValidity()
231 {
232 auto const seq = env.seq(alice);
233 auto const batchFee = batch::calcBatchFee(env, 0, 0);
234 env(batch::outer(alice, seq, batchFee, tfInnerBatchTxn),
236 env.close();
237 }
238
239 // temINVALID_FLAG: Batch: invalid flags.
240 {
241 auto const seq = env.seq(alice);
242 auto const batchFee = batch::calcBatchFee(env, 0, 0);
243 env(batch::outer(alice, seq, batchFee, tfDisallowXRP),
245 env.close();
246 }
247
248 // temINVALID_FLAG: Batch: too many flags.
249 {
250 auto const seq = env.seq(alice);
251 auto const batchFee = batch::calcBatchFee(env, 0, 0);
252 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
255 env.close();
256 }
257
258 // temARRAY_EMPTY: Batch: txns array must have at least 2 entries.
259 {
260 auto const seq = env.seq(alice);
261 auto const batchFee = batch::calcBatchFee(env, 0, 0);
262 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
264 env.close();
265 }
266
267 // temARRAY_EMPTY: Batch: txns array must have at least 2 entries.
268 {
269 auto const seq = env.seq(alice);
270 auto const batchFee = batch::calcBatchFee(env, 0, 0);
271 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
272 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
274 env.close();
275 }
276
277 // DEFENSIVE: temARRAY_TOO_LARGE: Batch: txns array exceeds 8 entries.
278 // ACTUAL: telENV_RPC_FAILED: isRawTransactionOkay()
279 {
280 auto const seq = env.seq(alice);
281 auto const batchFee = batch::calcBatchFee(env, 0, 9);
282 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
283 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
284 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
285 batch::inner(pay(alice, bob, XRP(1)), seq + 3),
286 batch::inner(pay(alice, bob, XRP(1)), seq + 4),
287 batch::inner(pay(alice, bob, XRP(1)), seq + 5),
288 batch::inner(pay(alice, bob, XRP(1)), seq + 6),
289 batch::inner(pay(alice, bob, XRP(1)), seq + 7),
290 batch::inner(pay(alice, bob, XRP(1)), seq + 8),
291 batch::inner(pay(alice, bob, XRP(1)), seq + 9),
293 env.close();
294 }
295
296 // temREDUNDANT: Batch: duplicate Txn found.
297 {
298 auto const batchFee = batch::calcBatchFee(env, 1, 2);
299 auto const seq = env.seq(alice);
300 auto jt = env.jtnofill(
301 batch::outer(alice, env.seq(alice), batchFee, tfAllOrNothing),
302 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
303 batch::inner(pay(alice, bob, XRP(10)), seq + 1));
304
305 env(jt.jv, batch::sig(bob), ter(temREDUNDANT));
306 env.close();
307 }
308
309 // DEFENSIVE: temINVALID: Batch: batch cannot have inner batch txn.
310 // ACTUAL: telENV_RPC_FAILED: isRawTransactionOkay()
311 {
312 auto const seq = env.seq(alice);
313 auto const batchFee = batch::calcBatchFee(env, 0, 2);
314 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
316 batch::outer(alice, seq, batchFee, tfAllOrNothing), seq),
317 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
319 env.close();
320 }
321
322 // temINVALID_FLAG: Batch: inner txn must have the
323 // tfInnerBatchTxn flag.
324 {
325 auto const batchFee = batch::calcBatchFee(env, 1, 2);
326 auto const seq = env.seq(alice);
327 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
328 tx1[jss::Flags] = 0;
329 auto jt = env.jtnofill(
330 batch::outer(alice, seq, batchFee, tfAllOrNothing),
331 tx1,
332 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
333
334 env(jt.jv, batch::sig(bob), ter(temINVALID_FLAG));
335 env.close();
336 }
337
338 // temBAD_SIGNATURE: Batch: inner txn cannot include TxnSignature.
339 {
340 auto const seq = env.seq(alice);
341 auto const batchFee = batch::calcBatchFee(env, 0, 2);
342 auto jt = env.jt(pay(alice, bob, XRP(1)));
343 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
344 batch::inner(jt.jv, seq + 1),
345 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
347 env.close();
348 }
349
350 // temBAD_SIGNER: Batch: inner txn cannot include Signers.
351 {
352 auto const seq = env.seq(alice);
353 auto const batchFee = batch::calcBatchFee(env, 0, 2);
354 auto tx1 = pay(alice, bob, XRP(1));
355 tx1[sfSigners.jsonName] = Json::arrayValue;
356 tx1[sfSigners.jsonName][0U][sfSigner.jsonName] = Json::objectValue;
357 tx1[sfSigners.jsonName][0U][sfSigner.jsonName][sfAccount.jsonName] =
358 alice.human();
359 tx1[sfSigners.jsonName][0U][sfSigner.jsonName]
360 [sfSigningPubKey.jsonName] = strHex(alice.pk());
361 tx1[sfSigners.jsonName][0U][sfSigner.jsonName]
362 [sfTxnSignature.jsonName] = "DEADBEEF";
363 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
364 batch::inner(tx1, seq + 1),
365 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
367 env.close();
368 }
369
370 // temBAD_REGKEY: Batch: inner txn must include empty
371 // SigningPubKey.
372 {
373 auto const seq = env.seq(alice);
374 auto const batchFee = batch::calcBatchFee(env, 0, 2);
375 auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
376 tx1[jss::SigningPubKey] = strHex(alice.pk());
377 auto jt = env.jtnofill(
378 batch::outer(alice, seq, batchFee, tfAllOrNothing),
379 tx1,
380 batch::inner(pay(alice, bob, XRP(1)), seq + 2));
381
382 env(jt.jv, ter(temBAD_REGKEY));
383 env.close();
384 }
385
386 // temINVALID_INNER_BATCH: Batch: inner txn preflight failed.
387 {
388 auto const seq = env.seq(alice);
389 auto const batchFee = batch::calcBatchFee(env, 0, 2);
390 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
391 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
392 // amount can't be negative
393 batch::inner(pay(alice, bob, XRP(-1)), seq + 2),
395 env.close();
396 }
397
398 // temBAD_FEE: Batch: inner txn must have a fee of 0.
399 {
400 auto const seq = env.seq(alice);
401 auto const batchFee = batch::calcBatchFee(env, 0, 2);
402 auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
403 tx1[jss::Fee] = to_string(env.current()->fees().base);
404 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
405 tx1,
406 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
407 ter(temBAD_FEE));
408 env.close();
409 }
410
411 // temBAD_FEE: Inner txn with negative fee
412 {
413 auto const seq = env.seq(alice);
414 auto const batchFee = batch::calcBatchFee(env, 0, 2);
415 auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
416 tx1[jss::Fee] = "-1";
417 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
418 tx1,
419 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
420 ter(temBAD_FEE));
421 env.close();
422 }
423
424 // temBAD_FEE: Inner txn with non-integer fee
425 {
426 auto const seq = env.seq(alice);
427 auto const batchFee = batch::calcBatchFee(env, 0, 2);
428 auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
429 tx1[jss::Fee] = "1.5";
430 try
431 {
432 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
433 tx1,
434 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
435 fail("Expected parse_error for fractional fee");
436 }
437 catch (jtx::parse_error const&)
438 {
439 BEAST_EXPECT(true);
440 }
441 }
442
443 // temSEQ_AND_TICKET: Batch: inner txn cannot have both Sequence
444 // and TicketSequence.
445 {
446 auto const seq = env.seq(alice);
447 auto const batchFee = batch::calcBatchFee(env, 0, 2);
448 auto tx1 = batch::inner(pay(alice, bob, XRP(1)), 0, 1);
449 tx1[jss::Sequence] = seq + 1;
450 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
451 tx1,
452 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
454 env.close();
455 }
456
457 // temSEQ_AND_TICKET: Batch: inner txn must have either Sequence or
458 // TicketSequence.
459 {
460 auto const seq = env.seq(alice);
461 auto const batchFee = batch::calcBatchFee(env, 0, 2);
462 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
463 batch::inner(pay(alice, bob, XRP(1)), 0),
464 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
466 env.close();
467 }
468
469 // temREDUNDANT: Batch: duplicate sequence found:
470 {
471 auto const seq = env.seq(alice);
472 auto const batchFee = batch::calcBatchFee(env, 0, 2);
473 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
474 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
475 batch::inner(pay(alice, bob, XRP(2)), seq + 1),
477 env.close();
478 }
479
480 // temREDUNDANT: Batch: duplicate ticket found:
481 {
482 auto const seq = env.seq(alice);
483 auto const batchFee = batch::calcBatchFee(env, 0, 2);
484 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
485 batch::inner(pay(alice, bob, XRP(1)), 0, seq + 1),
486 batch::inner(pay(alice, bob, XRP(2)), 0, seq + 1),
488 env.close();
489 }
490
491 // temREDUNDANT: Batch: duplicate ticket & sequence found:
492 {
493 auto const seq = env.seq(alice);
494 auto const batchFee = batch::calcBatchFee(env, 0, 2);
495 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
496 batch::inner(pay(alice, bob, XRP(1)), 0, seq + 1),
497 batch::inner(pay(alice, bob, XRP(2)), seq + 1),
499 env.close();
500 }
501
502 // DEFENSIVE: temARRAY_TOO_LARGE: Batch: signers array exceeds 8
503 // entries.
504 // ACTUAL: telENV_RPC_FAILED: isRawTransactionOkay()
505 {
506 auto const seq = env.seq(alice);
507 auto const batchFee = batch::calcBatchFee(env, 9, 2);
508 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
509 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
510 batch::inner(pay(alice, bob, XRP(5)), seq + 2),
512 bob,
513 carol,
514 alice,
515 bob,
516 carol,
517 alice,
518 bob,
519 carol,
520 alice,
521 alice),
523 env.close();
524 }
525
526 // temBAD_SIGNER: Batch: signer cannot be the outer account
527 {
528 auto const seq = env.seq(alice);
529 auto const batchFee = batch::calcBatchFee(env, 2, 2);
530 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
531 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
532 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
533 batch::sig(alice, bob),
535 env.close();
536 }
537
538 // temREDUNDANT: Batch: duplicate signer found
539 {
540 auto const seq = env.seq(alice);
541 auto const batchFee = batch::calcBatchFee(env, 2, 2);
542 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
543 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
544 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
545 batch::sig(bob, bob),
547 env.close();
548 }
549
550 // temBAD_SIGNER: Batch: no account signature for inner txn.
551 // Note: Extra signature by bob
552 {
553 auto const seq = env.seq(alice);
554 auto const batchFee = batch::calcBatchFee(env, 1, 2);
555 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
556 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
557 batch::inner(pay(alice, bob, XRP(5)), seq + 2),
558 batch::sig(bob),
560 env.close();
561 }
562
563 // temBAD_SIGNER: Batch: no account signature for inner txn.
564 {
565 auto const seq = env.seq(alice);
566 auto const batchFee = batch::calcBatchFee(env, 1, 2);
567 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
568 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
569 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
570 batch::sig(carol),
572 env.close();
573 }
574
575 // temBAD_SIGNATURE: Batch: invalid batch txn signature.
576 {
577 auto const seq = env.seq(alice);
578 auto const bobSeq = env.seq(bob);
579 auto const batchFee = batch::calcBatchFee(env, 1, 2);
580 auto jt = env.jtnofill(
581 batch::outer(alice, env.seq(alice), batchFee, tfAllOrNothing),
582 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
583 batch::inner(pay(bob, alice, XRP(5)), bobSeq));
584
585 Serializer msg;
587 msg, tfAllOrNothing, jt.stx->getBatchTransactionIDs());
588 auto const sig = xrpl::sign(bob.pk(), bob.sk(), msg.slice());
589 jt.jv[sfBatchSigners.jsonName][0u][sfBatchSigner.jsonName]
590 [sfAccount.jsonName] = bob.human();
591 jt.jv[sfBatchSigners.jsonName][0u][sfBatchSigner.jsonName]
592 [sfSigningPubKey.jsonName] = strHex(alice.pk());
593 jt.jv[sfBatchSigners.jsonName][0u][sfBatchSigner.jsonName]
594 [sfTxnSignature.jsonName] =
595 strHex(Slice{sig.data(), sig.size()});
596
597 env(jt.jv, ter(temBAD_SIGNATURE));
598 env.close();
599 }
600
601 // temBAD_SIGNER: Batch: invalid batch signers.
602 {
603 auto const seq = env.seq(alice);
604 auto const batchFee = batch::calcBatchFee(env, 2, 2);
605 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
606 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
607 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
608 batch::inner(pay(carol, alice, XRP(5)), env.seq(carol)),
609 batch::sig(bob),
611 env.close();
612 }
613 }
614
615 void
617 {
618 testcase("preclaim");
619
620 using namespace test::jtx;
621 using namespace std::literals;
622
623 //----------------------------------------------------------------------
624 // preclaim
625
626 test::jtx::Env env{*this, features};
627
628 auto const alice = Account("alice");
629 auto const bob = Account("bob");
630 auto const carol = Account("carol");
631 auto const dave = Account("dave");
632 auto const elsa = Account("elsa");
633 auto const frank = Account("frank");
634 auto const phantom = Account("phantom");
635 env.memoize(phantom);
636
637 env.fund(XRP(10000), alice, bob, carol, dave, elsa, frank);
638 env.close();
639
640 //----------------------------------------------------------------------
641 // checkSign.checkSingleSign
642
643 // tefBAD_AUTH: Bob is not authorized to sign for Alice
644 {
645 auto const seq = env.seq(alice);
646 auto const batchFee = batch::calcBatchFee(env, 3, 2);
647 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
648 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
649 batch::inner(pay(alice, bob, XRP(20)), seq + 2),
650 sig(bob),
652 env.close();
653 }
654
655 //----------------------------------------------------------------------
656 // checkBatchSign.checkMultiSign
657
658 // tefNOT_MULTI_SIGNING: SignersList not enabled
659 {
660 auto const seq = env.seq(alice);
661 auto const batchFee = batch::calcBatchFee(env, 3, 2);
662 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
663 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
664 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
665 batch::msig(bob, {dave, carol}),
667 env.close();
668 }
669
670 env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
671 env.close();
672
673 env(signers(bob, 2, {{carol, 1}, {dave, 1}, {elsa, 1}}));
674 env.close();
675
676 // tefBAD_SIGNATURE: Account not in SignersList
677 {
678 auto const seq = env.seq(alice);
679 auto const batchFee = batch::calcBatchFee(env, 3, 2);
680 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
681 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
682 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
683 batch::msig(bob, {carol, frank}),
685 env.close();
686 }
687
688 // tefBAD_SIGNATURE: Wrong publicKey type
689 {
690 auto const seq = env.seq(alice);
691 auto const batchFee = batch::calcBatchFee(env, 3, 2);
692 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
693 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
694 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
695 batch::msig(bob, {carol, Account("dave", KeyType::ed25519)}),
697 env.close();
698 }
699
700 // tefMASTER_DISABLED: Master key disabled
701 {
702 env(regkey(elsa, frank));
703 env(fset(elsa, asfDisableMaster), sig(elsa));
704 auto const seq = env.seq(alice);
705 auto const batchFee = batch::calcBatchFee(env, 3, 2);
706 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
707 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
708 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
709 batch::msig(bob, {carol, elsa}),
711 env.close();
712 }
713
714 // tefBAD_SIGNATURE: Signer does not exist
715 {
716 auto const seq = env.seq(alice);
717 auto const batchFee = batch::calcBatchFee(env, 3, 2);
718 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
719 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
720 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
721 batch::msig(bob, {carol, phantom}),
723 env.close();
724 }
725
726 // tefBAD_SIGNATURE: Signer has not enabled RegularKey
727 {
728 auto const seq = env.seq(alice);
729 auto const batchFee = batch::calcBatchFee(env, 3, 2);
730 Account const davo{"davo", KeyType::ed25519};
731 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
732 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
733 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
734 batch::msig(bob, {carol, Reg{dave, davo}}),
736 env.close();
737 }
738
739 // tefBAD_SIGNATURE: Wrong RegularKey Set
740 {
741 env(regkey(dave, frank));
742 auto const seq = env.seq(alice);
743 auto const batchFee = batch::calcBatchFee(env, 3, 2);
744 Account const davo{"davo", KeyType::ed25519};
745 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
746 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
747 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
748 batch::msig(bob, {carol, Reg{dave, davo}}),
749 ter(tefBAD_SIGNATURE));
750 env.close();
751 }
752
753 // tefBAD_QUORUM
754 {
755 auto const seq = env.seq(alice);
756 auto const batchFee = batch::calcBatchFee(env, 2, 2);
757 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
758 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
759 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
760 batch::msig(bob, {carol}),
761 ter(tefBAD_QUORUM));
762 env.close();
763 }
764
765 // tesSUCCESS: BatchSigners.Signers
766 {
767 auto const seq = env.seq(alice);
768 auto const batchFee = batch::calcBatchFee(env, 3, 2);
769 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
770 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
771 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
772 batch::msig(bob, {carol, dave}),
773 ter(tesSUCCESS));
774 env.close();
775 }
776
777 // tesSUCCESS: Multisign + BatchSigners.Signers
778 {
779 auto const seq = env.seq(alice);
780 auto const batchFee = batch::calcBatchFee(env, 4, 2);
781 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
782 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
783 batch::inner(pay(bob, alice, XRP(5)), env.seq(bob)),
784 batch::msig(bob, {carol, dave}),
785 msig(bob, carol),
786 ter(tesSUCCESS));
787 env.close();
788 }
789
790 //----------------------------------------------------------------------
791 // checkBatchSign.checkSingleSign
792
793 // tefBAD_AUTH: Inner Account is not signer
794 {
795 auto const ledSeq = env.current()->seq();
796 auto const seq = env.seq(alice);
797 auto const batchFee = batch::calcBatchFee(env, 1, 2);
798 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
799 batch::inner(pay(alice, phantom, XRP(1000)), seq + 1),
800 batch::inner(noop(phantom), ledSeq),
801 batch::sig(Reg{phantom, carol}),
802 ter(tefBAD_AUTH));
803 env.close();
804 }
805
806 // tefBAD_AUTH: Account is not signer
807 {
808 auto const ledSeq = env.current()->seq();
809 auto const seq = env.seq(alice);
810 auto const batchFee = batch::calcBatchFee(env, 1, 2);
811 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
812 batch::inner(pay(alice, bob, XRP(1000)), seq + 1),
813 batch::inner(noop(bob), ledSeq),
814 batch::sig(Reg{bob, carol}),
815 ter(tefBAD_AUTH));
816 env.close();
817 }
818
819 // tesSUCCESS: Signed With Regular Key
820 {
821 env(regkey(bob, carol));
822 auto const seq = env.seq(alice);
823 auto const batchFee = batch::calcBatchFee(env, 1, 2);
824 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
825 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
826 batch::inner(pay(bob, alice, XRP(2)), env.seq(bob)),
827 batch::sig(Reg{bob, carol}),
828 ter(tesSUCCESS));
829 env.close();
830 }
831
832 // tesSUCCESS: Signed With Master Key
833 {
834 auto const seq = env.seq(alice);
835 auto const batchFee = batch::calcBatchFee(env, 1, 2);
836 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
837 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
838 batch::inner(pay(bob, alice, XRP(2)), env.seq(bob)),
839 batch::sig(bob),
840 ter(tesSUCCESS));
841 env.close();
842 }
843
844 // tefMASTER_DISABLED: Signed With Master Key Disabled
845 {
846 env(regkey(bob, carol));
847 env(fset(bob, asfDisableMaster), sig(bob));
848 auto const seq = env.seq(alice);
849 auto const batchFee = batch::calcBatchFee(env, 1, 2);
850 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
851 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
852 batch::inner(pay(bob, alice, XRP(2)), env.seq(bob)),
853 batch::sig(bob),
854 ter(tefMASTER_DISABLED));
855 env.close();
856 }
857 }
858
859 void
861 {
862 testcase("bad raw txn");
863
864 using namespace test::jtx;
865 using namespace std::literals;
866
867 test::jtx::Env env{*this, features};
868
869 auto const alice = Account("alice");
870 auto const bob = Account("bob");
871
872 env.fund(XRP(10000), alice, bob);
873
874 // Invalid: sfTransactionType
875 {
876 auto const batchFee = batch::calcBatchFee(env, 1, 2);
877 auto const seq = env.seq(alice);
878 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
879 tx1.removeMember(jss::TransactionType);
880 auto jt = env.jtnofill(
881 batch::outer(alice, seq, batchFee, tfAllOrNothing),
882 tx1,
883 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
884
885 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
886 env.close();
887 }
888
889 // Invalid: sfAccount
890 {
891 auto const batchFee = batch::calcBatchFee(env, 1, 2);
892 auto const seq = env.seq(alice);
893 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
894 tx1.removeMember(jss::Account);
895 auto jt = env.jtnofill(
896 batch::outer(alice, seq, batchFee, tfAllOrNothing),
897 tx1,
898 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
899
900 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
901 env.close();
902 }
903
904 // Invalid: sfSequence
905 {
906 auto const batchFee = batch::calcBatchFee(env, 1, 2);
907 auto const seq = env.seq(alice);
908 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
909 tx1.removeMember(jss::Sequence);
910 auto jt = env.jtnofill(
911 batch::outer(alice, seq, batchFee, tfAllOrNothing),
912 tx1,
913 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
914
915 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
916 env.close();
917 }
918
919 // Invalid: sfFee
920 {
921 auto const batchFee = batch::calcBatchFee(env, 1, 2);
922 auto const seq = env.seq(alice);
923 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
924 tx1.removeMember(jss::Fee);
925 auto jt = env.jtnofill(
926 batch::outer(alice, seq, batchFee, tfAllOrNothing),
927 tx1,
928 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
929
930 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
931 env.close();
932 }
933
934 // Invalid: sfSigningPubKey
935 {
936 auto const batchFee = batch::calcBatchFee(env, 1, 2);
937 auto const seq = env.seq(alice);
938 auto tx1 = batch::inner(pay(alice, bob, XRP(10)), seq + 1);
939 tx1.removeMember(jss::SigningPubKey);
940 auto jt = env.jtnofill(
941 batch::outer(alice, seq, batchFee, tfAllOrNothing),
942 tx1,
943 batch::inner(pay(alice, bob, XRP(10)), seq + 2));
944
945 env(jt.jv, batch::sig(bob), ter(telENV_RPC_FAILED));
946 env.close();
947 }
948 }
949
950 void
952 {
953 testcase("bad sequence");
954
955 using namespace test::jtx;
956 using namespace std::literals;
957
958 test::jtx::Env env{*this, features};
959
960 auto const alice = Account("alice");
961 auto const bob = Account("bob");
962 auto const gw = Account("gw");
963 auto const USD = gw["USD"];
964
965 env.fund(XRP(10000), alice, bob, gw);
966 env.close();
967 env.trust(USD(1000), alice, bob);
968 env(pay(gw, alice, USD(100)));
969 env(pay(gw, bob, USD(100)));
970 env.close();
971
972 env(noop(bob), ter(tesSUCCESS));
973 env.close();
974
975 // Invalid: Alice Sequence is a past sequence
976 {
977 auto const preAliceSeq = env.seq(alice);
978 auto const preAlice = env.balance(alice);
979 auto const preAliceUSD = env.balance(alice, USD.issue());
980 auto const preBobSeq = env.seq(bob);
981 auto const preBob = env.balance(bob);
982 auto const preBobUSD = env.balance(bob, USD.issue());
983
984 auto const batchFee = batch::calcBatchFee(env, 1, 2);
985 auto const [txIDs, batchID] = submitBatch(
986 env,
988 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
989 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq - 10),
990 batch::inner(pay(bob, alice, XRP(5)), preBobSeq),
991 batch::sig(bob));
992
993 env.close();
994 {
995 std::vector<TestLedgerData> testCases = {
996 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
997 };
998 validateClosedLedger(env, testCases);
999 }
1000
1001 env.close();
1002 {
1003 // next ledger is empty
1004 std::vector<TestLedgerData> testCases = {};
1005 validateClosedLedger(env, testCases);
1006 }
1007
1008 // Alice pays fee & Bob should not be affected.
1009 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1010 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1011 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1012 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1013 BEAST_EXPECT(env.balance(bob) == preBob);
1014 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1015 }
1016
1017 // Invalid: Alice Sequence is a future sequence
1018 {
1019 auto const preAliceSeq = env.seq(alice);
1020 auto const preAlice = env.balance(alice);
1021 auto const preAliceUSD = env.balance(alice, USD.issue());
1022 auto const preBobSeq = env.seq(bob);
1023 auto const preBob = env.balance(bob);
1024 auto const preBobUSD = env.balance(bob, USD.issue());
1025
1026 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1027 auto const [txIDs, batchID] = submitBatch(
1028 env,
1029 tesSUCCESS,
1030 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
1031 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq + 10),
1032 batch::inner(pay(bob, alice, XRP(5)), preBobSeq),
1033 batch::sig(bob));
1034
1035 env.close();
1036 {
1037 std::vector<TestLedgerData> testCases = {
1038 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1039 };
1040 validateClosedLedger(env, testCases);
1041 }
1042
1043 env.close();
1044 {
1045 // next ledger is empty
1046 std::vector<TestLedgerData> testCases = {};
1047 validateClosedLedger(env, testCases);
1048 }
1049
1050 // Alice pays fee & Bob should not be affected.
1051 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1052 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1053 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1054 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1055 BEAST_EXPECT(env.balance(bob) == preBob);
1056 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1057 }
1058
1059 // Invalid: Bob Sequence is a past sequence
1060 {
1061 auto const preAliceSeq = env.seq(alice);
1062 auto const preAlice = env.balance(alice);
1063 auto const preAliceUSD = env.balance(alice, USD.issue());
1064 auto const preBobSeq = env.seq(bob);
1065 auto const preBob = env.balance(bob);
1066 auto const preBobUSD = env.balance(bob, USD.issue());
1067
1068 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1069 auto const [txIDs, batchID] = submitBatch(
1070 env,
1071 tesSUCCESS,
1072 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
1073 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq + 1),
1074 batch::inner(pay(bob, alice, XRP(5)), preBobSeq - 10),
1075 batch::sig(bob));
1076
1077 env.close();
1078 {
1079 std::vector<TestLedgerData> testCases = {
1080 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1081 };
1082 validateClosedLedger(env, testCases);
1083 }
1084
1085 env.close();
1086 {
1087 // next ledger is empty
1088 std::vector<TestLedgerData> testCases = {};
1089 validateClosedLedger(env, testCases);
1090 }
1091
1092 // Alice pays fee & Bob should not be affected.
1093 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1094 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1095 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1096 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1097 BEAST_EXPECT(env.balance(bob) == preBob);
1098 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1099 }
1100
1101 // Invalid: Bob Sequence is a future sequence
1102 {
1103 auto const preAliceSeq = env.seq(alice);
1104 auto const preAlice = env.balance(alice);
1105 auto const preAliceUSD = env.balance(alice, USD.issue());
1106 auto const preBobSeq = env.seq(bob);
1107 auto const preBob = env.balance(bob);
1108 auto const preBobUSD = env.balance(bob, USD.issue());
1109
1110 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1111 auto const [txIDs, batchID] = submitBatch(
1112 env,
1113 tesSUCCESS,
1114 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
1115 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq + 1),
1116 batch::inner(pay(bob, alice, XRP(5)), preBobSeq + 10),
1117 batch::sig(bob));
1118
1119 env.close();
1120 {
1121 std::vector<TestLedgerData> testCases = {
1122 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1123 };
1124 validateClosedLedger(env, testCases);
1125 }
1126
1127 env.close();
1128 {
1129 // next ledger is empty
1130 std::vector<TestLedgerData> testCases = {};
1131 validateClosedLedger(env, testCases);
1132 }
1133
1134 // Alice pays fee & Bob should not be affected.
1135 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1136 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1137 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1138 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1139 BEAST_EXPECT(env.balance(bob) == preBob);
1140 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1141 }
1142
1143 // Invalid: Outer and Inner Sequence are the same
1144 {
1145 auto const preAliceSeq = env.seq(alice);
1146 auto const preAlice = env.balance(alice);
1147 auto const preAliceUSD = env.balance(alice, USD.issue());
1148 auto const preBobSeq = env.seq(bob);
1149 auto const preBob = env.balance(bob);
1150 auto const preBobUSD = env.balance(bob, USD.issue());
1151
1152 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1153 auto const [txIDs, batchID] = submitBatch(
1154 env,
1155 tesSUCCESS,
1156 batch::outer(alice, preAliceSeq, batchFee, tfAllOrNothing),
1157 batch::inner(pay(alice, bob, XRP(10)), preAliceSeq),
1158 batch::inner(pay(bob, alice, XRP(5)), preBobSeq),
1159 batch::sig(bob));
1160
1161 env.close();
1162 {
1163 std::vector<TestLedgerData> testCases = {
1164 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1165 };
1166 validateClosedLedger(env, testCases);
1167 }
1168
1169 env.close();
1170 {
1171 // next ledger is empty
1172 std::vector<TestLedgerData> testCases = {};
1173 validateClosedLedger(env, testCases);
1174 }
1175
1176 // Alice pays fee & Bob should not be affected.
1177 BEAST_EXPECT(env.seq(alice) == preAliceSeq + 1);
1178 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1179 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
1180 BEAST_EXPECT(env.seq(bob) == preBobSeq);
1181 BEAST_EXPECT(env.balance(bob) == preBob);
1182 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
1183 }
1184 }
1185
1186 void
1188 {
1189 testcase("bad outer fee");
1190
1191 using namespace test::jtx;
1192 using namespace std::literals;
1193
1194 // Bad Fee Without Signer
1195 {
1196 test::jtx::Env env{*this, features};
1197
1198 auto const alice = Account("alice");
1199 auto const bob = Account("bob");
1200 env.fund(XRP(10000), alice, bob);
1201 env.close();
1202
1203 env(noop(bob), ter(tesSUCCESS));
1204 env.close();
1205
1206 // Bad Fee: Should be batch::calcBatchFee(env, 0, 2)
1207 auto const batchFee = batch::calcBatchFee(env, 0, 1);
1208 auto const aliceSeq = env.seq(alice);
1209 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1210 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1211 batch::inner(pay(alice, bob, XRP(15)), aliceSeq + 2),
1213 env.close();
1214 }
1215
1216 // Bad Fee With MultiSign
1217 {
1218 test::jtx::Env env{*this, features};
1219
1220 auto const alice = Account("alice");
1221 auto const bob = Account("bob");
1222 auto const carol = Account("carol");
1223 env.fund(XRP(10000), alice, bob, carol);
1224 env.close();
1225
1226 env(noop(bob), ter(tesSUCCESS));
1227 env.close();
1228
1229 env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
1230 env.close();
1231
1232 // Bad Fee: Should be batch::calcBatchFee(env, 2, 2)
1233 auto const batchFee = batch::calcBatchFee(env, 1, 2);
1234 auto const aliceSeq = env.seq(alice);
1235 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1236 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1237 batch::inner(pay(alice, bob, XRP(15)), aliceSeq + 2),
1238 msig(bob, carol),
1240 env.close();
1241 }
1242
1243 // Bad Fee With MultiSign + BatchSigners
1244 {
1245 test::jtx::Env env{*this, features};
1246
1247 auto const alice = Account("alice");
1248 auto const bob = Account("bob");
1249 auto const carol = Account("carol");
1250 env.fund(XRP(10000), alice, bob, carol);
1251 env.close();
1252
1253 env(noop(bob), ter(tesSUCCESS));
1254 env.close();
1255
1256 env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
1257 env.close();
1258
1259 // Bad Fee: Should be batch::calcBatchFee(env, 3, 2)
1260 auto const batchFee = batch::calcBatchFee(env, 2, 2);
1261 auto const aliceSeq = env.seq(alice);
1262 auto const bobSeq = env.seq(bob);
1263 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1264 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1265 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
1266 batch::sig(bob),
1267 msig(bob, carol),
1269 env.close();
1270 }
1271
1272 // Bad Fee With MultiSign + BatchSigners.Signers
1273 {
1274 test::jtx::Env env{*this, features};
1275
1276 auto const alice = Account("alice");
1277 auto const bob = Account("bob");
1278 auto const carol = Account("carol");
1279 env.fund(XRP(10000), alice, bob, carol);
1280 env.close();
1281
1282 env(noop(bob), ter(tesSUCCESS));
1283 env.close();
1284
1285 env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
1286 env.close();
1287
1288 env(signers(bob, 2, {{alice, 1}, {carol, 1}}));
1289 env.close();
1290
1291 // Bad Fee: Should be batch::calcBatchFee(env, 4, 2)
1292 auto const batchFee = batch::calcBatchFee(env, 3, 2);
1293 auto const aliceSeq = env.seq(alice);
1294 auto const bobSeq = env.seq(bob);
1295 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1296 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1297 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
1298 batch::msig(bob, {alice, carol}),
1299 msig(bob, carol),
1301 env.close();
1302 }
1303
1304 // Bad Fee With BatchSigners
1305 {
1306 test::jtx::Env env{*this, features};
1307
1308 auto const alice = Account("alice");
1309 auto const bob = Account("bob");
1310 env.fund(XRP(10000), alice, bob);
1311 env.close();
1312
1313 env(noop(bob), ter(tesSUCCESS));
1314 env.close();
1315
1316 // Bad Fee: Should be batch::calcBatchFee(env, 1, 2)
1317 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1318 auto const aliceSeq = env.seq(alice);
1319 auto const bobSeq = env.seq(bob);
1320 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1321 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1322 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
1323 batch::sig(bob),
1325 env.close();
1326 }
1327
1328 // Bad Fee Dynamic Fee Calculation
1329 {
1330 test::jtx::Env env{*this, features};
1331
1332 auto const alice = Account("alice");
1333 auto const bob = Account("bob");
1334 auto const gw = Account("gw");
1335 auto const USD = gw["USD"];
1336
1337 env.fund(XRP(10000), alice, bob, gw);
1338 env.close();
1339 auto const ammCreate =
1340 [&alice](STAmount const& amount, STAmount const& amount2) {
1341 Json::Value jv;
1342 jv[jss::Account] = alice.human();
1343 jv[jss::Amount] = amount.getJson(JsonOptions::none);
1344 jv[jss::Amount2] = amount2.getJson(JsonOptions::none);
1345 jv[jss::TradingFee] = 0;
1346 jv[jss::TransactionType] = jss::AMMCreate;
1347 return jv;
1348 };
1349
1350 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1351 auto const seq = env.seq(alice);
1352 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
1353 batch::inner(ammCreate(XRP(10), USD(10)), seq + 1),
1354 batch::inner(pay(alice, bob, XRP(10)), seq + 2),
1356 env.close();
1357 }
1358 }
1359
1360 void
1362 {
1363 testcase("calculate base fee");
1364
1365 using namespace test::jtx;
1366 using namespace std::literals;
1367
1368 // telENV_RPC_FAILED: Batch: txns array exceeds 8 entries.
1369 {
1370 test::jtx::Env env{*this, features};
1371
1372 auto const alice = Account("alice");
1373 auto const bob = Account("bob");
1374 env.fund(XRP(10000), alice, bob);
1375 env.close();
1376
1377 auto const batchFee = batch::calcBatchFee(env, 0, 9);
1378 auto const aliceSeq = env.seq(alice);
1379 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1380 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1381 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1382 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1383 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1384 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1385 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1386 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1387 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1388 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1390 env.close();
1391 }
1392
1393 // temARRAY_TOO_LARGE: Batch: txns array exceeds 8 entries.
1394 {
1395 test::jtx::Env env{*this, features};
1396
1397 auto const alice = Account("alice");
1398 auto const bob = Account("bob");
1399 env.fund(XRP(10000), alice, bob);
1400 env.close();
1401
1402 auto const batchFee = batch::calcBatchFee(env, 0, 9);
1403 auto const aliceSeq = env.seq(alice);
1404 auto jt = env.jtnofill(
1405 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1406 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1407 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1408 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1409 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1410 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1411 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1412 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1413 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
1414 batch::inner(pay(alice, bob, XRP(1)), aliceSeq));
1415
1416 env.app().openLedger().modify(
1417 [&](OpenView& view, beast::Journal j) {
1418 auto const result =
1419 xrpl::apply(env.app(), view, *jt.stx, tapNONE, j);
1420 BEAST_EXPECT(
1421 !result.applied && result.ter == temARRAY_TOO_LARGE);
1422 return result.applied;
1423 });
1424 }
1425
1426 // telENV_RPC_FAILED: Batch: signers array exceeds 8 entries.
1427 {
1428 test::jtx::Env env{*this, features};
1429
1430 auto const alice = Account("alice");
1431 auto const bob = Account("bob");
1432 env.fund(XRP(10000), alice, bob);
1433 env.close();
1434
1435 auto const aliceSeq = env.seq(alice);
1436 auto const batchFee = batch::calcBatchFee(env, 9, 2);
1437 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1438 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1439 batch::inner(pay(alice, bob, XRP(5)), aliceSeq + 2),
1440 batch::sig(bob, bob, bob, bob, bob, bob, bob, bob, bob, bob),
1442 env.close();
1443 }
1444
1445 // temARRAY_TOO_LARGE: Batch: signers array exceeds 8 entries.
1446 {
1447 test::jtx::Env env{*this, features};
1448
1449 auto const alice = Account("alice");
1450 auto const bob = Account("bob");
1451 env.fund(XRP(10000), alice, bob);
1452 env.close();
1453
1454 auto const batchFee = batch::calcBatchFee(env, 0, 9);
1455 auto const aliceSeq = env.seq(alice);
1456 auto jt = env.jtnofill(
1457 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
1458 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
1459 batch::inner(pay(alice, bob, XRP(5)), aliceSeq + 2),
1460 batch::sig(bob, bob, bob, bob, bob, bob, bob, bob, bob, bob));
1461
1462 env.app().openLedger().modify(
1463 [&](OpenView& view, beast::Journal j) {
1464 auto const result =
1465 xrpl::apply(env.app(), view, *jt.stx, tapNONE, j);
1466 BEAST_EXPECT(
1467 !result.applied && result.ter == temARRAY_TOO_LARGE);
1468 return result.applied;
1469 });
1470 }
1471 }
1472
1473 void
1475 {
1476 testcase("all or nothing");
1477
1478 using namespace test::jtx;
1479 using namespace std::literals;
1480
1481 test::jtx::Env env{*this, features};
1482
1483 auto const alice = Account("alice");
1484 auto const bob = Account("bob");
1485 auto const gw = Account("gw");
1486 auto const USD = gw["USD"];
1487 env.fund(XRP(10000), alice, bob, gw);
1488 env.close();
1489
1490 // all
1491 {
1492 auto const preAlice = env.balance(alice);
1493 auto const preBob = env.balance(bob);
1494
1495 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1496 auto const seq = env.seq(alice);
1497 auto const [txIDs, batchID] = submitBatch(
1498 env,
1499 tesSUCCESS,
1500 batch::outer(alice, seq, batchFee, tfAllOrNothing),
1501 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1502 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
1503 env.close();
1504
1505 std::vector<TestLedgerData> testCases = {
1506 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1507 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1508 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1509 };
1510 validateClosedLedger(env, testCases);
1511
1512 // Alice consumes sequences (# of txns)
1513 BEAST_EXPECT(env.seq(alice) == seq + 3);
1514
1515 // Alice pays XRP & Fee; Bob receives XRP
1516 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
1517 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
1518 }
1519
1520 // tec failure
1521 {
1522 auto const preAlice = env.balance(alice);
1523 auto const preBob = env.balance(bob);
1524
1525 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1526 auto const seq = env.seq(alice);
1527
1528 auto const [txIDs, batchID] = submitBatch(
1529 env,
1530 tesSUCCESS,
1531 batch::outer(alice, seq, batchFee, tfAllOrNothing),
1532 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1533 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1534 batch::inner(pay(alice, bob, XRP(9999)), seq + 2));
1535 env.close();
1536
1537 std::vector<TestLedgerData> testCases = {
1538 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1539 };
1540 validateClosedLedger(env, testCases);
1541
1542 // Alice consumes sequence
1543 BEAST_EXPECT(env.seq(alice) == seq + 1);
1544
1545 // Alice pays Fee; Bob should not be affected
1546 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1547 BEAST_EXPECT(env.balance(bob) == preBob);
1548 }
1549
1550 // tef failure
1551 {
1552 auto const preAlice = env.balance(alice);
1553 auto const preBob = env.balance(bob);
1554
1555 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1556 auto const seq = env.seq(alice);
1557 auto const [txIDs, batchID] = submitBatch(
1558 env,
1559 tesSUCCESS,
1560 batch::outer(alice, seq, batchFee, tfAllOrNothing),
1561 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1562 // tefNO_AUTH_REQUIRED: trustline auth is not required
1563 batch::inner(trust(alice, USD(1000), tfSetfAuth), seq + 2));
1564 env.close();
1565
1566 std::vector<TestLedgerData> testCases = {
1567 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1568 };
1569 validateClosedLedger(env, testCases);
1570
1571 // Alice consumes sequence
1572 BEAST_EXPECT(env.seq(alice) == seq + 1);
1573
1574 // Alice pays Fee; Bob should not be affected
1575 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1576 BEAST_EXPECT(env.balance(bob) == preBob);
1577 }
1578
1579 // ter failure
1580 {
1581 auto const preAlice = env.balance(alice);
1582 auto const preBob = env.balance(bob);
1583
1584 auto const batchFee = batch::calcBatchFee(env, 0, 2);
1585 auto const seq = env.seq(alice);
1586 auto const [txIDs, batchID] = submitBatch(
1587 env,
1588 tesSUCCESS,
1589 batch::outer(alice, seq, batchFee, tfAllOrNothing),
1590 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1591 // terPRE_TICKET: ticket does not exist
1592 batch::inner(trust(alice, USD(1000), tfSetfAuth), 0, seq + 2));
1593 env.close();
1594
1595 std::vector<TestLedgerData> testCases = {
1596 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1597 };
1598 validateClosedLedger(env, testCases);
1599
1600 // Alice consumes sequence
1601 BEAST_EXPECT(env.seq(alice) == seq + 1);
1602
1603 // Alice pays Fee; Bob should not be affected
1604 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1605 BEAST_EXPECT(env.balance(bob) == preBob);
1606 }
1607 }
1608
1609 void
1611 {
1612 testcase("only one");
1613
1614 using namespace test::jtx;
1615 using namespace std::literals;
1616
1617 test::jtx::Env env{*this, features};
1618
1619 auto const alice = Account("alice");
1620 auto const bob = Account("bob");
1621 auto const carol = Account("carol");
1622 auto const dave = Account("dave");
1623 auto const gw = Account("gw");
1624 auto const USD = gw["USD"];
1625 env.fund(XRP(10000), alice, bob, carol, dave, gw);
1626 env.close();
1627
1628 // all transactions fail
1629 {
1630 auto const preAlice = env.balance(alice);
1631 auto const preBob = env.balance(bob);
1632
1633 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1634 auto const seq = env.seq(alice);
1635 auto const [txIDs, batchID] = submitBatch(
1636 env,
1637 tesSUCCESS,
1638 batch::outer(alice, seq, batchFee, tfOnlyOne),
1639 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1640 batch::inner(pay(alice, bob, XRP(9999)), seq + 1),
1641 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1642 batch::inner(pay(alice, bob, XRP(9999)), seq + 2),
1643 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1644 batch::inner(pay(alice, bob, XRP(9999)), seq + 3));
1645 env.close();
1646
1647 std::vector<TestLedgerData> testCases = {
1648 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1649 {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID},
1650 {2, "Payment", "tecUNFUNDED_PAYMENT", txIDs[1], batchID},
1651 {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID},
1652 };
1653 validateClosedLedger(env, testCases);
1654
1655 // Alice consumes sequences (# of txns)
1656 BEAST_EXPECT(env.seq(alice) == seq + 4);
1657
1658 // Alice pays XRP & Fee; Bob receives XRP
1659 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1660 BEAST_EXPECT(env.balance(bob) == preBob);
1661 }
1662
1663 // first transaction fails
1664 {
1665 auto const preAlice = env.balance(alice);
1666 auto const preBob = env.balance(bob);
1667
1668 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1669 auto const seq = env.seq(alice);
1670 auto const [txIDs, batchID] = submitBatch(
1671 env,
1672 tesSUCCESS,
1673 batch::outer(alice, seq, batchFee, tfOnlyOne),
1674 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1675 batch::inner(pay(alice, bob, XRP(9999)), seq + 1),
1676 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
1677 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
1678 env.close();
1679
1680 std::vector<TestLedgerData> testCases = {
1681 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1682 {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID},
1683 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1684 };
1685 validateClosedLedger(env, testCases);
1686
1687 // Alice consumes sequences (# of txns)
1688 BEAST_EXPECT(env.seq(alice) == seq + 3);
1689
1690 // Alice pays XRP & Fee; Bob receives XRP
1691 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
1692 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
1693 }
1694
1695 // tec failure
1696 {
1697 auto const preAlice = env.balance(alice);
1698 auto const preBob = env.balance(bob);
1699
1700 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1701 auto const seq = env.seq(alice);
1702 auto const [txIDs, batchID] = submitBatch(
1703 env,
1704 tesSUCCESS,
1705 batch::outer(alice, seq, batchFee, tfOnlyOne),
1706 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1707 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1708 batch::inner(pay(alice, bob, XRP(9999)), seq + 2),
1709 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
1710 env.close();
1711
1712 std::vector<TestLedgerData> testCases = {
1713 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1714 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1715 };
1716 validateClosedLedger(env, testCases);
1717
1718 // Alice consumes sequences (# of txns)
1719 BEAST_EXPECT(env.seq(alice) == seq + 2);
1720
1721 // Alice pays XRP & Fee; Bob receives XRP
1722 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
1723 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
1724 }
1725
1726 // tef failure
1727 {
1728 auto const preAlice = env.balance(alice);
1729 auto const preBob = env.balance(bob);
1730
1731 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1732 auto const seq = env.seq(alice);
1733 auto const [txIDs, batchID] = submitBatch(
1734 env,
1735 tesSUCCESS,
1736 batch::outer(alice, seq, batchFee, tfOnlyOne),
1737 // tefNO_AUTH_REQUIRED: trustline auth is not required
1738 batch::inner(trust(alice, USD(1000), tfSetfAuth), seq + 1),
1739 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1740 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
1741 env.close();
1742
1743 std::vector<TestLedgerData> testCases = {
1744 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1745 {1, "Payment", "tesSUCCESS", txIDs[1], batchID},
1746 };
1747 validateClosedLedger(env, testCases);
1748
1749 // Alice consumes sequences (# of txns)
1750 BEAST_EXPECT(env.seq(alice) == seq + 2);
1751
1752 // Alice pays XRP & Fee; Bob receives XRP
1753 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee - XRP(1));
1754 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
1755 }
1756
1757 // ter failure
1758 {
1759 auto const preAlice = env.balance(alice);
1760 auto const preBob = env.balance(bob);
1761
1762 auto const batchFee = batch::calcBatchFee(env, 0, 3);
1763 auto const seq = env.seq(alice);
1764 auto const [txIDs, batchID] = submitBatch(
1765 env,
1766 tesSUCCESS,
1767 batch::outer(alice, seq, batchFee, tfOnlyOne),
1768 // terPRE_TICKET: ticket does not exist
1769 batch::inner(trust(alice, USD(1000), tfSetfAuth), 0, seq + 1),
1770 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1771 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
1772 env.close();
1773
1774 std::vector<TestLedgerData> testCases = {
1775 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1776 {1, "Payment", "tesSUCCESS", txIDs[1], batchID},
1777 };
1778 validateClosedLedger(env, testCases);
1779
1780 // Alice consumes sequences (# of txns)
1781 BEAST_EXPECT(env.seq(alice) == seq + 2);
1782
1783 // Alice pays XRP & Fee; Bob receives XRP
1784 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee - XRP(1));
1785 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
1786 }
1787
1788 // tec (tecKILLED) error
1789 {
1790 auto const preAlice = env.balance(alice);
1791 auto const preBob = env.balance(bob);
1792 auto const preCarol = env.balance(carol);
1793 auto const seq = env.seq(alice);
1794 auto const batchFee = batch::calcBatchFee(env, 0, 6);
1795
1796 auto const [txIDs, batchID] = submitBatch(
1797 env,
1798 tesSUCCESS,
1799 batch::outer(alice, seq, batchFee, tfOnlyOne),
1801 offer(
1802 alice,
1803 alice["USD"](100),
1804 XRP(100),
1806 seq + 1),
1808 offer(
1809 alice,
1810 alice["USD"](100),
1811 XRP(100),
1813 seq + 2),
1815 offer(
1816 alice,
1817 alice["USD"](100),
1818 XRP(100),
1820 seq + 3),
1821 batch::inner(pay(alice, bob, XRP(100)), seq + 4),
1822 batch::inner(pay(alice, carol, XRP(100)), seq + 5),
1823 batch::inner(pay(alice, dave, XRP(100)), seq + 6));
1824 env.close();
1825
1826 std::vector<TestLedgerData> testCases = {
1827 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1828 {1, "OfferCreate", "tecKILLED", txIDs[0], batchID},
1829 {2, "OfferCreate", "tecKILLED", txIDs[1], batchID},
1830 {3, "OfferCreate", "tecKILLED", txIDs[2], batchID},
1831 {4, "Payment", "tesSUCCESS", txIDs[3], batchID},
1832 };
1833 validateClosedLedger(env, testCases);
1834
1835 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(100) - batchFee);
1836 BEAST_EXPECT(env.balance(bob) == preBob + XRP(100));
1837 BEAST_EXPECT(env.balance(carol) == preCarol);
1838 }
1839 }
1840
1841 void
1843 {
1844 testcase("until failure");
1845
1846 using namespace test::jtx;
1847 using namespace std::literals;
1848
1849 test::jtx::Env env{*this, features};
1850
1851 auto const alice = Account("alice");
1852 auto const bob = Account("bob");
1853 auto const carol = Account("carol");
1854 auto const dave = Account("dave");
1855 auto const gw = Account("gw");
1856 auto const USD = gw["USD"];
1857 env.fund(XRP(10000), alice, bob, carol, dave, gw);
1858 env.close();
1859
1860 // first transaction fails
1861 {
1862 auto const preAlice = env.balance(alice);
1863 auto const preBob = env.balance(bob);
1864
1865 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1866 auto const seq = env.seq(alice);
1867 auto const [txIDs, batchID] = submitBatch(
1868 env,
1869 tesSUCCESS,
1870 batch::outer(alice, seq, batchFee, tfUntilFailure),
1871 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1872 batch::inner(pay(alice, bob, XRP(9999)), seq + 1),
1873 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
1874 batch::inner(pay(alice, bob, XRP(2)), seq + 3),
1875 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
1876 env.close();
1877
1878 std::vector<TestLedgerData> testCases = {
1879 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1880 {1, "Payment", "tecUNFUNDED_PAYMENT", txIDs[0], batchID},
1881 };
1882 validateClosedLedger(env, testCases);
1883
1884 // Alice consumes sequences (# of txns)
1885 BEAST_EXPECT(env.seq(alice) == seq + 2);
1886
1887 // Alice pays XRP & Fee; Bob receives XRP
1888 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
1889 BEAST_EXPECT(env.balance(bob) == preBob);
1890 }
1891
1892 // all transactions succeed
1893 {
1894 auto const preAlice = env.balance(alice);
1895 auto const preBob = env.balance(bob);
1896
1897 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1898 auto const seq = env.seq(alice);
1899 auto const [txIDs, batchID] = submitBatch(
1900 env,
1901 tesSUCCESS,
1902 batch::outer(alice, seq, batchFee, tfUntilFailure),
1903 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1904 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
1905 batch::inner(pay(alice, bob, XRP(3)), seq + 3),
1906 batch::inner(pay(alice, bob, XRP(4)), seq + 4));
1907 env.close();
1908
1909 std::vector<TestLedgerData> testCases = {
1910 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1911 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1912 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1913 {3, "Payment", "tesSUCCESS", txIDs[2], batchID},
1914 {4, "Payment", "tesSUCCESS", txIDs[3], batchID},
1915 };
1916 validateClosedLedger(env, testCases);
1917
1918 // Alice consumes sequences (# of txns)
1919 BEAST_EXPECT(env.seq(alice) == seq + 5);
1920
1921 // Alice pays XRP & Fee; Bob receives XRP
1922 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(10) - batchFee);
1923 BEAST_EXPECT(env.balance(bob) == preBob + XRP(10));
1924 }
1925
1926 // tec error
1927 {
1928 auto const preAlice = env.balance(alice);
1929 auto const preBob = env.balance(bob);
1930
1931 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1932 auto const seq = env.seq(alice);
1933 auto const [txIDs, batchID] = submitBatch(
1934 env,
1935 tesSUCCESS,
1936 batch::outer(alice, seq, batchFee, tfUntilFailure),
1937 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1938 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
1939 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
1940 batch::inner(pay(alice, bob, XRP(9999)), seq + 3),
1941 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
1942 env.close();
1943
1944 std::vector<TestLedgerData> testCases = {
1945 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1946 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1947 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1948 {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID},
1949 };
1950 validateClosedLedger(env, testCases);
1951
1952 // Alice consumes sequences (# of txns)
1953 BEAST_EXPECT(env.seq(alice) == seq + 4);
1954
1955 // Alice pays XRP & Fee; Bob receives XRP
1956 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
1957 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
1958 }
1959
1960 // tef error
1961 {
1962 auto const preAlice = env.balance(alice);
1963 auto const preBob = env.balance(bob);
1964
1965 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1966 auto const seq = env.seq(alice);
1967 auto const [txIDs, batchID] = submitBatch(
1968 env,
1969 tesSUCCESS,
1970 batch::outer(alice, seq, batchFee, tfUntilFailure),
1971 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
1972 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
1973 // tefNO_AUTH_REQUIRED: trustline auth is not required
1974 batch::inner(trust(alice, USD(1000), tfSetfAuth), seq + 3),
1975 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
1976 env.close();
1977
1978 std::vector<TestLedgerData> testCases = {
1979 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
1980 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
1981 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
1982 };
1983 validateClosedLedger(env, testCases);
1984
1985 // Alice consumes sequences (# of txns)
1986 BEAST_EXPECT(env.seq(alice) == seq + 3);
1987
1988 // Alice pays XRP & Fee; Bob receives XRP
1989 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
1990 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
1991 }
1992
1993 // ter error
1994 {
1995 auto const preAlice = env.balance(alice);
1996 auto const preBob = env.balance(bob);
1997
1998 auto const batchFee = batch::calcBatchFee(env, 0, 4);
1999 auto const seq = env.seq(alice);
2000 auto const [txIDs, batchID] = submitBatch(
2001 env,
2002 tesSUCCESS,
2003 batch::outer(alice, seq, batchFee, tfUntilFailure),
2004 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2005 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
2006 // terPRE_TICKET: ticket does not exist
2007 batch::inner(trust(alice, USD(1000), tfSetfAuth), 0, seq + 3),
2008 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
2009 env.close();
2010
2011 std::vector<TestLedgerData> testCases = {
2012 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2013 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2014 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2015 };
2016 validateClosedLedger(env, testCases);
2017
2018 // Alice consumes sequences (# of txns)
2019 BEAST_EXPECT(env.seq(alice) == seq + 3);
2020
2021 // Alice pays XRP & Fee; Bob receives XRP
2022 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
2023 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2024 }
2025
2026 // tec (tecKILLED) error
2027 {
2028 auto const preAlice = env.balance(alice);
2029 auto const preBob = env.balance(bob);
2030 auto const preCarol = env.balance(carol);
2031 auto const seq = env.seq(alice);
2032 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2033 auto const [txIDs, batchID] = submitBatch(
2034 env,
2035 tesSUCCESS,
2036 batch::outer(alice, seq, batchFee, tfUntilFailure),
2037 batch::inner(pay(alice, bob, XRP(100)), seq + 1),
2038 batch::inner(pay(alice, carol, XRP(100)), seq + 2),
2040 offer(
2041 alice,
2042 alice["USD"](100),
2043 XRP(100),
2045 seq + 3),
2046 batch::inner(pay(alice, dave, XRP(100)), seq + 4));
2047 env.close();
2048
2049 std::vector<TestLedgerData> testCases = {
2050 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2051 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2052 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2053 {3, "OfferCreate", "tecKILLED", txIDs[2], batchID},
2054 };
2055 validateClosedLedger(env, testCases);
2056
2057 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(200) - batchFee);
2058 BEAST_EXPECT(env.balance(bob) == preBob + XRP(100));
2059 BEAST_EXPECT(env.balance(carol) == preCarol + XRP(100));
2060 }
2061 }
2062
2063 void
2065 {
2066 testcase("independent");
2067
2068 using namespace test::jtx;
2069 using namespace std::literals;
2070
2071 test::jtx::Env env{*this, features};
2072
2073 auto const alice = Account("alice");
2074 auto const bob = Account("bob");
2075 auto const carol = Account("carol");
2076 auto const gw = Account("gw");
2077 auto const USD = gw["USD"];
2078 env.fund(XRP(10000), alice, bob, carol, gw);
2079 env.close();
2080
2081 // multiple transactions fail
2082 {
2083 auto const preAlice = env.balance(alice);
2084 auto const preBob = env.balance(bob);
2085
2086 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2087 auto const seq = env.seq(alice);
2088 auto const [txIDs, batchID] = submitBatch(
2089 env,
2090 tesSUCCESS,
2091 batch::outer(alice, seq, batchFee, tfIndependent),
2092 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2093 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
2094 batch::inner(pay(alice, bob, XRP(9999)), seq + 2),
2095 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
2096 batch::inner(pay(alice, bob, XRP(9999)), seq + 3),
2097 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
2098 env.close();
2099
2100 std::vector<TestLedgerData> testCases = {
2101 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2102 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2103 {2, "Payment", "tecUNFUNDED_PAYMENT", txIDs[1], batchID},
2104 {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID},
2105 {4, "Payment", "tesSUCCESS", txIDs[3], batchID},
2106 };
2107 validateClosedLedger(env, testCases);
2108
2109 // Alice consumes sequences (# of txns)
2110 BEAST_EXPECT(env.seq(alice) == seq + 5);
2111
2112 // Alice pays XRP & Fee; Bob receives XRP
2113 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(4) - batchFee);
2114 BEAST_EXPECT(env.balance(bob) == preBob + XRP(4));
2115 }
2116
2117 // tec error
2118 {
2119 auto const preAlice = env.balance(alice);
2120 auto const preBob = env.balance(bob);
2121
2122 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2123 auto const seq = env.seq(alice);
2124 auto const [txIDs, batchID] = submitBatch(
2125 env,
2126 tesSUCCESS,
2127 batch::outer(alice, seq, batchFee, tfIndependent),
2128 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2129 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
2130 // tecUNFUNDED_PAYMENT: alice does not have enough XRP
2131 batch::inner(pay(alice, bob, XRP(9999)), seq + 3),
2132 batch::inner(pay(alice, bob, XRP(3)), seq + 4));
2133 env.close();
2134
2135 std::vector<TestLedgerData> testCases = {
2136 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2137 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2138 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2139 {3, "Payment", "tecUNFUNDED_PAYMENT", txIDs[2], batchID},
2140 {4, "Payment", "tesSUCCESS", txIDs[3], batchID},
2141 };
2142 validateClosedLedger(env, testCases);
2143
2144 // Alice consumes sequences (# of txns)
2145 BEAST_EXPECT(env.seq(alice) == seq + 5);
2146
2147 // Alice pays XRP & Fee; Bob receives XRP
2148 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(6) - batchFee);
2149 BEAST_EXPECT(env.balance(bob) == preBob + XRP(6));
2150 }
2151
2152 // tef error
2153 {
2154 auto const preAlice = env.balance(alice);
2155 auto const preBob = env.balance(bob);
2156
2157 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2158 auto const seq = env.seq(alice);
2159 auto const [txIDs, batchID] = submitBatch(
2160 env,
2161 tesSUCCESS,
2162 batch::outer(alice, seq, batchFee, tfIndependent),
2163 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2164 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
2165 // tefNO_AUTH_REQUIRED: trustline auth is not required
2166 batch::inner(trust(alice, USD(1000), tfSetfAuth), seq + 3),
2167 batch::inner(pay(alice, bob, XRP(3)), seq + 3));
2168 env.close();
2169
2170 std::vector<TestLedgerData> testCases = {
2171 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2172 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2173 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2174 {3, "Payment", "tesSUCCESS", txIDs[3], batchID},
2175 };
2176 validateClosedLedger(env, testCases);
2177
2178 // Alice consumes sequences (# of txns)
2179 BEAST_EXPECT(env.seq(alice) == seq + 4);
2180
2181 // Alice pays XRP & Fee; Bob receives XRP
2182 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee - XRP(6));
2183 BEAST_EXPECT(env.balance(bob) == preBob + XRP(6));
2184 }
2185
2186 // ter error
2187 {
2188 auto const preAlice = env.balance(alice);
2189 auto const preBob = env.balance(bob);
2190
2191 auto const batchFee = batch::calcBatchFee(env, 0, 4);
2192 auto const seq = env.seq(alice);
2193 auto const [txIDs, batchID] = submitBatch(
2194 env,
2195 tesSUCCESS,
2196 batch::outer(alice, seq, batchFee, tfIndependent),
2197 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2198 batch::inner(pay(alice, bob, XRP(2)), seq + 2),
2199 // terPRE_TICKET: ticket does not exist
2200 batch::inner(trust(alice, USD(1000), tfSetfAuth), 0, seq + 3),
2201 batch::inner(pay(alice, bob, XRP(3)), seq + 3));
2202 env.close();
2203
2204 std::vector<TestLedgerData> testCases = {
2205 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2206 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2207 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2208 {3, "Payment", "tesSUCCESS", txIDs[3], batchID},
2209 };
2210 validateClosedLedger(env, testCases);
2211
2212 // Alice consumes sequences (# of txns)
2213 BEAST_EXPECT(env.seq(alice) == seq + 4);
2214
2215 // Alice pays XRP & Fee; Bob receives XRP
2216 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee - XRP(6));
2217 BEAST_EXPECT(env.balance(bob) == preBob + XRP(6));
2218 }
2219
2220 // tec (tecKILLED) error
2221 {
2222 auto const preAlice = env.balance(alice);
2223 auto const preBob = env.balance(bob);
2224 auto const preCarol = env.balance(carol);
2225 auto const seq = env.seq(alice);
2226 auto const batchFee = batch::calcBatchFee(env, 0, 3);
2227 auto const [txIDs, batchID] = submitBatch(
2228 env,
2229 tesSUCCESS,
2230 batch::outer(alice, seq, batchFee, tfIndependent),
2231 batch::inner(pay(alice, bob, XRP(100)), seq + 1),
2232 batch::inner(pay(alice, carol, XRP(100)), seq + 2),
2234 offer(
2235 alice,
2236 alice["USD"](100),
2237 XRP(100),
2239 seq + 3));
2240 env.close();
2241
2242 std::vector<TestLedgerData> testCases = {
2243 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2244 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2245 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2246 {3, "OfferCreate", "tecKILLED", txIDs[2], batchID},
2247 };
2248 validateClosedLedger(env, testCases);
2249
2250 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(200) - batchFee);
2251 BEAST_EXPECT(env.balance(bob) == preBob + XRP(100));
2252 BEAST_EXPECT(env.balance(carol) == preCarol + XRP(100));
2253 }
2254 }
2255
2256 void
2257 doTestInnerSubmitRPC(FeatureBitset features, bool withBatch)
2258 {
2259 bool const withInnerSigFix = features[fixBatchInnerSigs];
2260
2261 std::string const testName = [&]() {
2263 ss << "inner submit rpc: batch "
2264 << (withBatch ? "enabled" : "disabled") << ", inner sig fix: "
2265 << (withInnerSigFix ? "enabled" : "disabled") << ": ";
2266 return ss.str();
2267 }();
2268
2269 auto const amend = withBatch ? features : features - featureBatch;
2270
2271 using namespace test::jtx;
2272 using namespace std::literals;
2273
2274 test::jtx::Env env{*this, amend};
2275 if (!BEAST_EXPECT(amend[featureBatch] == withBatch))
2276 return;
2277
2278 auto const alice = Account("alice");
2279 auto const bob = Account("bob");
2280
2281 env.fund(XRP(10000), alice, bob);
2282 env.close();
2283
2284 auto submitAndValidate =
2285 [&](std::string caseName,
2286 Slice const& slice,
2287 int line,
2288 std::optional<std::string> expectedEnabled = std::nullopt,
2289 std::optional<std::string> expectedDisabled = std::nullopt,
2290 bool expectInvalidFlag = false) {
2291 testcase << testName << caseName
2292 << (expectInvalidFlag
2293 ? " - Expected to reach tx engine!"
2294 : "");
2295 auto const jrr = env.rpc("submit", strHex(slice))[jss::result];
2296 auto const expected = withBatch
2297 ? expectedEnabled.value_or(
2298 "fails local checks: Malformed: Invalid inner batch "
2299 "transaction.")
2300 : expectedDisabled.value_or(
2301 "fails local checks: Empty SigningPubKey.");
2302 if (expectInvalidFlag)
2303 {
2304 expect(
2305 jrr[jss::status] == "success" &&
2306 jrr[jss::engine_result] == "temINVALID_FLAG",
2307 pretty(jrr),
2308 __FILE__,
2309 line);
2310 }
2311 else
2312 {
2313 expect(
2314 jrr[jss::status] == "error" &&
2315 jrr[jss::error] == "invalidTransaction" &&
2316 jrr[jss::error_exception] == expected,
2317 pretty(jrr),
2318 __FILE__,
2319 line);
2320 }
2321 env.close();
2322 };
2323
2324 // Invalid RPC Submission: TxnSignature
2325 // + has `TxnSignature` field
2326 // - has no `SigningPubKey` field
2327 // - has no `Signers` field
2328 // + has `tfInnerBatchTxn` flag
2329 {
2330 auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2331 txn[sfTxnSignature] = "DEADBEEF";
2332 STParsedJSONObject parsed("test", txn.getTxn());
2333 Serializer s;
2334 parsed.object->add(s);
2335 submitAndValidate("TxnSignature set", s.slice(), __LINE__);
2336 }
2337
2338 // Invalid RPC Submission: SigningPubKey
2339 // - has no `TxnSignature` field
2340 // + has `SigningPubKey` field
2341 // - has no `Signers` field
2342 // + has `tfInnerBatchTxn` flag
2343 {
2344 auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2345 txn[sfSigningPubKey] = strHex(alice.pk());
2346 STParsedJSONObject parsed("test", txn.getTxn());
2347 Serializer s;
2348 parsed.object->add(s);
2349 submitAndValidate(
2350 "SigningPubKey set",
2351 s.slice(),
2352 __LINE__,
2354 "fails local checks: Invalid signature.");
2355 }
2356
2357 // Invalid RPC Submission: Signers
2358 // - has no `TxnSignature` field
2359 // + has empty `SigningPubKey` field
2360 // + has `Signers` field
2361 // + has `tfInnerBatchTxn` flag
2362 {
2363 auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2364 txn[sfSigners] = Json::arrayValue;
2365 STParsedJSONObject parsed("test", txn.getTxn());
2366 Serializer s;
2367 parsed.object->add(s);
2368 submitAndValidate(
2369 "Signers set",
2370 s.slice(),
2371 __LINE__,
2373 "fails local checks: Invalid Signers array size.");
2374 }
2375
2376 {
2377 // Fully signed inner batch transaction
2378 auto const txn =
2379 batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2380 auto const jt = env.jt(txn.getTxn());
2381
2382 STParsedJSONObject parsed("test", jt.jv);
2383 Serializer s;
2384 parsed.object->add(s);
2385 submitAndValidate(
2386 "Fully signed",
2387 s.slice(),
2388 __LINE__,
2391 !withBatch);
2392 }
2393
2394 // Invalid RPC Submission: tfInnerBatchTxn
2395 // - has no `TxnSignature` field
2396 // + has empty `SigningPubKey` field
2397 // - has no `Signers` field
2398 // + has `tfInnerBatchTxn` flag
2399 {
2400 auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice));
2401 STParsedJSONObject parsed("test", txn.getTxn());
2402 Serializer s;
2403 parsed.object->add(s);
2404 submitAndValidate(
2405 "No signing fields set",
2406 s.slice(),
2407 __LINE__,
2408 "fails local checks: Empty SigningPubKey.",
2409 "fails local checks: Empty SigningPubKey.",
2410 withBatch && !withInnerSigFix);
2411 }
2412
2413 // Invalid RPC Submission: tfInnerBatchTxn pseudo-transaction
2414 // - has no `TxnSignature` field
2415 // + has empty `SigningPubKey` field
2416 // - has no `Signers` field
2417 // + has `tfInnerBatchTxn` flag
2418 {
2419 STTx amendTx(
2420 ttAMENDMENT, [seq = env.closed()->header().seq + 1](auto& obj) {
2421 obj.setAccountID(sfAccount, AccountID());
2422 obj.setFieldH256(sfAmendment, fixBatchInnerSigs);
2423 obj.setFieldU32(sfLedgerSequence, seq);
2424 obj.setFieldU32(sfFlags, tfInnerBatchTxn);
2425 });
2426 auto txn = batch::inner(
2427 amendTx.getJson(JsonOptions::none), env.seq(alice));
2428 STParsedJSONObject parsed("test", txn.getTxn());
2429 Serializer s;
2430 parsed.object->add(s);
2431 submitAndValidate(
2432 "Pseudo-transaction",
2433 s.slice(),
2434 __LINE__,
2435 withInnerSigFix
2436 ? "fails local checks: Empty SigningPubKey."
2437 : "fails local checks: Cannot submit pseudo transactions.",
2438 "fails local checks: Empty SigningPubKey.");
2439 }
2440 }
2441
2442 void
2444 {
2445 for (bool const withBatch : {true, false})
2446 {
2447 doTestInnerSubmitRPC(features, withBatch);
2448 }
2449 }
2450
2451 void
2453 {
2454 testcase("account activation");
2455
2456 using namespace test::jtx;
2457 using namespace std::literals;
2458
2459 test::jtx::Env env{*this, features};
2460
2461 auto const alice = Account("alice");
2462 auto const bob = Account("bob");
2463 env.fund(XRP(10000), alice);
2464 env.close();
2465 env.memoize(bob);
2466
2467 auto const preAlice = env.balance(alice);
2468 auto const ledSeq = env.current()->seq();
2469 auto const seq = env.seq(alice);
2470 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2471 auto const [txIDs, batchID] = submitBatch(
2472 env,
2473 tesSUCCESS,
2474 batch::outer(alice, seq, batchFee, tfAllOrNothing),
2475 batch::inner(pay(alice, bob, XRP(1000)), seq + 1),
2477 batch::sig(bob));
2478 env.close();
2479
2480 std::vector<TestLedgerData> testCases = {
2481 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2482 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2483 {2, "AccountSet", "tesSUCCESS", txIDs[1], batchID},
2484 };
2485 validateClosedLedger(env, testCases);
2486
2487 // Alice consumes sequences (# of txns)
2488 BEAST_EXPECT(env.seq(alice) == seq + 2);
2489
2490 // Bob consumes sequences (# of txns)
2491 BEAST_EXPECT(env.seq(bob) == ledSeq + 1);
2492
2493 // Alice pays XRP & Fee; Bob receives XRP
2494 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1000) - batchFee);
2495 BEAST_EXPECT(env.balance(bob) == XRP(1000));
2496 }
2497
2498 void
2500 {
2501 testcase("account set");
2502
2503 using namespace test::jtx;
2504 using namespace std::literals;
2505
2506 test::jtx::Env env{*this, features};
2507
2508 auto const alice = Account("alice");
2509 auto const bob = Account("bob");
2510 env.fund(XRP(10000), alice, bob);
2511 env.close();
2512
2513 auto const preAlice = env.balance(alice);
2514 auto const preBob = env.balance(bob);
2515
2516 auto const seq = env.seq(alice);
2517 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2518 auto tx1 = batch::inner(noop(alice), seq + 1);
2519 std::string domain = "example.com";
2520 tx1[sfDomain] = strHex(domain);
2521 auto const [txIDs, batchID] = submitBatch(
2522 env,
2523 tesSUCCESS,
2524 batch::outer(alice, seq, batchFee, tfAllOrNothing),
2525 tx1,
2526 batch::inner(pay(alice, bob, XRP(1)), seq + 2));
2527 env.close();
2528
2529 std::vector<TestLedgerData> testCases = {
2530 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2531 {1, "AccountSet", "tesSUCCESS", txIDs[0], batchID},
2532 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2533 };
2534 validateClosedLedger(env, testCases);
2535
2536 auto const sle = env.le(keylet::account(alice));
2537 BEAST_EXPECT(sle);
2538 BEAST_EXPECT(
2539 sle->getFieldVL(sfDomain) == Blob(domain.begin(), domain.end()));
2540
2541 // Alice consumes sequences (# of txns)
2542 BEAST_EXPECT(env.seq(alice) == seq + 3);
2543
2544 // Alice pays XRP & Fee; Bob receives XRP
2545 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
2546 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
2547 }
2548
2549 void
2551 {
2552 testcase("account delete");
2553
2554 using namespace test::jtx;
2555 using namespace std::literals;
2556
2557 // tfIndependent: account delete success
2558 {
2559 test::jtx::Env env{*this, features};
2560
2561 auto const alice = Account("alice");
2562 auto const bob = Account("bob");
2563 env.fund(XRP(10000), alice, bob);
2564 env.close();
2565
2566 incLgrSeqForAccDel(env, alice);
2567 for (int i = 0; i < 5; ++i)
2568 env.close();
2569
2570 auto const preAlice = env.balance(alice);
2571 auto const preBob = env.balance(bob);
2572
2573 auto const seq = env.seq(alice);
2574 auto const batchFee = batch::calcBatchFee(env, 0, 2) +
2575 env.current()->fees().increment;
2576 auto const [txIDs, batchID] = submitBatch(
2577 env,
2578 tesSUCCESS,
2579 batch::outer(alice, seq, batchFee, tfIndependent),
2580 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2581 batch::inner(acctdelete(alice, bob), seq + 2),
2582 // terNO_ACCOUNT: alice does not exist
2583 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
2584 env.close();
2585
2586 std::vector<TestLedgerData> testCases = {
2587 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2588 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2589 {2, "AccountDelete", "tesSUCCESS", txIDs[1], batchID},
2590 };
2591 validateClosedLedger(env, testCases);
2592
2593 // Alice does not exist; Bob receives Alice's XRP
2594 BEAST_EXPECT(!env.le(keylet::account(alice)));
2595 BEAST_EXPECT(env.balance(bob) == preBob + (preAlice - batchFee));
2596 }
2597
2598 // tfIndependent: account delete fails
2599 {
2600 test::jtx::Env env{*this, features};
2601
2602 auto const alice = Account("alice");
2603 auto const bob = Account("bob");
2604 env.fund(XRP(10000), alice, bob);
2605 env.close();
2606
2607 incLgrSeqForAccDel(env, alice);
2608 for (int i = 0; i < 5; ++i)
2609 env.close();
2610
2611 auto const preAlice = env.balance(alice);
2612 auto const preBob = env.balance(bob);
2613
2614 env.trust(bob["USD"](1000), alice);
2615 env.close();
2616
2617 auto const seq = env.seq(alice);
2618 auto const batchFee = batch::calcBatchFee(env, 0, 2) +
2619 env.current()->fees().increment;
2620 auto const [txIDs, batchID] = submitBatch(
2621 env,
2622 tesSUCCESS,
2623 batch::outer(alice, seq, batchFee, tfIndependent),
2624 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2625 // tecHAS_OBLIGATIONS: alice has obligations
2626 batch::inner(acctdelete(alice, bob), seq + 2),
2627 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
2628 env.close();
2629
2630 std::vector<TestLedgerData> testCases = {
2631 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2632 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2633 {2, "AccountDelete", "tecHAS_OBLIGATIONS", txIDs[1], batchID},
2634 {3, "Payment", "tesSUCCESS", txIDs[2], batchID},
2635 };
2636 validateClosedLedger(env, testCases);
2637
2638 // Alice does not exist; Bob receives XRP
2639 BEAST_EXPECT(env.le(keylet::account(alice)));
2640 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2641 }
2642
2643 // tfAllOrNothing: account delete fails
2644 {
2645 test::jtx::Env env{*this, features};
2646
2647 auto const alice = Account("alice");
2648 auto const bob = Account("bob");
2649 env.fund(XRP(10000), alice, bob);
2650 env.close();
2651
2652 incLgrSeqForAccDel(env, alice);
2653 for (int i = 0; i < 5; ++i)
2654 env.close();
2655
2656 auto const preAlice = env.balance(alice);
2657 auto const preBob = env.balance(bob);
2658
2659 auto const seq = env.seq(alice);
2660 auto const batchFee = batch::calcBatchFee(env, 0, 2) +
2661 env.current()->fees().increment;
2662 auto const [txIDs, batchID] = submitBatch(
2663 env,
2664 tesSUCCESS,
2665 batch::outer(alice, seq, batchFee, tfAllOrNothing),
2666 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
2667 batch::inner(acctdelete(alice, bob), seq + 2),
2668 // terNO_ACCOUNT: alice does not exist
2669 batch::inner(pay(alice, bob, XRP(2)), seq + 3));
2670 env.close();
2671
2672 std::vector<TestLedgerData> testCases = {
2673 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2674 };
2675 validateClosedLedger(env, testCases);
2676
2677 // Alice still exists; Bob is unchanged
2678 BEAST_EXPECT(env.le(keylet::account(alice)));
2679 BEAST_EXPECT(env.balance(bob) == preBob);
2680 }
2681 }
2682
2683 void
2685 {
2686 testcase("loan");
2687
2688 bool const lendingBatchEnabled = !std::any_of(
2689 Batch::disabledTxTypes.begin(),
2691 [](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; });
2692
2693 using namespace test::jtx;
2694
2695 test::jtx::Env env{
2696 *this,
2697 features | featureSingleAssetVault | featureLendingProtocol |
2698 featureMPTokensV1};
2699
2700 Account const issuer{"issuer"};
2701 // For simplicity, lender will be the sole actor for the vault &
2702 // brokers.
2703 Account const lender{"lender"};
2704 // Borrower only wants to borrow
2705 Account const borrower{"borrower"};
2706
2707 // Fund the accounts and trust lines with the same amount so that tests
2708 // can use the same values regardless of the asset.
2709 env.fund(XRP(100'000), issuer, noripple(lender, borrower));
2710 env.close();
2711
2712 // Just use an XRP asset
2713 PrettyAsset const asset{xrpIssue(), 1'000'000};
2714
2715 Vault vault{env};
2716
2717 auto const deposit = asset(50'000);
2718 auto const debtMaximumValue = asset(25'000).value();
2719 auto const coverDepositValue = asset(1000).value();
2720
2721 auto [tx, vaultKeylet] =
2722 vault.create({.owner = lender, .asset = asset});
2723 env(tx);
2724 env.close();
2725 BEAST_EXPECT(env.le(vaultKeylet));
2726
2727 env(vault.deposit(
2728 {.depositor = lender, .id = vaultKeylet.key, .amount = deposit}));
2729 env.close();
2730
2731 auto const brokerKeylet =
2732 keylet::loanbroker(lender.id(), env.seq(lender));
2733
2734 {
2735 using namespace loanBroker;
2736 env(set(lender, vaultKeylet.key),
2737 managementFeeRate(TenthBips16(100)),
2738 debtMaximum(debtMaximumValue),
2739 coverRateMinimum(TenthBips32(percentageToTenthBips(10))),
2740 coverRateLiquidation(TenthBips32(percentageToTenthBips(25))));
2741
2742 env(coverDeposit(lender, brokerKeylet.key, coverDepositValue));
2743
2744 env.close();
2745 }
2746
2747 {
2748 using namespace loan;
2749 using namespace std::chrono_literals;
2750
2751 auto const lenderSeq = env.seq(lender);
2752 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2753
2754 auto const loanKeylet = keylet::loan(brokerKeylet.key, 1);
2755 {
2756 auto const [txIDs, batchID] = submitBatch(
2757 env,
2758 lendingBatchEnabled ? temBAD_SIGNATURE
2760 batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
2762 env.json(
2763 set(lender, brokerKeylet.key, asset(1000).value()),
2764 // Not allowed to include the counterparty signature
2765 sig(sfCounterpartySignature, borrower),
2766 sig(none),
2767 fee(none),
2768 seq(none)),
2769 lenderSeq + 1),
2771 pay(lender,
2772 loanKeylet.key,
2773 STAmount{asset, asset(500).value()}),
2774 lenderSeq + 2));
2775 }
2776 {
2777 auto const [txIDs, batchID] = submitBatch(
2778 env,
2780 batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
2782 env.json(
2783 set(lender, brokerKeylet.key, asset(1000).value()),
2784 // Counterparty must be set
2785 sig(none),
2786 fee(none),
2787 seq(none)),
2788 lenderSeq + 1),
2790 pay(lender,
2791 loanKeylet.key,
2792 STAmount{asset, asset(500).value()}),
2793 lenderSeq + 2));
2794 }
2795 {
2796 auto const [txIDs, batchID] = submitBatch(
2797 env,
2798 lendingBatchEnabled ? temBAD_SIGNER
2800 batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
2802 env.json(
2803 set(lender, brokerKeylet.key, asset(1000).value()),
2804 // Counterparty must sign the outer transaction
2805 counterparty(borrower.id()),
2806 sig(none),
2807 fee(none),
2808 seq(none)),
2809 lenderSeq + 1),
2811 pay(lender,
2812 loanKeylet.key,
2813 STAmount{asset, asset(500).value()}),
2814 lenderSeq + 2));
2815 }
2816 {
2817 // LoanSet normally charges at least 2x base fee, but since the
2818 // signature check is done by the batch, it only charges the
2819 // base fee.
2820 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2821 auto const [txIDs, batchID] = submitBatch(
2822 env,
2823 lendingBatchEnabled ? TER(tesSUCCESS)
2825 batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
2827 env.json(
2828 set(lender, brokerKeylet.key, asset(1000).value()),
2829 counterparty(borrower.id()),
2830 sig(none),
2831 fee(none),
2832 seq(none)),
2833 lenderSeq + 1),
2835 pay(
2836 // However, this inner transaction will fail,
2837 // because the lender is not allowed to draw the
2838 // transaction
2839 lender,
2840 loanKeylet.key,
2841 STAmount{asset, asset(500).value()}),
2842 lenderSeq + 2),
2843 batch::sig(borrower));
2844 }
2845 env.close();
2846 BEAST_EXPECT(env.le(brokerKeylet));
2847 BEAST_EXPECT(!env.le(loanKeylet));
2848 {
2849 // LoanSet normally charges at least 2x base fee, but since the
2850 // signature check is done by the batch, it only charges the
2851 // base fee.
2852 auto const lenderSeq = env.seq(lender);
2853 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2854 auto const [txIDs, batchID] = submitBatch(
2855 env,
2856 lendingBatchEnabled ? TER(tesSUCCESS)
2858 batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
2860 env.json(
2861 set(lender, brokerKeylet.key, asset(1000).value()),
2862 counterparty(borrower.id()),
2863 sig(none),
2864 fee(none),
2865 seq(none)),
2866 lenderSeq + 1),
2868 manage(lender, loanKeylet.key, tfLoanImpair),
2869 lenderSeq + 2),
2870 batch::sig(borrower));
2871 }
2872 env.close();
2873 BEAST_EXPECT(env.le(brokerKeylet));
2874 if (auto const sleLoan = env.le(loanKeylet); lendingBatchEnabled
2875 ? BEAST_EXPECT(sleLoan)
2876 : !BEAST_EXPECT(!sleLoan))
2877 {
2878 BEAST_EXPECT(sleLoan->isFlag(lsfLoanImpaired));
2879 }
2880 }
2881 }
2882
2883 void
2885 {
2886 testcase("object create w/ sequence");
2887
2888 using namespace test::jtx;
2889 using namespace std::literals;
2890
2891 test::jtx::Env env{*this, features};
2892
2893 auto const alice = Account("alice");
2894 auto const bob = Account("bob");
2895 auto const gw = Account("gw");
2896 auto const USD = gw["USD"];
2897
2898 env.fund(XRP(10000), alice, bob, gw);
2899 env.close();
2900
2901 env.trust(USD(1000), alice, bob);
2902 env(pay(gw, alice, USD(100)));
2903 env(pay(gw, bob, USD(100)));
2904 env.close();
2905
2906 // success
2907 {
2908 auto const aliceSeq = env.seq(alice);
2909 auto const bobSeq = env.seq(bob);
2910 auto const preAlice = env.balance(alice);
2911 auto const preBob = env.balance(bob);
2912 auto const preAliceUSD = env.balance(alice, USD.issue());
2913 auto const preBobUSD = env.balance(bob, USD.issue());
2914
2915 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2916 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
2917 auto const [txIDs, batchID] = submitBatch(
2918 env,
2919 tesSUCCESS,
2920 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
2921 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
2922 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
2923 batch::sig(bob));
2924 env.close();
2925
2926 std::vector<TestLedgerData> testCases = {
2927 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2928 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
2929 {2, "CheckCash", "tesSUCCESS", txIDs[1], batchID},
2930 };
2931 validateClosedLedger(env, testCases);
2932
2933 // Alice consumes sequences (# of txns)
2934 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2935
2936 // Alice consumes sequences (# of txns)
2937 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
2938
2939 // Alice pays Fee; Bob XRP Unchanged
2940 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
2941 BEAST_EXPECT(env.balance(bob) == preBob);
2942
2943 // Alice pays USD & Bob receives USD
2944 BEAST_EXPECT(
2945 env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
2946 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
2947 }
2948
2949 // failure
2950 {
2951 env(fset(alice, asfRequireDest));
2952 env.close();
2953
2954 auto const aliceSeq = env.seq(alice);
2955 auto const bobSeq = env.seq(bob);
2956 auto const preAlice = env.balance(alice);
2957 auto const preBob = env.balance(bob);
2958 auto const preAliceUSD = env.balance(alice, USD.issue());
2959 auto const preBobUSD = env.balance(bob, USD.issue());
2960
2961 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2962 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
2963 auto const [txIDs, batchID] = submitBatch(
2964 env,
2965 tesSUCCESS,
2966 batch::outer(alice, aliceSeq, batchFee, tfIndependent),
2967 // tecDST_TAG_NEEDED - alice has enabled asfRequireDest
2968 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
2969 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
2970 batch::sig(bob));
2971 env.close();
2972
2973 std::vector<TestLedgerData> testCases = {
2974 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2975 {1, "CheckCreate", "tecDST_TAG_NEEDED", txIDs[0], batchID},
2976 {2, "CheckCash", "tecNO_ENTRY", txIDs[1], batchID},
2977 };
2978 validateClosedLedger(env, testCases);
2979
2980 // Alice consumes sequences (# of txns)
2981 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2982
2983 // Bob consumes sequences (# of txns)
2984 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
2985
2986 // Alice pays Fee; Bob XRP Unchanged
2987 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
2988 BEAST_EXPECT(env.balance(bob) == preBob);
2989
2990 // Alice pays USD & Bob receives USD
2991 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
2992 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
2993 }
2994 }
2995
2996 void
2998 {
2999 testcase("object create w/ ticket");
3000
3001 using namespace test::jtx;
3002 using namespace std::literals;
3003
3004 test::jtx::Env env{*this, features};
3005
3006 auto const alice = Account("alice");
3007 auto const bob = Account("bob");
3008 auto const gw = Account("gw");
3009 auto const USD = gw["USD"];
3010
3011 env.fund(XRP(10000), alice, bob, gw);
3012 env.close();
3013
3014 env.trust(USD(1000), alice, bob);
3015 env(pay(gw, alice, USD(100)));
3016 env(pay(gw, bob, USD(100)));
3017 env.close();
3018
3019 auto const aliceSeq = env.seq(alice);
3020 auto const bobSeq = env.seq(bob);
3021 auto const preAlice = env.balance(alice);
3022 auto const preBob = env.balance(bob);
3023 auto const preAliceUSD = env.balance(alice, USD.issue());
3024 auto const preBobUSD = env.balance(bob, USD.issue());
3025
3026 auto const batchFee = batch::calcBatchFee(env, 1, 3);
3027 uint256 const chkID{getCheckIndex(bob, bobSeq + 1)};
3028 auto const [txIDs, batchID] = submitBatch(
3029 env,
3030 tesSUCCESS,
3031 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3032 batch::inner(ticket::create(bob, 10), bobSeq),
3033 batch::inner(check::create(bob, alice, USD(10)), 0, bobSeq + 1),
3034 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
3035 batch::sig(bob));
3036 env.close();
3037
3038 std::vector<TestLedgerData> testCases = {
3039 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3040 {1, "TicketCreate", "tesSUCCESS", txIDs[0], batchID},
3041 {2, "CheckCreate", "tesSUCCESS", txIDs[1], batchID},
3042 {3, "CheckCash", "tesSUCCESS", txIDs[2], batchID},
3043 };
3044 validateClosedLedger(env, testCases);
3045
3046 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
3047 BEAST_EXPECT(env.seq(bob) == bobSeq + 10 + 1);
3048 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
3049 BEAST_EXPECT(env.balance(bob) == preBob);
3050 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
3051 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
3052 }
3053
3054 void
3056 {
3057 testcase("object create w/ 3rd party");
3058
3059 using namespace test::jtx;
3060 using namespace std::literals;
3061
3062 test::jtx::Env env{*this, features};
3063
3064 auto const alice = Account("alice");
3065 auto const bob = Account("bob");
3066 auto const carol = Account("carol");
3067 auto const gw = Account("gw");
3068 auto const USD = gw["USD"];
3069
3070 env.fund(XRP(10000), alice, bob, carol, gw);
3071 env.close();
3072
3073 env.trust(USD(1000), alice, bob);
3074 env(pay(gw, alice, USD(100)));
3075 env(pay(gw, bob, USD(100)));
3076 env.close();
3077
3078 auto const aliceSeq = env.seq(alice);
3079 auto const bobSeq = env.seq(bob);
3080 auto const carolSeq = env.seq(carol);
3081 auto const preAlice = env.balance(alice);
3082 auto const preBob = env.balance(bob);
3083 auto const preCarol = env.balance(carol);
3084 auto const preAliceUSD = env.balance(alice, USD.issue());
3085 auto const preBobUSD = env.balance(bob, USD.issue());
3086
3087 auto const batchFee = batch::calcBatchFee(env, 2, 2);
3088 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
3089 auto const [txIDs, batchID] = submitBatch(
3090 env,
3091 tesSUCCESS,
3092 batch::outer(carol, carolSeq, batchFee, tfAllOrNothing),
3093 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
3094 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq),
3095 batch::sig(alice, bob));
3096 env.close();
3097
3098 std::vector<TestLedgerData> testCases = {
3099 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3100 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
3101 {2, "CheckCash", "tesSUCCESS", txIDs[1], batchID},
3102 };
3103 validateClosedLedger(env, testCases);
3104
3105 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
3106 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
3107 BEAST_EXPECT(env.seq(carol) == carolSeq + 1);
3108 BEAST_EXPECT(env.balance(alice) == preAlice);
3109 BEAST_EXPECT(env.balance(bob) == preBob);
3110 BEAST_EXPECT(env.balance(carol) == preCarol - batchFee);
3111 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
3112 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
3113 }
3114
3115 void
3117 {
3118 {
3119 testcase("tickets outer");
3120
3121 using namespace test::jtx;
3122 using namespace std::literals;
3123
3124 test::jtx::Env env{*this, features};
3125
3126 auto const alice = Account("alice");
3127 auto const bob = Account("bob");
3128
3129 env.fund(XRP(10000), alice, bob);
3130 env.close();
3131
3132 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3133 env(ticket::create(alice, 10));
3134 env.close();
3135
3136 auto const aliceSeq = env.seq(alice);
3137 auto const preAlice = env.balance(alice);
3138 auto const preBob = env.balance(bob);
3139
3140 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3141 auto const [txIDs, batchID] = submitBatch(
3142 env,
3143 tesSUCCESS,
3144 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3145 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 0),
3146 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
3147 ticket::use(aliceTicketSeq));
3148 env.close();
3149
3150 std::vector<TestLedgerData> testCases = {
3151 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3152 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3153 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3154 };
3155 validateClosedLedger(env, testCases);
3156
3157 auto const sle = env.le(keylet::account(alice));
3158 BEAST_EXPECT(sle);
3159 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 9);
3160 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 9);
3161
3162 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
3163 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
3164 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
3165 }
3166
3167 {
3168 testcase("tickets inner");
3169
3170 using namespace test::jtx;
3171 using namespace std::literals;
3172
3173 test::jtx::Env env{*this, features};
3174
3175 auto const alice = Account("alice");
3176 auto const bob = Account("bob");
3177
3178 env.fund(XRP(10000), alice, bob);
3179 env.close();
3180
3181 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3182 env(ticket::create(alice, 10));
3183 env.close();
3184
3185 auto const aliceSeq = env.seq(alice);
3186 auto const preAlice = env.balance(alice);
3187 auto const preBob = env.balance(bob);
3188
3189 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3190 auto const [txIDs, batchID] = submitBatch(
3191 env,
3192 tesSUCCESS,
3193 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3194 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq),
3195 batch::inner(pay(alice, bob, XRP(2)), 0, aliceTicketSeq + 1));
3196 env.close();
3197
3198 std::vector<TestLedgerData> testCases = {
3199 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3200 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3201 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3202 };
3203 validateClosedLedger(env, testCases);
3204
3205 auto const sle = env.le(keylet::account(alice));
3206 BEAST_EXPECT(sle);
3207 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 8);
3208 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8);
3209
3210 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
3211 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
3212 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
3213 }
3214
3215 {
3216 testcase("tickets outer inner");
3217
3218 using namespace test::jtx;
3219 using namespace std::literals;
3220
3221 test::jtx::Env env{*this, features};
3222
3223 auto const alice = Account("alice");
3224 auto const bob = Account("bob");
3225
3226 env.fund(XRP(10000), alice, bob);
3227 env.close();
3228
3229 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3230 env(ticket::create(alice, 10));
3231 env.close();
3232
3233 auto const aliceSeq = env.seq(alice);
3234 auto const preAlice = env.balance(alice);
3235 auto const preBob = env.balance(bob);
3236
3237 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3238 auto const [txIDs, batchID] = submitBatch(
3239 env,
3240 tesSUCCESS,
3241 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3242 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3243 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
3244 ticket::use(aliceTicketSeq));
3245 env.close();
3246
3247 std::vector<TestLedgerData> testCases = {
3248 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3249 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3250 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3251 };
3252 validateClosedLedger(env, testCases);
3253
3254 auto const sle = env.le(keylet::account(alice));
3255 BEAST_EXPECT(sle);
3256 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 8);
3257 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8);
3258
3259 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
3260 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
3261 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
3262 }
3263 }
3264
3265 void
3267 {
3268 testcase("sequence open ledger");
3269
3270 using namespace test::jtx;
3271 using namespace std::literals;
3272
3273 auto const alice = Account("alice");
3274 auto const bob = Account("bob");
3275 auto const carol = Account("carol");
3276
3277 // Before Batch Txn w/ retry following ledger
3278 {
3279 // IMPORTANT: The batch txn is applied first, then the noop txn.
3280 // Because of this ordering, the noop txn is not applied and is
3281 // overwritten by the payment in the batch transaction. Because the
3282 // terPRE_SEQ is outside of the batch this noop transaction will ge
3283 // reapplied in the following ledger
3284 test::jtx::Env env{*this, features};
3285 env.fund(XRP(10000), alice, bob, carol);
3286 env.close();
3287
3288 auto const aliceSeq = env.seq(alice);
3289 auto const carolSeq = env.seq(carol);
3290
3291 // AccountSet Txn
3292 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 2));
3293 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3294 env(noopTxn, ter(terPRE_SEQ));
3295
3296 // Batch Txn
3297 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3298 auto const [txIDs, batchID] = submitBatch(
3299 env,
3300 tesSUCCESS,
3301 batch::outer(carol, carolSeq, batchFee, tfAllOrNothing),
3302 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
3303 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
3304 batch::sig(alice));
3305 env.close();
3306
3307 {
3308 std::vector<TestLedgerData> testCases = {
3309 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3310 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3311 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3312 };
3313 validateClosedLedger(env, testCases);
3314 }
3315
3316 env.close();
3317 {
3318 // next ledger contains noop txn
3319 std::vector<TestLedgerData> testCases = {
3320 {0, "AccountSet", "tesSUCCESS", noopTxnID, std::nullopt},
3321 };
3322 validateClosedLedger(env, testCases);
3323 }
3324 }
3325
3326 // Before Batch Txn w/ same sequence
3327 {
3328 // IMPORTANT: The batch txn is applied first, then the noop txn.
3329 // Because of this ordering, the noop txn is not applied and is
3330 // overwritten by the payment in the batch transaction.
3331 test::jtx::Env env{*this, features};
3332 env.fund(XRP(10000), alice, bob);
3333 env.close();
3334
3335 auto const aliceSeq = env.seq(alice);
3336
3337 // AccountSet Txn
3338 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 1));
3339 env(noopTxn, ter(terPRE_SEQ));
3340
3341 // Batch Txn
3342 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3343 auto const [txIDs, batchID] = submitBatch(
3344 env,
3345 tesSUCCESS,
3346 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3347 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
3348 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 2));
3349 env.close();
3350
3351 {
3352 std::vector<TestLedgerData> testCases = {
3353 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3354 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3355 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3356 };
3357 validateClosedLedger(env, testCases);
3358 }
3359
3360 env.close();
3361 {
3362 // next ledger is empty
3363 std::vector<TestLedgerData> testCases = {};
3364 validateClosedLedger(env, testCases);
3365 }
3366 }
3367
3368 // After Batch Txn w/ same sequence
3369 {
3370 // IMPORTANT: The batch txn is applied first, then the noop txn.
3371 // Because of this ordering, the noop txn is not applied and is
3372 // overwritten by the payment in the batch transaction.
3373 test::jtx::Env env{*this, features};
3374 env.fund(XRP(10000), alice, bob);
3375 env.close();
3376
3377 auto const aliceSeq = env.seq(alice);
3378 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3379 auto const [txIDs, batchID] = submitBatch(
3380 env,
3381 tesSUCCESS,
3382 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3383 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
3384 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 2));
3385
3386 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 1));
3387 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3388 env(noopTxn, ter(tesSUCCESS));
3389 env.close();
3390
3391 {
3392 std::vector<TestLedgerData> testCases = {
3393 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3394 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3395 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3396 };
3397 validateClosedLedger(env, testCases);
3398 }
3399
3400 env.close();
3401 {
3402 // next ledger is empty
3403 std::vector<TestLedgerData> testCases = {};
3404 validateClosedLedger(env, testCases);
3405 }
3406 }
3407
3408 // Outer Batch terPRE_SEQ
3409 {
3410 test::jtx::Env env{*this, features};
3411 env.fund(XRP(10000), alice, bob, carol);
3412 env.close();
3413
3414 auto const aliceSeq = env.seq(alice);
3415 auto const carolSeq = env.seq(carol);
3416
3417 // Batch Txn
3418 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3419 auto const [txIDs, batchID] = submitBatch(
3420 env,
3421 terPRE_SEQ,
3422 batch::outer(carol, carolSeq + 1, batchFee, tfAllOrNothing),
3423 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
3424 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
3425 batch::sig(alice));
3426
3427 // AccountSet Txn
3428 auto const noopTxn = env.jt(noop(carol), seq(carolSeq));
3429 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3430 env(noopTxn, ter(tesSUCCESS));
3431 env.close();
3432
3433 {
3434 std::vector<TestLedgerData> testCases = {
3435 {0, "AccountSet", "tesSUCCESS", noopTxnID, std::nullopt},
3436 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3437 {2, "Payment", "tesSUCCESS", txIDs[0], batchID},
3438 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3439 };
3440 validateClosedLedger(env, testCases);
3441 }
3442
3443 env.close();
3444 {
3445 // next ledger contains no transactions
3446 std::vector<TestLedgerData> testCases = {};
3447 validateClosedLedger(env, testCases);
3448 }
3449 }
3450 }
3451
3452 void
3454 {
3455 testcase("tickets open ledger");
3456
3457 using namespace test::jtx;
3458 using namespace std::literals;
3459
3460 auto const alice = Account("alice");
3461 auto const bob = Account("bob");
3462
3463 // Before Batch Txn w/ same ticket
3464 {
3465 // IMPORTANT: The batch txn is applied first, then the noop txn.
3466 // Because of this ordering, the noop txn is not applied and is
3467 // overwritten by the payment in the batch transaction.
3468 test::jtx::Env env{*this, features};
3469 env.fund(XRP(10000), alice, bob);
3470 env.close();
3471
3472 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3473 env(ticket::create(alice, 10));
3474 env.close();
3475
3476 auto const aliceSeq = env.seq(alice);
3477
3478 // AccountSet Txn
3479 auto const noopTxn =
3480 env.jt(noop(alice), ticket::use(aliceTicketSeq + 1));
3481 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3482 env(noopTxn, ter(tesSUCCESS));
3483
3484 // Batch Txn
3485 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3486 auto const [txIDs, batchID] = submitBatch(
3487 env,
3488 tesSUCCESS,
3489 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3490 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3491 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
3492 ticket::use(aliceTicketSeq));
3493 env.close();
3494
3495 {
3496 std::vector<TestLedgerData> testCases = {
3497 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3498 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3499 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3500 };
3501 validateClosedLedger(env, testCases);
3502 }
3503
3504 env.close();
3505 {
3506 // next ledger is empty
3507 std::vector<TestLedgerData> testCases = {};
3508 validateClosedLedger(env, testCases);
3509 }
3510 }
3511
3512 // After Batch Txn w/ same ticket
3513 {
3514 // IMPORTANT: The batch txn is applied first, then the noop txn.
3515 // Because of this ordering, the noop txn is not applied and is
3516 // overwritten by the payment in the batch transaction.
3517 test::jtx::Env env{*this, features};
3518 env.fund(XRP(10000), alice, bob);
3519 env.close();
3520
3521 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3522 env(ticket::create(alice, 10));
3523 env.close();
3524
3525 auto const aliceSeq = env.seq(alice);
3526
3527 // Batch Txn
3528 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3529 auto const [txIDs, batchID] = submitBatch(
3530 env,
3531 tesSUCCESS,
3532 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3533 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3534 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
3535 ticket::use(aliceTicketSeq));
3536
3537 // AccountSet Txn
3538 auto const noopTxn =
3539 env.jt(noop(alice), ticket::use(aliceTicketSeq + 1));
3540 env(noopTxn);
3541
3542 env.close();
3543 {
3544 std::vector<TestLedgerData> testCases = {
3545 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3546 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3547 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3548 };
3549 validateClosedLedger(env, testCases);
3550 }
3551
3552 env.close();
3553 {
3554 // next ledger is empty
3555 std::vector<TestLedgerData> testCases = {};
3556 validateClosedLedger(env, testCases);
3557 }
3558 }
3559 }
3560
3561 void
3563 {
3564 testcase("objects open ledger");
3565
3566 using namespace test::jtx;
3567 using namespace std::literals;
3568
3569 auto const alice = Account("alice");
3570 auto const bob = Account("bob");
3571
3572 // Consume Object Before Batch Txn
3573 {
3574 // IMPORTANT: The initial result of `CheckCash` is tecNO_ENTRY
3575 // because the create transaction has not been applied because the
3576 // batch will run in the close ledger process. The batch will be
3577 // allied and then retry this transaction in the current ledger.
3578
3579 test::jtx::Env env{*this, features};
3580 env.fund(XRP(10000), alice, bob);
3581 env.close();
3582
3583 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3584 env(ticket::create(alice, 10));
3585 env.close();
3586
3587 auto const aliceSeq = env.seq(alice);
3588
3589 // CheckCash Txn
3590 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3591 auto const objTxn = env.jt(check::cash(bob, chkID, XRP(10)));
3592 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3593 env(objTxn, ter(tecNO_ENTRY));
3594
3595 // Batch Txn
3596 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3597 auto const [txIDs, batchID] = submitBatch(
3598 env,
3599 tesSUCCESS,
3600 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3601 batch::inner(check::create(alice, bob, XRP(10)), aliceSeq),
3602 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3603 ticket::use(aliceTicketSeq));
3604
3605 env.close();
3606 {
3607 std::vector<TestLedgerData> testCases = {
3608 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3609 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
3610 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3611 {3, "CheckCash", "tesSUCCESS", objTxnID, std::nullopt},
3612 };
3613 validateClosedLedger(env, testCases);
3614 }
3615
3616 env.close();
3617 {
3618 // next ledger is empty
3619 std::vector<TestLedgerData> testCases = {};
3620 validateClosedLedger(env, testCases);
3621 }
3622 }
3623
3624 // Create Object Before Batch Txn
3625 {
3626 test::jtx::Env env{*this, features};
3627 env.fund(XRP(10000), alice, bob);
3628 env.close();
3629
3630 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3631 env(ticket::create(alice, 10));
3632 env.close();
3633
3634 auto const aliceSeq = env.seq(alice);
3635 auto const bobSeq = env.seq(bob);
3636
3637 // CheckCreate Txn
3638 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3639 auto const objTxn = env.jt(check::create(alice, bob, XRP(10)));
3640 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3641 env(objTxn, ter(tesSUCCESS));
3642
3643 // Batch Txn
3644 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3645 auto const [txIDs, batchID] = submitBatch(
3646 env,
3647 tesSUCCESS,
3648 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3649 batch::inner(check::cash(bob, chkID, XRP(10)), bobSeq),
3650 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3651 ticket::use(aliceTicketSeq),
3652 batch::sig(bob));
3653
3654 env.close();
3655 {
3656 std::vector<TestLedgerData> testCases = {
3657 {0, "CheckCreate", "tesSUCCESS", objTxnID, std::nullopt},
3658 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3659 {2, "CheckCash", "tesSUCCESS", txIDs[0], batchID},
3660 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3661 };
3662 validateClosedLedger(env, testCases);
3663 }
3664 }
3665
3666 // After Batch Txn
3667 {
3668 // IMPORTANT: The initial result of `CheckCash` is tecNO_ENTRY
3669 // because the create transaction has not been applied because the
3670 // batch will run in the close ledger process. The batch will be
3671 // applied and then retry this transaction in the current ledger.
3672
3673 test::jtx::Env env{*this, features};
3674 env.fund(XRP(10000), alice, bob);
3675 env.close();
3676
3677 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3678 env(ticket::create(alice, 10));
3679 env.close();
3680
3681 auto const aliceSeq = env.seq(alice);
3682
3683 // Batch Txn
3684 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3685 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3686 auto const [txIDs, batchID] = submitBatch(
3687 env,
3688 tesSUCCESS,
3689 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3690 batch::inner(check::create(alice, bob, XRP(10)), aliceSeq),
3691 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3692 ticket::use(aliceTicketSeq));
3693
3694 // CheckCash Txn
3695 auto const objTxn = env.jt(check::cash(bob, chkID, XRP(10)));
3696 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3697 env(objTxn, ter(tecNO_ENTRY));
3698
3699 env.close();
3700 {
3701 std::vector<TestLedgerData> testCases = {
3702 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3703 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
3704 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3705 {3, "CheckCash", "tesSUCCESS", objTxnID, std::nullopt},
3706 };
3707 validateClosedLedger(env, testCases);
3708 }
3709 }
3710 }
3711
3712 void
3714 {
3715 testcase("pseudo txn with tfInnerBatchTxn");
3716
3717 using namespace test::jtx;
3718 using namespace std::literals;
3719
3720 test::jtx::Env env{*this, features};
3721
3722 auto const alice = Account("alice");
3723 auto const bob = Account("bob");
3724 env.fund(XRP(10000), alice, bob);
3725 env.close();
3726
3727 STTx const stx = STTx(ttAMENDMENT, [&](auto& obj) {
3728 obj.setAccountID(sfAccount, AccountID());
3729 obj.setFieldH256(sfAmendment, uint256(2));
3730 obj.setFieldU32(sfLedgerSequence, env.seq(alice));
3731 obj.setFieldU32(sfFlags, tfInnerBatchTxn);
3732 });
3733
3734 std::string reason;
3735 BEAST_EXPECT(isPseudoTx(stx));
3736 BEAST_EXPECT(!passesLocalChecks(stx, reason));
3737 BEAST_EXPECT(reason == "Cannot submit pseudo transactions.");
3738 env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
3739 auto const result = xrpl::apply(env.app(), view, stx, tapNONE, j);
3740 BEAST_EXPECT(!result.applied && result.ter == temINVALID_FLAG);
3741 return result.applied;
3742 });
3743 }
3744
3745 void
3747 {
3748 testcase("batch open ledger");
3749 // IMPORTANT: When a transaction is submitted outside of a batch and
3750 // another transaction is part of the batch, the batch might fail
3751 // because the sequence is out of order. This is because the canonical
3752 // order of transactions is determined by the account first. So in this
3753 // case, alice's batch comes after bobs self submitted transaction even
3754 // though the payment was submitted after the batch.
3755
3756 using namespace test::jtx;
3757 using namespace std::literals;
3758
3759 test::jtx::Env env{*this, features};
3760 XRPAmount const baseFee = env.current()->fees().base;
3761
3762 auto const alice = Account("alice");
3763 auto const bob = Account("bob");
3764
3765 env.fund(XRP(10000), alice, bob);
3766 env.close();
3767
3768 env(noop(bob), ter(tesSUCCESS));
3769 env.close();
3770
3771 auto const aliceSeq = env.seq(alice);
3772 auto const preAlice = env.balance(alice);
3773 auto const preBob = env.balance(bob);
3774 auto const bobSeq = env.seq(bob);
3775
3776 // Alice Pays Bob (Open Ledger)
3777 auto const payTxn1 = env.jt(pay(alice, bob, XRP(10)), seq(aliceSeq));
3778 auto const payTxn1ID = to_string(payTxn1.stx->getTransactionID());
3779 env(payTxn1, ter(tesSUCCESS));
3780
3781 // Alice & Bob Atomic Batch
3782 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3783 auto const [txIDs, batchID] = submitBatch(
3784 env,
3785 tesSUCCESS,
3786 batch::outer(alice, aliceSeq + 1, batchFee, tfAllOrNothing),
3787 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 2),
3788 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3789 batch::sig(bob));
3790
3791 // Bob pays Alice (Open Ledger)
3792 auto const payTxn2 = env.jt(pay(bob, alice, XRP(5)), seq(bobSeq + 1));
3793 auto const payTxn2ID = to_string(payTxn2.stx->getTransactionID());
3794 env(payTxn2, ter(terPRE_SEQ));
3795 env.close();
3796
3797 std::vector<TestLedgerData> testCases = {
3798 {0, "Payment", "tesSUCCESS", payTxn1ID, std::nullopt},
3799 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3800 {2, "Payment", "tesSUCCESS", txIDs[0], batchID},
3801 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3802 };
3803 validateClosedLedger(env, testCases);
3804
3805 env.close();
3806 {
3807 // next ledger includes the payment txn
3808 std::vector<TestLedgerData> testCases = {
3809 {0, "Payment", "tesSUCCESS", payTxn2ID, std::nullopt},
3810 };
3811 validateClosedLedger(env, testCases);
3812 }
3813
3814 // Alice consumes sequences (# of txns)
3815 BEAST_EXPECT(env.seq(alice) == aliceSeq + 3);
3816
3817 // Alice consumes sequences (# of txns)
3818 BEAST_EXPECT(env.seq(bob) == bobSeq + 2);
3819
3820 // Alice pays XRP & Fee; Bob receives XRP & pays Fee
3821 BEAST_EXPECT(
3822 env.balance(alice) == preAlice - XRP(10) - batchFee - baseFee);
3823 BEAST_EXPECT(env.balance(bob) == preBob + XRP(10) - baseFee);
3824 }
3825
3826 void
3828 {
3829 testcase("batch tx queue");
3830
3831 using namespace test::jtx;
3832 using namespace std::literals;
3833
3834 // only outer batch transactions are counter towards the queue size
3835 {
3836 test::jtx::Env env{
3837 *this,
3838 makeSmallQueueConfig(
3839 {{"minimum_txn_in_ledger_standalone", "2"}}),
3840 features,
3841 nullptr,
3843
3844 auto alice = Account("alice");
3845 auto bob = Account("bob");
3846 auto carol = Account("carol");
3847
3848 // Fund across several ledgers so the TxQ metrics stay restricted.
3849 env.fund(XRP(10000), noripple(alice, bob));
3850 env.close(env.now() + 5s, 10000ms);
3851 env.fund(XRP(10000), noripple(carol));
3852 env.close(env.now() + 5s, 10000ms);
3853
3854 // Fill the ledger
3855 env(noop(alice));
3856 env(noop(alice));
3857 env(noop(alice));
3858 checkMetrics(*this, env, 0, std::nullopt, 3, 2);
3859
3860 env(noop(carol), ter(terQUEUED));
3861 checkMetrics(*this, env, 1, std::nullopt, 3, 2);
3862
3863 auto const aliceSeq = env.seq(alice);
3864 auto const bobSeq = env.seq(bob);
3865 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3866
3867 // Queue Batch
3868 {
3869 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3870 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3871 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3872 batch::sig(bob),
3873 ter(terQUEUED));
3874 }
3875
3876 checkMetrics(*this, env, 2, std::nullopt, 3, 2);
3877
3878 // Replace Queued Batch
3879 {
3880 env(batch::outer(
3881 alice,
3882 aliceSeq,
3883 openLedgerFee(env, batchFee),
3885 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3886 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3887 batch::sig(bob),
3888 ter(tesSUCCESS));
3889 env.close();
3890 }
3891
3892 checkMetrics(*this, env, 0, 12, 1, 6);
3893 }
3894
3895 // inner batch transactions are counter towards the ledger tx count
3896 {
3897 test::jtx::Env env{
3898 *this,
3899 makeSmallQueueConfig(
3900 {{"minimum_txn_in_ledger_standalone", "2"}}),
3901 features,
3902 nullptr,
3904
3905 auto alice = Account("alice");
3906 auto bob = Account("bob");
3907 auto carol = Account("carol");
3908
3909 // Fund across several ledgers so the TxQ metrics stay restricted.
3910 env.fund(XRP(10000), noripple(alice, bob));
3911 env.close(env.now() + 5s, 10000ms);
3912 env.fund(XRP(10000), noripple(carol));
3913 env.close(env.now() + 5s, 10000ms);
3914
3915 // Fill the ledger leaving room for 1 queued transaction
3916 env(noop(alice));
3917 env(noop(alice));
3918 checkMetrics(*this, env, 0, std::nullopt, 2, 2);
3919
3920 auto const aliceSeq = env.seq(alice);
3921 auto const bobSeq = env.seq(bob);
3922 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3923
3924 // Batch Successful
3925 {
3926 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3927 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3928 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3929 batch::sig(bob),
3930 ter(tesSUCCESS));
3931 }
3932
3933 checkMetrics(*this, env, 0, std::nullopt, 3, 2);
3934
3935 env(noop(carol), ter(terQUEUED));
3936 checkMetrics(*this, env, 1, std::nullopt, 3, 2);
3937 }
3938 }
3939
3940 void
3942 {
3943 testcase("batch network ops");
3944
3945 using namespace test::jtx;
3946 using namespace std::literals;
3947
3948 Env env(
3949 *this,
3950 envconfig(),
3951 features,
3952 nullptr,
3954
3955 auto alice = Account("alice");
3956 auto bob = Account("bob");
3957 env.fund(XRP(10000), alice, bob);
3958 env.close();
3959
3960 auto submitTx = [&](std::uint32_t flags) -> uint256 {
3961 auto jt = env.jt(pay(alice, bob, XRP(1)), txflags(flags));
3962 Serializer s;
3963 jt.stx->add(s);
3964 env.app().getOPs().submitTransaction(jt.stx);
3965 return jt.stx->getTransactionID();
3966 };
3967
3968 auto processTxn = [&](std::uint32_t flags) -> uint256 {
3969 auto jt = env.jt(pay(alice, bob, XRP(1)), txflags(flags));
3970 Serializer s;
3971 jt.stx->add(s);
3972 std::string reason;
3973 auto transaction =
3974 std::make_shared<Transaction>(jt.stx, reason, env.app());
3976 transaction, false, true, NetworkOPs::FailHard::yes);
3977 return transaction->getID();
3978 };
3979
3980 // Validate: NetworkOPs::submitTransaction()
3981 {
3982 // Submit a tx with tfInnerBatchTxn
3983 uint256 const txBad = submitTx(tfInnerBatchTxn);
3984 BEAST_EXPECT(
3985 env.app().getHashRouter().getFlags(txBad) ==
3987 }
3988
3989 // Validate: NetworkOPs::processTransaction()
3990 {
3991 uint256 const txid = processTxn(tfInnerBatchTxn);
3992 // HashRouter::getFlags() should return LedgerFlags::BAD
3993 BEAST_EXPECT(
3994 env.app().getHashRouter().getFlags(txid) ==
3996 }
3997 }
3998
3999 void
4001 {
4002 testcase("batch delegate");
4003
4004 using namespace test::jtx;
4005 using namespace std::literals;
4006
4007 // delegated non atomic inner
4008 {
4009 test::jtx::Env env{*this, features};
4010
4011 auto const alice = Account("alice");
4012 auto const bob = Account("bob");
4013 auto const gw = Account("gw");
4014 auto const USD = gw["USD"];
4015 env.fund(XRP(10000), alice, bob, gw);
4016 env.close();
4017
4018 env(delegate::set(alice, bob, {"Payment"}));
4019 env.close();
4020
4021 auto const preAlice = env.balance(alice);
4022 auto const preBob = env.balance(bob);
4023
4024 auto const batchFee = batch::calcBatchFee(env, 0, 2);
4025 auto const seq = env.seq(alice);
4026
4027 auto tx = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
4028 tx[jss::Delegate] = bob.human();
4029 auto const [txIDs, batchID] = submitBatch(
4030 env,
4031 tesSUCCESS,
4032 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4033 tx,
4034 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
4035 env.close();
4036
4037 std::vector<TestLedgerData> testCases = {
4038 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
4039 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
4040 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
4041 };
4042 validateClosedLedger(env, testCases);
4043
4044 // Alice consumes sequences (# of txns)
4045 BEAST_EXPECT(env.seq(alice) == seq + 3);
4046
4047 // Alice pays XRP & Fee; Bob receives XRP
4048 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
4049 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
4050 }
4051
4052 // delegated atomic inner
4053 {
4054 test::jtx::Env env{*this, features};
4055
4056 auto const alice = Account("alice");
4057 auto const bob = Account("bob");
4058 auto const carol = Account("carol");
4059 auto const gw = Account("gw");
4060 auto const USD = gw["USD"];
4061 env.fund(XRP(10000), alice, bob, carol, gw);
4062 env.close();
4063
4064 env(delegate::set(bob, carol, {"Payment"}));
4065 env.close();
4066
4067 auto const preAlice = env.balance(alice);
4068 auto const preBob = env.balance(bob);
4069 auto const preCarol = env.balance(carol);
4070
4071 auto const batchFee = batch::calcBatchFee(env, 1, 2);
4072 auto const aliceSeq = env.seq(alice);
4073 auto const bobSeq = env.seq(bob);
4074
4075 auto tx = batch::inner(pay(bob, alice, XRP(1)), bobSeq);
4076 tx[jss::Delegate] = carol.human();
4077 auto const [txIDs, batchID] = submitBatch(
4078 env,
4079 tesSUCCESS,
4080 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
4081 tx,
4082 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
4083 batch::sig(bob));
4084 env.close();
4085
4086 std::vector<TestLedgerData> testCases = {
4087 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
4088 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
4089 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
4090 };
4091 validateClosedLedger(env, testCases);
4092
4093 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
4094 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
4095 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
4096 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
4097 // NOTE: Carol would normally pay the fee for delegated txns, but
4098 // because the batch is atomic, the fee is paid by the batch
4099 BEAST_EXPECT(env.balance(carol) == preCarol);
4100 }
4101
4102 // delegated non atomic inner (AccountSet)
4103 // this also makes sure tfInnerBatchTxn won't block delegated AccountSet
4104 // with granular permission
4105 {
4106 test::jtx::Env env{*this, features};
4107
4108 auto const alice = Account("alice");
4109 auto const bob = Account("bob");
4110 auto const gw = Account("gw");
4111 auto const USD = gw["USD"];
4112 env.fund(XRP(10000), alice, bob, gw);
4113 env.close();
4114
4115 env(delegate::set(alice, bob, {"AccountDomainSet"}));
4116 env.close();
4117
4118 auto const preAlice = env.balance(alice);
4119 auto const preBob = env.balance(bob);
4120
4121 auto const batchFee = batch::calcBatchFee(env, 0, 2);
4122 auto const seq = env.seq(alice);
4123
4124 auto tx = batch::inner(noop(alice), seq + 1);
4125 std::string const domain = "example.com";
4126 tx[sfDomain.jsonName] = strHex(domain);
4127 tx[jss::Delegate] = bob.human();
4128 auto const [txIDs, batchID] = submitBatch(
4129 env,
4130 tesSUCCESS,
4131 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4132 tx,
4133 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
4134 env.close();
4135
4136 std::vector<TestLedgerData> testCases = {
4137 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
4138 {1, "AccountSet", "tesSUCCESS", txIDs[0], batchID},
4139 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
4140 };
4141 validateClosedLedger(env, testCases);
4142
4143 // Alice consumes sequences (# of txns)
4144 BEAST_EXPECT(env.seq(alice) == seq + 3);
4145
4146 // Alice pays XRP & Fee; Bob receives XRP
4147 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee);
4148 BEAST_EXPECT(env.balance(bob) == preBob + XRP(2));
4149 }
4150
4151 // delegated non atomic inner (MPTokenIssuanceSet)
4152 // this also makes sure tfInnerBatchTxn won't block delegated
4153 // MPTokenIssuanceSet with granular permission
4154 {
4155 test::jtx::Env env{*this, features};
4156 Account alice{"alice"};
4157 Account bob{"bob"};
4158 env.fund(XRP(100000), alice, bob);
4159 env.close();
4160
4161 auto const mptID = makeMptID(env.seq(alice), alice);
4162 MPTTester mpt(env, alice, {.fund = false});
4163 env.close();
4164 mpt.create({.flags = tfMPTCanLock});
4165 env.close();
4166
4167 // alice gives granular permission to bob of MPTokenIssuanceLock
4168 env(delegate::set(
4169 alice, bob, {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"}));
4170 env.close();
4171
4172 auto const seq = env.seq(alice);
4173 auto const batchFee = batch::calcBatchFee(env, 0, 2);
4174
4175 Json::Value jv1;
4176 jv1[sfTransactionType] = jss::MPTokenIssuanceSet;
4177 jv1[sfAccount] = alice.human();
4178 jv1[sfDelegate] = bob.human();
4179 jv1[sfSequence] = seq + 1;
4180 jv1[sfMPTokenIssuanceID] = to_string(mptID);
4181 jv1[sfFlags] = tfMPTLock;
4182
4183 Json::Value jv2;
4184 jv2[sfTransactionType] = jss::MPTokenIssuanceSet;
4185 jv2[sfAccount] = alice.human();
4186 jv2[sfDelegate] = bob.human();
4187 jv2[sfSequence] = seq + 2;
4188 jv2[sfMPTokenIssuanceID] = to_string(mptID);
4189 jv2[sfFlags] = tfMPTUnlock;
4190
4191 auto const [txIDs, batchID] = submitBatch(
4192 env,
4193 tesSUCCESS,
4194 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4195 batch::inner(jv1, seq + 1),
4196 batch::inner(jv2, seq + 2));
4197 env.close();
4198
4199 std::vector<TestLedgerData> testCases = {
4200 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
4201 {1, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[0], batchID},
4202 {2, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[1], batchID},
4203 };
4204 validateClosedLedger(env, testCases);
4205 }
4206
4207 // delegated non atomic inner (TrustSet)
4208 // this also makes sure tfInnerBatchTxn won't block delegated TrustSet
4209 // with granular permission
4210 {
4211 test::jtx::Env env{*this, features};
4212 Account gw{"gw"};
4213 Account alice{"alice"};
4214 Account bob{"bob"};
4215 env.fund(XRP(10000), gw, alice, bob);
4216 env(fset(gw, asfRequireAuth));
4217 env.close();
4218 env(trust(alice, gw["USD"](50)));
4219 env.close();
4220
4221 env(delegate::set(
4222 gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
4223 env.close();
4224
4225 auto const seq = env.seq(gw);
4226 auto const batchFee = batch::calcBatchFee(env, 0, 2);
4227
4228 auto jv1 = trust(gw, gw["USD"](0), alice, tfSetfAuth);
4229 jv1[sfDelegate] = bob.human();
4230 auto jv2 = trust(gw, gw["USD"](0), alice, tfSetFreeze);
4231 jv2[sfDelegate] = bob.human();
4232
4233 auto const [txIDs, batchID] = submitBatch(
4234 env,
4235 tesSUCCESS,
4236 batch::outer(gw, seq, batchFee, tfAllOrNothing),
4237 batch::inner(jv1, seq + 1),
4238 batch::inner(jv2, seq + 2));
4239 env.close();
4240
4241 std::vector<TestLedgerData> testCases = {
4242 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
4243 {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
4244 {2, "TrustSet", "tesSUCCESS", txIDs[1], batchID},
4245 };
4246 validateClosedLedger(env, testCases);
4247 }
4248
4249 // inner transaction not authorized by the delegating account.
4250 {
4251 test::jtx::Env env{*this, features};
4252 Account gw{"gw"};
4253 Account alice{"alice"};
4254 Account bob{"bob"};
4255 env.fund(XRP(10000), gw, alice, bob);
4256 env(fset(gw, asfRequireAuth));
4257 env.close();
4258 env(trust(alice, gw["USD"](50)));
4259 env.close();
4260
4261 env(delegate::set(
4262 gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
4263 env.close();
4264
4265 auto const seq = env.seq(gw);
4266 auto const batchFee = batch::calcBatchFee(env, 0, 2);
4267
4268 auto jv1 = trust(gw, gw["USD"](0), alice, tfSetFreeze);
4269 jv1[sfDelegate] = bob.human();
4270 auto jv2 = trust(gw, gw["USD"](0), alice, tfClearFreeze);
4271 jv2[sfDelegate] = bob.human();
4272
4273 auto const [txIDs, batchID] = submitBatch(
4274 env,
4275 tesSUCCESS,
4276 batch::outer(gw, seq, batchFee, tfIndependent),
4277 batch::inner(jv1, seq + 1),
4278 // terNO_DELEGATE_PERMISSION: not authorized to clear freeze
4279 batch::inner(jv2, seq + 2));
4280 env.close();
4281
4282 std::vector<TestLedgerData> testCases = {
4283 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
4284 {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
4285 };
4286 validateClosedLedger(env, testCases);
4287 }
4288 }
4289
4290 void
4292 {
4293 // Verifying that the RPC response from submit includes
4294 // the account_sequence_available, account_sequence_next,
4295 // open_ledger_cost and validated_ledger_index fields.
4296 testcase("Validate RPC response");
4297
4298 using namespace jtx;
4299 Env env(*this, features);
4300 Account const alice("alice");
4301 Account const bob("bob");
4302 env.fund(XRP(10000), alice, bob);
4303 env.close();
4304
4305 // tes
4306 {
4307 auto const baseFee = env.current()->fees().base;
4308 auto const aliceSeq = env.seq(alice);
4309 auto jtx = env.jt(pay(alice, bob, XRP(1)));
4310
4311 Serializer s;
4312 jtx.stx->add(s);
4313 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
4314 env.close();
4315
4316 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
4317 BEAST_EXPECT(
4318 jr[jss::account_sequence_available].asUInt() == aliceSeq + 1);
4319 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
4320 BEAST_EXPECT(
4321 jr[jss::account_sequence_next].asUInt() == aliceSeq + 1);
4322 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
4323 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
4324 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
4325 }
4326
4327 // tec failure
4328 {
4329 auto const baseFee = env.current()->fees().base;
4330 auto const aliceSeq = env.seq(alice);
4331 env(fset(bob, asfRequireDest));
4332 auto jtx = env.jt(pay(alice, bob, XRP(1)), seq(aliceSeq));
4333
4334 Serializer s;
4335 jtx.stx->add(s);
4336 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
4337 env.close();
4338
4339 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
4340 BEAST_EXPECT(
4341 jr[jss::account_sequence_available].asUInt() == aliceSeq + 1);
4342 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
4343 BEAST_EXPECT(
4344 jr[jss::account_sequence_next].asUInt() == aliceSeq + 1);
4345 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
4346 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
4347 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
4348 }
4349
4350 // tem failure
4351 {
4352 auto const baseFee = env.current()->fees().base;
4353 auto const aliceSeq = env.seq(alice);
4354 auto jtx = env.jt(pay(alice, bob, XRP(1)), seq(aliceSeq + 1));
4355
4356 Serializer s;
4357 jtx.stx->add(s);
4358 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
4359 env.close();
4360
4361 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
4362 BEAST_EXPECT(
4363 jr[jss::account_sequence_available].asUInt() == aliceSeq);
4364 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
4365 BEAST_EXPECT(jr[jss::account_sequence_next].asUInt() == aliceSeq);
4366 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
4367 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
4368 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
4369 }
4370 }
4371
4372 void
4374 {
4375 using namespace jtx;
4376 Env env(*this, features);
4377 Account const alice("alice");
4378 Account const bob("bob");
4379 Account const carol("carol");
4380 env.fund(XRP(10000), alice, bob, carol);
4381 env.close();
4382
4383 auto getBaseFee = [&](JTx const& jtx) -> XRPAmount {
4384 Serializer s;
4385 jtx.stx->add(s);
4386 return Batch::calculateBaseFee(*env.current(), *jtx.stx);
4387 };
4388
4389 // bad: Inner Batch transaction found
4390 {
4391 auto const seq = env.seq(alice);
4392 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4393 auto jtx = env.jt(
4394 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4396 batch::outer(alice, seq, batchFee, tfAllOrNothing), seq),
4397 batch::inner(pay(alice, bob, XRP(1)), seq + 2));
4398 XRPAmount const txBaseFee = getBaseFee(jtx);
4399 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4400 }
4401
4402 // bad: Raw Transactions array exceeds max entries.
4403 {
4404 auto const seq = env.seq(alice);
4405 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4406
4407 auto jtx = env.jt(
4408 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4409 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
4410 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
4411 batch::inner(pay(alice, bob, XRP(1)), seq + 3),
4412 batch::inner(pay(alice, bob, XRP(1)), seq + 4),
4413 batch::inner(pay(alice, bob, XRP(1)), seq + 5),
4414 batch::inner(pay(alice, bob, XRP(1)), seq + 6),
4415 batch::inner(pay(alice, bob, XRP(1)), seq + 7),
4416 batch::inner(pay(alice, bob, XRP(1)), seq + 8),
4417 batch::inner(pay(alice, bob, XRP(1)), seq + 9));
4418
4419 XRPAmount const txBaseFee = getBaseFee(jtx);
4420 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4421 }
4422
4423 // bad: Signers array exceeds max entries.
4424 {
4425 auto const seq = env.seq(alice);
4426 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4427
4428 auto jtx = env.jt(
4429 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4430 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
4431 batch::inner(pay(alice, bob, XRP(5)), seq + 2),
4432 batch::sig(
4433 bob,
4434 carol,
4435 alice,
4436 bob,
4437 carol,
4438 alice,
4439 bob,
4440 carol,
4441 alice,
4442 alice));
4443 XRPAmount const txBaseFee = getBaseFee(jtx);
4444 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4445 }
4446
4447 // good:
4448 {
4449 auto const seq = env.seq(alice);
4450 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4451 auto jtx = env.jt(
4452 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4453 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
4454 batch::inner(pay(bob, alice, XRP(2)), seq + 2));
4455 XRPAmount const txBaseFee = getBaseFee(jtx);
4456 BEAST_EXPECT(txBaseFee == batchFee);
4457 }
4458 }
4459
4460 void
4462 {
4463 testEnable(features);
4464 testPreflight(features);
4465 testPreclaim(features);
4466 testBadRawTxn(features);
4467 testBadSequence(features);
4468 testBadOuterFee(features);
4469 testCalculateBaseFee(features);
4470 testAllOrNothing(features);
4471 testOnlyOne(features);
4472 testUntilFailure(features);
4473 testIndependent(features);
4474 testInnerSubmitRPC(features);
4475 testAccountActivation(features);
4476 testAccountSet(features);
4477 testAccountDelete(features);
4478 testLoan(features);
4479 testObjectCreateSequence(features);
4480 testObjectCreateTicket(features);
4481 testObjectCreate3rdParty(features);
4482 testTickets(features);
4483 testSequenceOpenLedger(features);
4484 testTicketsOpenLedger(features);
4485 testObjectsOpenLedger(features);
4486 testPseudoTxn(features);
4487 testOpenLedger(features);
4488 testBatchTxQueue(features);
4489 testBatchNetworkOps(features);
4490 testBatchDelegate(features);
4491 testValidateRPCResponse(features);
4492 testBatchCalculateBaseFee(features);
4493 }
4494
4495public:
4496 void
4497 run() override
4498 {
4499 using namespace test::jtx;
4500 auto const sa = testable_amendments();
4501 testWithFeats(sa - fixBatchInnerSigs);
4502 testWithFeats(sa);
4503 }
4504};
4505
4506BEAST_DEFINE_TESTSUITE(Batch, app, xrpl);
4507
4508} // namespace test
4509} // namespace xrpl
T any_of(T... args)
Represents a JSON value.
Definition json_value.h:131
A generic endpoint for log messages.
Definition Journal.h:41
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
virtual TxQ & getTxQ()=0
virtual HashRouter & getHashRouter()=0
virtual NetworkOPs & getOPs()=0
static constexpr auto disabledTxTypes
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Calculates the total base fee for a batch transaction.
Definition Batch.cpp:35
HashRouterFlags getFlags(uint256 const &key)
virtual void submitTransaction(std::shared_ptr< STTx const > const &)=0
virtual void processTransaction(std::shared_ptr< Transaction > &transaction, bool bUnlimited, bool bLocal, FailHard failType)=0
Process transactions as they arrive from the network or which are submitted by clients.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:46
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
Json::Value getJson(JsonOptions options) const override
Definition STTx.cpp:308
Slice slice() const noexcept
Definition Serializer.h:47
An immutable linear range of bytes.
Definition Slice.h:27
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition TxQ.cpp:1756
void testOpenLedger(FeatureBitset features)
void testTicketsOpenLedger(FeatureBitset features)
void run() override
Runs the suite.
void testBadRawTxn(FeatureBitset features)
void testObjectsOpenLedger(FeatureBitset features)
void testSequenceOpenLedger(FeatureBitset features)
void testEnable(FeatureBitset features)
void testPreflight(FeatureBitset features)
void testObjectCreate3rdParty(FeatureBitset features)
auto openLedgerFee(jtx::Env &env, XRPAmount const &batchFee)
void testIndependent(FeatureBitset features)
void doTestInnerSubmitRPC(FeatureBitset features, bool withBatch)
void testBatchDelegate(FeatureBitset features)
void testBadSequence(FeatureBitset features)
void testTickets(FeatureBitset features)
void testAccountSet(FeatureBitset features)
std::pair< std::vector< std::string >, std::string > submitBatch(jtx::Env &env, TER const &result, Args &&... args)
void testPreclaim(FeatureBitset features)
void testBatchTxQueue(FeatureBitset features)
void testAllOrNothing(FeatureBitset features)
void testValidateRPCResponse(FeatureBitset features)
void testWithFeats(FeatureBitset features)
void testBatchCalculateBaseFee(FeatureBitset features)
void testLoan(FeatureBitset features)
void testBatchNetworkOps(FeatureBitset features)
void testObjectCreateTicket(FeatureBitset features)
void testInnerSubmitRPC(FeatureBitset features)
void validateClosedLedger(jtx::Env &env, std::vector< TestLedgerData > const &ledgerResults)
void testCalculateBaseFee(FeatureBitset features)
void testAccountActivation(FeatureBitset features)
void testUntilFailure(FeatureBitset features)
static uint256 getCheckIndex(AccountID const &account, std::uint32_t uSequence)
void validateInnerTxn(jtx::Env &env, std::string const &batchID, TestLedgerData const &ledgerResult)
Json::Value getTxByIndex(Json::Value const &jrr, int const index)
Json::Value getLastLedger(jtx::Env &env)
void testObjectCreateSequence(FeatureBitset features)
void testOnlyOne(FeatureBitset features)
static std::unique_ptr< Config > makeSmallQueueConfig(std::map< std::string, std::string > extraTxQ={}, std::map< std::string, std::string > extraVoting={})
void testAccountDelete(FeatureBitset features)
void testPseudoTxn(FeatureBitset features)
void testBadOuterFee(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition Account.h:20
A transaction testing environment.
Definition Env.h:102
Application & app()
Definition Env.h:244
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:104
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:98
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:272
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:251
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:491
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:774
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:314
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:144
Adds a new Batch Txn on a JTx and autofills.
Definition batch.h:42
Set a batch nested multi-signature on a JTx.
Definition batch.h:115
Set a batch signature on a JTx.
Definition batch.h:92
Set the domain on a JTx.
Definition domain.h:11
Set the fee on a JTx.
Definition fee.h:18
Match set account flags.
Definition flags.h:109
Set a multisignature on a JTx.
Definition multisign.h:48
Set the regular signature on a JTx.
Definition sig.h:16
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:16
Set a ticket sequence on a JTx.
Definition ticket.h:29
Set the flags on a JTx.
Definition txflags.h:12
T is_same_v
T make_pair(T... args)
@ arrayValue
array value (ordered list)
Definition json_value.h:26
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:27
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:552
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:318
Keylet loan(uint256 const &loanBrokerID, std::uint32_t loanSeq) noexcept
Definition Indexes.cpp:558
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Batch.
Definition batch.cpp:30
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate Batch Fee.
Definition batch.cpp:19
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition multisign.cpp:15
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
void incLgrSeqForAccDel(jtx::Env &env, jtx::Account const &acc, std::uint32_t margin=0)
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
Definition Env.h:49
FeatureBitset testable_amendments()
Definition Env.h:55
auto const amount
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition regkey.cpp:10
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
void checkMetrics(Suite &test, jtx::Env &env, std::size_t expectedCount, std::optional< std::size_t > expectedMaxCount, std::size_t expectedInLedger, std::size_t expectedPerLedger, std::uint64_t expectedMinFeeLevel=baseFeeLevel.fee(), std::uint64_t expectedMedFeeLevel=minEscalationFeeLevel.fee(), source_location const location=source_location::current())
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t asfAllowTrustLineClawback
Definition TxFlags.h:75
@ telINSUF_FEE_P
Definition TER.h:38
@ telENV_RPC_FAILED
Definition TER.h:49
@ terPRE_SEQ
Definition TER.h:202
@ terQUEUED
Definition TER.h:206
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:58
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
constexpr std::uint32_t tfImmediateOrCancel
Definition TxFlags.h:80
constexpr std::uint32_t asfDisableMaster
Definition TxFlags.h:61
ApplyResult apply(Application &app, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:128
constexpr std::uint32_t tfInnerBatchTxn
Definition TxFlags.h:42
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
constexpr TenthBips32 percentageToTenthBips(std::uint32_t percentage)
Definition Protocol.h:96
constexpr std::uint32_t const tfLoanImpair
Definition TxFlags.h:291
@ tefBAD_QUORUM
Definition TER.h:161
@ tefMASTER_DISABLED
Definition TER.h:158
@ tefBAD_SIGNATURE
Definition TER.h:160
@ tefBAD_AUTH
Definition TER.h:150
@ tefNOT_MULTI_SIGNING
Definition TER.h:162
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
Definition TxQ.h:844
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:443
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
constexpr std::uint32_t const tfMPTUnlock
Definition TxFlags.h:158
constexpr std::uint32_t tfAllOrNothing
Definition TxFlags.h:257
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:129
base_uint< 256 > uint256
Definition base_uint.h:539
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:100
TERSubset< CanCvtToTER > TER
Definition TER.h:630
constexpr std::uint32_t const tfMPTLock
Definition TxFlags.h:157
bool passesLocalChecks(STObject const &st, std::string &)
Definition STTx.cpp:771
constexpr std::uint32_t tfOnlyOne
Definition TxFlags.h:258
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
@ tapNONE
Definition ApplyView.h:12
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:442
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
@ temBAD_REGKEY
Definition TER.h:79
@ temARRAY_TOO_LARGE
Definition TER.h:122
@ temBAD_FEE
Definition TER.h:73
@ temINVALID_FLAG
Definition TER.h:92
@ temARRAY_EMPTY
Definition TER.h:121
@ temSEQ_AND_TICKET
Definition TER.h:107
@ temDISABLED
Definition TER.h:95
@ temBAD_SIGNATURE
Definition TER.h:86
@ temINVALID_INNER_BATCH
Definition TER.h:124
@ temREDUNDANT
Definition TER.h:93
@ temBAD_SIGNER
Definition TER.h:96
Buffer sign(PublicKey const &pk, SecretKey const &sk, Slice const &message)
Generate a signature for a message.
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
constexpr std::uint32_t tfUntilFailure
Definition TxFlags.h:259
@ tecNO_ENTRY
Definition TER.h:288
void serializeBatch(Serializer &msg, std::uint32_t const &flags, std::vector< uint256 > const &txids)
@ lsfLoanImpaired
constexpr std::uint32_t tfIndependent
Definition TxFlags.h:260
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
constexpr std::uint32_t tfDisallowXRP
Definition TxFlags.h:51
bool isPseudoTx(STObject const &tx)
Check whether a transaction is a pseudo-transaction.
Definition STTx.cpp:810
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:152
@ tesSUCCESS
Definition TER.h:226
T push_back(T... args)
T size(T... args)
T str(T... args)
uint256 key
Definition Keylet.h:21
std::optional< std::string > batchID
Execution context for applying a JSON transaction.
Definition JTx.h:26
Thrown when parse fails.
Definition utility.h:19
Set the sequence number on a JTx.
Definition seq.h:15
seq(autofill_t)
Definition seq.h:21