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("object create w/ sequence");
2542
2543 using namespace test::jtx;
2544 using namespace std::literals;
2545
2546 test::jtx::Env env{*this, envconfig()};
2547
2548 auto const alice = Account("alice");
2549 auto const bob = Account("bob");
2550 auto const gw = Account("gw");
2551 auto const USD = gw["USD"];
2552
2553 env.fund(XRP(10000), alice, bob, gw);
2554 env.close();
2555
2556 env.trust(USD(1000), alice, bob);
2557 env(pay(gw, alice, USD(100)));
2558 env(pay(gw, bob, USD(100)));
2559 env.close();
2560
2561 // success
2562 {
2563 auto const aliceSeq = env.seq(alice);
2564 auto const bobSeq = env.seq(bob);
2565 auto const preAlice = env.balance(alice);
2566 auto const preBob = env.balance(bob);
2567 auto const preAliceUSD = env.balance(alice, USD.issue());
2568 auto const preBobUSD = env.balance(bob, USD.issue());
2569
2570 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2571 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
2572 auto const [txIDs, batchID] = submitBatch(
2573 env,
2574 tesSUCCESS,
2575 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
2576 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
2577 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
2578 batch::sig(bob));
2579 env.close();
2580
2581 std::vector<TestLedgerData> testCases = {
2582 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2583 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
2584 {2, "CheckCash", "tesSUCCESS", txIDs[1], batchID},
2585 };
2586 validateClosedLedger(env, testCases);
2587
2588 // Alice consumes sequences (# of txns)
2589 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2590
2591 // Alice consumes sequences (# of txns)
2592 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
2593
2594 // Alice pays Fee; Bob XRP Unchanged
2595 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
2596 BEAST_EXPECT(env.balance(bob) == preBob);
2597
2598 // Alice pays USD & Bob receives USD
2599 BEAST_EXPECT(
2600 env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
2601 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
2602 }
2603
2604 // failure
2605 {
2606 env(fset(alice, asfRequireDest));
2607 env.close();
2608
2609 auto const aliceSeq = env.seq(alice);
2610 auto const bobSeq = env.seq(bob);
2611 auto const preAlice = env.balance(alice);
2612 auto const preBob = env.balance(bob);
2613 auto const preAliceUSD = env.balance(alice, USD.issue());
2614 auto const preBobUSD = env.balance(bob, USD.issue());
2615
2616 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2617 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
2618 auto const [txIDs, batchID] = submitBatch(
2619 env,
2620 tesSUCCESS,
2621 batch::outer(alice, aliceSeq, batchFee, tfIndependent),
2622 // tecDST_TAG_NEEDED - alice has enabled asfRequireDest
2623 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
2624 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
2625 batch::sig(bob));
2626 env.close();
2627
2628 std::vector<TestLedgerData> testCases = {
2629 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2630 {1, "CheckCreate", "tecDST_TAG_NEEDED", txIDs[0], batchID},
2631 {2, "CheckCash", "tecNO_ENTRY", txIDs[1], batchID},
2632 };
2633 validateClosedLedger(env, testCases);
2634
2635 // Alice consumes sequences (# of txns)
2636 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2637
2638 // Bob consumes sequences (# of txns)
2639 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
2640
2641 // Alice pays Fee; Bob XRP Unchanged
2642 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
2643 BEAST_EXPECT(env.balance(bob) == preBob);
2644
2645 // Alice pays USD & Bob receives USD
2646 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD);
2647 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD);
2648 }
2649 }
2650
2651 void
2653 {
2654 testcase("object create w/ ticket");
2655
2656 using namespace test::jtx;
2657 using namespace std::literals;
2658
2659 test::jtx::Env env{*this, envconfig()};
2660
2661 auto const alice = Account("alice");
2662 auto const bob = Account("bob");
2663 auto const gw = Account("gw");
2664 auto const USD = gw["USD"];
2665
2666 env.fund(XRP(10000), alice, bob, gw);
2667 env.close();
2668
2669 env.trust(USD(1000), alice, bob);
2670 env(pay(gw, alice, USD(100)));
2671 env(pay(gw, bob, USD(100)));
2672 env.close();
2673
2674 auto const aliceSeq = env.seq(alice);
2675 auto const bobSeq = env.seq(bob);
2676 auto const preAlice = env.balance(alice);
2677 auto const preBob = env.balance(bob);
2678 auto const preAliceUSD = env.balance(alice, USD.issue());
2679 auto const preBobUSD = env.balance(bob, USD.issue());
2680
2681 auto const batchFee = batch::calcBatchFee(env, 1, 3);
2682 uint256 const chkID{getCheckIndex(bob, bobSeq + 1)};
2683 auto const [txIDs, batchID] = submitBatch(
2684 env,
2685 tesSUCCESS,
2686 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
2687 batch::inner(ticket::create(bob, 10), bobSeq),
2688 batch::inner(check::create(bob, alice, USD(10)), 0, bobSeq + 1),
2689 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq + 1),
2690 batch::sig(bob));
2691 env.close();
2692
2693 std::vector<TestLedgerData> testCases = {
2694 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2695 {1, "TicketCreate", "tesSUCCESS", txIDs[0], batchID},
2696 {2, "CheckCreate", "tesSUCCESS", txIDs[1], batchID},
2697 {3, "CheckCash", "tesSUCCESS", txIDs[2], batchID},
2698 };
2699 validateClosedLedger(env, testCases);
2700
2701 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2702 BEAST_EXPECT(env.seq(bob) == bobSeq + 10 + 1);
2703 BEAST_EXPECT(env.balance(alice) == preAlice - batchFee);
2704 BEAST_EXPECT(env.balance(bob) == preBob);
2705 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
2706 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
2707 }
2708
2709 void
2711 {
2712 testcase("object create w/ 3rd party");
2713
2714 using namespace test::jtx;
2715 using namespace std::literals;
2716
2717 test::jtx::Env env{*this, envconfig()};
2718
2719 auto const alice = Account("alice");
2720 auto const bob = Account("bob");
2721 auto const carol = Account("carol");
2722 auto const gw = Account("gw");
2723 auto const USD = gw["USD"];
2724
2725 env.fund(XRP(10000), alice, bob, carol, gw);
2726 env.close();
2727
2728 env.trust(USD(1000), alice, bob);
2729 env(pay(gw, alice, USD(100)));
2730 env(pay(gw, bob, USD(100)));
2731 env.close();
2732
2733 auto const aliceSeq = env.seq(alice);
2734 auto const bobSeq = env.seq(bob);
2735 auto const carolSeq = env.seq(carol);
2736 auto const preAlice = env.balance(alice);
2737 auto const preBob = env.balance(bob);
2738 auto const preCarol = env.balance(carol);
2739 auto const preAliceUSD = env.balance(alice, USD.issue());
2740 auto const preBobUSD = env.balance(bob, USD.issue());
2741
2742 auto const batchFee = batch::calcBatchFee(env, 2, 2);
2743 uint256 const chkID{getCheckIndex(bob, env.seq(bob))};
2744 auto const [txIDs, batchID] = submitBatch(
2745 env,
2746 tesSUCCESS,
2747 batch::outer(carol, carolSeq, batchFee, tfAllOrNothing),
2748 batch::inner(check::create(bob, alice, USD(10)), bobSeq),
2749 batch::inner(check::cash(alice, chkID, USD(10)), aliceSeq),
2750 batch::sig(alice, bob));
2751 env.close();
2752
2753 std::vector<TestLedgerData> testCases = {
2754 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2755 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
2756 {2, "CheckCash", "tesSUCCESS", txIDs[1], batchID},
2757 };
2758 validateClosedLedger(env, testCases);
2759
2760 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
2761 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
2762 BEAST_EXPECT(env.seq(carol) == carolSeq + 1);
2763 BEAST_EXPECT(env.balance(alice) == preAlice);
2764 BEAST_EXPECT(env.balance(bob) == preBob);
2765 BEAST_EXPECT(env.balance(carol) == preCarol - batchFee);
2766 BEAST_EXPECT(env.balance(alice, USD.issue()) == preAliceUSD + USD(10));
2767 BEAST_EXPECT(env.balance(bob, USD.issue()) == preBobUSD - USD(10));
2768 }
2769
2770 void
2772 {
2773 {
2774 testcase("tickets outer");
2775
2776 using namespace test::jtx;
2777 using namespace std::literals;
2778
2779 test::jtx::Env env{*this, envconfig()};
2780
2781 auto const alice = Account("alice");
2782 auto const bob = Account("bob");
2783
2784 env.fund(XRP(10000), alice, bob);
2785 env.close();
2786
2787 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
2788 env(ticket::create(alice, 10));
2789 env.close();
2790
2791 auto const aliceSeq = env.seq(alice);
2792 auto const preAlice = env.balance(alice);
2793 auto const preBob = env.balance(bob);
2794
2795 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2796 auto const [txIDs, batchID] = submitBatch(
2797 env,
2798 tesSUCCESS,
2799 batch::outer(alice, 0, batchFee, tfAllOrNothing),
2800 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 0),
2801 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
2802 ticket::use(aliceTicketSeq));
2803 env.close();
2804
2805 std::vector<TestLedgerData> testCases = {
2806 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2807 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2808 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2809 };
2810 validateClosedLedger(env, testCases);
2811
2812 auto const sle = env.le(keylet::account(alice));
2813 BEAST_EXPECT(sle);
2814 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 9);
2815 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 9);
2816
2817 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
2818 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
2819 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2820 }
2821
2822 {
2823 testcase("tickets inner");
2824
2825 using namespace test::jtx;
2826 using namespace std::literals;
2827
2828 test::jtx::Env env{*this, envconfig()};
2829
2830 auto const alice = Account("alice");
2831 auto const bob = Account("bob");
2832
2833 env.fund(XRP(10000), alice, bob);
2834 env.close();
2835
2836 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
2837 env(ticket::create(alice, 10));
2838 env.close();
2839
2840 auto const aliceSeq = env.seq(alice);
2841 auto const preAlice = env.balance(alice);
2842 auto const preBob = env.balance(bob);
2843
2844 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2845 auto const [txIDs, batchID] = submitBatch(
2846 env,
2847 tesSUCCESS,
2848 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
2849 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq),
2850 batch::inner(pay(alice, bob, XRP(2)), 0, aliceTicketSeq + 1));
2851 env.close();
2852
2853 std::vector<TestLedgerData> testCases = {
2854 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2855 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2856 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2857 };
2858 validateClosedLedger(env, testCases);
2859
2860 auto const sle = env.le(keylet::account(alice));
2861 BEAST_EXPECT(sle);
2862 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 8);
2863 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8);
2864
2865 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
2866 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
2867 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2868 }
2869
2870 {
2871 testcase("tickets outer inner");
2872
2873 using namespace test::jtx;
2874 using namespace std::literals;
2875
2876 test::jtx::Env env{*this, envconfig()};
2877
2878 auto const alice = Account("alice");
2879 auto const bob = Account("bob");
2880
2881 env.fund(XRP(10000), alice, bob);
2882 env.close();
2883
2884 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
2885 env(ticket::create(alice, 10));
2886 env.close();
2887
2888 auto const aliceSeq = env.seq(alice);
2889 auto const preAlice = env.balance(alice);
2890 auto const preBob = env.balance(bob);
2891
2892 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2893 auto const [txIDs, batchID] = submitBatch(
2894 env,
2895 tesSUCCESS,
2896 batch::outer(alice, 0, batchFee, tfAllOrNothing),
2897 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
2898 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
2899 ticket::use(aliceTicketSeq));
2900 env.close();
2901
2902 std::vector<TestLedgerData> testCases = {
2903 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2904 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2905 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2906 };
2907 validateClosedLedger(env, testCases);
2908
2909 auto const sle = env.le(keylet::account(alice));
2910 BEAST_EXPECT(sle);
2911 BEAST_EXPECT(sle->getFieldU32(sfOwnerCount) == 8);
2912 BEAST_EXPECT(sle->getFieldU32(sfTicketCount) == 8);
2913
2914 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
2915 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
2916 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
2917 }
2918 }
2919
2920 void
2922 {
2923 testcase("sequence open ledger");
2924
2925 using namespace test::jtx;
2926 using namespace std::literals;
2927
2928 auto const alice = Account("alice");
2929 auto const bob = Account("bob");
2930 auto const carol = Account("carol");
2931
2932 // Before Batch Txn w/ retry following ledger
2933 {
2934 // IMPORTANT: The batch txn is applied first, then the noop txn.
2935 // Because of this ordering, the noop txn is not applied and is
2936 // overwritten by the payment in the batch transaction. Because the
2937 // terPRE_SEQ is outside of the batch this noop transaction will ge
2938 // reapplied in the following ledger
2939 test::jtx::Env env{*this, envconfig()};
2940 env.fund(XRP(10000), alice, bob, carol);
2941 env.close();
2942
2943 auto const aliceSeq = env.seq(alice);
2944 auto const carolSeq = env.seq(carol);
2945
2946 // AccountSet Txn
2947 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 2));
2948 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
2949 env(noopTxn, ter(terPRE_SEQ));
2950
2951 // Batch Txn
2952 auto const batchFee = batch::calcBatchFee(env, 1, 2);
2953 auto const [txIDs, batchID] = submitBatch(
2954 env,
2955 tesSUCCESS,
2956 batch::outer(carol, carolSeq, batchFee, tfAllOrNothing),
2957 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
2958 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
2959 batch::sig(alice));
2960 env.close();
2961
2962 {
2963 std::vector<TestLedgerData> testCases = {
2964 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
2965 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
2966 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
2967 };
2968 validateClosedLedger(env, testCases);
2969 }
2970
2971 env.close();
2972 {
2973 // next ledger contains noop txn
2974 std::vector<TestLedgerData> testCases = {
2975 {0, "AccountSet", "tesSUCCESS", noopTxnID, std::nullopt},
2976 };
2977 validateClosedLedger(env, testCases);
2978 }
2979 }
2980
2981 // Before Batch Txn w/ same sequence
2982 {
2983 // IMPORTANT: The batch txn is applied first, then the noop txn.
2984 // Because of this ordering, the noop txn is not applied and is
2985 // overwritten by the payment in the batch transaction.
2986 test::jtx::Env env{*this, envconfig()};
2987 env.fund(XRP(10000), alice, bob);
2988 env.close();
2989
2990 auto const aliceSeq = env.seq(alice);
2991
2992 // AccountSet Txn
2993 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 1));
2994 env(noopTxn, ter(terPRE_SEQ));
2995
2996 // Batch Txn
2997 auto const batchFee = batch::calcBatchFee(env, 0, 2);
2998 auto const [txIDs, batchID] = submitBatch(
2999 env,
3000 tesSUCCESS,
3001 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3002 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
3003 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 2));
3004 env.close();
3005
3006 {
3007 std::vector<TestLedgerData> testCases = {
3008 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3009 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3010 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3011 };
3012 validateClosedLedger(env, testCases);
3013 }
3014
3015 env.close();
3016 {
3017 // next ledger is empty
3018 std::vector<TestLedgerData> testCases = {};
3019 validateClosedLedger(env, testCases);
3020 }
3021 }
3022
3023 // After Batch Txn w/ same sequence
3024 {
3025 // IMPORTANT: The batch txn is applied first, then the noop txn.
3026 // Because of this ordering, the noop txn is not applied and is
3027 // overwritten by the payment in the batch transaction.
3028 test::jtx::Env env{*this, envconfig()};
3029 env.fund(XRP(10000), alice, bob);
3030 env.close();
3031
3032 auto const aliceSeq = env.seq(alice);
3033 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3034 auto const [txIDs, batchID] = submitBatch(
3035 env,
3036 tesSUCCESS,
3037 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3038 batch::inner(pay(alice, bob, XRP(1)), aliceSeq + 1),
3039 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 2));
3040
3041 auto const noopTxn = env.jt(noop(alice), seq(aliceSeq + 1));
3042 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3043 env(noopTxn, ter(tesSUCCESS));
3044 env.close();
3045
3046 {
3047 std::vector<TestLedgerData> testCases = {
3048 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3049 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3050 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3051 };
3052 validateClosedLedger(env, testCases);
3053 }
3054
3055 env.close();
3056 {
3057 // next ledger is empty
3058 std::vector<TestLedgerData> testCases = {};
3059 validateClosedLedger(env, testCases);
3060 }
3061 }
3062
3063 // Outer Batch terPRE_SEQ
3064 {
3065 test::jtx::Env env{*this, envconfig()};
3066 env.fund(XRP(10000), alice, bob, carol);
3067 env.close();
3068
3069 auto const aliceSeq = env.seq(alice);
3070 auto const carolSeq = env.seq(carol);
3071
3072 // Batch Txn
3073 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3074 auto const [txIDs, batchID] = submitBatch(
3075 env,
3076 terPRE_SEQ,
3077 batch::outer(carol, carolSeq + 1, batchFee, tfAllOrNothing),
3078 batch::inner(pay(alice, bob, XRP(1)), aliceSeq),
3079 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
3080 batch::sig(alice));
3081
3082 // AccountSet Txn
3083 auto const noopTxn = env.jt(noop(carol), seq(carolSeq));
3084 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3085 env(noopTxn, ter(tesSUCCESS));
3086 env.close();
3087
3088 {
3089 std::vector<TestLedgerData> testCases = {
3090 {0, "AccountSet", "tesSUCCESS", noopTxnID, std::nullopt},
3091 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3092 {2, "Payment", "tesSUCCESS", txIDs[0], batchID},
3093 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3094 };
3095 validateClosedLedger(env, testCases);
3096 }
3097
3098 env.close();
3099 {
3100 // next ledger contains no transactions
3101 std::vector<TestLedgerData> testCases = {};
3102 validateClosedLedger(env, testCases);
3103 }
3104 }
3105 }
3106
3107 void
3109 {
3110 testcase("tickets open ledger");
3111
3112 using namespace test::jtx;
3113 using namespace std::literals;
3114
3115 auto const alice = Account("alice");
3116 auto const bob = Account("bob");
3117
3118 // Before Batch Txn w/ same ticket
3119 {
3120 // IMPORTANT: The batch txn is applied first, then the noop txn.
3121 // Because of this ordering, the noop txn is not applied and is
3122 // overwritten by the payment in the batch transaction.
3123 test::jtx::Env env{*this, envconfig()};
3124 env.fund(XRP(10000), alice, bob);
3125 env.close();
3126
3127 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3128 env(ticket::create(alice, 10));
3129 env.close();
3130
3131 auto const aliceSeq = env.seq(alice);
3132
3133 // AccountSet Txn
3134 auto const noopTxn =
3135 env.jt(noop(alice), ticket::use(aliceTicketSeq + 1));
3136 auto const noopTxnID = to_string(noopTxn.stx->getTransactionID());
3137 env(noopTxn, ter(tesSUCCESS));
3138
3139 // Batch Txn
3140 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3141 auto const [txIDs, batchID] = submitBatch(
3142 env,
3143 tesSUCCESS,
3144 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3145 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3146 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
3147 ticket::use(aliceTicketSeq));
3148 env.close();
3149
3150 {
3151 std::vector<TestLedgerData> testCases = {
3152 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3153 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3154 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3155 };
3156 validateClosedLedger(env, testCases);
3157 }
3158
3159 env.close();
3160 {
3161 // next ledger is empty
3162 std::vector<TestLedgerData> testCases = {};
3163 validateClosedLedger(env, testCases);
3164 }
3165 }
3166
3167 // After Batch Txn w/ same ticket
3168 {
3169 // IMPORTANT: The batch txn is applied first, then the noop txn.
3170 // Because of this ordering, the noop txn is not applied and is
3171 // overwritten by the payment in the batch transaction.
3172 test::jtx::Env env{*this, envconfig()};
3173 env.fund(XRP(10000), alice, bob);
3174 env.close();
3175
3176 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3177 env(ticket::create(alice, 10));
3178 env.close();
3179
3180 auto const aliceSeq = env.seq(alice);
3181
3182 // Batch Txn
3183 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3184 auto const [txIDs, batchID] = submitBatch(
3185 env,
3186 tesSUCCESS,
3187 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3188 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3189 batch::inner(pay(alice, bob, XRP(2)), aliceSeq),
3190 ticket::use(aliceTicketSeq));
3191
3192 // AccountSet Txn
3193 auto const noopTxn =
3194 env.jt(noop(alice), ticket::use(aliceTicketSeq + 1));
3195 env(noopTxn);
3196
3197 env.close();
3198 {
3199 std::vector<TestLedgerData> testCases = {
3200 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3201 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3202 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3203 };
3204 validateClosedLedger(env, testCases);
3205 }
3206
3207 env.close();
3208 {
3209 // next ledger is empty
3210 std::vector<TestLedgerData> testCases = {};
3211 validateClosedLedger(env, testCases);
3212 }
3213 }
3214 }
3215
3216 void
3218 {
3219 testcase("objects open ledger");
3220
3221 using namespace test::jtx;
3222 using namespace std::literals;
3223
3224 auto const alice = Account("alice");
3225 auto const bob = Account("bob");
3226
3227 // Consume Object Before Batch Txn
3228 {
3229 // IMPORTANT: The initial result of `CheckCash` is tecNO_ENTRY
3230 // because the create transaction has not been applied because the
3231 // batch will run in the close ledger process. The batch will be
3232 // allied and then retry this transaction in the current ledger.
3233
3234 test::jtx::Env env{*this, envconfig()};
3235 env.fund(XRP(10000), alice, bob);
3236 env.close();
3237
3238 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3239 env(ticket::create(alice, 10));
3240 env.close();
3241
3242 auto const aliceSeq = env.seq(alice);
3243
3244 // CheckCash Txn
3245 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3246 auto const objTxn = env.jt(check::cash(bob, chkID, XRP(10)));
3247 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3248 env(objTxn, ter(tecNO_ENTRY));
3249
3250 // Batch Txn
3251 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3252 auto const [txIDs, batchID] = submitBatch(
3253 env,
3254 tesSUCCESS,
3255 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3256 batch::inner(check::create(alice, bob, XRP(10)), aliceSeq),
3257 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3258 ticket::use(aliceTicketSeq));
3259
3260 env.close();
3261 {
3262 std::vector<TestLedgerData> testCases = {
3263 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3264 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
3265 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3266 {3, "CheckCash", "tesSUCCESS", objTxnID, std::nullopt},
3267 };
3268 validateClosedLedger(env, testCases);
3269 }
3270
3271 env.close();
3272 {
3273 // next ledger is empty
3274 std::vector<TestLedgerData> testCases = {};
3275 validateClosedLedger(env, testCases);
3276 }
3277 }
3278
3279 // Create Object Before Batch Txn
3280 {
3281 test::jtx::Env env{*this, envconfig()};
3282 env.fund(XRP(10000), alice, bob);
3283 env.close();
3284
3285 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3286 env(ticket::create(alice, 10));
3287 env.close();
3288
3289 auto const aliceSeq = env.seq(alice);
3290 auto const bobSeq = env.seq(bob);
3291
3292 // CheckCreate Txn
3293 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3294 auto const objTxn = env.jt(check::create(alice, bob, XRP(10)));
3295 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3296 env(objTxn, ter(tesSUCCESS));
3297
3298 // Batch Txn
3299 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3300 auto const [txIDs, batchID] = submitBatch(
3301 env,
3302 tesSUCCESS,
3303 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3304 batch::inner(check::cash(bob, chkID, XRP(10)), bobSeq),
3305 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3306 ticket::use(aliceTicketSeq),
3307 batch::sig(bob));
3308
3309 env.close();
3310 {
3311 std::vector<TestLedgerData> testCases = {
3312 {0, "CheckCreate", "tesSUCCESS", objTxnID, std::nullopt},
3313 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3314 {2, "CheckCash", "tesSUCCESS", txIDs[0], batchID},
3315 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3316 };
3317 validateClosedLedger(env, testCases);
3318 }
3319 }
3320
3321 // After Batch Txn
3322 {
3323 // IMPORTANT: The initial result of `CheckCash` is tecNO_ENTRY
3324 // because the create transaction has not been applied because the
3325 // batch will run in the close ledger process. The batch will be
3326 // applied and then retry this transaction in the current ledger.
3327
3328 test::jtx::Env env{*this, envconfig()};
3329 env.fund(XRP(10000), alice, bob);
3330 env.close();
3331
3332 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
3333 env(ticket::create(alice, 10));
3334 env.close();
3335
3336 auto const aliceSeq = env.seq(alice);
3337
3338 // Batch Txn
3339 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3340 uint256 const chkID{getCheckIndex(alice, aliceSeq)};
3341 auto const [txIDs, batchID] = submitBatch(
3342 env,
3343 tesSUCCESS,
3344 batch::outer(alice, 0, batchFee, tfAllOrNothing),
3345 batch::inner(check::create(alice, bob, XRP(10)), aliceSeq),
3346 batch::inner(pay(alice, bob, XRP(1)), 0, aliceTicketSeq + 1),
3347 ticket::use(aliceTicketSeq));
3348
3349 // CheckCash Txn
3350 auto const objTxn = env.jt(check::cash(bob, chkID, XRP(10)));
3351 auto const objTxnID = to_string(objTxn.stx->getTransactionID());
3352 env(objTxn, ter(tecNO_ENTRY));
3353
3354 env.close();
3355 {
3356 std::vector<TestLedgerData> testCases = {
3357 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3358 {1, "CheckCreate", "tesSUCCESS", txIDs[0], batchID},
3359 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3360 {3, "CheckCash", "tesSUCCESS", objTxnID, std::nullopt},
3361 };
3362 validateClosedLedger(env, testCases);
3363 }
3364 }
3365 }
3366
3367 void
3369 {
3370 testcase("pseudo txn with tfInnerBatchTxn");
3371
3372 using namespace test::jtx;
3373 using namespace std::literals;
3374
3375 test::jtx::Env env{*this, envconfig()};
3376
3377 auto const alice = Account("alice");
3378 auto const bob = Account("bob");
3379 env.fund(XRP(10000), alice, bob);
3380 env.close();
3381
3382 STTx const stx = STTx(ttAMENDMENT, [&](auto& obj) {
3383 obj.setAccountID(sfAccount, AccountID());
3384 obj.setFieldH256(sfAmendment, uint256(2));
3385 obj.setFieldU32(sfLedgerSequence, env.seq(alice));
3386 obj.setFieldU32(sfFlags, tfInnerBatchTxn);
3387 });
3388
3389 std::string reason;
3390 BEAST_EXPECT(isPseudoTx(stx));
3391 BEAST_EXPECT(!passesLocalChecks(stx, reason));
3392 BEAST_EXPECT(reason == "Cannot submit pseudo transactions.");
3393 env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
3394 auto const result = ripple::apply(env.app(), view, stx, tapNONE, j);
3395 BEAST_EXPECT(!result.applied && result.ter == temINVALID_FLAG);
3396 return result.applied;
3397 });
3398 }
3399
3400 void
3402 {
3403 testcase("batch open ledger");
3404 // IMPORTANT: When a transaction is submitted outside of a batch and
3405 // another transaction is part of the batch, the batch might fail
3406 // because the sequence is out of order. This is because the canonical
3407 // order of transactions is determined by the account first. So in this
3408 // case, alice's batch comes after bobs self submitted transaction even
3409 // though the payment was submitted after the batch.
3410
3411 using namespace test::jtx;
3412 using namespace std::literals;
3413
3414 test::jtx::Env env{*this, envconfig()};
3415 XRPAmount const baseFee = env.current()->fees().base;
3416
3417 auto const alice = Account("alice");
3418 auto const bob = Account("bob");
3419
3420 env.fund(XRP(10000), alice, bob);
3421 env.close();
3422
3423 env(noop(bob), ter(tesSUCCESS));
3424 env.close();
3425
3426 auto const aliceSeq = env.seq(alice);
3427 auto const preAlice = env.balance(alice);
3428 auto const preBob = env.balance(bob);
3429 auto const bobSeq = env.seq(bob);
3430
3431 // Alice Pays Bob (Open Ledger)
3432 auto const payTxn1 = env.jt(pay(alice, bob, XRP(10)), seq(aliceSeq));
3433 auto const payTxn1ID = to_string(payTxn1.stx->getTransactionID());
3434 env(payTxn1, ter(tesSUCCESS));
3435
3436 // Alice & Bob Atomic Batch
3437 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3438 auto const [txIDs, batchID] = submitBatch(
3439 env,
3440 tesSUCCESS,
3441 batch::outer(alice, aliceSeq + 1, batchFee, tfAllOrNothing),
3442 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 2),
3443 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3444 batch::sig(bob));
3445
3446 // Bob pays Alice (Open Ledger)
3447 auto const payTxn2 = env.jt(pay(bob, alice, XRP(5)), seq(bobSeq + 1));
3448 auto const payTxn2ID = to_string(payTxn2.stx->getTransactionID());
3449 env(payTxn2, ter(terPRE_SEQ));
3450 env.close();
3451
3452 std::vector<TestLedgerData> testCases = {
3453 {0, "Payment", "tesSUCCESS", payTxn1ID, std::nullopt},
3454 {1, "Batch", "tesSUCCESS", batchID, std::nullopt},
3455 {2, "Payment", "tesSUCCESS", txIDs[0], batchID},
3456 {3, "Payment", "tesSUCCESS", txIDs[1], batchID},
3457 };
3458 validateClosedLedger(env, testCases);
3459
3460 env.close();
3461 {
3462 // next ledger includes the payment txn
3463 std::vector<TestLedgerData> testCases = {
3464 {0, "Payment", "tesSUCCESS", payTxn2ID, std::nullopt},
3465 };
3466 validateClosedLedger(env, testCases);
3467 }
3468
3469 // Alice consumes sequences (# of txns)
3470 BEAST_EXPECT(env.seq(alice) == aliceSeq + 3);
3471
3472 // Alice consumes sequences (# of txns)
3473 BEAST_EXPECT(env.seq(bob) == bobSeq + 2);
3474
3475 // Alice pays XRP & Fee; Bob receives XRP & pays Fee
3476 BEAST_EXPECT(
3477 env.balance(alice) == preAlice - XRP(10) - batchFee - baseFee);
3478 BEAST_EXPECT(env.balance(bob) == preBob + XRP(10) - baseFee);
3479 }
3480
3481 void
3483 {
3484 testcase("batch tx queue");
3485
3486 using namespace test::jtx;
3487 using namespace std::literals;
3488
3489 // only outer batch transactions are counter towards the queue size
3490 {
3491 test::jtx::Env env{
3492 *this,
3493 makeSmallQueueConfig(
3494 {{"minimum_txn_in_ledger_standalone", "2"}}),
3495 nullptr,
3497
3498 auto alice = Account("alice");
3499 auto bob = Account("bob");
3500 auto carol = Account("carol");
3501
3502 // Fund across several ledgers so the TxQ metrics stay restricted.
3503 env.fund(XRP(10000), noripple(alice, bob));
3504 env.close(env.now() + 5s, 10000ms);
3505 env.fund(XRP(10000), noripple(carol));
3506 env.close(env.now() + 5s, 10000ms);
3507
3508 // Fill the ledger
3509 env(noop(alice));
3510 env(noop(alice));
3511 env(noop(alice));
3512 checkMetrics(*this, env, 0, std::nullopt, 3, 2);
3513
3514 env(noop(carol), ter(terQUEUED));
3515 checkMetrics(*this, env, 1, std::nullopt, 3, 2);
3516
3517 auto const aliceSeq = env.seq(alice);
3518 auto const bobSeq = env.seq(bob);
3519 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3520
3521 // Queue Batch
3522 {
3523 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3524 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3525 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3526 batch::sig(bob),
3527 ter(terQUEUED));
3528 }
3529
3530 checkMetrics(*this, env, 2, std::nullopt, 3, 2);
3531
3532 // Replace Queued Batch
3533 {
3534 env(batch::outer(
3535 alice,
3536 aliceSeq,
3537 openLedgerFee(env, batchFee),
3539 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3540 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3541 batch::sig(bob),
3542 ter(tesSUCCESS));
3543 env.close();
3544 }
3545
3546 checkMetrics(*this, env, 0, 12, 1, 6);
3547 }
3548
3549 // inner batch transactions are counter towards the ledger tx count
3550 {
3551 test::jtx::Env env{
3552 *this,
3553 makeSmallQueueConfig(
3554 {{"minimum_txn_in_ledger_standalone", "2"}}),
3555 nullptr,
3557
3558 auto alice = Account("alice");
3559 auto bob = Account("bob");
3560 auto carol = Account("carol");
3561
3562 // Fund across several ledgers so the TxQ metrics stay restricted.
3563 env.fund(XRP(10000), noripple(alice, bob));
3564 env.close(env.now() + 5s, 10000ms);
3565 env.fund(XRP(10000), noripple(carol));
3566 env.close(env.now() + 5s, 10000ms);
3567
3568 // Fill the ledger leaving room for 1 queued transaction
3569 env(noop(alice));
3570 env(noop(alice));
3571 checkMetrics(*this, env, 0, std::nullopt, 2, 2);
3572
3573 auto const aliceSeq = env.seq(alice);
3574 auto const bobSeq = env.seq(bob);
3575 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3576
3577 // Batch Successful
3578 {
3579 env(batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3580 batch::inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
3581 batch::inner(pay(bob, alice, XRP(5)), bobSeq),
3582 batch::sig(bob),
3583 ter(tesSUCCESS));
3584 }
3585
3586 checkMetrics(*this, env, 0, std::nullopt, 3, 2);
3587
3588 env(noop(carol), ter(terQUEUED));
3589 checkMetrics(*this, env, 1, std::nullopt, 3, 2);
3590 }
3591 }
3592
3593 void
3595 {
3596 testcase("batch network ops");
3597
3598 using namespace test::jtx;
3599 using namespace std::literals;
3600
3601 Env env(
3602 *this,
3603 envconfig(),
3604 features,
3605 nullptr,
3607
3608 auto alice = Account("alice");
3609 auto bob = Account("bob");
3610 env.fund(XRP(10000), alice, bob);
3611 env.close();
3612
3613 auto submitTx = [&](std::uint32_t flags) -> uint256 {
3614 auto jt = env.jt(pay(alice, bob, XRP(1)), txflags(flags));
3615 Serializer s;
3616 jt.stx->add(s);
3617 env.app().getOPs().submitTransaction(jt.stx);
3618 return jt.stx->getTransactionID();
3619 };
3620
3621 auto processTxn = [&](std::uint32_t flags) -> uint256 {
3622 auto jt = env.jt(pay(alice, bob, XRP(1)), txflags(flags));
3623 Serializer s;
3624 jt.stx->add(s);
3625 std::string reason;
3626 auto transaction =
3627 std::make_shared<Transaction>(jt.stx, reason, env.app());
3629 transaction, false, true, NetworkOPs::FailHard::yes);
3630 return transaction->getID();
3631 };
3632
3633 // Validate: NetworkOPs::submitTransaction()
3634 {
3635 // Submit a tx with tfInnerBatchTxn
3636 uint256 const txBad = submitTx(tfInnerBatchTxn);
3637 BEAST_EXPECT(
3638 env.app().getHashRouter().getFlags(txBad) ==
3640 }
3641
3642 // Validate: NetworkOPs::processTransaction()
3643 {
3644 uint256 const txid = processTxn(tfInnerBatchTxn);
3645 // HashRouter::getFlags() should return LedgerFlags::BAD
3646 BEAST_EXPECT(
3647 env.app().getHashRouter().getFlags(txid) ==
3649 }
3650 }
3651
3652 void
3654 {
3655 testcase("batch delegate");
3656
3657 using namespace test::jtx;
3658 using namespace std::literals;
3659
3660 // delegated non atomic inner
3661 {
3662 test::jtx::Env env{*this, envconfig()};
3663
3664 auto const alice = Account("alice");
3665 auto const bob = Account("bob");
3666 auto const gw = Account("gw");
3667 auto const USD = gw["USD"];
3668 env.fund(XRP(10000), alice, bob, gw);
3669 env.close();
3670
3671 env(delegate::set(alice, bob, {"Payment"}));
3672 env.close();
3673
3674 auto const preAlice = env.balance(alice);
3675 auto const preBob = env.balance(bob);
3676
3677 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3678 auto const seq = env.seq(alice);
3679
3680 auto tx = batch::inner(pay(alice, bob, XRP(1)), seq + 1);
3681 tx[jss::Delegate] = bob.human();
3682 auto const [txIDs, batchID] = submitBatch(
3683 env,
3684 tesSUCCESS,
3685 batch::outer(alice, seq, batchFee, tfAllOrNothing),
3686 tx,
3687 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
3688 env.close();
3689
3690 std::vector<TestLedgerData> testCases = {
3691 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3692 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3693 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3694 };
3695 validateClosedLedger(env, testCases);
3696
3697 // Alice consumes sequences (# of txns)
3698 BEAST_EXPECT(env.seq(alice) == seq + 3);
3699
3700 // Alice pays XRP & Fee; Bob receives XRP
3701 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(3) - batchFee);
3702 BEAST_EXPECT(env.balance(bob) == preBob + XRP(3));
3703 }
3704
3705 // delegated atomic inner
3706 {
3707 test::jtx::Env env{*this, envconfig()};
3708
3709 auto const alice = Account("alice");
3710 auto const bob = Account("bob");
3711 auto const carol = Account("carol");
3712 auto const gw = Account("gw");
3713 auto const USD = gw["USD"];
3714 env.fund(XRP(10000), alice, bob, carol, gw);
3715 env.close();
3716
3717 env(delegate::set(bob, carol, {"Payment"}));
3718 env.close();
3719
3720 auto const preAlice = env.balance(alice);
3721 auto const preBob = env.balance(bob);
3722 auto const preCarol = env.balance(carol);
3723
3724 auto const batchFee = batch::calcBatchFee(env, 1, 2);
3725 auto const aliceSeq = env.seq(alice);
3726 auto const bobSeq = env.seq(bob);
3727
3728 auto tx = batch::inner(pay(bob, alice, XRP(1)), bobSeq);
3729 tx[jss::Delegate] = carol.human();
3730 auto const [txIDs, batchID] = submitBatch(
3731 env,
3732 tesSUCCESS,
3733 batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
3734 tx,
3735 batch::inner(pay(alice, bob, XRP(2)), aliceSeq + 1),
3736 batch::sig(bob));
3737 env.close();
3738
3739 std::vector<TestLedgerData> testCases = {
3740 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3741 {1, "Payment", "tesSUCCESS", txIDs[0], batchID},
3742 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3743 };
3744 validateClosedLedger(env, testCases);
3745
3746 BEAST_EXPECT(env.seq(alice) == aliceSeq + 2);
3747 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
3748 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(1) - batchFee);
3749 BEAST_EXPECT(env.balance(bob) == preBob + XRP(1));
3750 // NOTE: Carol would normally pay the fee for delegated txns, but
3751 // because the batch is atomic, the fee is paid by the batch
3752 BEAST_EXPECT(env.balance(carol) == preCarol);
3753 }
3754
3755 // delegated non atomic inner (AccountSet)
3756 // this also makes sure tfInnerBatchTxn won't block delegated AccountSet
3757 // with granular permission
3758 {
3759 test::jtx::Env env{*this, envconfig()};
3760
3761 auto const alice = Account("alice");
3762 auto const bob = Account("bob");
3763 auto const gw = Account("gw");
3764 auto const USD = gw["USD"];
3765 env.fund(XRP(10000), alice, bob, gw);
3766 env.close();
3767
3768 env(delegate::set(alice, bob, {"AccountDomainSet"}));
3769 env.close();
3770
3771 auto const preAlice = env.balance(alice);
3772 auto const preBob = env.balance(bob);
3773
3774 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3775 auto const seq = env.seq(alice);
3776
3777 auto tx = batch::inner(noop(alice), seq + 1);
3778 std::string const domain = "example.com";
3779 tx[sfDomain.jsonName] = strHex(domain);
3780 tx[jss::Delegate] = bob.human();
3781 auto const [txIDs, batchID] = submitBatch(
3782 env,
3783 tesSUCCESS,
3784 batch::outer(alice, seq, batchFee, tfAllOrNothing),
3785 tx,
3786 batch::inner(pay(alice, bob, XRP(2)), seq + 2));
3787 env.close();
3788
3789 std::vector<TestLedgerData> testCases = {
3790 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3791 {1, "AccountSet", "tesSUCCESS", txIDs[0], batchID},
3792 {2, "Payment", "tesSUCCESS", txIDs[1], batchID},
3793 };
3794 validateClosedLedger(env, testCases);
3795
3796 // Alice consumes sequences (# of txns)
3797 BEAST_EXPECT(env.seq(alice) == seq + 3);
3798
3799 // Alice pays XRP & Fee; Bob receives XRP
3800 BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee);
3801 BEAST_EXPECT(env.balance(bob) == preBob + XRP(2));
3802 }
3803
3804 // delegated non atomic inner (MPTokenIssuanceSet)
3805 // this also makes sure tfInnerBatchTxn won't block delegated
3806 // MPTokenIssuanceSet with granular permission
3807 {
3808 test::jtx::Env env{*this, envconfig()};
3809 Account alice{"alice"};
3810 Account bob{"bob"};
3811 env.fund(XRP(100000), alice, bob);
3812 env.close();
3813
3814 auto const mptID = makeMptID(env.seq(alice), alice);
3815 MPTTester mpt(env, alice, {.fund = false});
3816 env.close();
3817 mpt.create({.flags = tfMPTCanLock});
3818 env.close();
3819
3820 // alice gives granular permission to bob of MPTokenIssuanceLock
3821 env(delegate::set(
3822 alice, bob, {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"}));
3823 env.close();
3824
3825 auto const seq = env.seq(alice);
3826 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3827
3828 Json::Value jv1;
3829 jv1[sfTransactionType] = jss::MPTokenIssuanceSet;
3830 jv1[sfAccount] = alice.human();
3831 jv1[sfDelegate] = bob.human();
3832 jv1[sfSequence] = seq + 1;
3833 jv1[sfMPTokenIssuanceID] = to_string(mptID);
3834 jv1[sfFlags] = tfMPTLock;
3835
3836 Json::Value jv2;
3837 jv2[sfTransactionType] = jss::MPTokenIssuanceSet;
3838 jv2[sfAccount] = alice.human();
3839 jv2[sfDelegate] = bob.human();
3840 jv2[sfSequence] = seq + 2;
3841 jv2[sfMPTokenIssuanceID] = to_string(mptID);
3842 jv2[sfFlags] = tfMPTUnlock;
3843
3844 auto const [txIDs, batchID] = submitBatch(
3845 env,
3846 tesSUCCESS,
3847 batch::outer(alice, seq, batchFee, tfAllOrNothing),
3848 batch::inner(jv1, seq + 1),
3849 batch::inner(jv2, seq + 2));
3850 env.close();
3851
3852 std::vector<TestLedgerData> testCases = {
3853 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3854 {1, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[0], batchID},
3855 {2, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[1], batchID},
3856 };
3857 validateClosedLedger(env, testCases);
3858 }
3859
3860 // delegated non atomic inner (TrustSet)
3861 // this also makes sure tfInnerBatchTxn won't block delegated TrustSet
3862 // with granular permission
3863 {
3864 test::jtx::Env env{*this, envconfig()};
3865 Account gw{"gw"};
3866 Account alice{"alice"};
3867 Account bob{"bob"};
3868 env.fund(XRP(10000), gw, alice, bob);
3869 env(fset(gw, asfRequireAuth));
3870 env.close();
3871 env(trust(alice, gw["USD"](50)));
3872 env.close();
3873
3874 env(delegate::set(
3875 gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
3876 env.close();
3877
3878 auto const seq = env.seq(gw);
3879 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3880
3881 auto jv1 = trust(gw, gw["USD"](0), alice, tfSetfAuth);
3882 jv1[sfDelegate] = bob.human();
3883 auto jv2 = trust(gw, gw["USD"](0), alice, tfSetFreeze);
3884 jv2[sfDelegate] = bob.human();
3885
3886 auto const [txIDs, batchID] = submitBatch(
3887 env,
3888 tesSUCCESS,
3889 batch::outer(gw, seq, batchFee, tfAllOrNothing),
3890 batch::inner(jv1, seq + 1),
3891 batch::inner(jv2, seq + 2));
3892 env.close();
3893
3894 std::vector<TestLedgerData> testCases = {
3895 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3896 {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
3897 {2, "TrustSet", "tesSUCCESS", txIDs[1], batchID},
3898 };
3899 validateClosedLedger(env, testCases);
3900 }
3901
3902 // inner transaction not authorized by the delegating account.
3903 {
3904 test::jtx::Env env{*this, envconfig()};
3905 Account gw{"gw"};
3906 Account alice{"alice"};
3907 Account bob{"bob"};
3908 env.fund(XRP(10000), gw, alice, bob);
3909 env(fset(gw, asfRequireAuth));
3910 env.close();
3911 env(trust(alice, gw["USD"](50)));
3912 env.close();
3913
3914 env(delegate::set(
3915 gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
3916 env.close();
3917
3918 auto const seq = env.seq(gw);
3919 auto const batchFee = batch::calcBatchFee(env, 0, 2);
3920
3921 auto jv1 = trust(gw, gw["USD"](0), alice, tfSetFreeze);
3922 jv1[sfDelegate] = bob.human();
3923 auto jv2 = trust(gw, gw["USD"](0), alice, tfClearFreeze);
3924 jv2[sfDelegate] = bob.human();
3925
3926 auto const [txIDs, batchID] = submitBatch(
3927 env,
3928 tesSUCCESS,
3929 batch::outer(gw, seq, batchFee, tfIndependent),
3930 batch::inner(jv1, seq + 1),
3931 // terNO_DELEGATE_PERMISSION: not authorized to clear freeze
3932 batch::inner(jv2, seq + 2));
3933 env.close();
3934
3935 std::vector<TestLedgerData> testCases = {
3936 {0, "Batch", "tesSUCCESS", batchID, std::nullopt},
3937 {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
3938 };
3939 validateClosedLedger(env, testCases);
3940 }
3941 }
3942
3943 void
3945 {
3946 // Verifying that the RPC response from submit includes
3947 // the account_sequence_available, account_sequence_next,
3948 // open_ledger_cost and validated_ledger_index fields.
3949 testcase("Validate RPC response");
3950
3951 using namespace jtx;
3952 Env env(*this);
3953 Account const alice("alice");
3954 Account const bob("bob");
3955 env.fund(XRP(10000), alice, bob);
3956 env.close();
3957
3958 // tes
3959 {
3960 auto const baseFee = env.current()->fees().base;
3961 auto const aliceSeq = env.seq(alice);
3962 auto jtx = env.jt(pay(alice, bob, XRP(1)));
3963
3964 Serializer s;
3965 jtx.stx->add(s);
3966 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
3967 env.close();
3968
3969 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
3970 BEAST_EXPECT(
3971 jr[jss::account_sequence_available].asUInt() == aliceSeq + 1);
3972 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
3973 BEAST_EXPECT(
3974 jr[jss::account_sequence_next].asUInt() == aliceSeq + 1);
3975 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
3976 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
3977 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
3978 }
3979
3980 // tec failure
3981 {
3982 auto const baseFee = env.current()->fees().base;
3983 auto const aliceSeq = env.seq(alice);
3984 env(fset(bob, asfRequireDest));
3985 auto jtx = env.jt(pay(alice, bob, XRP(1)), seq(aliceSeq));
3986
3987 Serializer s;
3988 jtx.stx->add(s);
3989 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
3990 env.close();
3991
3992 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
3993 BEAST_EXPECT(
3994 jr[jss::account_sequence_available].asUInt() == aliceSeq + 1);
3995 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
3996 BEAST_EXPECT(
3997 jr[jss::account_sequence_next].asUInt() == aliceSeq + 1);
3998 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
3999 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
4000 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
4001 }
4002
4003 // tem failure
4004 {
4005 auto const baseFee = env.current()->fees().base;
4006 auto const aliceSeq = env.seq(alice);
4007 auto jtx = env.jt(pay(alice, bob, XRP(1)), seq(aliceSeq + 1));
4008
4009 Serializer s;
4010 jtx.stx->add(s);
4011 auto const jr = env.rpc("submit", strHex(s.slice()))[jss::result];
4012 env.close();
4013
4014 BEAST_EXPECT(jr.isMember(jss::account_sequence_available));
4015 BEAST_EXPECT(
4016 jr[jss::account_sequence_available].asUInt() == aliceSeq);
4017 BEAST_EXPECT(jr.isMember(jss::account_sequence_next));
4018 BEAST_EXPECT(jr[jss::account_sequence_next].asUInt() == aliceSeq);
4019 BEAST_EXPECT(jr.isMember(jss::open_ledger_cost));
4020 BEAST_EXPECT(jr[jss::open_ledger_cost] == to_string(baseFee));
4021 BEAST_EXPECT(jr.isMember(jss::validated_ledger_index));
4022 }
4023 }
4024
4025 void
4027 {
4028 using namespace jtx;
4029 Env env(*this);
4030 Account const alice("alice");
4031 Account const bob("bob");
4032 Account const carol("carol");
4033 env.fund(XRP(10000), alice, bob, carol);
4034 env.close();
4035
4036 auto getBaseFee = [&](JTx const& jtx) -> XRPAmount {
4037 Serializer s;
4038 jtx.stx->add(s);
4039 return Batch::calculateBaseFee(*env.current(), *jtx.stx);
4040 };
4041
4042 // bad: Inner Batch transaction found
4043 {
4044 auto const seq = env.seq(alice);
4045 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4046 auto jtx = env.jt(
4047 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4049 batch::outer(alice, seq, batchFee, tfAllOrNothing), seq),
4050 batch::inner(pay(alice, bob, XRP(1)), seq + 2));
4051 XRPAmount const txBaseFee = getBaseFee(jtx);
4052 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4053 }
4054
4055 // bad: Raw Transactions array exceeds max entries.
4056 {
4057 auto const seq = env.seq(alice);
4058 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4059
4060 auto jtx = env.jt(
4061 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4062 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
4063 batch::inner(pay(alice, bob, XRP(1)), seq + 2),
4064 batch::inner(pay(alice, bob, XRP(1)), seq + 3),
4065 batch::inner(pay(alice, bob, XRP(1)), seq + 4),
4066 batch::inner(pay(alice, bob, XRP(1)), seq + 5),
4067 batch::inner(pay(alice, bob, XRP(1)), seq + 6),
4068 batch::inner(pay(alice, bob, XRP(1)), seq + 7),
4069 batch::inner(pay(alice, bob, XRP(1)), seq + 8),
4070 batch::inner(pay(alice, bob, XRP(1)), seq + 9));
4071
4072 XRPAmount const txBaseFee = getBaseFee(jtx);
4073 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4074 }
4075
4076 // bad: Signers array exceeds max entries.
4077 {
4078 auto const seq = env.seq(alice);
4079 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4080
4081 auto jtx = env.jt(
4082 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4083 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
4084 batch::inner(pay(alice, bob, XRP(5)), seq + 2),
4085 batch::sig(
4086 bob,
4087 carol,
4088 alice,
4089 bob,
4090 carol,
4091 alice,
4092 bob,
4093 carol,
4094 alice,
4095 alice));
4096 XRPAmount const txBaseFee = getBaseFee(jtx);
4097 BEAST_EXPECT(txBaseFee == XRPAmount(INITIAL_XRP));
4098 }
4099
4100 // good:
4101 {
4102 auto const seq = env.seq(alice);
4103 XRPAmount const batchFee = batch::calcBatchFee(env, 0, 2);
4104 auto jtx = env.jt(
4105 batch::outer(alice, seq, batchFee, tfAllOrNothing),
4106 batch::inner(pay(alice, bob, XRP(1)), seq + 1),
4107 batch::inner(pay(bob, alice, XRP(2)), seq + 2));
4108 XRPAmount const txBaseFee = getBaseFee(jtx);
4109 BEAST_EXPECT(txBaseFee == batchFee);
4110 }
4111 }
4112
4113 void
4115 {
4116 testEnable(features);
4117 testPreflight(features);
4118 testPreclaim(features);
4119 testBadRawTxn(features);
4120 testBadSequence(features);
4121 testBadOuterFee(features);
4122 testCalculateBaseFee(features);
4123 testAllOrNothing(features);
4124 testOnlyOne(features);
4125 testUntilFailure(features);
4126 testIndependent(features);
4127 testInnerSubmitRPC(features);
4128 testAccountActivation(features);
4129 testAccountSet(features);
4130 testAccountDelete(features);
4131 testObjectCreateSequence(features);
4132 testObjectCreateTicket(features);
4133 testObjectCreate3rdParty(features);
4134 testTickets(features);
4135 testSequenceOpenLedger(features);
4136 testTicketsOpenLedger(features);
4137 testObjectsOpenLedger(features);
4138 testPseudoTxn(features);
4139 testOpenLedger(features);
4140 testBatchTxQueue(features);
4141 testBatchNetworkOps(features);
4142 testBatchDelegate(features);
4143 testValidateRPCResponse(features);
4144 testBatchCalculateBaseFee(features);
4145 }
4146
4147public:
4148 void
4149 run() override
4150 {
4151 using namespace test::jtx;
4152 auto const sa = testable_amendments();
4153 testWithFeats(sa);
4154 }
4155};
4156
4157BEAST_DEFINE_TESTSUITE(Batch, app, ripple);
4158
4159} // namespace test
4160} // namespace ripple
Represents a JSON value.
Definition json_value.h:130
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 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
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:753
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 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:312
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:489
Application & app()
Definition Env.h:242
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:772
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:271
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:68
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:116
Set a batch signature on a JTx.
Definition batch.h:93
Set the domain on a JTx.
Definition domain.h:11
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:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:317
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)
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
constexpr std::uint32_t tfAllOrNothing
Definition TxFlags.h:257
@ 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:851
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
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:127
bool passesLocalChecks(STObject const &st, std::string &)
Definition STTx.cpp:812
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:151
@ terPRE_SEQ
Definition TER.h:202
@ terQUEUED
Definition TER.h:206
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