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