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