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