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