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