xrpld
Loading...
Searching...
No Matches
AccountSet_test.cpp
1
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/balance.h>
6#include <test/jtx/flags.h>
7#include <test/jtx/multisign.h>
8#include <test/jtx/noop.h>
9#include <test/jtx/owners.h>
10#include <test/jtx/pay.h>
11#include <test/jtx/rate.h>
12#include <test/jtx/regkey.h>
13#include <test/jtx/sendmax.h>
14#include <test/jtx/sig.h>
15#include <test/jtx/tags.h>
16#include <test/jtx/ter.h>
17#include <test/jtx/ticket.h>
18#include <test/jtx/token.h>
19
20#include <xrpl/basics/Slice.h>
21#include <xrpl/basics/base_uint.h>
22#include <xrpl/basics/strHex.h>
23#include <xrpl/beast/unit_test/suite.h>
24#include <xrpl/beast/utility/Journal.h>
25#include <xrpl/core/ServiceRegistry.h>
26#include <xrpl/ledger/ApplyView.h>
27#include <xrpl/ledger/OpenView.h>
28#include <xrpl/ledger/helpers/DirectoryHelpers.h>
29#include <xrpl/protocol/AmountConversions.h>
30#include <xrpl/protocol/Feature.h>
31#include <xrpl/protocol/Indexes.h>
32#include <xrpl/protocol/KeyType.h>
33#include <xrpl/protocol/Quality.h>
34#include <xrpl/protocol/Rate.h>
35#include <xrpl/protocol/SField.h>
36#include <xrpl/protocol/STAmount.h>
37#include <xrpl/protocol/STTx.h>
38#include <xrpl/protocol/SecretKey.h>
39#include <xrpl/protocol/TER.h>
40#include <xrpl/protocol/TxFlags.h>
41#include <xrpl/protocol/jss.h>
42#include <xrpl/tx/apply.h>
43
44#include <algorithm>
45#include <cstddef>
46#include <cstdint>
47#include <initializer_list>
48#include <limits>
49#include <memory>
50#include <string>
51
52namespace xrpl {
53
55{
56public:
57 void
59 {
60 testcase("No AccountSet");
61
62 using namespace test::jtx;
63 Env env(*this);
64 Account const alice("alice");
65 env.fund(XRP(10000), noripple(alice));
66 // ask for the ledger entry - account root, to check its flags
67 auto const jrr = env.le(alice);
68 BEAST_EXPECT(jrr && jrr->at(sfFlags) == 0u);
69 }
70
71 void
73 {
74 testcase("Most Flags");
75
76 using namespace test::jtx;
77 Account const alice("alice");
78
79 Env env(*this, testableAmendments());
80 env.fund(XRP(10000), noripple(alice));
81
82 // Give alice a regular key so she can legally set and clear
83 // her asfDisableMaster flag.
84 Account const alie{"alie", KeyType::Secp256k1};
85 env(regkey(alice, alie));
86 env.close();
87
88 auto testFlags = [this, &alice, &alie, &env](
90 std::uint32_t const origFlags = (*env.le(alice))[sfFlags];
91 for (std::uint32_t flag{1u}; flag < std::numeric_limits<std::uint32_t>::digits; ++flag)
92 {
93 if (flag == asfNoFreeze)
94 {
95 // The asfNoFreeze flag can't be cleared. It is tested
96 // elsewhere.
97 continue;
98 }
99
100 if (flag == asfAuthorizedNFTokenMinter)
101 {
102 // The asfAuthorizedNFTokenMinter flag requires the
103 // presence or absence of the sfNFTokenMinter field in
104 // the transaction. It is tested elsewhere.
105 continue;
106 }
107
108 if (flag == asfDisallowIncomingCheck || flag == asfDisallowIncomingPayChan ||
109 flag == asfDisallowIncomingNFTokenOffer || flag == asfDisallowIncomingTrustline)
110 {
111 // These flags are part of the DisallowIncoming amendment
112 // and are tested elsewhere
113 continue;
114 }
115 if (flag == asfAllowTrustLineClawback)
116 {
117 // The asfAllowTrustLineClawback flag can't be cleared. It
118 // is tested elsewhere.
119 continue;
120 }
121 if (flag == asfAllowTrustLineLocking)
122 {
123 // These flags are part of the AllowTokenLocking amendment
124 // and are tested elsewhere
125 continue;
126 }
127
128 if (std::ranges::find(goodFlags, flag) != goodFlags.end())
129 {
130 // Good flag
131 env.require(Nflags(alice, flag));
132 env(fset(alice, flag), Sig(alice));
133 env.close();
134 env.require(Flags(alice, flag));
135 env(fclear(alice, flag), Sig(alie));
136 env.close();
137 env.require(Nflags(alice, flag));
138 std::uint32_t const nowFlags = (*env.le(alice))[sfFlags];
139 BEAST_EXPECT(nowFlags == origFlags);
140 }
141 else
142 {
143 // Bad flag
144 BEAST_EXPECT((*env.le(alice))[sfFlags] == origFlags);
145 env(fset(alice, flag), Sig(alice));
146 env.close();
147 BEAST_EXPECT((*env.le(alice))[sfFlags] == origFlags);
148 env(fclear(alice, flag), Sig(alie));
149 env.close();
150 BEAST_EXPECT((*env.le(alice))[sfFlags] == origFlags);
151 }
152 }
153 };
154 testFlags(
155 {asfRequireDest,
156 asfRequireAuth,
157 asfDisallowXRP,
158 asfGlobalFreeze,
159 asfDisableMaster,
160 asfDefaultRipple,
161 asfDepositAuth});
162 }
163
164 void
166 {
167 testcase("Set and reset AccountTxnID");
168
169 using namespace test::jtx;
170 Env env(*this);
171 Account const alice("alice");
172 env.fund(XRP(10000), noripple(alice));
173
174 std::uint32_t const origFlags = (*env.le(alice))[sfFlags];
175
176 // asfAccountTxnID is special and not actually set as a flag,
177 // so we check the field presence instead
178 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAccountTxnID));
179 env(fset(alice, asfAccountTxnID), Sig(alice));
180 BEAST_EXPECT(env.le(alice)->isFieldPresent(sfAccountTxnID));
181 env(fclear(alice, asfAccountTxnID));
182 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfAccountTxnID));
183 std::uint32_t const nowFlags = (*env.le(alice))[sfFlags];
184 BEAST_EXPECT(nowFlags == origFlags);
185 }
186
187 void
189 {
190 testcase("Set NoFreeze");
191
192 using namespace test::jtx;
193 Env env(*this);
194 Account const alice("alice");
195 env.fund(XRP(10000), noripple(alice));
196 env.memoize("eric");
197 env(regkey(alice, "eric"));
198
199 env.require(Nflags(alice, asfNoFreeze));
200 env(fset(alice, asfNoFreeze), Sig("eric"), Ter(tecNEED_MASTER_KEY));
201 env(fset(alice, asfNoFreeze), Sig(alice));
202 env.require(Flags(alice, asfNoFreeze));
203 env(fclear(alice, asfNoFreeze), Sig(alice));
204 // verify flag is still set (clear does not clear in this case)
205 env.require(Flags(alice, asfNoFreeze));
206 }
207
208 void
210 {
211 testcase("Domain");
212
213 using namespace test::jtx;
214 Env env(*this);
215 Account const alice("alice");
216 env.fund(XRP(10000), alice);
217 auto jt = noop(alice);
218 // The Domain field is represented as the hex string of the lowercase
219 // ASCII of the domain. For example, the domain example.com would be
220 // represented as "6578616d706c652e636f6d".
221 //
222 // To remove the Domain field from an account, send an AccountSet with
223 // the Domain set to an empty string.
224 std::string const domain = "example.com";
225 jt[sfDomain.fieldName] = strHex(domain);
226 env(jt);
227 BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
228
229 jt[sfDomain.fieldName] = "";
230 env(jt);
231 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfDomain));
232
233 // The upper limit on the length is 256 bytes
234 // (defined as DOMAIN_BYTES_MAX in AccountSet)
235 // test the edge cases: 255, 256, 257.
236 std::size_t const maxLength = 256;
237 for (std::size_t len = maxLength - 1; len <= maxLength + 1; ++len)
238 {
239 std::string const domain2 = std::string(len - domain.length() - 1, 'a') + "." + domain;
240
241 BEAST_EXPECT(domain2.length() == len);
242
243 jt[sfDomain.fieldName] = strHex(domain2);
244
245 if (len <= maxLength)
246 {
247 env(jt);
248 BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain2));
249 }
250 else
251 {
252 env(jt, Ter(telBAD_DOMAIN));
253 }
254 }
255 }
256
257 void
259 {
260 testcase("MessageKey");
261
262 using namespace test::jtx;
263 Env env(*this);
264 Account const alice("alice");
265 env.fund(XRP(10000), alice);
266 auto jt = noop(alice);
267
268 auto const rkp = randomKeyPair(KeyType::Ed25519);
269 jt[sfMessageKey.fieldName] = strHex(rkp.first.slice());
270 env(jt);
271 BEAST_EXPECT(strHex((*env.le(alice))[sfMessageKey]) == strHex(rkp.first.slice()));
272
273 jt[sfMessageKey.fieldName] = "";
274 env(jt);
275 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfMessageKey));
276
277 using namespace std::string_literals;
278 jt[sfMessageKey.fieldName] = strHex("NOT_REALLY_A_PUBKEY"s);
279 env(jt, Ter(telBAD_PUBLIC_KEY));
280 }
281
282 void
284 {
285 testcase("WalletID");
286
287 using namespace test::jtx;
288 Env env(*this);
289 Account const alice("alice");
290 env.fund(XRP(10000), alice);
291 auto jt = noop(alice);
292
293 std::string const locator =
294 "9633EC8AF54F16B5286DB1D7B519EF49EEFC050C0C8AC4384F1D88ACD1BFDF05";
295 jt[sfWalletLocator.fieldName] = locator;
296 env(jt);
297 BEAST_EXPECT(to_string((*env.le(alice))[sfWalletLocator]) == locator);
298
299 jt[sfWalletLocator.fieldName] = "";
300 env(jt);
301 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfWalletLocator));
302 }
303
304 void
306 {
307 testcase("EmailHash");
308
309 using namespace test::jtx;
310 Env env(*this);
311 Account const alice("alice");
312 env.fund(XRP(10000), alice);
313 auto jt = noop(alice);
314
315 std::string const mh("5F31A79367DC3137FADA860C05742EE6");
316 jt[sfEmailHash.fieldName] = mh;
317 env(jt);
318 BEAST_EXPECT(to_string((*env.le(alice))[sfEmailHash]) == mh);
319
320 jt[sfEmailHash.fieldName] = "";
321 env(jt);
322 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfEmailHash));
323 }
324
325 void
327 {
328 struct TestResults
329 {
330 double set;
331 TER code;
332 double get;
333 };
334
335 testcase("TransferRate");
336
337 using namespace test::jtx;
338 auto doTests =
339 [this](FeatureBitset const& features, std::initializer_list<TestResults> testData) {
340 Env env(*this, features);
341
342 Account const alice("alice");
343 env.fund(XRP(10000), alice);
344
345 for (auto const& r : testData)
346 {
347 env(rate(alice, r.set), Ter(r.code));
348 env.close();
349
350 // If the field is not present expect the default value
351 if (!(*env.le(alice))[~sfTransferRate])
352 {
353 BEAST_EXPECT(r.get == 1.0);
354 }
355 else
356 {
357 BEAST_EXPECT(*(*env.le(alice))[~sfTransferRate] == r.get * QUALITY_ONE);
358 }
359 }
360 };
361
362 doTests(
363 testableAmendments(),
364 {{.set = 1.0, .code = tesSUCCESS, .get = 1.0},
365 {.set = 1.1, .code = tesSUCCESS, .get = 1.1},
366 {.set = 2.0, .code = tesSUCCESS, .get = 2.0},
367 {.set = 2.1, .code = temBAD_TRANSFER_RATE, .get = 2.0},
368 {.set = 0.0, .code = tesSUCCESS, .get = 1.0},
369 {.set = 2.0, .code = tesSUCCESS, .get = 2.0},
370 {.set = 0.9, .code = temBAD_TRANSFER_RATE, .get = 2.0}});
371 }
372
373 void
375 {
376 testcase("Gateway");
377
378 using namespace test::jtx;
379
380 Account const alice("alice");
381 Account const bob("bob");
382 Account const gw("gateway");
383 auto const usd = gw["USD"];
384
385 // Test gateway with a variety of allowed transfer rates
386 // NOLINTNEXTLINE(bugprone-float-loop-counter)
387 for (double transferRate = 1.0; transferRate <= 2.0; transferRate += 0.03125)
388 {
389 Env env(*this);
390 env.fund(XRP(10000), gw, alice, bob);
391 env.close();
392 env.trust(usd(10), alice, bob);
393 env.close();
394 env(rate(gw, transferRate));
395 env.close();
396
397 auto const amount = usd(1);
398 Rate const rate(transferRate * QUALITY_ONE);
399 auto const amountWithRate = toAmount<STAmount>(multiply(amount.value(), rate));
400
401 env(pay(gw, alice, usd(10)));
402 env.close();
403 env(pay(alice, bob, usd(1)), Sendmax(usd(10)));
404 env.close();
405
406 env.require(Balance(alice, usd(10) - amountWithRate));
407 env.require(Balance(bob, usd(1)));
408 }
409
410 // Since fix1201 was enabled on Nov 14 2017 a rate in excess of
411 // 2.0 has been blocked by the transactor. But there are a few
412 // accounts on the MainNet that have larger-than-currently-allowed
413 // TransferRates. We'll bypass the transactor so we can check
414 // operation of these legacy TransferRates.
415 //
416 // Two out-of-bound values are currently in the ledger (March 2020)
417 // They are 4.0 and 4.294967295. So those are the values we test.
418 for (double const transferRate : {4.0, 4.294967295})
419 {
420 Env env(*this);
421 env.fund(XRP(10000), gw, alice, bob);
422 env.close();
423 env.trust(usd(10), alice, bob);
424 env.close();
425
426 // We'd like to use transferRate here, but the transactor
427 // blocks transfer rates that large. So we use an acceptable
428 // transfer rate here and later hack the ledger to replace
429 // the acceptable value with an out-of-bounds value.
430 env(rate(gw, 2.0));
431 env.close();
432
433 // Because we're hacking the ledger we need the account to have
434 // non-zero sfMintedNFTokens and sfBurnedNFTokens fields. This
435 // prevents an exception when the AccountRoot template is applied.
436 {
437 uint256 const nftId0{token::getNextID(env, gw, 0u)};
438 env(token::mint(gw, 0u));
439 env.close();
440
441 env(token::burn(gw, nftId0));
442 env.close();
443 }
444
445 // Note that we're bypassing almost all of the ledger's safety
446 // checks with this modify() call. If you call close() between
447 // here and the end of the test all the effort will be lost.
448 env.app().getOpenLedger().modify([&gw, transferRate](OpenView& view, beast::Journal j) {
449 // Get the account root we want to hijack.
450 auto const sle = view.read(keylet::account(gw.id()));
451 if (!sle)
452 return false; // This would be really surprising!
453
454 // We'll insert a replacement for the account root
455 // with the higher (currently invalid) transfer rate.
456 auto replacement = std::make_shared<SLE>(*sle, sle->key());
457 (*replacement)[sfTransferRate] =
458 static_cast<std::uint32_t>(transferRate * QUALITY_ONE);
459 view.rawReplace(replacement);
460 return true;
461 });
462
463 auto const amount = usd(1);
464 auto const amountWithRate =
465 toAmount<STAmount>(multiply(amount.value(), Rate(transferRate * QUALITY_ONE)));
466
467 env(pay(gw, alice, usd(10)));
468 env(pay(alice, bob, amount), Sendmax(usd(10)));
469
470 env.require(Balance(alice, usd(10) - amountWithRate));
471 env.require(Balance(bob, amount));
472 }
473 }
474
475 void
477 {
478 testcase("Bad inputs");
479
480 using namespace test::jtx;
481 Env env(*this);
482 Account const alice("alice");
483 env.fund(XRP(10000), alice);
484
485 auto jt = fset(alice, asfDisallowXRP);
486 jt[jss::ClearFlag] = asfDisallowXRP;
487 env(jt, Ter(temINVALID_FLAG));
488
489 jt = fset(alice, asfRequireAuth);
490 jt[jss::ClearFlag] = asfRequireAuth;
491 env(jt, Ter(temINVALID_FLAG));
492
493 jt = fset(alice, asfRequireDest);
494 jt[jss::ClearFlag] = asfRequireDest;
495 env(jt, Ter(temINVALID_FLAG));
496
497 jt = fset(alice, asfDisallowXRP);
498 jt[sfFlags.fieldName] = tfAllowXRP;
499 env(jt, Ter(temINVALID_FLAG));
500
501 jt = fset(alice, asfRequireAuth);
502 jt[sfFlags.fieldName] = tfOptionalAuth;
503 env(jt, Ter(temINVALID_FLAG));
504
505 jt = fset(alice, asfRequireDest);
506 jt[sfFlags.fieldName] = tfOptionalDestTag;
507 env(jt, Ter(temINVALID_FLAG));
508
509 jt = fset(alice, asfRequireDest);
510 jt[sfFlags.fieldName] = tfAccountSetMask;
511 env(jt, Ter(temINVALID_FLAG));
512
513 env(fset(alice, asfDisableMaster), Sig(alice), Ter(tecNO_ALTERNATIVE_KEY));
514 }
515
516 void
518 {
519 testcase("Require auth");
520
521 using namespace test::jtx;
522 Env env(*this);
523 Account const alice("alice");
524 Account const bob("bob");
525
526 env.fund(XRP(10000), alice);
527 env.close();
528
529 // alice should have an empty directory.
530 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
531
532 // Give alice a signer list, then there will be stuff in the directory.
533 env(signers(alice, 1, {{bob, 1}}));
534 env.close();
535 BEAST_EXPECT(!dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
536
537 env(fset(alice, asfRequireAuth), Ter(tecOWNERS));
538
539 // Remove the signer list. After that asfRequireAuth should succeed.
540 env(signers(alice, test::jtx::kNone));
541 env.close();
542 BEAST_EXPECT(dirIsEmpty(*env.closed(), keylet::ownerDir(alice)));
543
544 env(fset(alice, asfRequireAuth));
545 }
546
547 void
549 {
550 using namespace test::jtx;
551 Env env(*this);
552 Account const alice("alice");
553
554 env.fund(XRP(10000), alice);
555 env.close();
556
557 std::uint32_t const ticketSeq{env.seq(alice) + 1};
558 env(ticket::create(alice, 1));
559 env.close();
560 env.require(Owners(alice, 1), tickets(alice, 1));
561
562 // Try using a ticket that alice doesn't have.
563 env(noop(alice), ticket::Use(ticketSeq + 1), Ter(terPRE_TICKET));
564 env.close();
565 env.require(Owners(alice, 1), tickets(alice, 1));
566
567 // Actually use alice's ticket. Note that if a transaction consumes
568 // a ticket then the account's sequence number does not advance.
569 std::uint32_t const aliceSeq{env.seq(alice)};
570 env(noop(alice), ticket::Use(ticketSeq));
571 env.close();
572 env.require(Owners(alice, 0), tickets(alice, 0));
573 BEAST_EXPECT(aliceSeq == env.seq(alice));
574
575 // Try re-using a ticket that alice already used.
576 env(noop(alice), ticket::Use(ticketSeq), Ter(tefNO_TICKET));
577 env.close();
578 }
579
580 void
582 {
583 using namespace test::jtx;
584 testcase("Bad signing key");
585 Env env(*this);
586 Account const alice("alice");
587
588 env.fund(XRP(10000), alice);
589 env.close();
590
591 auto jtx = env.jt(noop("alice"), Ter(temBAD_SIGNATURE));
592 if (!BEAST_EXPECT(jtx.stx))
593 return;
594 auto stx = std::make_shared<STTx>(*jtx.stx);
595 stx->at(sfSigningPubKey) = makeSlice(std::string("badkey"));
596
597 env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) {
598 auto const result = xrpl::apply(env.app(), view, *stx, TapNone, j);
599 BEAST_EXPECT(result.ter == temBAD_SIGNATURE);
600 BEAST_EXPECT(!result.applied);
601 return result.applied;
602 });
603 }
604
605 void
623};
624
626
627} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
A testsuite class.
Definition suite.h:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
void run() override
Runs the suite.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:45
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
Definition OpenView.cpp:167
void rawReplace(SLE::ref sle) override
Unconditionally replace a state item.
Definition OpenView.cpp:243
T find(T... args)
T make_shared(T... args)
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:357
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
static NoneT const kNone
Definition tags.h:9
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ telBAD_DOMAIN
Definition TER.h:37
@ telBAD_PUBLIC_KEY
Definition TER.h:39
@ terPRE_TICKET
Definition TER.h:218
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
ApplyResult apply(ServiceRegistry &registry, OpenView &view, STTx const &tx, ApplyFlags flags, beast::Journal journal)
Apply a transaction to an OpenView.
Definition apply.cpp:139
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
@ tefNO_TICKET
Definition TER.h:175
BEAST_DEFINE_TESTSUITE_PRIO(AccountSet, app, xrpl, 1)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
@ TapNone
Definition ApplyView.h:13
@ temINVALID_FLAG
Definition TER.h:97
@ temBAD_TRANSFER_RATE
Definition TER.h:93
@ temBAD_SIGNATURE
Definition TER.h:91
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecNEED_MASTER_KEY
Definition TER.h:306
@ tecNO_ALTERNATIVE_KEY
Definition TER.h:294
@ tecOWNERS
Definition TER.h:296
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
BaseUInt< 256 > uint256
Definition base_uint.h:562
@ tesSUCCESS
Definition TER.h:240
std::enable_if_t< std::is_same_v< T, char >||std::is_same_v< T, unsigned char >, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:215
STAmount toAmount< STAmount >(STAmount const &amt)
T length(T... args)
Represents a transfer rate.
Definition Rate.h:20