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