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