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