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