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