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