rippled
Loading...
Searching...
No Matches
Invariants_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2017 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/AMM.h>
22#include <test/jtx/Env.h>
23
24#include <xrpld/app/tx/apply.h>
25#include <xrpld/app/tx/detail/ApplyContext.h>
26
27#include <xrpl/beast/unit_test/suite.h>
28#include <xrpl/beast/utility/Journal.h>
29#include <xrpl/protocol/AccountID.h>
30#include <xrpl/protocol/Indexes.h>
31#include <xrpl/protocol/InnerObjectFormats.h>
32#include <xrpl/protocol/MPTIssue.h>
33#include <xrpl/protocol/SField.h>
34#include <xrpl/protocol/STLedgerEntry.h>
35#include <xrpl/protocol/STNumber.h>
36#include <xrpl/protocol/TER.h>
37#include <xrpl/protocol/TxFormats.h>
38#include <xrpl/protocol/XRPAmount.h>
39
40#include <boost/algorithm/string/predicate.hpp>
41
42namespace ripple {
43namespace test {
44
46{
47 // The optional Preclose function is used to process additional transactions
48 // on the ledger after creating two accounts, but before closing it, and
49 // before the Precheck function. These should only be valid functions, and
50 // not direct manipulations. Preclose is not commonly used.
51 using Preclose = std::function<bool(
52 test::jtx::Account const& a,
53 test::jtx::Account const& b,
54 test::jtx::Env& env)>;
55
56 // this is common setup/method for running a failing invariant check. The
57 // precheck function is used to manipulate the ApplyContext with view
58 // changes that will cause the check to fail.
59 using Precheck = std::function<bool(
60 test::jtx::Account const& a,
61 test::jtx::Account const& b,
62 ApplyContext& ac)>;
63
80 enum class TxAccount : int { None = 0, A1, A2 };
81 void
83 std::vector<std::string> const& expect_logs,
84 Precheck const& precheck,
86 STTx tx = STTx{ttACCOUNT_SET, [](STObject&) {}},
89 Preclose const& preclose = {},
90 TxAccount setTxAccount = TxAccount::None)
91 {
92 using namespace test::jtx;
93 FeatureBitset amendments = testable_amendments() |
94 featureInvariantsV1_1 | featureSingleAssetVault;
95 Env env{*this, amendments};
96
97 Account const A1{"A1"};
98 Account const A2{"A2"};
99 env.fund(XRP(1000), A1, A2);
100 if (preclose)
101 BEAST_EXPECT(preclose(A1, A2, env));
102 env.close();
103
104 OpenView ov{*env.current()};
105 test::StreamSink sink{beast::severities::kWarning};
106 beast::Journal jlog{sink};
107 if (setTxAccount != TxAccount::None)
108 tx.setAccountID(
109 sfAccount, setTxAccount == TxAccount::A1 ? A1.id() : A2.id());
110 ApplyContext ac{
111 env.app(),
112 ov,
113 tx,
115 env.current()->fees().base,
116 tapNONE,
117 jlog};
118
119 BEAST_EXPECT(precheck(A1, A2, ac));
120
121 // invoke check twice to cover tec and tef cases
122 if (!BEAST_EXPECT(ters.size() == 2))
123 return;
124
125 TER terActual = tesSUCCESS;
126 for (TER const& terExpect : ters)
127 {
128 terActual = ac.checkInvariants(terActual, fee);
129 BEAST_EXPECT(terExpect == terActual);
130 auto const messages = sink.messages().str();
131 BEAST_EXPECT(
132 messages.starts_with("Invariant failed:") ||
133 messages.starts_with("Transaction caused an exception"));
134 // std::cerr << messages << '\n';
135 for (auto const& m : expect_logs)
136 {
137 if (messages.find(m) == std::string::npos)
138 {
139 // uncomment if you want to log the invariant failure
140 // std::cerr << " --> " << m << std::endl;
141 fail();
142 }
143 }
144 }
145 }
146
147 void
149 {
150 using namespace test::jtx;
151 testcase << "XRP created";
153 {{"XRP net change was positive: 500"}},
154 [](Account const& A1, Account const&, ApplyContext& ac) {
155 // put a single account in the view and "manufacture" some XRP
156 auto const sle = ac.view().peek(keylet::account(A1.id()));
157 if (!sle)
158 return false;
159 auto amt = sle->getFieldAmount(sfBalance);
160 sle->setFieldAmount(sfBalance, amt + STAmount{500});
161 ac.view().update(sle);
162 return true;
163 });
164 }
165
166 void
168 {
169 using namespace test::jtx;
170 testcase << "account root removed";
171
172 // An account was deleted, but not by an AccountDelete transaction.
174 {{"an account root was deleted"}},
175 [](Account const& A1, Account const&, ApplyContext& ac) {
176 // remove an account from the view
177 auto const sle = ac.view().peek(keylet::account(A1.id()));
178 if (!sle)
179 return false;
180 ac.view().erase(sle);
181 return true;
182 });
183
184 // Successful AccountDelete transaction that didn't delete an account.
185 //
186 // Note that this is a case where a second invocation of the invariant
187 // checker returns a tecINVARIANT_FAILED, not a tefINVARIANT_FAILED.
188 // After a discussion with the team, we believe that's okay.
190 {{"account deletion succeeded without deleting an account"}},
191 [](Account const&, Account const&, ApplyContext& ac) {
192 return true;
193 },
194 XRPAmount{},
195 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
197
198 // Successful AccountDelete that deleted more than one account.
200 {{"account deletion succeeded but deleted multiple accounts"}},
201 [](Account const& A1, Account const& A2, ApplyContext& ac) {
202 // remove two accounts from the view
203 auto const sleA1 = ac.view().peek(keylet::account(A1.id()));
204 auto const sleA2 = ac.view().peek(keylet::account(A2.id()));
205 if (!sleA1 || !sleA2)
206 return false;
207 ac.view().erase(sleA1);
208 ac.view().erase(sleA2);
209 return true;
210 },
211 XRPAmount{},
212 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
213 }
214
215 void
217 {
218 using namespace test::jtx;
219 testcase << "account root deletion left artifact";
220
221 for (auto const& keyletInfo : directAccountKeylets)
222 {
223 // TODO: Use structured binding once LLVM 16 is the minimum
224 // supported version. See also:
225 // https://github.com/llvm/llvm-project/issues/48582
226 // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
227 if (!keyletInfo.includeInTests)
228 continue;
229 auto const& keyletfunc = keyletInfo.function;
230 auto const& type = keyletInfo.expectedLEName;
231
232 using namespace std::string_literals;
233
235 {{"account deletion left behind a "s + type.c_str() +
236 " object"}},
237 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
238 // Add an object to the ledger for account A1, then delete
239 // A1
240 auto const a1 = A1.id();
241 auto const sleA1 = ac.view().peek(keylet::account(a1));
242 if (!sleA1)
243 return false;
244
245 auto const key = std::invoke(keyletfunc, a1);
246 auto const newSLE = std::make_shared<SLE>(key);
247 ac.view().insert(newSLE);
248 ac.view().erase(sleA1);
249
250 return true;
251 },
252 XRPAmount{},
253 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
254 };
255
256 // NFT special case
258 {{"account deletion left behind a NFTokenPage object"}},
259 [&](Account const& A1, Account const&, ApplyContext& ac) {
260 // remove an account from the view
261 auto const sle = ac.view().peek(keylet::account(A1.id()));
262 if (!sle)
263 return false;
264 ac.view().erase(sle);
265 return true;
266 },
267 XRPAmount{},
268 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
270 [&](Account const& A1, Account const&, Env& env) {
271 // Preclose callback to mint the NFT which will be deleted in
272 // the Precheck callback above.
273 env(token::mint(A1));
274
275 return true;
276 });
277
278 // AMM special cases
279 AccountID ammAcctID;
280 uint256 ammKey;
281 Issue ammIssue;
283 {{"account deletion left behind a DirectoryNode object"}},
284 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
285 // Delete the AMM account without cleaning up the directory or
286 // deleting the AMM object
287 auto const sle = ac.view().peek(keylet::account(ammAcctID));
288 if (!sle)
289 return false;
290
291 BEAST_EXPECT(sle->at(~sfAMMID));
292 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
293
294 ac.view().erase(sle);
295
296 return true;
297 },
298 XRPAmount{},
299 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
301 [&](Account const& A1, Account const& A2, Env& env) {
302 // Preclose callback to create the AMM which will be partially
303 // deleted in the Precheck callback above.
304 AMM const amm(env, A1, XRP(100), A1["USD"](50));
305 ammAcctID = amm.ammAccount();
306 ammKey = amm.ammID();
307 ammIssue = amm.lptIssue();
308 return true;
309 });
311 {{"account deletion left behind a AMM object"}},
312 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
313 // Delete all the AMM's trust lines, remove the AMM from the AMM
314 // account's directory (this deletes the directory), and delete
315 // the AMM account. Do not delete the AMM object.
316 auto const sle = ac.view().peek(keylet::account(ammAcctID));
317 if (!sle)
318 return false;
319
320 BEAST_EXPECT(sle->at(~sfAMMID));
321 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
322
323 for (auto const& trustKeylet :
324 {keylet::line(ammAcctID, A1["USD"]),
325 keylet::line(A1, ammIssue)})
326 {
327 if (auto const line = ac.view().peek(trustKeylet); !line)
328 {
329 return false;
330 }
331 else
332 {
333 STAmount const lowLimit = line->at(sfLowLimit);
334 STAmount const highLimit = line->at(sfHighLimit);
335 BEAST_EXPECT(
337 ac.view(),
338 line,
339 lowLimit.getIssuer(),
340 highLimit.getIssuer(),
341 ac.journal) == tesSUCCESS);
342 }
343 }
344
345 auto const ammSle = ac.view().peek(keylet::amm(ammKey));
346 if (!BEAST_EXPECT(ammSle))
347 return false;
348 auto const ownerDirKeylet = keylet::ownerDir(ammAcctID);
349
350 BEAST_EXPECT(ac.view().dirRemove(
351 ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey, false));
352 BEAST_EXPECT(
353 !ac.view().exists(ownerDirKeylet) ||
354 ac.view().emptyDirDelete(ownerDirKeylet));
355
356 ac.view().erase(sle);
357
358 return true;
359 },
360 XRPAmount{},
361 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
363 [&](Account const& A1, Account const& A2, Env& env) {
364 // Preclose callback to create the AMM which will be partially
365 // deleted in the Precheck callback above.
366 AMM const amm(env, A1, XRP(100), A1["USD"](50));
367 ammAcctID = amm.ammAccount();
368 ammKey = amm.ammID();
369 ammIssue = amm.lptIssue();
370 return true;
371 });
372 }
373
374 void
376 {
377 using namespace test::jtx;
378 testcase << "ledger entry types don't match";
380 {{"ledger entry type mismatch"},
381 {"XRP net change of -1000000000 doesn't match fee 0"}},
382 [](Account const& A1, Account const&, ApplyContext& ac) {
383 // replace an entry in the table with an SLE of a different type
384 auto const sle = ac.view().peek(keylet::account(A1.id()));
385 if (!sle)
386 return false;
387 auto const sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
388 ac.rawView().rawReplace(sleNew);
389 return true;
390 });
391
393 {{"invalid ledger entry type added"}},
394 [](Account const& A1, Account const&, ApplyContext& ac) {
395 // add an entry in the table with an SLE of an invalid type
396 auto const sle = ac.view().peek(keylet::account(A1.id()));
397 if (!sle)
398 return false;
399
400 // make a dummy escrow ledger entry, then change the type to an
401 // unsupported value so that the valid type invariant check
402 // will fail.
403 auto const sleNew = std::make_shared<SLE>(
404 keylet::escrow(A1, (*sle)[sfSequence] + 2));
405
406 // We don't use ltNICKNAME directly since it's marked deprecated
407 // to prevent accidental use elsewhere.
408 sleNew->type_ = static_cast<LedgerEntryType>('n');
409 ac.view().insert(sleNew);
410 return true;
411 });
412 }
413
414 void
416 {
417 using namespace test::jtx;
418 testcase << "trust lines with XRP not allowed";
420 {{"an XRP trust line was created"}},
421 [](Account const& A1, Account const& A2, ApplyContext& ac) {
422 // create simple trust SLE with xrp currency
423 auto const sleNew = std::make_shared<SLE>(
424 keylet::line(A1, A2, xrpIssue().currency));
425 ac.view().insert(sleNew);
426 return true;
427 });
428 }
429
430 void
432 {
433 using namespace test::jtx;
434 testcase << "trust lines with deep freeze flag without freeze "
435 "not allowed";
437 {{"a trust line with deep freeze flag without normal freeze was "
438 "created"}},
439 [](Account const& A1, Account const& A2, ApplyContext& ac) {
440 auto const sleNew = std::make_shared<SLE>(
441 keylet::line(A1, A2, A1["USD"].currency));
442 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
443 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
444
445 std::uint32_t uFlags = 0u;
446 uFlags |= lsfLowDeepFreeze;
447 sleNew->setFieldU32(sfFlags, uFlags);
448 ac.view().insert(sleNew);
449 return true;
450 });
451
453 {{"a trust line with deep freeze flag without normal freeze was "
454 "created"}},
455 [](Account const& A1, Account const& A2, ApplyContext& ac) {
456 auto const sleNew = std::make_shared<SLE>(
457 keylet::line(A1, A2, A1["USD"].currency));
458 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
459 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
460 std::uint32_t uFlags = 0u;
461 uFlags |= lsfHighDeepFreeze;
462 sleNew->setFieldU32(sfFlags, uFlags);
463 ac.view().insert(sleNew);
464 return true;
465 });
466
468 {{"a trust line with deep freeze flag without normal freeze was "
469 "created"}},
470 [](Account const& A1, Account const& A2, ApplyContext& ac) {
471 auto const sleNew = std::make_shared<SLE>(
472 keylet::line(A1, A2, A1["USD"].currency));
473 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
474 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
475 std::uint32_t uFlags = 0u;
477 sleNew->setFieldU32(sfFlags, uFlags);
478 ac.view().insert(sleNew);
479 return true;
480 });
481
483 {{"a trust line with deep freeze flag without normal freeze was "
484 "created"}},
485 [](Account const& A1, Account const& A2, ApplyContext& ac) {
486 auto const sleNew = std::make_shared<SLE>(
487 keylet::line(A1, A2, A1["USD"].currency));
488 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
489 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
490 std::uint32_t uFlags = 0u;
492 sleNew->setFieldU32(sfFlags, uFlags);
493 ac.view().insert(sleNew);
494 return true;
495 });
496
498 {{"a trust line with deep freeze flag without normal freeze was "
499 "created"}},
500 [](Account const& A1, Account const& A2, ApplyContext& ac) {
501 auto const sleNew = std::make_shared<SLE>(
502 keylet::line(A1, A2, A1["USD"].currency));
503 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
504 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
505 std::uint32_t uFlags = 0u;
507 sleNew->setFieldU32(sfFlags, uFlags);
508 ac.view().insert(sleNew);
509 return true;
510 });
511 }
512
513 void
515 {
516 using namespace test::jtx;
517 testcase << "transfers when frozen";
518
519 Account G1{"G1"};
520 // Helper function to establish the trustlines
521 auto const createTrustlines =
522 [&](Account const& A1, Account const& A2, Env& env) {
523 // Preclose callback to establish trust lines with gateway
524 env.fund(XRP(1000), G1);
525
526 env.trust(G1["USD"](10000), A1);
527 env.trust(G1["USD"](10000), A2);
528 env.close();
529
530 env(pay(G1, A1, G1["USD"](1000)));
531 env(pay(G1, A2, G1["USD"](1000)));
532 env.close();
533
534 return true;
535 };
536
537 auto const A1FrozenByIssuer =
538 [&](Account const& A1, Account const& A2, Env& env) {
539 createTrustlines(A1, A2, env);
540 env(trust(G1, A1["USD"](10000), tfSetFreeze));
541 env.close();
542
543 return true;
544 };
545
546 auto const A1DeepFrozenByIssuer =
547 [&](Account const& A1, Account const& A2, Env& env) {
548 A1FrozenByIssuer(A1, A2, env);
549 env(trust(G1, A1["USD"](10000), tfSetDeepFreeze));
550 env.close();
551
552 return true;
553 };
554
555 auto const changeBalances = [&](Account const& A1,
556 Account const& A2,
557 ApplyContext& ac,
558 int A1Balance,
559 int A2Balance) {
560 auto const sleA1 = ac.view().peek(keylet::line(A1, G1["USD"]));
561 auto const sleA2 = ac.view().peek(keylet::line(A2, G1["USD"]));
562
563 sleA1->setFieldAmount(sfBalance, G1["USD"](A1Balance));
564 sleA2->setFieldAmount(sfBalance, G1["USD"](A2Balance));
565
566 ac.view().update(sleA1);
567 ac.view().update(sleA2);
568 };
569
570 // test: imitating frozen A1 making a payment to A2.
572 {{"Attempting to move frozen funds"}},
573 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
574 changeBalances(A1, A2, ac, -900, -1100);
575 return true;
576 },
577 XRPAmount{},
578 STTx{ttPAYMENT, [](STObject& tx) {}},
580 A1FrozenByIssuer);
581
582 // test: imitating deep frozen A1 making a payment to A2.
584 {{"Attempting to move frozen funds"}},
585 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
586 changeBalances(A1, A2, ac, -900, -1100);
587 return true;
588 },
589 XRPAmount{},
590 STTx{ttPAYMENT, [](STObject& tx) {}},
592 A1DeepFrozenByIssuer);
593
594 // test: imitating A2 making a payment to deep frozen A1.
596 {{"Attempting to move frozen funds"}},
597 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
598 changeBalances(A1, A2, ac, -1100, -900);
599 return true;
600 },
601 XRPAmount{},
602 STTx{ttPAYMENT, [](STObject& tx) {}},
604 A1DeepFrozenByIssuer);
605 }
606
607 void
609 {
610 using namespace test::jtx;
611 testcase << "XRP balance checks";
612
614 {{"Cannot return non-native STAmount as XRPAmount"}},
615 [](Account const& A1, Account const& A2, ApplyContext& ac) {
616 // non-native balance
617 auto const sle = ac.view().peek(keylet::account(A1.id()));
618 if (!sle)
619 return false;
620 STAmount const nonNative(A2["USD"](51));
621 sle->setFieldAmount(sfBalance, nonNative);
622 ac.view().update(sle);
623 return true;
624 });
625
627 {{"incorrect account XRP balance"},
628 {"XRP net change was positive: 99999999000000001"}},
629 [this](Account const& A1, Account const&, ApplyContext& ac) {
630 // balance exceeds genesis amount
631 auto const sle = ac.view().peek(keylet::account(A1.id()));
632 if (!sle)
633 return false;
634 // Use `drops(1)` to bypass a call to STAmount::canonicalize
635 // with an invalid value
636 sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
637 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
638 ac.view().update(sle);
639 return true;
640 });
641
643 {{"incorrect account XRP balance"},
644 {"XRP net change of -1000000001 doesn't match fee 0"}},
645 [this](Account const& A1, Account const&, ApplyContext& ac) {
646 // balance is negative
647 auto const sle = ac.view().peek(keylet::account(A1.id()));
648 if (!sle)
649 return false;
650 sle->setFieldAmount(sfBalance, STAmount{1, true});
651 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
652 ac.view().update(sle);
653 return true;
654 });
655 }
656
657 void
659 {
660 using namespace test::jtx;
661 using namespace std::string_literals;
662 testcase << "Transaction fee checks";
663
665 {{"fee paid was negative: -1"},
666 {"XRP net change of 0 doesn't match fee -1"}},
667 [](Account const&, Account const&, ApplyContext&) { return true; },
668 XRPAmount{-1});
669
671 {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
672 {"XRP net change of 0 doesn't match fee "s +
674 [](Account const&, Account const&, ApplyContext&) { return true; },
676
678 {{"fee paid is 20 exceeds fee specified in transaction."},
679 {"XRP net change of 0 doesn't match fee 20"}},
680 [](Account const&, Account const&, ApplyContext&) { return true; },
681 XRPAmount{20},
682 STTx{ttACCOUNT_SET, [](STObject& tx) {
683 tx.setFieldAmount(sfFee, XRPAmount{10});
684 }});
685 }
686
687 void
689 {
690 using namespace test::jtx;
691 testcase << "no bad offers";
692
694 {{"offer with a bad amount"}},
695 [](Account const& A1, Account const&, ApplyContext& ac) {
696 // offer with negative takerpays
697 auto const sle = ac.view().peek(keylet::account(A1.id()));
698 if (!sle)
699 return false;
700 auto sleNew = std::make_shared<SLE>(
701 keylet::offer(A1.id(), (*sle)[sfSequence]));
702 sleNew->setAccountID(sfAccount, A1.id());
703 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
704 sleNew->setFieldAmount(sfTakerPays, XRP(-1));
705 ac.view().insert(sleNew);
706 return true;
707 });
708
710 {{"offer with a bad amount"}},
711 [](Account const& A1, Account const&, ApplyContext& ac) {
712 // offer with negative takergets
713 auto const sle = ac.view().peek(keylet::account(A1.id()));
714 if (!sle)
715 return false;
716 auto sleNew = std::make_shared<SLE>(
717 keylet::offer(A1.id(), (*sle)[sfSequence]));
718 sleNew->setAccountID(sfAccount, A1.id());
719 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
720 sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
721 sleNew->setFieldAmount(sfTakerGets, XRP(-1));
722 ac.view().insert(sleNew);
723 return true;
724 });
725
727 {{"offer with a bad amount"}},
728 [](Account const& A1, Account const&, ApplyContext& ac) {
729 // offer XRP to XRP
730 auto const sle = ac.view().peek(keylet::account(A1.id()));
731 if (!sle)
732 return false;
733 auto sleNew = std::make_shared<SLE>(
734 keylet::offer(A1.id(), (*sle)[sfSequence]));
735 sleNew->setAccountID(sfAccount, A1.id());
736 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
737 sleNew->setFieldAmount(sfTakerPays, XRP(10));
738 sleNew->setFieldAmount(sfTakerGets, XRP(11));
739 ac.view().insert(sleNew);
740 return true;
741 });
742 }
743
744 void
746 {
747 using namespace test::jtx;
748 testcase << "no zero escrow";
749
751 {{"XRP net change of -1000000 doesn't match fee 0"},
752 {"escrow specifies invalid amount"}},
753 [](Account const& A1, Account const&, ApplyContext& ac) {
754 // escrow with negative amount
755 auto const sle = ac.view().peek(keylet::account(A1.id()));
756 if (!sle)
757 return false;
758 auto sleNew = std::make_shared<SLE>(
759 keylet::escrow(A1, (*sle)[sfSequence] + 2));
760 sleNew->setFieldAmount(sfAmount, XRP(-1));
761 ac.view().insert(sleNew);
762 return true;
763 });
764
766 {{"XRP net change was positive: 100000000000000001"},
767 {"escrow specifies invalid amount"}},
768 [](Account const& A1, Account const&, ApplyContext& ac) {
769 // escrow with too-large amount
770 auto const sle = ac.view().peek(keylet::account(A1.id()));
771 if (!sle)
772 return false;
773 auto sleNew = std::make_shared<SLE>(
774 keylet::escrow(A1, (*sle)[sfSequence] + 2));
775 // Use `drops(1)` to bypass a call to STAmount::canonicalize
776 // with an invalid value
777 sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
778 ac.view().insert(sleNew);
779 return true;
780 });
781
782 // IOU < 0
784 {{"escrow specifies invalid amount"}},
785 [](Account const& A1, Account const&, ApplyContext& ac) {
786 // escrow with too-little iou
787 auto const sle = ac.view().peek(keylet::account(A1.id()));
788 if (!sle)
789 return false;
790 auto sleNew = std::make_shared<SLE>(
791 keylet::escrow(A1, (*sle)[sfSequence] + 2));
792
793 Issue const usd{
794 Currency(0x5553440000000000), AccountID(0x4985601)};
795 STAmount amt(usd, -1);
796 sleNew->setFieldAmount(sfAmount, amt);
797 ac.view().insert(sleNew);
798 return true;
799 });
800
801 // IOU bad currency
803 {{"escrow specifies invalid amount"}},
804 [](Account const& A1, Account const&, ApplyContext& ac) {
805 // escrow with bad iou currency
806 auto const sle = ac.view().peek(keylet::account(A1.id()));
807 if (!sle)
808 return false;
809 auto sleNew = std::make_shared<SLE>(
810 keylet::escrow(A1, (*sle)[sfSequence] + 2));
811
812 Issue const bad{badCurrency(), AccountID(0x4985601)};
813 STAmount amt(bad, 1);
814 sleNew->setFieldAmount(sfAmount, amt);
815 ac.view().insert(sleNew);
816 return true;
817 });
818
819 // MPT < 0
821 {{"escrow specifies invalid amount"}},
822 [](Account const& A1, Account const&, ApplyContext& ac) {
823 // escrow with too-little mpt
824 auto const sle = ac.view().peek(keylet::account(A1.id()));
825 if (!sle)
826 return false;
827 auto sleNew = std::make_shared<SLE>(
828 keylet::escrow(A1, (*sle)[sfSequence] + 2));
829
830 MPTIssue const mpt{
831 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
832 STAmount amt(mpt, -1);
833 sleNew->setFieldAmount(sfAmount, amt);
834 ac.view().insert(sleNew);
835 return true;
836 });
837
838 // MPT OutstandingAmount < 0
840 {{"escrow specifies invalid amount"}},
841 [](Account const& A1, Account const&, ApplyContext& ac) {
842 // mpissuance outstanding is negative
843 auto const sle = ac.view().peek(keylet::account(A1.id()));
844 if (!sle)
845 return false;
846
847 MPTIssue const mpt{
848 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
849 auto sleNew =
851 sleNew->setFieldU64(sfOutstandingAmount, -1);
852 ac.view().insert(sleNew);
853 return true;
854 });
855
856 // MPT LockedAmount < 0
858 {{"escrow specifies invalid amount"}},
859 [](Account const& A1, Account const&, ApplyContext& ac) {
860 // mpissuance locked is less than locked
861 auto const sle = ac.view().peek(keylet::account(A1.id()));
862 if (!sle)
863 return false;
864
865 MPTIssue const mpt{
866 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
867 auto sleNew =
869 sleNew->setFieldU64(sfLockedAmount, -1);
870 ac.view().insert(sleNew);
871 return true;
872 });
873
874 // MPT OutstandingAmount < LockedAmount
876 {{"escrow specifies invalid amount"}},
877 [](Account const& A1, Account const&, ApplyContext& ac) {
878 // mpissuance outstanding is less than locked
879 auto const sle = ac.view().peek(keylet::account(A1.id()));
880 if (!sle)
881 return false;
882
883 MPTIssue const mpt{
884 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
885 auto sleNew =
887 sleNew->setFieldU64(sfOutstandingAmount, 1);
888 sleNew->setFieldU64(sfLockedAmount, 10);
889 ac.view().insert(sleNew);
890 return true;
891 });
892
893 // MPT MPTAmount < 0
895 {{"escrow specifies invalid amount"}},
896 [](Account const& A1, Account const&, ApplyContext& ac) {
897 // mptoken amount is negative
898 auto const sle = ac.view().peek(keylet::account(A1.id()));
899 if (!sle)
900 return false;
901
902 MPTIssue const mpt{
903 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
904 auto sleNew =
905 std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
906 sleNew->setFieldU64(sfMPTAmount, -1);
907 ac.view().insert(sleNew);
908 return true;
909 });
910
911 // MPT LockedAmount < 0
913 {{"escrow specifies invalid amount"}},
914 [](Account const& A1, Account const&, ApplyContext& ac) {
915 // mptoken locked amount is negative
916 auto const sle = ac.view().peek(keylet::account(A1.id()));
917 if (!sle)
918 return false;
919
920 MPTIssue const mpt{
921 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
922 auto sleNew =
923 std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
924 sleNew->setFieldU64(sfLockedAmount, -1);
925 ac.view().insert(sleNew);
926 return true;
927 });
928 }
929
930 void
932 {
933 using namespace test::jtx;
934 testcase << "valid new account root";
935
937 {{"account root created illegally"}},
938 [](Account const&, Account const&, ApplyContext& ac) {
939 // Insert a new account root created by a non-payment into
940 // the view.
941 Account const A3{"A3"};
942 Keylet const acctKeylet = keylet::account(A3);
943 auto const sleNew = std::make_shared<SLE>(acctKeylet);
944 ac.view().insert(sleNew);
945 return true;
946 });
947
949 {{"multiple accounts created in a single transaction"}},
950 [](Account const&, Account const&, ApplyContext& ac) {
951 // Insert two new account roots into the view.
952 {
953 Account const A3{"A3"};
954 Keylet const acctKeylet = keylet::account(A3);
955 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
956 ac.view().insert(sleA3);
957 }
958 {
959 Account const A4{"A4"};
960 Keylet const acctKeylet = keylet::account(A4);
961 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
962 ac.view().insert(sleA4);
963 }
964 return true;
965 });
966
968 {{"account created with wrong starting sequence number"}},
969 [](Account const&, Account const&, ApplyContext& ac) {
970 // Insert a new account root with the wrong starting sequence.
971 Account const A3{"A3"};
972 Keylet const acctKeylet = keylet::account(A3);
973 auto const sleNew = std::make_shared<SLE>(acctKeylet);
974 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
975 ac.view().insert(sleNew);
976 return true;
977 },
978 XRPAmount{},
979 STTx{ttPAYMENT, [](STObject& tx) {}});
980
982 {{"pseudo-account created by a wrong transaction type"}},
983 [](Account const&, Account const&, ApplyContext& ac) {
984 Account const A3{"A3"};
985 Keylet const acctKeylet = keylet::account(A3);
986 auto const sleNew = std::make_shared<SLE>(acctKeylet);
987 sleNew->setFieldU32(sfSequence, 0);
988 sleNew->setFieldH256(sfAMMID, uint256(1));
989 sleNew->setFieldU32(
990 sfFlags,
992 ac.view().insert(sleNew);
993 return true;
994 },
995 XRPAmount{},
996 STTx{ttPAYMENT, [](STObject& tx) {}});
997
999 {{"account created with wrong starting sequence number"}},
1000 [](Account const&, Account const&, ApplyContext& ac) {
1001 Account const A3{"A3"};
1002 Keylet const acctKeylet = keylet::account(A3);
1003 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1004 sleNew->setFieldU32(sfSequence, ac.view().seq());
1005 sleNew->setFieldH256(sfAMMID, uint256(1));
1006 sleNew->setFieldU32(
1007 sfFlags,
1009 ac.view().insert(sleNew);
1010 return true;
1011 },
1012 XRPAmount{},
1013 STTx{ttAMM_CREATE, [](STObject& tx) {}});
1014
1016 {{"pseudo-account created with wrong flags"}},
1017 [](Account const&, Account const&, ApplyContext& ac) {
1018 Account const A3{"A3"};
1019 Keylet const acctKeylet = keylet::account(A3);
1020 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1021 sleNew->setFieldU32(sfSequence, 0);
1022 sleNew->setFieldH256(sfAMMID, uint256(1));
1023 sleNew->setFieldU32(
1025 ac.view().insert(sleNew);
1026 return true;
1027 },
1028 XRPAmount{},
1029 STTx{ttVAULT_CREATE, [](STObject& tx) {}});
1030
1032 {{"pseudo-account created with wrong flags"}},
1033 [](Account const&, Account const&, ApplyContext& ac) {
1034 Account const A3{"A3"};
1035 Keylet const acctKeylet = keylet::account(A3);
1036 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1037 sleNew->setFieldU32(sfSequence, 0);
1038 sleNew->setFieldH256(sfAMMID, uint256(1));
1039 sleNew->setFieldU32(
1040 sfFlags,
1043 ac.view().insert(sleNew);
1044 return true;
1045 },
1046 XRPAmount{},
1047 STTx{ttAMM_CREATE, [](STObject& tx) {}});
1048 }
1049
1050 void
1052 {
1053 using namespace test::jtx;
1054 testcase << "NFTokenPage";
1055
1056 // lambda that returns an STArray of NFTokenIDs.
1057 uint256 const firstNFTID(
1058 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
1059 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
1060 SOTemplate const* nfTokenTemplate =
1062 sfNFToken);
1063
1064 uint256 nftID(firstNFTID);
1065 STArray ret;
1066 for (int i = 0; i < nftCount; ++i)
1067 {
1068 STObject newNFToken(
1069 *nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
1070 object.setFieldH256(sfNFTokenID, nftID);
1071 });
1072 ret.push_back(std::move(newNFToken));
1073 ++nftID;
1074 }
1075 return ret;
1076 };
1077
1079 {{"NFT page has invalid size"}},
1080 [&makeNFTokenIDs](
1081 Account const& A1, Account const&, ApplyContext& ac) {
1083 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
1084
1085 ac.view().insert(nftPage);
1086 return true;
1087 });
1088
1090 {{"NFT page has invalid size"}},
1091 [&makeNFTokenIDs](
1092 Account const& A1, Account const&, ApplyContext& ac) {
1094 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
1095
1096 ac.view().insert(nftPage);
1097 return true;
1098 });
1099
1101 {{"NFTs on page are not sorted"}},
1102 [&makeNFTokenIDs](
1103 Account const& A1, Account const&, ApplyContext& ac) {
1104 STArray nfTokens = makeNFTokenIDs(2);
1105 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
1106
1108 nftPage->setFieldArray(sfNFTokens, nfTokens);
1109
1110 ac.view().insert(nftPage);
1111 return true;
1112 });
1113
1115 {{"NFT contains empty URI"}},
1116 [&makeNFTokenIDs](
1117 Account const& A1, Account const&, ApplyContext& ac) {
1118 STArray nfTokens = makeNFTokenIDs(1);
1119 nfTokens[0].setFieldVL(sfURI, Blob{});
1120
1122 nftPage->setFieldArray(sfNFTokens, nfTokens);
1123
1124 ac.view().insert(nftPage);
1125 return true;
1126 });
1127
1129 {{"NFT page is improperly linked"}},
1130 [&makeNFTokenIDs](
1131 Account const& A1, Account const&, ApplyContext& ac) {
1133 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1134 nftPage->setFieldH256(
1135 sfPreviousPageMin, keylet::nftpage_max(A1).key);
1136
1137 ac.view().insert(nftPage);
1138 return true;
1139 });
1140
1142 {{"NFT page is improperly linked"}},
1143 [&makeNFTokenIDs](
1144 Account const& A1, Account const& A2, ApplyContext& ac) {
1146 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1147 nftPage->setFieldH256(
1148 sfPreviousPageMin, keylet::nftpage_min(A2).key);
1149
1150 ac.view().insert(nftPage);
1151 return true;
1152 });
1153
1155 {{"NFT page is improperly linked"}},
1156 [&makeNFTokenIDs](
1157 Account const& A1, Account const&, ApplyContext& ac) {
1159 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1160 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
1161
1162 ac.view().insert(nftPage);
1163 return true;
1164 });
1165
1167 {{"NFT page is improperly linked"}},
1168 [&makeNFTokenIDs](
1169 Account const& A1, Account const& A2, ApplyContext& ac) {
1170 STArray nfTokens = makeNFTokenIDs(1);
1173 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
1174 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1175 nftPage->setFieldH256(
1176 sfNextPageMin, keylet::nftpage_max(A2).key);
1177
1178 ac.view().insert(nftPage);
1179 return true;
1180 });
1181
1183 {{"NFT found in incorrect page"}},
1184 [&makeNFTokenIDs](
1185 Account const& A1, Account const&, ApplyContext& ac) {
1186 STArray nfTokens = makeNFTokenIDs(2);
1189 (nfTokens[1].getFieldH256(sfNFTokenID))));
1190 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1191
1192 ac.view().insert(nftPage);
1193 return true;
1194 });
1195 }
1196
1197 void
1199 ApplyContext& ac,
1201 test::jtx::Account const& A1,
1202 test::jtx::Account const& A2)
1203 {
1204 sle->setAccountID(sfOwner, A1);
1205 sle->setFieldU32(sfSequence, 10);
1206
1207 STArray credentials(sfAcceptedCredentials, 2);
1208 for (std::size_t n = 0; n < 2; ++n)
1209 {
1210 auto cred = STObject::makeInnerObject(sfCredential);
1211 cred.setAccountID(sfIssuer, A2);
1212 auto credType = "cred_type" + std::to_string(n);
1213 cred.setFieldVL(
1214 sfCredentialType, Slice(credType.c_str(), credType.size()));
1215 credentials.push_back(std::move(cred));
1216 }
1217 sle->setFieldArray(sfAcceptedCredentials, credentials);
1218 ac.view().insert(sle);
1219 };
1220
1221 void
1223 {
1224 using namespace test::jtx;
1225
1226 testcase << "PermissionedDomain";
1228 {{"permissioned domain with no rules."}},
1229 [](Account const& A1, Account const&, ApplyContext& ac) {
1230 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1231 auto slePd = std::make_shared<SLE>(pdKeylet);
1232 slePd->setAccountID(sfOwner, A1);
1233 slePd->setFieldU32(sfSequence, 10);
1234
1235 ac.view().insert(slePd);
1236 return true;
1237 },
1238 XRPAmount{},
1239 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1241
1242 testcase << "PermissionedDomain 2";
1243
1244 auto constexpr tooBig = maxPermissionedDomainCredentialsArraySize + 1;
1246 {{"permissioned domain bad credentials size " +
1247 std::to_string(tooBig)}},
1248 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1249 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1250 auto slePd = std::make_shared<SLE>(pdKeylet);
1251 slePd->setAccountID(sfOwner, A1);
1252 slePd->setFieldU32(sfSequence, 10);
1253
1254 STArray credentials(sfAcceptedCredentials, tooBig);
1255 for (std::size_t n = 0; n < tooBig; ++n)
1256 {
1257 auto cred = STObject::makeInnerObject(sfCredential);
1258 cred.setAccountID(sfIssuer, A2);
1259 auto credType =
1260 std::string("cred_type") + std::to_string(n);
1261 cred.setFieldVL(
1262 sfCredentialType,
1263 Slice(credType.c_str(), credType.size()));
1264 credentials.push_back(std::move(cred));
1265 }
1266 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1267 ac.view().insert(slePd);
1268 return true;
1269 },
1270 XRPAmount{},
1271 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1273
1274 testcase << "PermissionedDomain 3";
1276 {{"permissioned domain credentials aren't sorted"}},
1277 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1278 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1279 auto slePd = std::make_shared<SLE>(pdKeylet);
1280 slePd->setAccountID(sfOwner, A1);
1281 slePd->setFieldU32(sfSequence, 10);
1282
1283 STArray credentials(sfAcceptedCredentials, 2);
1284 for (std::size_t n = 0; n < 2; ++n)
1285 {
1286 auto cred = STObject::makeInnerObject(sfCredential);
1287 cred.setAccountID(sfIssuer, A2);
1288 auto credType =
1289 std::string("cred_type") + std::to_string(9 - n);
1290 cred.setFieldVL(
1291 sfCredentialType,
1292 Slice(credType.c_str(), credType.size()));
1293 credentials.push_back(std::move(cred));
1294 }
1295 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1296 ac.view().insert(slePd);
1297 return true;
1298 },
1299 XRPAmount{},
1300 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1302
1303 testcase << "PermissionedDomain 4";
1305 {{"permissioned domain credentials aren't unique"}},
1306 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1307 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1308 auto slePd = std::make_shared<SLE>(pdKeylet);
1309 slePd->setAccountID(sfOwner, A1);
1310 slePd->setFieldU32(sfSequence, 10);
1311
1312 STArray credentials(sfAcceptedCredentials, 2);
1313 for (std::size_t n = 0; n < 2; ++n)
1314 {
1315 auto cred = STObject::makeInnerObject(sfCredential);
1316 cred.setAccountID(sfIssuer, A2);
1317 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
1318 credentials.push_back(std::move(cred));
1319 }
1320 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1321 ac.view().insert(slePd);
1322 return true;
1323 },
1324 XRPAmount{},
1325 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1327
1328 testcase << "PermissionedDomain Set 1";
1330 {{"permissioned domain with no rules."}},
1331 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1332 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1333 auto slePd = std::make_shared<SLE>(pdKeylet);
1334
1335 // create PD
1336 createPermissionedDomain(ac, slePd, A1, A2);
1337
1338 // update PD with empty rules
1339 {
1340 STArray credentials(sfAcceptedCredentials, 2);
1341 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1342 ac.view().update(slePd);
1343 }
1344
1345 return true;
1346 },
1347 XRPAmount{},
1348 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1350
1351 testcase << "PermissionedDomain Set 2";
1353 {{"permissioned domain bad credentials size " +
1354 std::to_string(tooBig)}},
1355 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1356 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1357 auto slePd = std::make_shared<SLE>(pdKeylet);
1358
1359 // create PD
1360 createPermissionedDomain(ac, slePd, A1, A2);
1361
1362 // update PD
1363 {
1364 STArray credentials(sfAcceptedCredentials, tooBig);
1365
1366 for (std::size_t n = 0; n < tooBig; ++n)
1367 {
1368 auto cred = STObject::makeInnerObject(sfCredential);
1369 cred.setAccountID(sfIssuer, A2);
1370 auto credType = "cred_type2" + std::to_string(n);
1371 cred.setFieldVL(
1372 sfCredentialType,
1373 Slice(credType.c_str(), credType.size()));
1374 credentials.push_back(std::move(cred));
1375 }
1376
1377 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1378 ac.view().update(slePd);
1379 }
1380
1381 return true;
1382 },
1383 XRPAmount{},
1384 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1386
1387 testcase << "PermissionedDomain Set 3";
1389 {{"permissioned domain credentials aren't sorted"}},
1390 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1391 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1392 auto slePd = std::make_shared<SLE>(pdKeylet);
1393
1394 // create PD
1395 createPermissionedDomain(ac, slePd, A1, A2);
1396
1397 // update PD
1398 {
1399 STArray credentials(sfAcceptedCredentials, 2);
1400 for (std::size_t n = 0; n < 2; ++n)
1401 {
1402 auto cred = STObject::makeInnerObject(sfCredential);
1403 cred.setAccountID(sfIssuer, A2);
1404 auto credType =
1405 std::string("cred_type2") + std::to_string(9 - n);
1406 cred.setFieldVL(
1407 sfCredentialType,
1408 Slice(credType.c_str(), credType.size()));
1409 credentials.push_back(std::move(cred));
1410 }
1411
1412 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1413 ac.view().update(slePd);
1414 }
1415
1416 return true;
1417 },
1418 XRPAmount{},
1419 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1421
1422 testcase << "PermissionedDomain Set 4";
1424 {{"permissioned domain credentials aren't unique"}},
1425 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1426 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1427 auto slePd = std::make_shared<SLE>(pdKeylet);
1428
1429 // create PD
1430 createPermissionedDomain(ac, slePd, A1, A2);
1431
1432 // update PD
1433 {
1434 STArray credentials(sfAcceptedCredentials, 2);
1435 for (std::size_t n = 0; n < 2; ++n)
1436 {
1437 auto cred = STObject::makeInnerObject(sfCredential);
1438 cred.setAccountID(sfIssuer, A2);
1439 cred.setFieldVL(
1440 sfCredentialType, Slice("cred_type", 9));
1441 credentials.push_back(std::move(cred));
1442 }
1443 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1444 ac.view().update(slePd);
1445 }
1446
1447 return true;
1448 },
1449 XRPAmount{},
1450 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1452 }
1453
1454 void
1456 {
1457 testcase << "valid pseudo accounts";
1458
1459 using namespace jtx;
1460
1461 AccountID pseudoAccountID;
1462 Preclose createPseudo =
1463 [&, this](Account const& a, Account const& b, Env& env) {
1464 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
1465
1466 // Create vault
1467 Vault vault{env};
1468 auto [tx, vKeylet] =
1469 vault.create({.owner = a, .asset = xrpAsset});
1470 env(tx);
1471 env.close();
1472 if (auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle))
1473 {
1474 pseudoAccountID = vSle->at(sfAccount);
1475 }
1476
1477 return BEAST_EXPECT(env.le(keylet::account(pseudoAccountID)));
1478 };
1479
1480 /* Cases to check
1481 "pseudo-account has 0 pseudo-account fields set"
1482 "pseudo-account has 2 pseudo-account fields set"
1483 "pseudo-account sequence changed"
1484 "pseudo-account flags are not set"
1485 "pseudo-account has a regular key"
1486 */
1487 struct Mod
1488 {
1489 std::string expectedFailure;
1490 std::function<void(SLE::pointer&)> func;
1491 };
1492 auto const mods = std::to_array<Mod>({
1493 {
1494 "pseudo-account has 0 pseudo-account fields set",
1495 [this](SLE::pointer& sle) {
1496 BEAST_EXPECT(sle->at(~sfVaultID));
1497 sle->at(~sfVaultID) = std::nullopt;
1498 },
1499 },
1500 {
1501 "pseudo-account sequence changed",
1502 [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; },
1503 },
1504 {
1505 "pseudo-account flags are not set",
1506 [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; },
1507 },
1508 {
1509 "pseudo-account has a regular key",
1510 [](SLE::pointer& sle) {
1511 sle->at(sfRegularKey) = Account("regular").id();
1512 },
1513 },
1514 });
1515
1516 for (auto const& mod : mods)
1517 {
1519 {{mod.expectedFailure}},
1520 [&](Account const& A1, Account const&, ApplyContext& ac) {
1521 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
1522 if (!sle)
1523 return false;
1524 mod.func(sle);
1525 ac.view().update(sle);
1526 return true;
1527 },
1528 XRPAmount{},
1529 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1531 createPseudo);
1532 }
1533 for (auto const pField : getPseudoAccountFields())
1534 {
1535 // createPseudo creates a vault, so sfVaultID will be set, and
1536 // setting it again will not cause an error
1537 if (pField == &sfVaultID)
1538 continue;
1540 {{"pseudo-account has 2 pseudo-account fields set"}},
1541 [&](Account const& A1, Account const&, ApplyContext& ac) {
1542 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
1543 if (!sle)
1544 return false;
1545
1546 auto const vaultID = ~sle->at(~sfVaultID);
1547 BEAST_EXPECT(vaultID && !sle->isFieldPresent(*pField));
1548 sle->setFieldH256(*pField, *vaultID);
1549
1550 ac.view().update(sle);
1551 return true;
1552 },
1553 XRPAmount{},
1554 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1556 createPseudo);
1557 }
1558
1559 // Take one of the regular accounts and set the sequence to 0, which
1560 // will make it look like a pseudo-account
1562 {{"pseudo-account has 0 pseudo-account fields set"},
1563 {"pseudo-account sequence changed"},
1564 {"pseudo-account flags are not set"}},
1565 [&](Account const& A1, Account const&, ApplyContext& ac) {
1566 auto sle = ac.view().peek(keylet::account(A1.id()));
1567 if (!sle)
1568 return false;
1569 sle->at(sfSequence) = 0;
1570 ac.view().update(sle);
1571 return true;
1572 });
1573 }
1574
1575 void
1577 {
1578 using namespace test::jtx;
1579 testcase << "PermissionedDEX";
1580
1582 {{"domain doesn't exist"}},
1583 [](Account const& A1, Account const&, ApplyContext& ac) {
1584 Keylet const offerKey = keylet::offer(A1.id(), 10);
1585 auto sleOffer = std::make_shared<SLE>(offerKey);
1586 sleOffer->setAccountID(sfAccount, A1);
1587 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1588 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1589 ac.view().insert(sleOffer);
1590 return true;
1591 },
1592 XRPAmount{},
1593 STTx{
1594 ttOFFER_CREATE,
1595 [](STObject& tx) {
1596 tx.setFieldH256(
1597 sfDomainID,
1598 uint256{
1599 "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
1600 "70F3649CE134E5"});
1601 Account const A1{"A1"};
1602 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1603 tx.setFieldAmount(sfTakerGets, XRP(1));
1604 }},
1606
1607 // missing domain ID in offer object
1609 {{"hybrid offer is malformed"}},
1610 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1611 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1612 auto slePd = std::make_shared<SLE>(pdKeylet);
1613 createPermissionedDomain(ac, slePd, A1, A2);
1614
1615 Keylet const offerKey = keylet::offer(A2.id(), 10);
1616 auto sleOffer = std::make_shared<SLE>(offerKey);
1617 sleOffer->setAccountID(sfAccount, A2);
1618 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1619 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1620 sleOffer->setFlag(lsfHybrid);
1621
1622 STArray bookArr;
1623 bookArr.push_back(STObject::makeInnerObject(sfBook));
1624 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1625 ac.view().insert(sleOffer);
1626 return true;
1627 },
1628 XRPAmount{},
1629 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1631
1632 // more than one entry in sfAdditionalBooks
1634 {{"hybrid offer is malformed"}},
1635 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1636 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1637 auto slePd = std::make_shared<SLE>(pdKeylet);
1638 createPermissionedDomain(ac, slePd, A1, A2);
1639
1640 Keylet const offerKey = keylet::offer(A2.id(), 10);
1641 auto sleOffer = std::make_shared<SLE>(offerKey);
1642 sleOffer->setAccountID(sfAccount, A2);
1643 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1644 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1645 sleOffer->setFlag(lsfHybrid);
1646 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1647
1648 STArray bookArr;
1649 bookArr.push_back(STObject::makeInnerObject(sfBook));
1650 bookArr.push_back(STObject::makeInnerObject(sfBook));
1651 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1652 ac.view().insert(sleOffer);
1653 return true;
1654 },
1655 XRPAmount{},
1656 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1658
1659 // hybrid offer missing sfAdditionalBooks
1661 {{"hybrid offer is malformed"}},
1662 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1663 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1664 auto slePd = std::make_shared<SLE>(pdKeylet);
1665 createPermissionedDomain(ac, slePd, A1, A2);
1666
1667 Keylet const offerKey = keylet::offer(A2.id(), 10);
1668 auto sleOffer = std::make_shared<SLE>(offerKey);
1669 sleOffer->setAccountID(sfAccount, A2);
1670 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1671 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1672 sleOffer->setFlag(lsfHybrid);
1673 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1674 ac.view().insert(sleOffer);
1675 return true;
1676 },
1677 XRPAmount{},
1678 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1680
1682 {{"transaction consumed wrong domains"}},
1683 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1684 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1685 auto slePd = std::make_shared<SLE>(pdKeylet);
1686 createPermissionedDomain(ac, slePd, A1, A2);
1687
1688 Keylet const badDomainKeylet =
1690 auto sleBadPd = std::make_shared<SLE>(badDomainKeylet);
1691 createPermissionedDomain(ac, sleBadPd, A1, A2);
1692
1693 Keylet const offerKey = keylet::offer(A2.id(), 10);
1694 auto sleOffer = std::make_shared<SLE>(offerKey);
1695 sleOffer->setAccountID(sfAccount, A2);
1696 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1697 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1698 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1699 ac.view().insert(sleOffer);
1700 return true;
1701 },
1702 XRPAmount{},
1703 STTx{
1704 ttOFFER_CREATE,
1705 [&](STObject& tx) {
1706 Account const A1{"A1"};
1707 Keylet const badDomainKey =
1709 tx.setFieldH256(sfDomainID, badDomainKey.key);
1710 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1711 tx.setFieldAmount(sfTakerGets, XRP(1));
1712 }},
1714
1716 {{"domain transaction affected regular offers"}},
1717 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1718 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1719 auto slePd = std::make_shared<SLE>(pdKeylet);
1720 createPermissionedDomain(ac, slePd, A1, A2);
1721
1722 Keylet const offerKey = keylet::offer(A2.id(), 10);
1723 auto sleOffer = std::make_shared<SLE>(offerKey);
1724 sleOffer->setAccountID(sfAccount, A2);
1725 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1726 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1727 ac.view().insert(sleOffer);
1728 return true;
1729 },
1730 XRPAmount{},
1731 STTx{
1732 ttOFFER_CREATE,
1733 [&](STObject& tx) {
1734 Account const A1{"A1"};
1735 Keylet const domainKey =
1737 tx.setFieldH256(sfDomainID, domainKey.key);
1738 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1739 tx.setFieldAmount(sfTakerGets, XRP(1));
1740 }},
1742 }
1743
1744 void
1746 {
1747 using namespace test::jtx;
1748
1749 struct AccountAmount
1750 {
1751 AccountID account;
1752 int amount;
1753 };
1754 struct Adjustements
1755 {
1756 std::optional<int> assetsTotal = {};
1757 std::optional<int> assetsAvailable = {};
1758 std::optional<int> lossUnrealized = {};
1759 std::optional<int> assetsMaximum = {};
1760 std::optional<int> sharesTotal = {};
1761 std::optional<int> vaultAssets = {};
1762 std::optional<AccountAmount> accountAssets = {};
1763 std::optional<AccountAmount> accountShares = {};
1764 };
1765 auto constexpr adjust = [&](ApplyView& ac,
1766 ripple::Keylet keylet,
1767 Adjustements args) {
1768 auto sleVault = ac.peek(keylet);
1769 if (!sleVault)
1770 return false;
1771
1772 auto const mptIssuanceID = (*sleVault)[sfShareMPTID];
1773 auto sleShares = ac.peek(keylet::mptIssuance(mptIssuanceID));
1774 if (!sleShares)
1775 return false;
1776
1777 // These two fields are adjusted in absolute terms
1778 if (args.lossUnrealized)
1779 (*sleVault)[sfLossUnrealized] = *args.lossUnrealized;
1780 if (args.assetsMaximum)
1781 (*sleVault)[sfAssetsMaximum] = *args.assetsMaximum;
1782
1783 // Remaining fields are adjusted in terms of difference
1784 if (args.assetsTotal)
1785 (*sleVault)[sfAssetsTotal] =
1786 *(*sleVault)[sfAssetsTotal] + *args.assetsTotal;
1787 if (args.assetsAvailable)
1788 (*sleVault)[sfAssetsAvailable] =
1789 *(*sleVault)[sfAssetsAvailable] + *args.assetsAvailable;
1790 ac.update(sleVault);
1791
1792 if (args.sharesTotal)
1793 (*sleShares)[sfOutstandingAmount] =
1794 *(*sleShares)[sfOutstandingAmount] + *args.sharesTotal;
1795 ac.update(sleShares);
1796
1797 auto const assets = *(*sleVault)[sfAsset];
1798 auto const pseudoId = *(*sleVault)[sfAccount];
1799 if (args.vaultAssets)
1800 {
1801 if (assets.native())
1802 {
1803 auto slePseudoAccount = ac.peek(keylet::account(pseudoId));
1804 if (!slePseudoAccount)
1805 return false;
1806 (*slePseudoAccount)[sfBalance] =
1807 *(*slePseudoAccount)[sfBalance] + *args.vaultAssets;
1808 ac.update(slePseudoAccount);
1809 }
1810 else if (assets.holds<MPTIssue>())
1811 {
1812 auto const mptId = assets.get<MPTIssue>().getMptID();
1813 auto sleMPToken = ac.peek(keylet::mptoken(mptId, pseudoId));
1814 if (!sleMPToken)
1815 return false;
1816 (*sleMPToken)[sfMPTAmount] =
1817 *(*sleMPToken)[sfMPTAmount] + *args.vaultAssets;
1818 ac.update(sleMPToken);
1819 }
1820 else
1821 return false; // Not supporting testing with IOU
1822 }
1823
1824 if (args.accountAssets)
1825 {
1826 auto const& pair = *args.accountAssets;
1827 if (assets.native())
1828 {
1829 auto sleAccount = ac.peek(keylet::account(pair.account));
1830 if (!sleAccount)
1831 return false;
1832 (*sleAccount)[sfBalance] =
1833 *(*sleAccount)[sfBalance] + pair.amount;
1834 ac.update(sleAccount);
1835 }
1836 else if (assets.holds<MPTIssue>())
1837 {
1838 auto const mptID = assets.get<MPTIssue>().getMptID();
1839 auto sleMPToken =
1840 ac.peek(keylet::mptoken(mptID, pair.account));
1841 if (!sleMPToken)
1842 return false;
1843 (*sleMPToken)[sfMPTAmount] =
1844 *(*sleMPToken)[sfMPTAmount] + pair.amount;
1845 ac.update(sleMPToken);
1846 }
1847 else
1848 return false; // Not supporting testing with IOU
1849 }
1850
1851 if (args.accountShares)
1852 {
1853 auto const& pair = *args.accountShares;
1854 auto sleMPToken =
1855 ac.peek(keylet::mptoken(mptIssuanceID, pair.account));
1856 if (!sleMPToken)
1857 return false;
1858 (*sleMPToken)[sfMPTAmount] =
1859 *(*sleMPToken)[sfMPTAmount] + pair.amount;
1860 ac.update(sleMPToken);
1861 }
1862 return true;
1863 };
1864
1865 constexpr auto args =
1866 [](AccountID id, int adjustement, auto fn) -> Adjustements {
1867 Adjustements sample = {
1868 .assetsTotal = adjustement,
1869 .assetsAvailable = adjustement,
1870 .lossUnrealized = 0,
1871 .sharesTotal = adjustement,
1872 .vaultAssets = adjustement,
1873 .accountAssets = //
1874 AccountAmount{id, -adjustement},
1875 .accountShares = //
1876 AccountAmount{id, adjustement}};
1877 fn(sample);
1878 return sample;
1879 };
1880
1881 Account A3{"A3"};
1882 Account A4{"A4"};
1883 auto const precloseXrp =
1884 [&](Account const& A1, Account const& A2, Env& env) -> bool {
1885 env.fund(XRP(1000), A3, A4);
1886 Vault vault{env};
1887 auto [tx, keylet] =
1888 vault.create({.owner = A1, .asset = xrpIssue()});
1889 env(tx);
1890 env(vault.deposit(
1891 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
1892 env(vault.deposit(
1893 {.depositor = A2, .id = keylet.key, .amount = XRP(10)}));
1894 env(vault.deposit(
1895 {.depositor = A3, .id = keylet.key, .amount = XRP(10)}));
1896 return true;
1897 };
1898
1899 testcase << "Vault general checks";
1901 {"vault deletion succeeded without deleting a vault"},
1902 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1903 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1904 auto sleVault = ac.view().peek(keylet);
1905 if (!sleVault)
1906 return false;
1907 ac.view().update(sleVault);
1908 return true;
1909 },
1910 XRPAmount{},
1911 STTx{ttVAULT_DELETE, [](STObject&) {}},
1913 [&](Account const& A1, Account const& A2, Env& env) {
1914 Vault vault{env};
1915 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1916 env(tx);
1917 return true;
1918 });
1919
1921 {"vault updated by a wrong transaction type"},
1922 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1923 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1924 auto sleVault = ac.view().peek(keylet);
1925 if (!sleVault)
1926 return false;
1927 ac.view().erase(sleVault);
1928 return true;
1929 },
1930 XRPAmount{},
1931 STTx{ttPAYMENT, [](STObject&) {}},
1933 [&](Account const& A1, Account const& A2, Env& env) {
1934 Vault vault{env};
1935 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1936 env(tx);
1937 return true;
1938 });
1939
1941 {"vault updated by a wrong transaction type"},
1942 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1943 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1944 auto sleVault = ac.view().peek(keylet);
1945 if (!sleVault)
1946 return false;
1947 ac.view().update(sleVault);
1948 return true;
1949 },
1950 XRPAmount{},
1951 STTx{ttPAYMENT, [](STObject&) {}},
1953 [&](Account const& A1, Account const& A2, Env& env) {
1954 Vault vault{env};
1955 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1956 env(tx);
1957 return true;
1958 });
1959
1961 {"vault updated by a wrong transaction type"},
1962 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1963 auto const sequence = ac.view().seq();
1964 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
1965 auto sleVault = std::make_shared<SLE>(vaultKeylet);
1966 auto const vaultPage = ac.view().dirInsert(
1967 keylet::ownerDir(A1.id()),
1968 sleVault->key(),
1969 describeOwnerDir(A1.id()));
1970 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
1971 ac.view().insert(sleVault);
1972 return true;
1973 },
1974 XRPAmount{},
1975 STTx{ttPAYMENT, [](STObject&) {}},
1977
1979 {"vault deleted by a wrong transaction type"},
1980 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1981 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1982 auto sleVault = ac.view().peek(keylet);
1983 if (!sleVault)
1984 return false;
1985 ac.view().erase(sleVault);
1986 return true;
1987 },
1988 XRPAmount{},
1989 STTx{ttVAULT_SET, [](STObject&) {}},
1991 [&](Account const& A1, Account const& A2, Env& env) {
1992 Vault vault{env};
1993 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1994 env(tx);
1995 return true;
1996 });
1997
1999 {"vault operation updated more than single vault"},
2000 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2001 {
2002 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2003 auto sleVault = ac.view().peek(keylet);
2004 if (!sleVault)
2005 return false;
2006 ac.view().erase(sleVault);
2007 }
2008 {
2009 auto const keylet = keylet::vault(A2.id(), ac.view().seq());
2010 auto sleVault = ac.view().peek(keylet);
2011 if (!sleVault)
2012 return false;
2013 ac.view().erase(sleVault);
2014 }
2015 return true;
2016 },
2017 XRPAmount{},
2018 STTx{ttVAULT_DELETE, [](STObject&) {}},
2020 [&](Account const& A1, Account const& A2, Env& env) {
2021 Vault vault{env};
2022 {
2023 auto [tx, _] =
2024 vault.create({.owner = A1, .asset = xrpIssue()});
2025 env(tx);
2026 }
2027 {
2028 auto [tx, _] =
2029 vault.create({.owner = A2, .asset = xrpIssue()});
2030 env(tx);
2031 }
2032 return true;
2033 });
2034
2036 {"vault operation updated more than single vault"},
2037 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2038 auto const sequence = ac.view().seq();
2039 auto const insertVault = [&](Account const A) {
2040 auto const vaultKeylet = keylet::vault(A.id(), sequence);
2041 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2042 auto const vaultPage = ac.view().dirInsert(
2043 keylet::ownerDir(A.id()),
2044 sleVault->key(),
2045 describeOwnerDir(A.id()));
2046 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2047 ac.view().insert(sleVault);
2048 };
2049 insertVault(A1);
2050 insertVault(A2);
2051 return true;
2052 },
2053 XRPAmount{},
2054 STTx{ttVAULT_CREATE, [](STObject&) {}},
2056
2058 {"deleted vault must also delete shares"},
2059 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2060 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2061 auto sleVault = ac.view().peek(keylet);
2062 if (!sleVault)
2063 return false;
2064 ac.view().erase(sleVault);
2065 return true;
2066 },
2067 XRPAmount{},
2068 STTx{ttVAULT_DELETE, [](STObject&) {}},
2070 [&](Account const& A1, Account const& A2, Env& env) {
2071 Vault vault{env};
2072 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2073 env(tx);
2074 return true;
2075 });
2076
2078 {"deleted vault must have no shares outstanding",
2079 "deleted vault must have no assets outstanding",
2080 "deleted vault must have no assets available"},
2081 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2082 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2083 auto sleVault = ac.view().peek(keylet);
2084 if (!sleVault)
2085 return false;
2086 auto sleShares = ac.view().peek(
2087 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2088 if (!sleShares)
2089 return false;
2090 ac.view().erase(sleVault);
2091 ac.view().erase(sleShares);
2092 return true;
2093 },
2094 XRPAmount{},
2095 STTx{ttVAULT_DELETE, [](STObject&) {}},
2097 [&](Account const& A1, Account const& A2, Env& env) {
2098 Vault vault{env};
2099 auto [tx, keylet] =
2100 vault.create({.owner = A1, .asset = xrpIssue()});
2101 env(tx);
2102 env(vault.deposit(
2103 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2104 return true;
2105 });
2106
2108 {"vault operation succeeded without modifying a vault"},
2109 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2110 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2111 auto sleVault = ac.view().peek(keylet);
2112 if (!sleVault)
2113 return false;
2114 auto sleShares = ac.view().peek(
2115 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2116 if (!sleShares)
2117 return false;
2118 // Note, such an "orphaned" update of MPT issuance attached to a
2119 // vault is invalid; ttVAULT_SET must also update Vault object.
2120 sleShares->setFieldH256(sfDomainID, uint256(13));
2121 ac.view().update(sleShares);
2122 return true;
2123 },
2124 XRPAmount{},
2125 STTx{ttVAULT_SET, [](STObject& tx) {}},
2127 precloseXrp,
2129
2131 {"vault operation succeeded without modifying a vault"},
2132 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2133 return true;
2134 },
2135 XRPAmount{},
2136 STTx{ttVAULT_CREATE, [](STObject&) {}},
2138 [&](Account const& A1, Account const& A2, Env& env) {
2139 Vault vault{env};
2140 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2141 env(tx);
2142 return true;
2143 });
2144
2146 {"vault operation succeeded without modifying a vault"},
2147 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2148 return true;
2149 },
2150 XRPAmount{},
2151 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2153 [&](Account const& A1, Account const& A2, Env& env) {
2154 Vault vault{env};
2155 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2156 env(tx);
2157 return true;
2158 });
2159
2161 {"vault operation succeeded without modifying a vault"},
2162 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2163 return true;
2164 },
2165 XRPAmount{},
2166 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2168 [&](Account const& A1, Account const& A2, Env& env) {
2169 Vault vault{env};
2170 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2171 env(tx);
2172 return true;
2173 });
2174
2176 {"vault operation succeeded without modifying a vault"},
2177 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2178 return true;
2179 },
2180 XRPAmount{},
2181 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
2183 [&](Account const& A1, Account const& A2, Env& env) {
2184 Vault vault{env};
2185 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2186 env(tx);
2187 return true;
2188 });
2189
2191 {"vault operation succeeded without modifying a vault"},
2192 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2193 return true;
2194 },
2195 XRPAmount{},
2196 STTx{ttVAULT_DELETE, [](STObject&) {}},
2198 [&](Account const& A1, Account const& A2, Env& env) {
2199 Vault vault{env};
2200 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2201 env(tx);
2202 return true;
2203 });
2204
2206 {"updated vault must have shares"},
2207 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2208 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2209 auto sleVault = ac.view().peek(keylet);
2210 if (!sleVault)
2211 return false;
2212 (*sleVault)[sfAssetsMaximum] = 200;
2213 ac.view().update(sleVault);
2214
2215 auto sleShares = ac.view().peek(
2216 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2217 if (!sleShares)
2218 return false;
2219 ac.view().erase(sleShares);
2220 return true;
2221 },
2222 XRPAmount{},
2223 STTx{ttVAULT_SET, [](STObject&) {}},
2225 [&](Account const& A1, Account const& A2, Env& env) {
2226 Vault vault{env};
2227 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2228 env(tx);
2229 return true;
2230 });
2231
2233 {"vault operation succeeded without updating shares",
2234 "assets available must not be greater than assets outstanding"},
2235 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2236 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2237 auto sleVault = ac.view().peek(keylet);
2238 if (!sleVault)
2239 return false;
2240 (*sleVault)[sfAssetsTotal] = 9;
2241 ac.view().update(sleVault);
2242 return true;
2243 },
2244 XRPAmount{},
2245 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2247 [&](Account const& A1, Account const& A2, Env& env) {
2248 Vault vault{env};
2249 auto [tx, keylet] =
2250 vault.create({.owner = A1, .asset = xrpIssue()});
2251 env(tx);
2252 env(vault.deposit(
2253 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2254 return true;
2255 });
2256
2258 {"set must not change assets outstanding",
2259 "set must not change assets available",
2260 "set must not change shares outstanding",
2261 "set must not change vault balance",
2262 "assets available must be positive",
2263 "assets available must not be greater than assets outstanding",
2264 "assets outstanding must be positive"},
2265 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2266 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2267 auto sleVault = ac.view().peek(keylet);
2268 if (!sleVault)
2269 return false;
2270 auto slePseudoAccount =
2271 ac.view().peek(keylet::account(*(*sleVault)[sfAccount]));
2272 if (!slePseudoAccount)
2273 return false;
2274 (*slePseudoAccount)[sfBalance] =
2275 *(*slePseudoAccount)[sfBalance] - 10;
2276 ac.view().update(slePseudoAccount);
2277
2278 // Move 10 drops to A4 to enforce total XRP balance
2279 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
2280 if (!sleA4)
2281 return false;
2282 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2283 ac.view().update(sleA4);
2284
2285 return adjust(
2286 ac.view(),
2287 keylet,
2288 args(A2.id(), 0, [&](Adjustements& sample) {
2289 sample.assetsAvailable = (DROPS_PER_XRP * -100).value();
2290 sample.assetsTotal = (DROPS_PER_XRP * -200).value();
2291 sample.sharesTotal = -1;
2292 }));
2293 },
2294 XRPAmount{},
2295 STTx{ttVAULT_SET, [](STObject& tx) {}},
2297 precloseXrp,
2299
2301 {"violation of vault immutable data"},
2302 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2303 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2304 auto sleVault = ac.view().peek(keylet);
2305 if (!sleVault)
2306 return false;
2307 sleVault->setFieldIssue(
2308 sfAsset, STIssue{sfAsset, MPTIssue(MPTID(42))});
2309 ac.view().update(sleVault);
2310 return true;
2311 },
2312 XRPAmount{},
2313 STTx{ttVAULT_SET, [](STObject& tx) {}},
2315 precloseXrp);
2316
2318 {"violation of vault immutable data"},
2319 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2320 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2321 auto sleVault = ac.view().peek(keylet);
2322 if (!sleVault)
2323 return false;
2324 sleVault->setAccountID(sfAccount, A2.id());
2325 ac.view().update(sleVault);
2326 return true;
2327 },
2328 XRPAmount{},
2329 STTx{ttVAULT_SET, [](STObject& tx) {}},
2331 precloseXrp);
2332
2334 {"violation of vault immutable data"},
2335 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2336 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2337 auto sleVault = ac.view().peek(keylet);
2338 if (!sleVault)
2339 return false;
2340 (*sleVault)[sfShareMPTID] = MPTID(42);
2341 ac.view().update(sleVault);
2342 return true;
2343 },
2344 XRPAmount{},
2345 STTx{ttVAULT_SET, [](STObject& tx) {}},
2347 precloseXrp);
2348
2350 {"vault transaction must not change loss unrealized",
2351 "set must not change assets outstanding"},
2352 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2353 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2354 return adjust(
2355 ac.view(),
2356 keylet,
2357 args(A2.id(), 0, [&](Adjustements& sample) {
2358 sample.lossUnrealized = 13;
2359 sample.assetsTotal = 20;
2360 }));
2361 },
2362 XRPAmount{},
2363 STTx{ttVAULT_SET, [](STObject& tx) {}},
2365 precloseXrp,
2367
2369 {"loss unrealized must not exceed the difference "
2370 "between assets outstanding and available",
2371 "vault transaction must not change loss unrealized"},
2372 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2373 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2374 return adjust(
2375 ac.view(),
2376 keylet,
2377 args(A2.id(), 100, [&](Adjustements& sample) {
2378 sample.lossUnrealized = 13;
2379 }));
2380 },
2381 XRPAmount{},
2382 STTx{
2383 ttVAULT_DEPOSIT,
2384 [](STObject& tx) {
2385 tx.setFieldAmount(sfAmount, XRPAmount(200));
2386 }},
2388 precloseXrp,
2390
2392 {"set assets outstanding must not exceed assets maximum"},
2393 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2394 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2395 return adjust(
2396 ac.view(),
2397 keylet,
2398 args(A2.id(), 0, [&](Adjustements& sample) {
2399 sample.assetsMaximum = 1;
2400 }));
2401 },
2402 XRPAmount{},
2403 STTx{ttVAULT_SET, [](STObject& tx) {}},
2405 precloseXrp,
2407
2409 {"assets maximum must be positive"},
2410 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2411 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2412 return adjust(
2413 ac.view(),
2414 keylet,
2415 args(A2.id(), 0, [&](Adjustements& sample) {
2416 sample.assetsMaximum = -1;
2417 }));
2418 },
2419 XRPAmount{},
2420 STTx{ttVAULT_SET, [](STObject& tx) {}},
2422 precloseXrp,
2424
2426 {"set must not change shares outstanding",
2427 "updated zero sized vault must have no assets outstanding",
2428 "updated zero sized vault must have no assets available"},
2429 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2430 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2431 auto sleVault = ac.view().peek(keylet);
2432 if (!sleVault)
2433 return false;
2434 ac.view().update(sleVault);
2435 auto sleShares = ac.view().peek(
2436 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2437 if (!sleShares)
2438 return false;
2439 (*sleShares)[sfOutstandingAmount] = 0;
2440 ac.view().update(sleShares);
2441 return true;
2442 },
2443 XRPAmount{},
2444 STTx{ttVAULT_SET, [](STObject& tx) {}},
2446 precloseXrp,
2448
2450 {"updated shares must not exceed maximum"},
2451 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2452 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2453 auto sleVault = ac.view().peek(keylet);
2454 if (!sleVault)
2455 return false;
2456 auto sleShares = ac.view().peek(
2457 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2458 if (!sleShares)
2459 return false;
2460 (*sleShares)[sfMaximumAmount] = 10;
2461 ac.view().update(sleShares);
2462
2463 return adjust(
2464 ac.view(), keylet, args(A2.id(), 10, [](Adjustements&) {}));
2465 },
2466 XRPAmount{},
2467 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2469 precloseXrp,
2471
2473 {"updated shares must not exceed maximum"},
2474 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2475 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2476 adjust(
2477 ac.view(), keylet, args(A2.id(), 10, [](Adjustements&) {}));
2478
2479 auto sleVault = ac.view().peek(keylet);
2480 if (!sleVault)
2481 return false;
2482 auto sleShares = ac.view().peek(
2483 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2484 if (!sleShares)
2485 return false;
2486 (*sleShares)[sfOutstandingAmount] = maxMPTokenAmount + 1;
2487 ac.view().update(sleShares);
2488 return true;
2489 },
2490 XRPAmount{},
2491 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2493 precloseXrp,
2495
2496 testcase << "Vault create";
2498 {
2499 "created vault must be empty",
2500 "updated zero sized vault must have no assets outstanding",
2501 "create operation must not have updated a vault",
2502 },
2503 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2504 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2505 auto sleVault = ac.view().peek(keylet);
2506 if (!sleVault)
2507 return false;
2508 (*sleVault)[sfAssetsTotal] = 9;
2509 ac.view().update(sleVault);
2510 return true;
2511 },
2512 XRPAmount{},
2513 STTx{ttVAULT_CREATE, [](STObject&) {}},
2515 [&](Account const& A1, Account const& A2, Env& env) {
2516 Vault vault{env};
2517 auto [tx, keylet] =
2518 vault.create({.owner = A1, .asset = xrpIssue()});
2519 env(tx);
2520 return true;
2521 });
2522
2524 {
2525 "created vault must be empty",
2526 "updated zero sized vault must have no assets available",
2527 "assets available must not be greater than assets outstanding",
2528 "create operation must not have updated a vault",
2529 },
2530 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2531 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2532 auto sleVault = ac.view().peek(keylet);
2533 if (!sleVault)
2534 return false;
2535 (*sleVault)[sfAssetsAvailable] = 9;
2536 ac.view().update(sleVault);
2537 return true;
2538 },
2539 XRPAmount{},
2540 STTx{ttVAULT_CREATE, [](STObject&) {}},
2542 [&](Account const& A1, Account const& A2, Env& env) {
2543 Vault vault{env};
2544 auto [tx, keylet] =
2545 vault.create({.owner = A1, .asset = xrpIssue()});
2546 env(tx);
2547 return true;
2548 });
2549
2551 {
2552 "created vault must be empty",
2553 "loss unrealized must not exceed the difference between assets "
2554 "outstanding and available",
2555 "vault transaction must not change loss unrealized",
2556 "create operation must not have updated a vault",
2557 },
2558 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2559 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2560 auto sleVault = ac.view().peek(keylet);
2561 if (!sleVault)
2562 return false;
2563 (*sleVault)[sfLossUnrealized] = 1;
2564 ac.view().update(sleVault);
2565 return true;
2566 },
2567 XRPAmount{},
2568 STTx{ttVAULT_CREATE, [](STObject&) {}},
2570 [&](Account const& A1, Account const& A2, Env& env) {
2571 Vault vault{env};
2572 auto [tx, keylet] =
2573 vault.create({.owner = A1, .asset = xrpIssue()});
2574 env(tx);
2575 return true;
2576 });
2577
2579 {
2580 "created vault must be empty",
2581 "create operation must not have updated a vault",
2582 },
2583 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2584 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2585 auto sleVault = ac.view().peek(keylet);
2586 if (!sleVault)
2587 return false;
2588 auto sleShares = ac.view().peek(
2589 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2590 if (!sleShares)
2591 return false;
2592 ac.view().update(sleVault);
2593 (*sleShares)[sfOutstandingAmount] = 9;
2594 ac.view().update(sleShares);
2595 return true;
2596 },
2597 XRPAmount{},
2598 STTx{ttVAULT_CREATE, [](STObject&) {}},
2600 [&](Account const& A1, Account const& A2, Env& env) {
2601 Vault vault{env};
2602 auto [tx, keylet] =
2603 vault.create({.owner = A1, .asset = xrpIssue()});
2604 env(tx);
2605 return true;
2606 });
2607
2609 {
2610 "assets maximum must be positive",
2611 "create operation must not have updated a vault",
2612 },
2613 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2614 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2615 auto sleVault = ac.view().peek(keylet);
2616 if (!sleVault)
2617 return false;
2618 (*sleVault)[sfAssetsMaximum] = Number(-1);
2619 ac.view().update(sleVault);
2620 return true;
2621 },
2622 XRPAmount{},
2623 STTx{ttVAULT_CREATE, [](STObject&) {}},
2625 [&](Account const& A1, Account const& A2, Env& env) {
2626 Vault vault{env};
2627 auto [tx, keylet] =
2628 vault.create({.owner = A1, .asset = xrpIssue()});
2629 env(tx);
2630 return true;
2631 });
2632
2634 {"create operation must not have updated a vault",
2635 "shares issuer and vault pseudo-account must be the same",
2636 "shares issuer must be a pseudo-account",
2637 "shares issuer pseudo-account must point back to the vault"},
2638 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2639 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2640 auto sleVault = ac.view().peek(keylet);
2641 if (!sleVault)
2642 return false;
2643 auto sleShares = ac.view().peek(
2644 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2645 if (!sleShares)
2646 return false;
2647 ac.view().update(sleVault);
2648 (*sleShares)[sfIssuer] = A1.id();
2649 ac.view().update(sleShares);
2650 return true;
2651 },
2652 XRPAmount{},
2653 STTx{ttVAULT_CREATE, [](STObject&) {}},
2655 [&](Account const& A1, Account const& A2, Env& env) {
2656 Vault vault{env};
2657 auto [tx, keylet] =
2658 vault.create({.owner = A1, .asset = xrpIssue()});
2659 env(tx);
2660 return true;
2661 });
2662
2664 {"vault created by a wrong transaction type",
2665 "account root created illegally"},
2666 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2667 // The code below will create a valid vault with (almost) all
2668 // the invariants holding. Except one: it is created by the
2669 // wrong transaction type.
2670 auto const sequence = ac.view().seq();
2671 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2672 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2673 auto const vaultPage = ac.view().dirInsert(
2674 keylet::ownerDir(A1.id()),
2675 sleVault->key(),
2676 describeOwnerDir(A1.id()));
2677 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2678
2679 auto pseudoId =
2680 pseudoAccountAddress(ac.view(), vaultKeylet.key);
2681 // Create pseudo-account.
2682 auto sleAccount =
2684 sleAccount->setAccountID(sfAccount, pseudoId);
2685 sleAccount->setFieldAmount(sfBalance, STAmount{});
2686 std::uint32_t const seqno = //
2687 ac.view().rules().enabled(featureSingleAssetVault) //
2688 ? 0 //
2689 : sequence;
2690 sleAccount->setFieldU32(sfSequence, seqno);
2691 sleAccount->setFieldU32(
2692 sfFlags,
2694 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
2695 ac.view().insert(sleAccount);
2696
2697 auto const sharesMptId = makeMptID(sequence, pseudoId);
2698 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2699 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2700 auto const sharesPage = ac.view().dirInsert(
2701 keylet::ownerDir(pseudoId),
2702 sharesKeylet,
2703 describeOwnerDir(pseudoId));
2704 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2705
2706 sleShares->at(sfFlags) = 0;
2707 sleShares->at(sfIssuer) = pseudoId;
2708 sleShares->at(sfOutstandingAmount) = 0;
2709 sleShares->at(sfSequence) = sequence;
2710
2711 sleVault->at(sfAccount) = pseudoId;
2712 sleVault->at(sfFlags) = 0;
2713 sleVault->at(sfSequence) = sequence;
2714 sleVault->at(sfOwner) = A1.id();
2715 sleVault->at(sfAssetsTotal) = Number(0);
2716 sleVault->at(sfAssetsAvailable) = Number(0);
2717 sleVault->at(sfLossUnrealized) = Number(0);
2718 sleVault->at(sfShareMPTID) = sharesMptId;
2719 sleVault->at(sfWithdrawalPolicy) =
2721
2722 ac.view().insert(sleVault);
2723 ac.view().insert(sleShares);
2724 return true;
2725 },
2726 XRPAmount{},
2727 STTx{ttVAULT_SET, [](STObject&) {}},
2729
2731 {"shares issuer and vault pseudo-account must be the same",
2732 "shares issuer pseudo-account must point back to the vault"},
2733 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2734 auto const sequence = ac.view().seq();
2735 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2736 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2737 auto const vaultPage = ac.view().dirInsert(
2738 keylet::ownerDir(A1.id()),
2739 sleVault->key(),
2740 describeOwnerDir(A1.id()));
2741 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2742
2743 auto pseudoId =
2744 pseudoAccountAddress(ac.view(), vaultKeylet.key);
2745 // Create pseudo-account.
2746 auto sleAccount =
2748 sleAccount->setAccountID(sfAccount, pseudoId);
2749 sleAccount->setFieldAmount(sfBalance, STAmount{});
2750 std::uint32_t const seqno = //
2751 ac.view().rules().enabled(featureSingleAssetVault) //
2752 ? 0 //
2753 : sequence;
2754 sleAccount->setFieldU32(sfSequence, seqno);
2755 sleAccount->setFieldU32(
2756 sfFlags,
2758 // sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
2759 // Setting wrong vault key
2760 sleAccount->setFieldH256(sfVaultID, uint256(42));
2761 ac.view().insert(sleAccount);
2762
2763 auto const sharesMptId = makeMptID(sequence, pseudoId);
2764 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2765 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2766 auto const sharesPage = ac.view().dirInsert(
2767 keylet::ownerDir(pseudoId),
2768 sharesKeylet,
2769 describeOwnerDir(pseudoId));
2770 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2771
2772 sleShares->at(sfFlags) = 0;
2773 sleShares->at(sfIssuer) = pseudoId;
2774 sleShares->at(sfOutstandingAmount) = 0;
2775 sleShares->at(sfSequence) = sequence;
2776
2777 // sleVault->at(sfAccount) = pseudoId;
2778 // Setting wrong pseudo acocunt ID
2779 sleVault->at(sfAccount) = A2.id();
2780 sleVault->at(sfFlags) = 0;
2781 sleVault->at(sfSequence) = sequence;
2782 sleVault->at(sfOwner) = A1.id();
2783 sleVault->at(sfAssetsTotal) = Number(0);
2784 sleVault->at(sfAssetsAvailable) = Number(0);
2785 sleVault->at(sfLossUnrealized) = Number(0);
2786 sleVault->at(sfShareMPTID) = sharesMptId;
2787 sleVault->at(sfWithdrawalPolicy) =
2789
2790 ac.view().insert(sleVault);
2791 ac.view().insert(sleShares);
2792 return true;
2793 },
2794 XRPAmount{},
2795 STTx{ttVAULT_CREATE, [](STObject&) {}},
2797
2799 {"shares issuer and vault pseudo-account must be the same",
2800 "shares issuer must exist"},
2801 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2802 auto const sequence = ac.view().seq();
2803 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2804 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2805 auto const vaultPage = ac.view().dirInsert(
2806 keylet::ownerDir(A1.id()),
2807 sleVault->key(),
2808 describeOwnerDir(A1.id()));
2809 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2810
2811 auto const sharesMptId = makeMptID(sequence, A2.id());
2812 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2813 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2814 auto const sharesPage = ac.view().dirInsert(
2815 keylet::ownerDir(A2.id()),
2816 sharesKeylet,
2817 describeOwnerDir(A2.id()));
2818 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2819
2820 sleShares->at(sfFlags) = 0;
2821 // Setting wrong pseudo acocunt ID
2822 sleShares->at(sfIssuer) = AccountID(uint160(42));
2823 sleShares->at(sfOutstandingAmount) = 0;
2824 sleShares->at(sfSequence) = sequence;
2825
2826 sleVault->at(sfAccount) = A2.id();
2827 sleVault->at(sfFlags) = 0;
2828 sleVault->at(sfSequence) = sequence;
2829 sleVault->at(sfOwner) = A1.id();
2830 sleVault->at(sfAssetsTotal) = Number(0);
2831 sleVault->at(sfAssetsAvailable) = Number(0);
2832 sleVault->at(sfLossUnrealized) = Number(0);
2833 sleVault->at(sfShareMPTID) = sharesMptId;
2834 sleVault->at(sfWithdrawalPolicy) =
2836
2837 ac.view().insert(sleVault);
2838 ac.view().insert(sleShares);
2839 return true;
2840 },
2841 XRPAmount{},
2842 STTx{ttVAULT_CREATE, [](STObject&) {}},
2844
2845 testcase << "Vault deposit";
2847 {"deposit must change vault balance"},
2848 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2849 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2850 return adjust(
2851 ac.view(),
2852 keylet,
2853 args(A2.id(), 0, [&](Adjustements& sample) {}));
2854 },
2855 XRPAmount{},
2856 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2858 precloseXrp);
2859
2861 {"deposit assets outstanding must not exceed assets maximum"},
2862 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2863 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2864 return adjust(
2865 ac.view(),
2866 keylet,
2867 args(A2.id(), 200, [&](Adjustements& sample) {
2868 sample.assetsMaximum = 1;
2869 }));
2870 },
2871 XRPAmount{},
2872 STTx{
2873 ttVAULT_DEPOSIT,
2874 [](STObject& tx) {
2875 tx.setFieldAmount(sfAmount, XRPAmount(200));
2876 }},
2878 precloseXrp,
2880
2881 // This really convoluted unit tests makes the zero balance on the
2882 // depositor, by sending them the same amount as the transaction fee.
2883 // The operation makes no sense, but the defensive check in
2884 // ValidVault::finalize is otherwise impossible to trigger.
2886 {"deposit must increase vault balance",
2887 "deposit must change depositor balance"},
2888 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2889 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2890
2891 // Move 10 drops to A4 to enforce total XRP balance
2892 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
2893 if (!sleA4)
2894 return false;
2895 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2896 ac.view().update(sleA4);
2897
2898 return adjust(
2899 ac.view(),
2900 keylet,
2901 args(A3.id(), -10, [&](Adjustements& sample) {
2902 sample.accountAssets->amount = -100;
2903 }));
2904 },
2905 XRPAmount{100},
2906 STTx{
2907 ttVAULT_DEPOSIT,
2908 [&](STObject& tx) {
2909 tx[sfFee] = XRPAmount(100);
2910 tx[sfAccount] = A3.id();
2911 }},
2913 precloseXrp);
2914
2916 {"deposit must increase vault balance",
2917 "deposit must decrease depositor balance",
2918 "deposit must change vault and depositor balance by equal amount",
2919 "deposit and assets outstanding must add up",
2920 "deposit and assets available must add up"},
2921 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2922 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2923
2924 // Move 10 drops from A2 to A3 to enforce total XRP balance
2925 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
2926 if (!sleA3)
2927 return false;
2928 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
2929 ac.view().update(sleA3);
2930
2931 return adjust(
2932 ac.view(),
2933 keylet,
2934 args(A2.id(), 10, [&](Adjustements& sample) {
2935 sample.vaultAssets = -20;
2936 sample.accountAssets->amount = 10;
2937 }));
2938 },
2939 XRPAmount{},
2940 STTx{
2941 ttVAULT_DEPOSIT,
2942 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2944 precloseXrp,
2946
2948 {"deposit must change depositor balance"},
2949 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2950 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2951
2952 // Move 10 drops from A3 to vault to enforce total XRP balance
2953 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
2954 if (!sleA3)
2955 return false;
2956 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 10;
2957 ac.view().update(sleA3);
2958
2959 return adjust(
2960 ac.view(),
2961 keylet,
2962 args(A2.id(), 10, [&](Adjustements& sample) {
2963 sample.accountAssets->amount = 0;
2964 }));
2965 },
2966 XRPAmount{},
2967 STTx{
2968 ttVAULT_DEPOSIT,
2969 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2971 precloseXrp,
2973
2975 {"deposit must change depositor shares"},
2976 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2977 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2978 return adjust(
2979 ac.view(),
2980 keylet,
2981 args(A2.id(), 10, [&](Adjustements& sample) {
2982 sample.accountShares->amount = 0;
2983 }));
2984 },
2985 XRPAmount{},
2986 STTx{
2987 ttVAULT_DEPOSIT,
2988 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2990 precloseXrp,
2992
2994 {"deposit must change vault shares"},
2995 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2996 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2997 return adjust(
2998 ac.view(),
2999 keylet,
3000 args(A2.id(), 10, [&](Adjustements& sample) {
3001 sample.sharesTotal = 0;
3002 }));
3003 },
3004 XRPAmount{},
3005 STTx{
3006 ttVAULT_DEPOSIT,
3007 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3009 precloseXrp,
3011
3013 {"deposit must increase depositor shares",
3014 "deposit must change depositor and vault shares by equal amount",
3015 "deposit must not change vault balance by more than deposited "
3016 "amount"},
3017 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3018 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3019 return adjust(
3020 ac.view(),
3021 keylet,
3022 args(A2.id(), 10, [&](Adjustements& sample) {
3023 sample.accountShares->amount = -5;
3024 sample.sharesTotal = -10;
3025 }));
3026 },
3027 XRPAmount{},
3028 STTx{
3029 ttVAULT_DEPOSIT,
3030 [](STObject& tx) { tx[sfAmount] = XRPAmount(5); }},
3032 precloseXrp,
3034
3036 {"deposit and assets outstanding must add up",
3037 "deposit and assets available must add up"},
3038 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3039 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3040 return adjust(
3041 ac.view(),
3042 keylet,
3043 args(A2.id(), 10, [&](Adjustements& sample) {
3044 sample.assetsTotal = 7;
3045 sample.assetsAvailable = 7;
3046 }));
3047 },
3048 XRPAmount{},
3049 STTx{
3050 ttVAULT_DEPOSIT,
3051 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3053 precloseXrp,
3055
3056 testcase << "Vault withdrawal";
3058 {"withdrawal must change vault balance"},
3059 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3060 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3061 return adjust(
3062 ac.view(),
3063 keylet,
3064 args(A2.id(), 0, [&](Adjustements& sample) {}));
3065 },
3066 XRPAmount{},
3067 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3069 precloseXrp);
3070
3071 // Almost identical to the really convoluted test for deposit, where the
3072 // depositor spends only the transaction fee. In case of withdrawal,
3073 // this test is almost the same as normal withdrawal where the
3074 // sfDestination would have been A4, but has been omitted.
3076 {"withdrawal must change one destination balance"},
3077 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3078 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3079
3080 // Move 10 drops to A4 to enforce total XRP balance
3081 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
3082 if (!sleA4)
3083 return false;
3084 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3085 ac.view().update(sleA4);
3086
3087 return adjust(
3088 ac.view(),
3089 keylet,
3090 args(A3.id(), -10, [&](Adjustements& sample) {
3091 sample.accountAssets->amount = -100;
3092 }));
3093 },
3094 XRPAmount{100},
3095 STTx{
3096 ttVAULT_WITHDRAW,
3097 [&](STObject& tx) {
3098 tx[sfFee] = XRPAmount(100);
3099 tx[sfAccount] = A3.id();
3100 // This commented out line causes the invariant violation.
3101 // tx[sfDestination] = A4.id();
3102 }},
3104 precloseXrp);
3105
3107 {"withdrawal must change vault and destination balance by "
3108 "equal amount",
3109 "withdrawal must decrease vault balance",
3110 "withdrawal must increase destination balance",
3111 "withdrawal and assets outstanding must add up",
3112 "withdrawal and assets available must add up"},
3113 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3114 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3115
3116 // Move 10 drops from A2 to A3 to enforce total XRP balance
3117 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3118 if (!sleA3)
3119 return false;
3120 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
3121 ac.view().update(sleA3);
3122
3123 return adjust(
3124 ac.view(),
3125 keylet,
3126 args(A2.id(), -10, [&](Adjustements& sample) {
3127 sample.vaultAssets = 10;
3128 sample.accountAssets->amount = -20;
3129 }));
3130 },
3131 XRPAmount{},
3132 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3134 precloseXrp,
3136
3138 {"withdrawal must change one destination balance"},
3139 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3140 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3141 if (!adjust(
3142 ac.view(),
3143 keylet,
3144 args(A2.id(), -10, [&](Adjustements& sample) {
3145 *sample.vaultAssets -= 5;
3146 })))
3147 return false;
3148 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3149 if (!sleA3)
3150 return false;
3151 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 5;
3152 ac.view().update(sleA3);
3153 return true;
3154 },
3155 XRPAmount{},
3156 STTx{
3157 ttVAULT_WITHDRAW,
3158 [&](STObject& tx) { tx.setAccountID(sfDestination, A3.id()); }},
3160 precloseXrp,
3162
3164 {"withdrawal must change depositor shares"},
3165 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3166 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3167 return adjust(
3168 ac.view(),
3169 keylet,
3170 args(A2.id(), -10, [&](Adjustements& sample) {
3171 sample.accountShares->amount = 0;
3172 }));
3173 },
3174 XRPAmount{},
3175 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3177 precloseXrp,
3179
3181 {"withdrawal must change vault shares"},
3182 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3183 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3184 return adjust(
3185 ac.view(),
3186 keylet,
3187 args(A2.id(), -10, [&](Adjustements& sample) {
3188 sample.sharesTotal = 0;
3189 }));
3190 },
3191 XRPAmount{},
3192 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3194 precloseXrp,
3196
3198 {"withdrawal must decrease depositor shares",
3199 "withdrawal must change depositor and vault shares by equal "
3200 "amount"},
3201 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3202 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3203 return adjust(
3204 ac.view(),
3205 keylet,
3206 args(A2.id(), -10, [&](Adjustements& sample) {
3207 sample.accountShares->amount = 5;
3208 sample.sharesTotal = 10;
3209 }));
3210 },
3211 XRPAmount{},
3212 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3214 precloseXrp,
3216
3218 {"withdrawal and assets outstanding must add up",
3219 "withdrawal and assets available must add up"},
3220 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3221 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3222 return adjust(
3223 ac.view(),
3224 keylet,
3225 args(A2.id(), -10, [&](Adjustements& sample) {
3226 sample.assetsTotal = -15;
3227 sample.assetsAvailable = -15;
3228 }));
3229 },
3230 XRPAmount{},
3231 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3233 precloseXrp,
3235
3236 auto const precloseMpt =
3237 [&](Account const& A1, Account const& A2, Env& env) -> bool {
3238 env.fund(XRP(1000), A3, A4);
3239
3240 // Create MPT asset
3241 {
3242 Json::Value jv;
3243 jv[sfAccount] = A3.human();
3244 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
3245 jv[sfFlags] = tfMPTCanTransfer;
3246 env(jv);
3247 env.close();
3248 }
3249
3250 auto const mptID = makeMptID(env.seq(A3) - 1, A3);
3251 Asset asset = MPTIssue(mptID);
3252 // Authorize A1 A2 A4
3253 {
3254 Json::Value jv;
3255 jv[sfAccount] = A1.human();
3256 jv[sfTransactionType] = jss::MPTokenAuthorize;
3257 jv[sfMPTokenIssuanceID] = to_string(mptID);
3258 env(jv);
3259 jv[sfAccount] = A2.human();
3260 env(jv);
3261 jv[sfAccount] = A4.human();
3262 env(jv);
3263
3264 env.close();
3265 }
3266 // Send tokens to A1 A2 A4
3267 {
3268 env(pay(A3, A1, asset(1000)));
3269 env(pay(A3, A2, asset(1000)));
3270 env(pay(A3, A4, asset(1000)));
3271 env.close();
3272 }
3273
3274 Vault vault{env};
3275 auto [tx, keylet] = vault.create({.owner = A1, .asset = asset});
3276 env(tx);
3277 env(vault.deposit(
3278 {.depositor = A1, .id = keylet.key, .amount = asset(10)}));
3279 env(vault.deposit(
3280 {.depositor = A2, .id = keylet.key, .amount = asset(10)}));
3281 env(vault.deposit(
3282 {.depositor = A4, .id = keylet.key, .amount = asset(10)}));
3283 return true;
3284 };
3285
3287 {"withdrawal must decrease depositor shares",
3288 "withdrawal must change depositor and vault shares by equal "
3289 "amount"},
3290 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3291 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3292 return adjust(
3293 ac.view(),
3294 keylet,
3295 args(A2.id(), -10, [&](Adjustements& sample) {
3296 sample.accountShares->amount = 5;
3297 }));
3298 },
3299 XRPAmount{},
3300 STTx{
3301 ttVAULT_WITHDRAW,
3302 [&](STObject& tx) { tx[sfAccount] = A3.id(); }},
3304 precloseMpt,
3306
3307 testcase << "Vault clawback";
3309 {"clawback must change vault balance"},
3310 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3311 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3312 return adjust(
3313 ac.view(),
3314 keylet,
3315 args(A2.id(), -1, [&](Adjustements& sample) {
3316 sample.vaultAssets = 0;
3317 }));
3318 },
3319 XRPAmount{},
3320 STTx{
3321 ttVAULT_CLAWBACK,
3322 [&](STObject& tx) { tx[sfAccount] = A3.id(); }},
3324 precloseMpt);
3325
3326 // Not the same as below check: attempt to clawback XRP
3328 {"clawback may only be performed by the asset issuer"},
3329 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3330 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3331 return adjust(
3332 ac.view(),
3333 keylet,
3334 args(A2.id(), 0, [&](Adjustements& sample) {}));
3335 },
3336 XRPAmount{},
3337 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
3339 precloseXrp);
3340
3341 // Not the same as above check: attempt to clawback MPT by bad account
3343 {"clawback may only be performed by the asset issuer"},
3344 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3345 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3346 return adjust(
3347 ac.view(),
3348 keylet,
3349 args(A2.id(), 0, [&](Adjustements& sample) {}));
3350 },
3351 XRPAmount{},
3352 STTx{
3353 ttVAULT_CLAWBACK,
3354 [&](STObject& tx) { tx[sfAccount] = A4.id(); }},
3356 precloseMpt);
3357
3359 {"clawback must decrease vault balance",
3360 "clawback must decrease holder shares",
3361 "clawback must change vault shares"},
3362 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3363 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3364 return adjust(
3365 ac.view(),
3366 keylet,
3367 args(A4.id(), 10, [&](Adjustements& sample) {
3368 sample.sharesTotal = 0;
3369 }));
3370 },
3371 XRPAmount{},
3372 STTx{
3373 ttVAULT_CLAWBACK,
3374 [&](STObject& tx) {
3375 tx[sfAccount] = A3.id();
3376 tx[sfHolder] = A4.id();
3377 }},
3379 precloseMpt);
3380
3382 {"clawback must change holder shares"},
3383 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3384 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3385 return adjust(
3386 ac.view(),
3387 keylet,
3388 args(A4.id(), -10, [&](Adjustements& sample) {
3389 sample.accountShares->amount = 0;
3390 }));
3391 },
3392 XRPAmount{},
3393 STTx{
3394 ttVAULT_CLAWBACK,
3395 [&](STObject& tx) {
3396 tx[sfAccount] = A3.id();
3397 tx[sfHolder] = A4.id();
3398 }},
3400 precloseMpt);
3401
3403 {"clawback must change holder and vault shares by equal amount",
3404 "clawback and assets outstanding must add up",
3405 "clawback and assets available must add up"},
3406 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3407 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3408 return adjust(
3409 ac.view(),
3410 keylet,
3411 args(A4.id(), -10, [&](Adjustements& sample) {
3412 sample.accountShares->amount = -8;
3413 sample.assetsTotal = -7;
3414 sample.assetsAvailable = -7;
3415 }));
3416 },
3417 XRPAmount{},
3418 STTx{
3419 ttVAULT_CLAWBACK,
3420 [&](STObject& tx) {
3421 tx[sfAccount] = A3.id();
3422 tx[sfHolder] = A4.id();
3423 }},
3425 precloseMpt);
3426 }
3427
3428public:
3429 void
3450};
3451
3452BEAST_DEFINE_TESTSUITE(Invariants, app, ripple);
3453
3454} // namespace test
3455} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
A generic endpoint for log messages.
Definition Journal.h:60
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:533
State information when applying a tx.
ApplyView & view()
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:143
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:319
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
SOTemplate const * findSOTemplateBySField(SField const &sField) const
static InnerObjectFormats const & getInstance()
A currency issued by an account.
Definition Issue.h:33
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:118
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:113
AccountID const & getIssuer() const
Definition STAmount.h:508
void push_back(STObject const &object)
Definition STArray.h:212
iterator begin()
Definition STArray.h:224
void setFieldH256(SField const &field, uint256 const &)
Definition STObject.cpp:765
void setFieldAmount(SField const &field, STAmount const &)
Definition STObject.cpp:801
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:95
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:783
An immutable linear range of bytes.
Definition Slice.h:46
void run() override
Runs the suite.
void createPermissionedDomain(ApplyContext &ac, std::shared_ptr< SLE > &sle, test::jtx::Account const &A1, test::jtx::Account const &A2)
void doInvariantCheck(std::vector< std::string > const &expect_logs, Precheck const &precheck, XRPAmount fee=XRPAmount{}, STTx tx=STTx{ttACCOUNT_SET, [](STObject &) {}}, std::initializer_list< TER > ters={tecINVARIANT_FAILED, tefINVARIANT_FAILED}, Preclose const &preclose={}, TxAccount setTxAccount=TxAccount::None)
TxAccount
Run a specific test case to put the ledger into a state that will be detected by an invariant.
std::function< bool(test::jtx::Account const &a, test::jtx::Account const &b, test::jtx::Env &env)> Preclose
Convenience class to test AMM functionality.
Definition AMM.h:124
Immutable cryptographic account descriptor.
Definition Account.h:39
AccountID id() const
Returns the Account ID.
Definition Account.h:111
A transaction testing environment.
Definition Env.h:121
Set the fee on a JTx.
Definition fee.h:37
T invoke(T... args)
T is_same_v
T iter_swap(T... args)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:540
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:570
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:446
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:244
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:214
Keylet nftpage(Keylet const &k, uint256 const &token)
Definition Indexes.cpp:419
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:526
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:564
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition Indexes.cpp:389
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:403
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:411
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:274
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:34
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:32
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
FeatureBitset testable_amendments()
Definition Env.h:74
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:48
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:115
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:120
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:152
base_uint< 256 > uint256
Definition base_uint.h:558
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:110
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:116
base_uint< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:64
@ lsfHighDeepFreeze
@ lsfRequireDestTag
@ lsfDefaultRipple
@ lsfDisableMaster
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1050
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition Indexes.h:384
@ tefINVARIANT_FAILED
Definition TER.h:183
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:56
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1597
@ tecINVARIANT_FAILED
Definition TER.h:313
@ tesSUCCESS
Definition TER.h:244
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1069
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
LedgerEntryType
Identifiers for on-ledger objects.
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:122
@ tapNONE
Definition ApplyView.h:31
base_uint< 160 > uint160
Definition base_uint.h:557
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:170
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1092
TERSubset< CanCvtToTER > TER
Definition TER.h:645
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:118
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:39
uint256 key
Definition Keylet.h:40
T to_string(T... args)