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 Adjustments
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 Adjustments 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 {
1794 (*sleShares)[sfOutstandingAmount] =
1795 *(*sleShares)[sfOutstandingAmount] + *args.sharesTotal;
1796 ac.update(sleShares);
1797 }
1798
1799 auto const assets = *(*sleVault)[sfAsset];
1800 auto const pseudoId = *(*sleVault)[sfAccount];
1801 if (args.vaultAssets)
1802 {
1803 if (assets.native())
1804 {
1805 auto slePseudoAccount = ac.peek(keylet::account(pseudoId));
1806 if (!slePseudoAccount)
1807 return false;
1808 (*slePseudoAccount)[sfBalance] =
1809 *(*slePseudoAccount)[sfBalance] + *args.vaultAssets;
1810 ac.update(slePseudoAccount);
1811 }
1812 else if (assets.holds<MPTIssue>())
1813 {
1814 auto const mptId = assets.get<MPTIssue>().getMptID();
1815 auto sleMPToken = ac.peek(keylet::mptoken(mptId, pseudoId));
1816 if (!sleMPToken)
1817 return false;
1818 (*sleMPToken)[sfMPTAmount] =
1819 *(*sleMPToken)[sfMPTAmount] + *args.vaultAssets;
1820 ac.update(sleMPToken);
1821 }
1822 else
1823 return false; // Not supporting testing with IOU
1824 }
1825
1826 if (args.accountAssets)
1827 {
1828 auto const& pair = *args.accountAssets;
1829 if (assets.native())
1830 {
1831 auto sleAccount = ac.peek(keylet::account(pair.account));
1832 if (!sleAccount)
1833 return false;
1834 (*sleAccount)[sfBalance] =
1835 *(*sleAccount)[sfBalance] + pair.amount;
1836 ac.update(sleAccount);
1837 }
1838 else if (assets.holds<MPTIssue>())
1839 {
1840 auto const mptID = assets.get<MPTIssue>().getMptID();
1841 auto sleMPToken =
1842 ac.peek(keylet::mptoken(mptID, pair.account));
1843 if (!sleMPToken)
1844 return false;
1845 (*sleMPToken)[sfMPTAmount] =
1846 *(*sleMPToken)[sfMPTAmount] + pair.amount;
1847 ac.update(sleMPToken);
1848 }
1849 else
1850 return false; // Not supporting testing with IOU
1851 }
1852
1853 if (args.accountShares)
1854 {
1855 auto const& pair = *args.accountShares;
1856 auto sleMPToken =
1857 ac.peek(keylet::mptoken(mptIssuanceID, pair.account));
1858 if (!sleMPToken)
1859 return false;
1860 (*sleMPToken)[sfMPTAmount] =
1861 *(*sleMPToken)[sfMPTAmount] + pair.amount;
1862 ac.update(sleMPToken);
1863 }
1864 return true;
1865 };
1866
1867 constexpr auto args =
1868 [](AccountID id, int adjustment, auto fn) -> Adjustments {
1869 Adjustments sample = {
1870 .assetsTotal = adjustment,
1871 .assetsAvailable = adjustment,
1872 .lossUnrealized = 0,
1873 .sharesTotal = adjustment,
1874 .vaultAssets = adjustment,
1875 .accountAssets = //
1876 AccountAmount{id, -adjustment},
1877 .accountShares = //
1878 AccountAmount{id, adjustment}};
1879 fn(sample);
1880 return sample;
1881 };
1882
1883 Account A3{"A3"};
1884 Account A4{"A4"};
1885 auto const precloseXrp =
1886 [&](Account const& A1, Account const& A2, Env& env) -> bool {
1887 env.fund(XRP(1000), A3, A4);
1888 Vault vault{env};
1889 auto [tx, keylet] =
1890 vault.create({.owner = A1, .asset = xrpIssue()});
1891 env(tx);
1892 env(vault.deposit(
1893 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
1894 env(vault.deposit(
1895 {.depositor = A2, .id = keylet.key, .amount = XRP(10)}));
1896 env(vault.deposit(
1897 {.depositor = A3, .id = keylet.key, .amount = XRP(10)}));
1898 return true;
1899 };
1900
1901 testcase << "Vault general checks";
1903 {"vault deletion succeeded without deleting a vault"},
1904 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1905 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1906 auto sleVault = ac.view().peek(keylet);
1907 if (!sleVault)
1908 return false;
1909 ac.view().update(sleVault);
1910 return true;
1911 },
1912 XRPAmount{},
1913 STTx{ttVAULT_DELETE, [](STObject&) {}},
1915 [&](Account const& A1, Account const& A2, Env& env) {
1916 Vault vault{env};
1917 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1918 env(tx);
1919 return true;
1920 });
1921
1923 {"vault updated by a wrong transaction type"},
1924 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1925 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1926 auto sleVault = ac.view().peek(keylet);
1927 if (!sleVault)
1928 return false;
1929 ac.view().erase(sleVault);
1930 return true;
1931 },
1932 XRPAmount{},
1933 STTx{ttPAYMENT, [](STObject&) {}},
1935 [&](Account const& A1, Account const& A2, Env& env) {
1936 Vault vault{env};
1937 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1938 env(tx);
1939 return true;
1940 });
1941
1943 {"vault updated by a wrong transaction type"},
1944 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1945 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1946 auto sleVault = ac.view().peek(keylet);
1947 if (!sleVault)
1948 return false;
1949 ac.view().update(sleVault);
1950 return true;
1951 },
1952 XRPAmount{},
1953 STTx{ttPAYMENT, [](STObject&) {}},
1955 [&](Account const& A1, Account const& A2, Env& env) {
1956 Vault vault{env};
1957 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1958 env(tx);
1959 return true;
1960 });
1961
1963 {"vault updated by a wrong transaction type"},
1964 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1965 auto const sequence = ac.view().seq();
1966 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
1967 auto sleVault = std::make_shared<SLE>(vaultKeylet);
1968 auto const vaultPage = ac.view().dirInsert(
1969 keylet::ownerDir(A1.id()),
1970 sleVault->key(),
1971 describeOwnerDir(A1.id()));
1972 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
1973 ac.view().insert(sleVault);
1974 return true;
1975 },
1976 XRPAmount{},
1977 STTx{ttPAYMENT, [](STObject&) {}},
1979
1981 {"vault deleted by a wrong transaction type"},
1982 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1983 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1984 auto sleVault = ac.view().peek(keylet);
1985 if (!sleVault)
1986 return false;
1987 ac.view().erase(sleVault);
1988 return true;
1989 },
1990 XRPAmount{},
1991 STTx{ttVAULT_SET, [](STObject&) {}},
1993 [&](Account const& A1, Account const& A2, Env& env) {
1994 Vault vault{env};
1995 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1996 env(tx);
1997 return true;
1998 });
1999
2001 {"vault operation updated more than single vault"},
2002 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2003 {
2004 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2005 auto sleVault = ac.view().peek(keylet);
2006 if (!sleVault)
2007 return false;
2008 ac.view().erase(sleVault);
2009 }
2010 {
2011 auto const keylet = keylet::vault(A2.id(), ac.view().seq());
2012 auto sleVault = ac.view().peek(keylet);
2013 if (!sleVault)
2014 return false;
2015 ac.view().erase(sleVault);
2016 }
2017 return true;
2018 },
2019 XRPAmount{},
2020 STTx{ttVAULT_DELETE, [](STObject&) {}},
2022 [&](Account const& A1, Account const& A2, Env& env) {
2023 Vault vault{env};
2024 {
2025 auto [tx, _] =
2026 vault.create({.owner = A1, .asset = xrpIssue()});
2027 env(tx);
2028 }
2029 {
2030 auto [tx, _] =
2031 vault.create({.owner = A2, .asset = xrpIssue()});
2032 env(tx);
2033 }
2034 return true;
2035 });
2036
2038 {"vault operation updated more than single vault"},
2039 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2040 auto const sequence = ac.view().seq();
2041 auto const insertVault = [&](Account const A) {
2042 auto const vaultKeylet = keylet::vault(A.id(), sequence);
2043 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2044 auto const vaultPage = ac.view().dirInsert(
2045 keylet::ownerDir(A.id()),
2046 sleVault->key(),
2047 describeOwnerDir(A.id()));
2048 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2049 ac.view().insert(sleVault);
2050 };
2051 insertVault(A1);
2052 insertVault(A2);
2053 return true;
2054 },
2055 XRPAmount{},
2056 STTx{ttVAULT_CREATE, [](STObject&) {}},
2058
2060 {"deleted vault must also delete shares"},
2061 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2062 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2063 auto sleVault = ac.view().peek(keylet);
2064 if (!sleVault)
2065 return false;
2066 ac.view().erase(sleVault);
2067 return true;
2068 },
2069 XRPAmount{},
2070 STTx{ttVAULT_DELETE, [](STObject&) {}},
2072 [&](Account const& A1, Account const& A2, Env& env) {
2073 Vault vault{env};
2074 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2075 env(tx);
2076 return true;
2077 });
2078
2080 {"deleted vault must have no shares outstanding",
2081 "deleted vault must have no assets outstanding",
2082 "deleted vault must have no assets available"},
2083 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2084 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2085 auto sleVault = ac.view().peek(keylet);
2086 if (!sleVault)
2087 return false;
2088 auto sleShares = ac.view().peek(
2089 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2090 if (!sleShares)
2091 return false;
2092 ac.view().erase(sleVault);
2093 ac.view().erase(sleShares);
2094 return true;
2095 },
2096 XRPAmount{},
2097 STTx{ttVAULT_DELETE, [](STObject&) {}},
2099 [&](Account const& A1, Account const& A2, Env& env) {
2100 Vault vault{env};
2101 auto [tx, keylet] =
2102 vault.create({.owner = A1, .asset = xrpIssue()});
2103 env(tx);
2104 env(vault.deposit(
2105 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2106 return true;
2107 });
2108
2110 {"vault operation succeeded without modifying a vault"},
2111 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2112 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2113 auto sleVault = ac.view().peek(keylet);
2114 if (!sleVault)
2115 return false;
2116 auto sleShares = ac.view().peek(
2117 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2118 if (!sleShares)
2119 return false;
2120 // Note, such an "orphaned" update of MPT issuance attached to a
2121 // vault is invalid; ttVAULT_SET must also update Vault object.
2122 sleShares->setFieldH256(sfDomainID, uint256(13));
2123 ac.view().update(sleShares);
2124 return true;
2125 },
2126 XRPAmount{},
2127 STTx{ttVAULT_SET, [](STObject& tx) {}},
2129 precloseXrp,
2131
2133 {"vault operation succeeded without modifying a vault"},
2134 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2135 return true;
2136 },
2137 XRPAmount{},
2138 STTx{ttVAULT_CREATE, [](STObject&) {}},
2140 [&](Account const& A1, Account const& A2, Env& env) {
2141 Vault vault{env};
2142 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2143 env(tx);
2144 return true;
2145 });
2146
2148 {"vault operation succeeded without modifying a vault"},
2149 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2150 return true;
2151 },
2152 XRPAmount{},
2153 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2155 [&](Account const& A1, Account const& A2, Env& env) {
2156 Vault vault{env};
2157 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2158 env(tx);
2159 return true;
2160 });
2161
2163 {"vault operation succeeded without modifying a vault"},
2164 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2165 return true;
2166 },
2167 XRPAmount{},
2168 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2170 [&](Account const& A1, Account const& A2, Env& env) {
2171 Vault vault{env};
2172 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2173 env(tx);
2174 return true;
2175 });
2176
2178 {"vault operation succeeded without modifying a vault"},
2179 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2180 return true;
2181 },
2182 XRPAmount{},
2183 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
2185 [&](Account const& A1, Account const& A2, Env& env) {
2186 Vault vault{env};
2187 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2188 env(tx);
2189 return true;
2190 });
2191
2193 {"vault operation succeeded without modifying a vault"},
2194 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2195 return true;
2196 },
2197 XRPAmount{},
2198 STTx{ttVAULT_DELETE, [](STObject&) {}},
2200 [&](Account const& A1, Account const& A2, Env& env) {
2201 Vault vault{env};
2202 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2203 env(tx);
2204 return true;
2205 });
2206
2208 {"updated vault must have shares"},
2209 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2210 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2211 auto sleVault = ac.view().peek(keylet);
2212 if (!sleVault)
2213 return false;
2214 (*sleVault)[sfAssetsMaximum] = 200;
2215 ac.view().update(sleVault);
2216
2217 auto sleShares = ac.view().peek(
2218 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2219 if (!sleShares)
2220 return false;
2221 ac.view().erase(sleShares);
2222 return true;
2223 },
2224 XRPAmount{},
2225 STTx{ttVAULT_SET, [](STObject&) {}},
2227 [&](Account const& A1, Account const& A2, Env& env) {
2228 Vault vault{env};
2229 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2230 env(tx);
2231 return true;
2232 });
2233
2235 {"vault operation succeeded without updating shares",
2236 "assets available must not be greater than assets outstanding"},
2237 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2238 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2239 auto sleVault = ac.view().peek(keylet);
2240 if (!sleVault)
2241 return false;
2242 (*sleVault)[sfAssetsTotal] = 9;
2243 ac.view().update(sleVault);
2244 return true;
2245 },
2246 XRPAmount{},
2247 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2249 [&](Account const& A1, Account const& A2, Env& env) {
2250 Vault vault{env};
2251 auto [tx, keylet] =
2252 vault.create({.owner = A1, .asset = xrpIssue()});
2253 env(tx);
2254 env(vault.deposit(
2255 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2256 return true;
2257 });
2258
2260 {"set must not change assets outstanding",
2261 "set must not change assets available",
2262 "set must not change shares outstanding",
2263 "set must not change vault balance",
2264 "assets available must be positive",
2265 "assets available must not be greater than assets outstanding",
2266 "assets outstanding must be positive"},
2267 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2268 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2269 auto sleVault = ac.view().peek(keylet);
2270 if (!sleVault)
2271 return false;
2272 auto slePseudoAccount =
2273 ac.view().peek(keylet::account(*(*sleVault)[sfAccount]));
2274 if (!slePseudoAccount)
2275 return false;
2276 (*slePseudoAccount)[sfBalance] =
2277 *(*slePseudoAccount)[sfBalance] - 10;
2278 ac.view().update(slePseudoAccount);
2279
2280 // Move 10 drops to A4 to enforce total XRP balance
2281 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
2282 if (!sleA4)
2283 return false;
2284 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2285 ac.view().update(sleA4);
2286
2287 return adjust(
2288 ac.view(),
2289 keylet,
2290 args(A2.id(), 0, [&](Adjustments& sample) {
2291 sample.assetsAvailable = (DROPS_PER_XRP * -100).value();
2292 sample.assetsTotal = (DROPS_PER_XRP * -200).value();
2293 sample.sharesTotal = -1;
2294 }));
2295 },
2296 XRPAmount{},
2297 STTx{ttVAULT_SET, [](STObject& tx) {}},
2299 precloseXrp,
2301
2303 {"violation of vault immutable data"},
2304 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2305 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2306 auto sleVault = ac.view().peek(keylet);
2307 if (!sleVault)
2308 return false;
2309 sleVault->setFieldIssue(
2310 sfAsset, STIssue{sfAsset, MPTIssue(MPTID(42))});
2311 ac.view().update(sleVault);
2312 return true;
2313 },
2314 XRPAmount{},
2315 STTx{ttVAULT_SET, [](STObject& tx) {}},
2317 precloseXrp);
2318
2320 {"violation of vault immutable data"},
2321 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2322 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2323 auto sleVault = ac.view().peek(keylet);
2324 if (!sleVault)
2325 return false;
2326 sleVault->setAccountID(sfAccount, A2.id());
2327 ac.view().update(sleVault);
2328 return true;
2329 },
2330 XRPAmount{},
2331 STTx{ttVAULT_SET, [](STObject& tx) {}},
2333 precloseXrp);
2334
2336 {"violation of vault immutable data"},
2337 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2338 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2339 auto sleVault = ac.view().peek(keylet);
2340 if (!sleVault)
2341 return false;
2342 (*sleVault)[sfShareMPTID] = MPTID(42);
2343 ac.view().update(sleVault);
2344 return true;
2345 },
2346 XRPAmount{},
2347 STTx{ttVAULT_SET, [](STObject& tx) {}},
2349 precloseXrp);
2350
2352 {"vault transaction must not change loss unrealized",
2353 "set must not change assets outstanding"},
2354 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2355 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2356 return adjust(
2357 ac.view(),
2358 keylet,
2359 args(A2.id(), 0, [&](Adjustments& sample) {
2360 sample.lossUnrealized = 13;
2361 sample.assetsTotal = 20;
2362 }));
2363 },
2364 XRPAmount{},
2365 STTx{ttVAULT_SET, [](STObject& tx) {}},
2367 precloseXrp,
2369
2371 {"loss unrealized must not exceed the difference "
2372 "between assets outstanding and available",
2373 "vault transaction must not change loss unrealized"},
2374 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2375 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2376 return adjust(
2377 ac.view(),
2378 keylet,
2379 args(A2.id(), 100, [&](Adjustments& sample) {
2380 sample.lossUnrealized = 13;
2381 }));
2382 },
2383 XRPAmount{},
2384 STTx{
2385 ttVAULT_DEPOSIT,
2386 [](STObject& tx) {
2387 tx.setFieldAmount(sfAmount, XRPAmount(200));
2388 }},
2390 precloseXrp,
2392
2394 {"set assets outstanding must not exceed assets maximum"},
2395 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2396 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2397 return adjust(
2398 ac.view(),
2399 keylet,
2400 args(A2.id(), 0, [&](Adjustments& sample) {
2401 sample.assetsMaximum = 1;
2402 }));
2403 },
2404 XRPAmount{},
2405 STTx{ttVAULT_SET, [](STObject& tx) {}},
2407 precloseXrp,
2409
2411 {"assets maximum must be positive"},
2412 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2413 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2414 return adjust(
2415 ac.view(),
2416 keylet,
2417 args(A2.id(), 0, [&](Adjustments& sample) {
2418 sample.assetsMaximum = -1;
2419 }));
2420 },
2421 XRPAmount{},
2422 STTx{ttVAULT_SET, [](STObject& tx) {}},
2424 precloseXrp,
2426
2428 {"set must not change shares outstanding",
2429 "updated zero sized vault must have no assets outstanding",
2430 "updated zero sized vault must have no assets available"},
2431 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2432 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2433 auto sleVault = ac.view().peek(keylet);
2434 if (!sleVault)
2435 return false;
2436 ac.view().update(sleVault);
2437 auto sleShares = ac.view().peek(
2438 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2439 if (!sleShares)
2440 return false;
2441 (*sleShares)[sfOutstandingAmount] = 0;
2442 ac.view().update(sleShares);
2443 return true;
2444 },
2445 XRPAmount{},
2446 STTx{ttVAULT_SET, [](STObject& tx) {}},
2448 precloseXrp,
2450
2452 {"updated shares must not exceed maximum"},
2453 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2454 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2455 auto sleVault = ac.view().peek(keylet);
2456 if (!sleVault)
2457 return false;
2458 auto sleShares = ac.view().peek(
2459 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2460 if (!sleShares)
2461 return false;
2462 (*sleShares)[sfMaximumAmount] = 10;
2463 ac.view().update(sleShares);
2464
2465 return adjust(
2466 ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {}));
2467 },
2468 XRPAmount{},
2469 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2471 precloseXrp,
2473
2475 {"updated shares must not exceed maximum"},
2476 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2477 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2478 adjust(
2479 ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {}));
2480
2481 auto sleVault = ac.view().peek(keylet);
2482 if (!sleVault)
2483 return false;
2484 auto sleShares = ac.view().peek(
2485 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2486 if (!sleShares)
2487 return false;
2488 (*sleShares)[sfOutstandingAmount] = maxMPTokenAmount + 1;
2489 ac.view().update(sleShares);
2490 return true;
2491 },
2492 XRPAmount{},
2493 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2495 precloseXrp,
2497
2498 testcase << "Vault create";
2500 {
2501 "created vault must be empty",
2502 "updated zero sized vault must have no assets outstanding",
2503 "create operation must not have updated a vault",
2504 },
2505 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2506 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2507 auto sleVault = ac.view().peek(keylet);
2508 if (!sleVault)
2509 return false;
2510 (*sleVault)[sfAssetsTotal] = 9;
2511 ac.view().update(sleVault);
2512 return true;
2513 },
2514 XRPAmount{},
2515 STTx{ttVAULT_CREATE, [](STObject&) {}},
2517 [&](Account const& A1, Account const& A2, Env& env) {
2518 Vault vault{env};
2519 auto [tx, keylet] =
2520 vault.create({.owner = A1, .asset = xrpIssue()});
2521 env(tx);
2522 return true;
2523 });
2524
2526 {
2527 "created vault must be empty",
2528 "updated zero sized vault must have no assets available",
2529 "assets available must not be greater than assets outstanding",
2530 "create operation must not have updated a vault",
2531 },
2532 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2533 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2534 auto sleVault = ac.view().peek(keylet);
2535 if (!sleVault)
2536 return false;
2537 (*sleVault)[sfAssetsAvailable] = 9;
2538 ac.view().update(sleVault);
2539 return true;
2540 },
2541 XRPAmount{},
2542 STTx{ttVAULT_CREATE, [](STObject&) {}},
2544 [&](Account const& A1, Account const& A2, Env& env) {
2545 Vault vault{env};
2546 auto [tx, keylet] =
2547 vault.create({.owner = A1, .asset = xrpIssue()});
2548 env(tx);
2549 return true;
2550 });
2551
2553 {
2554 "created vault must be empty",
2555 "loss unrealized must not exceed the difference between assets "
2556 "outstanding and available",
2557 "vault transaction must not change loss unrealized",
2558 "create operation must not have updated a vault",
2559 },
2560 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2561 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2562 auto sleVault = ac.view().peek(keylet);
2563 if (!sleVault)
2564 return false;
2565 (*sleVault)[sfLossUnrealized] = 1;
2566 ac.view().update(sleVault);
2567 return true;
2568 },
2569 XRPAmount{},
2570 STTx{ttVAULT_CREATE, [](STObject&) {}},
2572 [&](Account const& A1, Account const& A2, Env& env) {
2573 Vault vault{env};
2574 auto [tx, keylet] =
2575 vault.create({.owner = A1, .asset = xrpIssue()});
2576 env(tx);
2577 return true;
2578 });
2579
2581 {
2582 "created vault must be empty",
2583 "create operation must not have updated a vault",
2584 },
2585 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2586 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2587 auto sleVault = ac.view().peek(keylet);
2588 if (!sleVault)
2589 return false;
2590 auto sleShares = ac.view().peek(
2591 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2592 if (!sleShares)
2593 return false;
2594 ac.view().update(sleVault);
2595 (*sleShares)[sfOutstandingAmount] = 9;
2596 ac.view().update(sleShares);
2597 return true;
2598 },
2599 XRPAmount{},
2600 STTx{ttVAULT_CREATE, [](STObject&) {}},
2602 [&](Account const& A1, Account const& A2, Env& env) {
2603 Vault vault{env};
2604 auto [tx, keylet] =
2605 vault.create({.owner = A1, .asset = xrpIssue()});
2606 env(tx);
2607 return true;
2608 });
2609
2611 {
2612 "assets maximum must be positive",
2613 "create operation must not have updated a vault",
2614 },
2615 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2616 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2617 auto sleVault = ac.view().peek(keylet);
2618 if (!sleVault)
2619 return false;
2620 (*sleVault)[sfAssetsMaximum] = Number(-1);
2621 ac.view().update(sleVault);
2622 return true;
2623 },
2624 XRPAmount{},
2625 STTx{ttVAULT_CREATE, [](STObject&) {}},
2627 [&](Account const& A1, Account const& A2, Env& env) {
2628 Vault vault{env};
2629 auto [tx, keylet] =
2630 vault.create({.owner = A1, .asset = xrpIssue()});
2631 env(tx);
2632 return true;
2633 });
2634
2636 {"create operation must not have updated a vault",
2637 "shares issuer and vault pseudo-account must be the same",
2638 "shares issuer must be a pseudo-account",
2639 "shares issuer pseudo-account must point back to the vault"},
2640 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2641 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2642 auto sleVault = ac.view().peek(keylet);
2643 if (!sleVault)
2644 return false;
2645 auto sleShares = ac.view().peek(
2646 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2647 if (!sleShares)
2648 return false;
2649 ac.view().update(sleVault);
2650 (*sleShares)[sfIssuer] = A1.id();
2651 ac.view().update(sleShares);
2652 return true;
2653 },
2654 XRPAmount{},
2655 STTx{ttVAULT_CREATE, [](STObject&) {}},
2657 [&](Account const& A1, Account const& A2, Env& env) {
2658 Vault vault{env};
2659 auto [tx, keylet] =
2660 vault.create({.owner = A1, .asset = xrpIssue()});
2661 env(tx);
2662 return true;
2663 });
2664
2666 {"vault created by a wrong transaction type",
2667 "account root created illegally"},
2668 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2669 // The code below will create a valid vault with (almost) all
2670 // the invariants holding. Except one: it is created by the
2671 // wrong transaction type.
2672 auto const sequence = ac.view().seq();
2673 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2674 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2675 auto const vaultPage = ac.view().dirInsert(
2676 keylet::ownerDir(A1.id()),
2677 sleVault->key(),
2678 describeOwnerDir(A1.id()));
2679 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2680
2681 auto pseudoId =
2682 pseudoAccountAddress(ac.view(), vaultKeylet.key);
2683 // Create pseudo-account.
2684 auto sleAccount =
2686 sleAccount->setAccountID(sfAccount, pseudoId);
2687 sleAccount->setFieldAmount(sfBalance, STAmount{});
2688 std::uint32_t const seqno = //
2689 ac.view().rules().enabled(featureSingleAssetVault) //
2690 ? 0 //
2691 : sequence;
2692 sleAccount->setFieldU32(sfSequence, seqno);
2693 sleAccount->setFieldU32(
2694 sfFlags,
2696 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
2697 ac.view().insert(sleAccount);
2698
2699 auto const sharesMptId = makeMptID(sequence, pseudoId);
2700 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2701 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2702 auto const sharesPage = ac.view().dirInsert(
2703 keylet::ownerDir(pseudoId),
2704 sharesKeylet,
2705 describeOwnerDir(pseudoId));
2706 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2707
2708 sleShares->at(sfFlags) = 0;
2709 sleShares->at(sfIssuer) = pseudoId;
2710 sleShares->at(sfOutstandingAmount) = 0;
2711 sleShares->at(sfSequence) = sequence;
2712
2713 sleVault->at(sfAccount) = pseudoId;
2714 sleVault->at(sfFlags) = 0;
2715 sleVault->at(sfSequence) = sequence;
2716 sleVault->at(sfOwner) = A1.id();
2717 sleVault->at(sfAssetsTotal) = Number(0);
2718 sleVault->at(sfAssetsAvailable) = Number(0);
2719 sleVault->at(sfLossUnrealized) = Number(0);
2720 sleVault->at(sfShareMPTID) = sharesMptId;
2721 sleVault->at(sfWithdrawalPolicy) =
2723
2724 ac.view().insert(sleVault);
2725 ac.view().insert(sleShares);
2726 return true;
2727 },
2728 XRPAmount{},
2729 STTx{ttVAULT_SET, [](STObject&) {}},
2731
2733 {"shares issuer and vault pseudo-account must be the same",
2734 "shares issuer pseudo-account must point back to the vault"},
2735 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2736 auto const sequence = ac.view().seq();
2737 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2738 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2739 auto const vaultPage = ac.view().dirInsert(
2740 keylet::ownerDir(A1.id()),
2741 sleVault->key(),
2742 describeOwnerDir(A1.id()));
2743 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2744
2745 auto pseudoId =
2746 pseudoAccountAddress(ac.view(), vaultKeylet.key);
2747 // Create pseudo-account.
2748 auto sleAccount =
2750 sleAccount->setAccountID(sfAccount, pseudoId);
2751 sleAccount->setFieldAmount(sfBalance, STAmount{});
2752 std::uint32_t const seqno = //
2753 ac.view().rules().enabled(featureSingleAssetVault) //
2754 ? 0 //
2755 : sequence;
2756 sleAccount->setFieldU32(sfSequence, seqno);
2757 sleAccount->setFieldU32(
2758 sfFlags,
2760 // sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
2761 // Setting wrong vault key
2762 sleAccount->setFieldH256(sfVaultID, uint256(42));
2763 ac.view().insert(sleAccount);
2764
2765 auto const sharesMptId = makeMptID(sequence, pseudoId);
2766 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2767 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2768 auto const sharesPage = ac.view().dirInsert(
2769 keylet::ownerDir(pseudoId),
2770 sharesKeylet,
2771 describeOwnerDir(pseudoId));
2772 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2773
2774 sleShares->at(sfFlags) = 0;
2775 sleShares->at(sfIssuer) = pseudoId;
2776 sleShares->at(sfOutstandingAmount) = 0;
2777 sleShares->at(sfSequence) = sequence;
2778
2779 // sleVault->at(sfAccount) = pseudoId;
2780 // Setting wrong pseudo acocunt ID
2781 sleVault->at(sfAccount) = A2.id();
2782 sleVault->at(sfFlags) = 0;
2783 sleVault->at(sfSequence) = sequence;
2784 sleVault->at(sfOwner) = A1.id();
2785 sleVault->at(sfAssetsTotal) = Number(0);
2786 sleVault->at(sfAssetsAvailable) = Number(0);
2787 sleVault->at(sfLossUnrealized) = Number(0);
2788 sleVault->at(sfShareMPTID) = sharesMptId;
2789 sleVault->at(sfWithdrawalPolicy) =
2791
2792 ac.view().insert(sleVault);
2793 ac.view().insert(sleShares);
2794 return true;
2795 },
2796 XRPAmount{},
2797 STTx{ttVAULT_CREATE, [](STObject&) {}},
2799
2801 {"shares issuer and vault pseudo-account must be the same",
2802 "shares issuer must exist"},
2803 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2804 auto const sequence = ac.view().seq();
2805 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2806 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2807 auto const vaultPage = ac.view().dirInsert(
2808 keylet::ownerDir(A1.id()),
2809 sleVault->key(),
2810 describeOwnerDir(A1.id()));
2811 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2812
2813 auto const sharesMptId = makeMptID(sequence, A2.id());
2814 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2815 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2816 auto const sharesPage = ac.view().dirInsert(
2817 keylet::ownerDir(A2.id()),
2818 sharesKeylet,
2819 describeOwnerDir(A2.id()));
2820 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2821
2822 sleShares->at(sfFlags) = 0;
2823 // Setting wrong pseudo acocunt ID
2824 sleShares->at(sfIssuer) = AccountID(uint160(42));
2825 sleShares->at(sfOutstandingAmount) = 0;
2826 sleShares->at(sfSequence) = sequence;
2827
2828 sleVault->at(sfAccount) = A2.id();
2829 sleVault->at(sfFlags) = 0;
2830 sleVault->at(sfSequence) = sequence;
2831 sleVault->at(sfOwner) = A1.id();
2832 sleVault->at(sfAssetsTotal) = Number(0);
2833 sleVault->at(sfAssetsAvailable) = Number(0);
2834 sleVault->at(sfLossUnrealized) = Number(0);
2835 sleVault->at(sfShareMPTID) = sharesMptId;
2836 sleVault->at(sfWithdrawalPolicy) =
2838
2839 ac.view().insert(sleVault);
2840 ac.view().insert(sleShares);
2841 return true;
2842 },
2843 XRPAmount{},
2844 STTx{ttVAULT_CREATE, [](STObject&) {}},
2846
2847 testcase << "Vault deposit";
2849 {"deposit must change vault balance"},
2850 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2851 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2852 return adjust(
2853 ac.view(),
2854 keylet,
2855 args(A2.id(), 0, [](Adjustments& sample) {
2856 sample.vaultAssets.reset();
2857 }));
2858 },
2859 XRPAmount{},
2860 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2862 precloseXrp);
2863
2865 {"deposit assets outstanding must not exceed assets maximum"},
2866 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2867 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2868 return adjust(
2869 ac.view(),
2870 keylet,
2871 args(A2.id(), 200, [&](Adjustments& sample) {
2872 sample.assetsMaximum = 1;
2873 }));
2874 },
2875 XRPAmount{},
2876 STTx{
2877 ttVAULT_DEPOSIT,
2878 [](STObject& tx) {
2879 tx.setFieldAmount(sfAmount, XRPAmount(200));
2880 }},
2882 precloseXrp,
2884
2885 // This really convoluted unit tests makes the zero balance on the
2886 // depositor, by sending them the same amount as the transaction fee.
2887 // The operation makes no sense, but the defensive check in
2888 // ValidVault::finalize is otherwise impossible to trigger.
2890 {"deposit must increase vault balance",
2891 "deposit must change depositor balance"},
2892 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2893 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2894
2895 // Move 10 drops to A4 to enforce total XRP balance
2896 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
2897 if (!sleA4)
2898 return false;
2899 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2900 ac.view().update(sleA4);
2901
2902 return adjust(
2903 ac.view(),
2904 keylet,
2905 args(A3.id(), -10, [&](Adjustments& sample) {
2906 sample.accountAssets->amount = -100;
2907 }));
2908 },
2909 XRPAmount{100},
2910 STTx{
2911 ttVAULT_DEPOSIT,
2912 [&](STObject& tx) {
2913 tx[sfFee] = XRPAmount(100);
2914 tx[sfAccount] = A3.id();
2915 }},
2917 precloseXrp);
2918
2920 {"deposit must increase vault balance",
2921 "deposit must decrease depositor balance",
2922 "deposit must change vault and depositor balance by equal amount",
2923 "deposit and assets outstanding must add up",
2924 "deposit and assets available must add up"},
2925 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2926 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2927
2928 // Move 10 drops from A2 to A3 to enforce total XRP balance
2929 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
2930 if (!sleA3)
2931 return false;
2932 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
2933 ac.view().update(sleA3);
2934
2935 return adjust(
2936 ac.view(),
2937 keylet,
2938 args(A2.id(), 10, [&](Adjustments& sample) {
2939 sample.vaultAssets = -20;
2940 sample.accountAssets->amount = 10;
2941 }));
2942 },
2943 XRPAmount{},
2944 STTx{
2945 ttVAULT_DEPOSIT,
2946 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2948 precloseXrp,
2950
2952 {"deposit must change depositor balance"},
2953 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2954 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2955
2956 // Move 10 drops from A3 to vault to enforce total XRP balance
2957 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
2958 if (!sleA3)
2959 return false;
2960 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 10;
2961 ac.view().update(sleA3);
2962
2963 return adjust(
2964 ac.view(),
2965 keylet,
2966 args(A2.id(), 10, [&](Adjustments& sample) {
2967 sample.accountAssets->amount = 0;
2968 }));
2969 },
2970 XRPAmount{},
2971 STTx{
2972 ttVAULT_DEPOSIT,
2973 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2975 precloseXrp,
2977
2979 {"deposit must change depositor shares"},
2980 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2981 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2982 return adjust(
2983 ac.view(),
2984 keylet,
2985 args(A2.id(), 10, [&](Adjustments& sample) {
2986 sample.accountShares.reset();
2987 }));
2988 },
2989 XRPAmount{},
2990 STTx{
2991 ttVAULT_DEPOSIT,
2992 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2994 precloseXrp,
2996
2998 {"deposit must change vault shares"},
2999 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3000 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3001
3002 return adjust(
3003 ac.view(),
3004 keylet,
3005 args(A2.id(), 10, [](Adjustments& sample) {
3006 sample.sharesTotal = 0;
3007 }));
3008 },
3009 XRPAmount{},
3010 STTx{
3011 ttVAULT_DEPOSIT,
3012 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3014 precloseXrp,
3016
3018 {"deposit must increase depositor shares",
3019 "deposit must change depositor and vault shares by equal amount",
3020 "deposit must not change vault balance by more than deposited "
3021 "amount"},
3022 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3023 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3024 return adjust(
3025 ac.view(),
3026 keylet,
3027 args(A2.id(), 10, [&](Adjustments& sample) {
3028 sample.accountShares->amount = -5;
3029 sample.sharesTotal = -10;
3030 }));
3031 },
3032 XRPAmount{},
3033 STTx{
3034 ttVAULT_DEPOSIT,
3035 [](STObject& tx) { tx[sfAmount] = XRPAmount(5); }},
3037 precloseXrp,
3039
3041 {"deposit and assets outstanding must add up"},
3042 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3043 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3044 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
3045 ac.view().update(sleA3);
3046
3047 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3048 return adjust(
3049 ac.view(),
3050 keylet,
3051 args(A2.id(), 10, [&](Adjustments& sample) {
3052 sample.assetsTotal = 11;
3053 }));
3054 },
3055 XRPAmount{2000},
3056 STTx{
3057 ttVAULT_DEPOSIT,
3058 [&](STObject& tx) {
3059 tx[sfAmount] = XRPAmount(10);
3060 tx[sfDelegate] = A3.id();
3061 tx[sfFee] = XRPAmount(2000);
3062 }},
3064 precloseXrp,
3066
3068 {"deposit and assets outstanding must add up",
3069 "deposit and assets available must add up"},
3070 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3071 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3072 return adjust(
3073 ac.view(),
3074 keylet,
3075 args(A2.id(), 10, [&](Adjustments& sample) {
3076 sample.assetsTotal = 7;
3077 sample.assetsAvailable = 7;
3078 }));
3079 },
3080 XRPAmount{},
3081 STTx{
3082 ttVAULT_DEPOSIT,
3083 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3085 precloseXrp,
3087
3088 testcase << "Vault withdrawal";
3090 {"withdrawal must change vault balance"},
3091 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3092 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3093 return adjust(
3094 ac.view(),
3095 keylet,
3096 args(A2.id(), 0, [](Adjustments& sample) {
3097 sample.vaultAssets.reset();
3098 }));
3099 },
3100 XRPAmount{},
3101 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3103 precloseXrp);
3104
3105 // Almost identical to the really convoluted test for deposit, where the
3106 // depositor spends only the transaction fee. In case of withdrawal,
3107 // this test is almost the same as normal withdrawal where the
3108 // sfDestination would have been A4, but has been omitted.
3110 {"withdrawal must change one destination balance"},
3111 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3112 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3113
3114 // Move 10 drops to A4 to enforce total XRP balance
3115 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
3116 if (!sleA4)
3117 return false;
3118 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3119 ac.view().update(sleA4);
3120
3121 return adjust(
3122 ac.view(),
3123 keylet,
3124 args(A3.id(), -10, [&](Adjustments& sample) {
3125 sample.accountAssets->amount = -100;
3126 }));
3127 },
3128 XRPAmount{100},
3129 STTx{
3130 ttVAULT_WITHDRAW,
3131 [&](STObject& tx) {
3132 tx[sfFee] = XRPAmount(100);
3133 tx[sfAccount] = A3.id();
3134 // This commented out line causes the invariant violation.
3135 // tx[sfDestination] = A4.id();
3136 }},
3138 precloseXrp);
3139
3141 {"withdrawal must change vault and destination balance by "
3142 "equal amount",
3143 "withdrawal must decrease vault balance",
3144 "withdrawal must increase destination balance",
3145 "withdrawal and assets outstanding must add up",
3146 "withdrawal and assets available must add up"},
3147 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3148 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3149
3150 // Move 10 drops from A2 to A3 to enforce total XRP balance
3151 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3152 if (!sleA3)
3153 return false;
3154 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
3155 ac.view().update(sleA3);
3156
3157 return adjust(
3158 ac.view(),
3159 keylet,
3160 args(A2.id(), -10, [&](Adjustments& sample) {
3161 sample.vaultAssets = 10;
3162 sample.accountAssets->amount = -20;
3163 }));
3164 },
3165 XRPAmount{},
3166 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3168 precloseXrp,
3170
3172 {"withdrawal must change one destination balance"},
3173 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3174 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3175 if (!adjust(
3176 ac.view(),
3177 keylet,
3178 args(A2.id(), -10, [&](Adjustments& sample) {
3179 *sample.vaultAssets -= 5;
3180 })))
3181 return false;
3182 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3183 if (!sleA3)
3184 return false;
3185 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 5;
3186 ac.view().update(sleA3);
3187 return true;
3188 },
3189 XRPAmount{},
3190 STTx{
3191 ttVAULT_WITHDRAW,
3192 [&](STObject& tx) { tx.setAccountID(sfDestination, A3.id()); }},
3194 precloseXrp,
3196
3198 {"withdrawal must change depositor shares"},
3199 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3200 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3201 return adjust(
3202 ac.view(),
3203 keylet,
3204 args(A2.id(), -10, [&](Adjustments& sample) {
3205 sample.accountShares.reset();
3206 }));
3207 },
3208 XRPAmount{},
3209 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3211 precloseXrp,
3213
3215 {"withdrawal must change vault shares"},
3216 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3217 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3218 return adjust(
3219 ac.view(),
3220 keylet,
3221 args(A2.id(), -10, [](Adjustments& sample) {
3222 sample.sharesTotal = 0;
3223 }));
3224 },
3225 XRPAmount{},
3226 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3228 precloseXrp,
3230
3232 {"withdrawal must decrease depositor shares",
3233 "withdrawal must change depositor and vault shares by equal "
3234 "amount"},
3235 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3236 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3237 return adjust(
3238 ac.view(),
3239 keylet,
3240 args(A2.id(), -10, [&](Adjustments& sample) {
3241 sample.accountShares->amount = 5;
3242 sample.sharesTotal = 10;
3243 }));
3244 },
3245 XRPAmount{},
3246 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3248 precloseXrp,
3250
3252 {"withdrawal and assets outstanding must add up",
3253 "withdrawal and assets available must add up"},
3254 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3255 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3256 return adjust(
3257 ac.view(),
3258 keylet,
3259 args(A2.id(), -10, [&](Adjustments& sample) {
3260 sample.assetsTotal = -15;
3261 sample.assetsAvailable = -15;
3262 }));
3263 },
3264 XRPAmount{},
3265 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3267 precloseXrp,
3269
3271 {"withdrawal and assets outstanding must add up"},
3272 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3273 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3274 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
3275 ac.view().update(sleA3);
3276
3277 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3278 return adjust(
3279 ac.view(),
3280 keylet,
3281 args(A2.id(), -10, [&](Adjustments& sample) {
3282 sample.assetsTotal = -7;
3283 }));
3284 },
3285 XRPAmount{2000},
3286 STTx{
3287 ttVAULT_WITHDRAW,
3288 [&](STObject& tx) {
3289 tx[sfAmount] = XRPAmount(10);
3290 tx[sfDelegate] = A3.id();
3291 tx[sfFee] = XRPAmount(2000);
3292 }},
3294 precloseXrp,
3296
3297 auto const precloseMpt =
3298 [&](Account const& A1, Account const& A2, Env& env) -> bool {
3299 env.fund(XRP(1000), A3, A4);
3300
3301 // Create MPT asset
3302 {
3303 Json::Value jv;
3304 jv[sfAccount] = A3.human();
3305 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
3306 jv[sfFlags] = tfMPTCanTransfer;
3307 env(jv);
3308 env.close();
3309 }
3310
3311 auto const mptID = makeMptID(env.seq(A3) - 1, A3);
3312 Asset asset = MPTIssue(mptID);
3313 // Authorize A1 A2 A4
3314 {
3315 Json::Value jv;
3316 jv[sfAccount] = A1.human();
3317 jv[sfTransactionType] = jss::MPTokenAuthorize;
3318 jv[sfMPTokenIssuanceID] = to_string(mptID);
3319 env(jv);
3320 jv[sfAccount] = A2.human();
3321 env(jv);
3322 jv[sfAccount] = A4.human();
3323 env(jv);
3324
3325 env.close();
3326 }
3327 // Send tokens to A1 A2 A4
3328 {
3329 env(pay(A3, A1, asset(1000)));
3330 env(pay(A3, A2, asset(1000)));
3331 env(pay(A3, A4, asset(1000)));
3332 env.close();
3333 }
3334
3335 Vault vault{env};
3336 auto [tx, keylet] = vault.create({.owner = A1, .asset = asset});
3337 env(tx);
3338 env(vault.deposit(
3339 {.depositor = A1, .id = keylet.key, .amount = asset(10)}));
3340 env(vault.deposit(
3341 {.depositor = A2, .id = keylet.key, .amount = asset(10)}));
3342 env(vault.deposit(
3343 {.depositor = A4, .id = keylet.key, .amount = asset(10)}));
3344 return true;
3345 };
3346
3348 {"withdrawal must decrease depositor shares",
3349 "withdrawal must change depositor and vault shares by equal "
3350 "amount"},
3351 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3352 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3353 return adjust(
3354 ac.view(),
3355 keylet,
3356 args(A2.id(), -10, [&](Adjustments& sample) {
3357 sample.accountShares->amount = 5;
3358 }));
3359 },
3360 XRPAmount{},
3361 STTx{
3362 ttVAULT_WITHDRAW,
3363 [&](STObject& tx) { tx[sfAccount] = A3.id(); }},
3365 precloseMpt,
3367
3368 testcase << "Vault clawback";
3370 {"clawback must change vault balance"},
3371 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3372 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3373 return adjust(
3374 ac.view(),
3375 keylet,
3376 args(A2.id(), -1, [&](Adjustments& sample) {
3377 sample.vaultAssets.reset();
3378 }));
3379 },
3380 XRPAmount{},
3381 STTx{
3382 ttVAULT_CLAWBACK,
3383 [&](STObject& tx) { tx[sfAccount] = A3.id(); }},
3385 precloseMpt);
3386
3387 // Not the same as below check: attempt to clawback XRP
3389 {"clawback may only be performed by the asset issuer"},
3390 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3391 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3392 return adjust(
3393 ac.view(),
3394 keylet,
3395 args(A2.id(), 0, [&](Adjustments& sample) {}));
3396 },
3397 XRPAmount{},
3398 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
3400 precloseXrp);
3401
3402 // Not the same as above check: attempt to clawback MPT by bad account
3404 {"clawback may only be performed by the asset issuer"},
3405 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3406 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3407 return adjust(
3408 ac.view(),
3409 keylet,
3410 args(A2.id(), 0, [&](Adjustments& sample) {}));
3411 },
3412 XRPAmount{},
3413 STTx{
3414 ttVAULT_CLAWBACK,
3415 [&](STObject& tx) { tx[sfAccount] = A4.id(); }},
3417 precloseMpt);
3418
3420 {"clawback must decrease vault balance",
3421 "clawback must decrease holder shares",
3422 "clawback must change vault shares"},
3423 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3424 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3425 return adjust(
3426 ac.view(),
3427 keylet,
3428 args(A4.id(), 10, [&](Adjustments& sample) {
3429 sample.sharesTotal = 0;
3430 }));
3431 },
3432 XRPAmount{},
3433 STTx{
3434 ttVAULT_CLAWBACK,
3435 [&](STObject& tx) {
3436 tx[sfAccount] = A3.id();
3437 tx[sfHolder] = A4.id();
3438 }},
3440 precloseMpt);
3441
3443 {"clawback must change holder shares"},
3444 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3445 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3446 return adjust(
3447 ac.view(),
3448 keylet,
3449 args(A4.id(), -10, [&](Adjustments& sample) {
3450 sample.accountShares.reset();
3451 }));
3452 },
3453 XRPAmount{},
3454 STTx{
3455 ttVAULT_CLAWBACK,
3456 [&](STObject& tx) {
3457 tx[sfAccount] = A3.id();
3458 tx[sfHolder] = A4.id();
3459 }},
3461 precloseMpt);
3462
3464 {"clawback must change holder and vault shares by equal amount",
3465 "clawback and assets outstanding must add up",
3466 "clawback and assets available must add up"},
3467 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3468 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3469 return adjust(
3470 ac.view(),
3471 keylet,
3472 args(A4.id(), -10, [&](Adjustments& sample) {
3473 sample.accountShares->amount = -8;
3474 sample.assetsTotal = -7;
3475 sample.assetsAvailable = -7;
3476 }));
3477 },
3478 XRPAmount{},
3479 STTx{
3480 ttVAULT_CLAWBACK,
3481 [&](STObject& tx) {
3482 tx[sfAccount] = A3.id();
3483 tx[sfHolder] = A4.id();
3484 }},
3486 precloseMpt);
3487 }
3488
3489public:
3490 void
3511};
3512
3513BEAST_DEFINE_TESTSUITE(Invariants, app, ripple);
3514
3515} // namespace test
3516} // 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:775
void setFieldAmount(SField const &field, STAmount const &)
Definition STObject.cpp:811
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:95
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:793
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:113
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:119
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:1607
@ tecINVARIANT_FAILED
Definition TER.h:314
@ tesSUCCESS
Definition TER.h:245
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:125
@ 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:649
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)