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