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