xrpld
Loading...
Searching...
No Matches
InvariantsEscrowNFT_test.cpp
1#include <test/app/invariants/InvariantsBase.h>
2#include <test/jtx/Account.h>
3#include <test/jtx/amount.h>
4
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/beast/unit_test/suite.h>
7#include <xrpl/ledger/ApplyView.h>
8#include <xrpl/protocol/AccountID.h>
9#include <xrpl/protocol/Indexes.h>
10#include <xrpl/protocol/InnerObjectFormats.h>
11#include <xrpl/protocol/Issue.h>
12#include <xrpl/protocol/Keylet.h>
13#include <xrpl/protocol/MPTIssue.h>
14#include <xrpl/protocol/SField.h>
15#include <xrpl/protocol/SOTemplate.h>
16#include <xrpl/protocol/STAmount.h>
17#include <xrpl/protocol/STArray.h>
18#include <xrpl/protocol/STLedgerEntry.h>
19#include <xrpl/protocol/STObject.h>
20#include <xrpl/protocol/SeqProxy.h>
21#include <xrpl/protocol/SystemParameters.h>
22#include <xrpl/protocol/UintTypes.h>
23#include <xrpl/tx/ApplyContext.h>
24
25#include <algorithm>
26#include <cstdint>
27#include <initializer_list>
28#include <limits>
29#include <memory>
30#include <utility>
31#include <vector>
32
33namespace xrpl::test {
34
36{
37 void
39 {
40 using namespace test::jtx;
41 testcase << "no zero escrow";
42
44 {{"XRP net change of -1000000 doesn't match fee 0"},
45 {"escrow specifies invalid amount"}},
46 [](Account const& a1, Account const&, ApplyContext& ac) {
47 // escrow with negative amount
48 auto const sle = ac.view().peek(keylet::account(a1.id()));
49 if (!sle)
50 return false;
51 auto sleNew = std::make_shared<SLE>(
52 keylet::escrow(a1, SeqProxy::rawSequence((*sle)[sfSequence] + 2)));
53 sleNew->setFieldAmount(sfAmount, XRP(-1));
54 ac.view().insert(sleNew);
55 return true;
56 });
57
59 {{"XRP net change was positive: 100000000000000001"},
60 {"escrow specifies invalid amount"}},
61 [](Account const& a1, Account const&, ApplyContext& ac) {
62 // escrow with too-large amount
63 auto const sle = ac.view().peek(keylet::account(a1.id()));
64 if (!sle)
65 return false;
66 auto sleNew = std::make_shared<SLE>(
67 keylet::escrow(a1, SeqProxy::rawSequence((*sle)[sfSequence] + 2)));
68 // Use `drops(1)` to bypass a call to STAmount::canonicalize
69 // with an invalid value
70 sleNew->setFieldAmount(sfAmount, kInitialXrp + drops(1));
71 ac.view().insert(sleNew);
72 return true;
73 });
74
75 // IOU < 0
77 {{"escrow specifies invalid amount"}},
78 [](Account const& a1, Account const&, ApplyContext& ac) {
79 // escrow with too-little iou
80 auto const sle = ac.view().peek(keylet::account(a1.id()));
81 if (!sle)
82 return false;
83 auto sleNew = std::make_shared<SLE>(
84 keylet::escrow(a1, SeqProxy::rawSequence((*sle)[sfSequence] + 2)));
85
86 Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
87 STAmount const amt(usd, -1);
88 sleNew->setFieldAmount(sfAmount, amt);
89 ac.view().insert(sleNew);
90 return true;
91 });
92
93 // IOU bad currency
95 {{"escrow specifies invalid amount"}},
96 [](Account const& a1, Account const&, ApplyContext& ac) {
97 // escrow with bad iou currency
98 auto const sle = ac.view().peek(keylet::account(a1.id()));
99 if (!sle)
100 return false;
101 auto sleNew = std::make_shared<SLE>(
102 keylet::escrow(a1, SeqProxy::rawSequence((*sle)[sfSequence] + 2)));
103
104 Issue const bad{badCurrency(), AccountID(0x4985601)};
105 STAmount const amt(bad, 1);
106 sleNew->setFieldAmount(sfAmount, amt);
107 ac.view().insert(sleNew);
108 return true;
109 });
110
111 // MPT < 0
113 {{"escrow specifies invalid amount"}},
114 [](Account const& a1, Account const&, ApplyContext& ac) {
115 // escrow with too-little mpt
116 auto const sle = ac.view().peek(keylet::account(a1.id()));
117 if (!sle)
118 return false;
119 auto sleNew = std::make_shared<SLE>(
120 keylet::escrow(a1, SeqProxy::rawSequence((*sle)[sfSequence] + 2)));
121
122 MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))};
123 STAmount const amt(mpt, -1);
124 sleNew->setFieldAmount(sfAmount, amt);
125 ac.view().insert(sleNew);
126 return true;
127 });
128
129 // MPT OutstandingAmount < 0
131 {{"escrow specifies invalid amount"}},
132 [](Account const& a1, Account const&, ApplyContext& ac) {
133 // mptissuance outstanding is negative
134 auto const sle = ac.view().peek(keylet::account(a1.id()));
135 if (!sle)
136 return false;
137
138 MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))};
140 sleNew->setFieldU64(sfOutstandingAmount, std::numeric_limits<std::uint64_t>::max());
141 ac.view().insert(sleNew);
142 return true;
143 });
144
145 // MPT LockedAmount < 0
147 {{"escrow specifies invalid amount"}},
148 [](Account const& a1, Account const&, ApplyContext& ac) {
149 // mptissuance locked is less than locked
150 auto const sle = ac.view().peek(keylet::account(a1.id()));
151 if (!sle)
152 return false;
153
154 MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))};
156 sleNew->setFieldU64(sfLockedAmount, std::numeric_limits<std::uint64_t>::max());
157 ac.view().insert(sleNew);
158 return true;
159 });
160
161 // MPT OutstandingAmount < LockedAmount
163 {{"escrow specifies invalid amount"}},
164 [](Account const& a1, Account const&, ApplyContext& ac) {
165 // mptissuance outstanding is less than locked
166 auto const sle = ac.view().peek(keylet::account(a1.id()));
167 if (!sle)
168 return false;
169
170 MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))};
172 sleNew->setFieldU64(sfOutstandingAmount, 1);
173 sleNew->setFieldU64(sfLockedAmount, 10);
174 ac.view().insert(sleNew);
175 return true;
176 });
177
178 // MPT MPTAmount < 0
180 {{"escrow specifies invalid amount"}},
181 [](Account const& a1, Account const&, ApplyContext& ac) {
182 // mptoken amount is negative
183 auto const sle = ac.view().peek(keylet::account(a1.id()));
184 if (!sle)
185 return false;
186
187 MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))};
188 auto sleNew = std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), a1));
189 sleNew->setFieldU64(sfMPTAmount, std::numeric_limits<std::uint64_t>::max());
190 ac.view().insert(sleNew);
191 return true;
192 });
193
194 // MPT LockedAmount < 0
196 {{"escrow specifies invalid amount"}},
197 [](Account const& a1, Account const&, ApplyContext& ac) {
198 // mptoken locked amount is negative
199 auto const sle = ac.view().peek(keylet::account(a1.id()));
200 if (!sle)
201 return false;
202
203 MPTIssue const mpt{makeMptID(1, AccountID(0x4985601))};
204 auto sleNew = std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), a1));
205 sleNew->setFieldU64(sfLockedAmount, std::numeric_limits<std::uint64_t>::max());
206 ac.view().insert(sleNew);
207 return true;
208 });
209 }
210
211 void
213 {
214 using namespace test::jtx;
215 testcase << "NFTokenPage";
216
217 // lambda that returns an STArray of NFTokenIDs.
218 UInt256 const firstNFTID(
219 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
220 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
221 SOTemplate const* nfTokenTemplate =
223
224 UInt256 nftID(firstNFTID);
225 STArray ret;
226 for (int i = 0; i < nftCount; ++i)
227 {
228 STObject newNFToken(*nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
229 object.setFieldH256(sfNFTokenID, nftID);
230 });
231 ret.pushBack(std::move(newNFToken));
232 ++nftID;
233 }
234 return ret;
235 };
236
238 {{"NFT page has invalid size"}},
239 [&makeNFTokenIDs](Account const& a1, Account const&, ApplyContext& ac) {
241 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
242
243 ac.view().insert(nftPage);
244 return true;
245 });
246
248 {{"NFT page has invalid size"}},
249 [&makeNFTokenIDs](Account const& a1, Account const&, ApplyContext& ac) {
251 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
252
253 ac.view().insert(nftPage);
254 return true;
255 });
256
258 {{"NFTs on page are not sorted"}},
259 [&makeNFTokenIDs](Account const& a1, Account const&, ApplyContext& ac) {
260 STArray nfTokens = makeNFTokenIDs(2);
261 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
262
264 nftPage->setFieldArray(sfNFTokens, nfTokens);
265
266 ac.view().insert(nftPage);
267 return true;
268 });
269
271 {{"NFT contains empty URI"}},
272 [&makeNFTokenIDs](Account const& a1, Account const&, ApplyContext& ac) {
273 STArray nfTokens = makeNFTokenIDs(1);
274 nfTokens[0].setFieldVL(sfURI, Blob{});
275
277 nftPage->setFieldArray(sfNFTokens, nfTokens);
278
279 ac.view().insert(nftPage);
280 return true;
281 });
282
284 {{"NFT page is improperly linked"}},
285 [&makeNFTokenIDs](Account const& a1, Account const&, ApplyContext& ac) {
287 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
288 nftPage->setFieldH256(sfPreviousPageMin, keylet::nftokenPageMax(a1).key);
289
290 ac.view().insert(nftPage);
291 return true;
292 });
293
295 {{"NFT page is improperly linked"}},
296 [&makeNFTokenIDs](Account const& a1, Account const& a2, ApplyContext& ac) {
298 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
299 nftPage->setFieldH256(sfPreviousPageMin, keylet::nftokenPageMin(a2).key);
300
301 ac.view().insert(nftPage);
302 return true;
303 });
304
306 {{"NFT page is improperly linked"}},
307 [&makeNFTokenIDs](Account const& a1, Account const&, ApplyContext& ac) {
309 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
310 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
311
312 ac.view().insert(nftPage);
313 return true;
314 });
315
317 {{"NFT page is improperly linked"}},
318 [&makeNFTokenIDs](Account const& a1, Account const& a2, ApplyContext& ac) {
319 STArray nfTokens = makeNFTokenIDs(1);
321 keylet::nftokenPageMax(a1), ++(nfTokens[0].getFieldH256(sfNFTokenID))));
322 nftPage->setFieldArray(sfNFTokens, nfTokens);
323 nftPage->setFieldH256(sfNextPageMin, keylet::nftokenPageMax(a2).key);
324
325 ac.view().insert(nftPage);
326 return true;
327 });
328
330 {{"NFT found in incorrect page"}},
331 [&makeNFTokenIDs](Account const& a1, Account const&, ApplyContext& ac) {
332 STArray nfTokens = makeNFTokenIDs(2);
334 keylet::nftokenPageMax(a1), (nfTokens[1].getFieldH256(sfNFTokenID))));
335 nftPage->setFieldArray(sfNFTokens, nfTokens);
336
337 ac.view().insert(nftPage);
338 return true;
339 });
340 }
341
342 void
343 run() override
344 {
347 }
348};
349
350BEAST_DEFINE_TESTSUITE(InvariantsEscrowNFT, app, xrpl);
351
352} // namespace xrpl::test
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
State information when applying a tx.
SOTemplate const * findSOTemplateBySField(SField const &sField) const
static InnerObjectFormats const & getInstance()
A currency issued by an account.
Definition Issue.h:18
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:43
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:105
iterator begin()
Definition STArray.h:224
void pushBack(STObject const &object)
Definition STArray.h:212
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
void doInvariantCheck(std::vector< std::string > const &expectLogs, 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, std::source_location const &loc=std::source_location::current(), TER initialResult=tesSUCCESS)
Run a specific test case to put the ledger into a state that will be detected by an invariant.
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
AccountID id() const
Returns the Account ID.
T iter_swap(T... args)
T make_shared(T... args)
T max(T... args)
Keylet escrow(AccountID const &src, SeqProxy const &seq) noexcept
An escrow entry.
Definition Indexes.cpp:418
Keylet nftokenPage(Keylet const &k, UInt256 const &token)
Definition Indexes.cpp:446
Keylet nftokenPageMin(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:430
Keylet nftokenPageMax(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:438
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:573
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:42
BaseUInt< 256 > UInt256
Definition base_uint.h:580
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:206
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
std::vector< unsigned char > Blob
Storage for linear binary data.
Definition Blob.h:11
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr XRPAmount kInitialXrp
Configure the native currency.