rippled
Loading...
Searching...
No Matches
Feature_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2017 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/misc/AmendmentTable.h>
23
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/jss.h>
26
27namespace ripple {
28
30{
31 void
33 {
34 testcase("internals");
35
36 auto const& supportedAmendments = ripple::detail::supportedAmendments();
38
39 BEAST_EXPECT(
40 supportedAmendments.size() ==
43 {
44 std::size_t up = 0, down = 0, obsolete = 0;
45 for (auto const& [name, vote] : supportedAmendments)
46 {
47 switch (vote)
48 {
50 ++up;
51 break;
53 ++down;
54 break;
56 ++obsolete;
57 break;
58 default:
59 fail("Unknown VoteBehavior", __FILE__, __LINE__);
60 }
61
62 if (vote == VoteBehavior::Obsolete)
63 {
64 BEAST_EXPECT(
65 allAmendments.contains(name) &&
67 }
68 else
69 {
70 BEAST_EXPECT(
71 allAmendments.contains(name) &&
73 }
74 }
75 BEAST_EXPECT(
78 }
79 {
80 std::size_t supported = 0, unsupported = 0, retired = 0;
81 for (auto const& [name, support] : allAmendments)
82 {
83 switch (support)
84 {
86 ++supported;
87 BEAST_EXPECT(supportedAmendments.contains(name));
88 break;
90 ++unsupported;
91 break;
93 ++retired;
94 break;
95 default:
96 fail("Unknown AmendmentSupport", __FILE__, __LINE__);
97 }
98 }
99
100 BEAST_EXPECT(supported + retired == supportedAmendments.size());
101 BEAST_EXPECT(
102 allAmendments.size() - unsupported ==
103 supportedAmendments.size());
104 }
105 }
106
107 void
109 {
110 testcase("featureToName");
111
112 // Test all the supported features. In a perfect world, this would test
113 // FeatureCollections::featureNames, but that's private. Leave it that
114 // way.
115 auto const supported = ripple::detail::supportedAmendments();
116
117 for (auto const& [feature, vote] : supported)
118 {
119 (void)vote;
120 auto const registered = getRegisteredFeature(feature);
121 if (BEAST_EXPECT(registered))
122 {
123 BEAST_EXPECT(featureToName(*registered) == feature);
124 BEAST_EXPECT(
126 *registered);
127 }
128 }
129
130 // Test an arbitrary unknown feature
131 uint256 zero{0};
132 BEAST_EXPECT(featureToName(zero) == to_string(zero));
133 BEAST_EXPECT(
134 featureToName(zero) ==
135 "0000000000000000000000000000000000000000000000000000000000000000");
136
137 // Test looking up an unknown feature
138 BEAST_EXPECT(!getRegisteredFeature("unknown"));
139
140 // Test a random sampling of the variables. If any of these get retired
141 // or removed, swap out for any other feature.
142 BEAST_EXPECT(featureToName(featureOwnerPaysFee) == "OwnerPaysFee");
143 BEAST_EXPECT(featureToName(featureFlow) == "Flow");
144 BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL");
145 BEAST_EXPECT(featureToName(fix1578) == "fix1578");
146 BEAST_EXPECT(
147 featureToName(fixTakerDryOfferRemoval) ==
148 "fixTakerDryOfferRemoval");
149 }
150
151 void
153 {
154 testcase("No Params, None Enabled");
155
156 using namespace test::jtx;
157 Env env{*this};
158
161
162 auto jrr = env.rpc("feature")[jss::result];
163 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
164 return;
165 for (auto const& feature : jrr[jss::features])
166 {
167 if (!BEAST_EXPECT(feature.isMember(jss::name)))
168 return;
169 // default config - so all should be disabled, and
170 // supported. Some may be vetoed.
171 bool expectVeto =
172 (votes.at(feature[jss::name].asString()) ==
174 bool expectObsolete =
175 (votes.at(feature[jss::name].asString()) ==
177 BEAST_EXPECTS(
178 feature.isMember(jss::enabled) &&
179 !feature[jss::enabled].asBool(),
180 feature[jss::name].asString() + " enabled");
181 BEAST_EXPECTS(
182 feature.isMember(jss::vetoed) &&
183 feature[jss::vetoed].isBool() == !expectObsolete &&
184 (!feature[jss::vetoed].isBool() ||
185 feature[jss::vetoed].asBool() == expectVeto) &&
186 (feature[jss::vetoed].isBool() ||
187 feature[jss::vetoed].asString() == "Obsolete"),
188 feature[jss::name].asString() + " vetoed");
189 BEAST_EXPECTS(
190 feature.isMember(jss::supported) &&
191 feature[jss::supported].asBool(),
192 feature[jss::name].asString() + " supported");
193 }
194 }
195
196 void
198 {
199 testcase("Feature Param");
200
201 using namespace test::jtx;
202 Env env{*this};
203
204 auto jrr = env.rpc("feature", "MultiSignReserve")[jss::result];
205 BEAST_EXPECTS(jrr[jss::status] == jss::success, "status");
206 jrr.removeMember(jss::status);
207 BEAST_EXPECT(jrr.size() == 1);
208 BEAST_EXPECT(
209 jrr.isMember("586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF"
210 "1FC07EFE41D"));
211 auto feature = *(jrr.begin());
212
213 BEAST_EXPECTS(feature[jss::name] == "MultiSignReserve", "name");
214 BEAST_EXPECTS(!feature[jss::enabled].asBool(), "enabled");
215 BEAST_EXPECTS(
216 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
217 "vetoed");
218 BEAST_EXPECTS(feature[jss::supported].asBool(), "supported");
219
220 // feature names are case-sensitive - expect error here
221 jrr = env.rpc("feature", "multiSignReserve")[jss::result];
222 BEAST_EXPECT(jrr[jss::error] == "badFeature");
223 BEAST_EXPECT(jrr[jss::error_message] == "Feature unknown or invalid.");
224 }
225
226 void
228 {
229 testcase("Invalid Feature");
230
231 using namespace test::jtx;
232 Env env{*this};
233
234 auto testInvalidParam = [&](auto const& param) {
235 Json::Value params;
236 params[jss::feature] = param;
237 auto jrr =
238 env.rpc("json", "feature", to_string(params))[jss::result];
239 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
240 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
241 };
242
243 testInvalidParam(1);
244 testInvalidParam(1.1);
245 testInvalidParam(true);
246 testInvalidParam(Json::Value(Json::nullValue));
247 testInvalidParam(Json::Value(Json::objectValue));
248 testInvalidParam(Json::Value(Json::arrayValue));
249
250 {
251 auto jrr = env.rpc("feature", "AllTheThings")[jss::result];
252 BEAST_EXPECT(jrr[jss::error] == "badFeature");
253 BEAST_EXPECT(
254 jrr[jss::error_message] == "Feature unknown or invalid.");
255 }
256 }
257
258 void
260 {
261 testcase("Feature Without Admin");
262
263 using namespace test::jtx;
264 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
265 (*cfg)["port_rpc"].set("admin", "");
266 (*cfg)["port_ws"].set("admin", "");
267 return cfg;
268 })};
269
270 {
271 auto result = env.rpc("feature")[jss::result];
272 BEAST_EXPECT(result.isMember(jss::features));
273 // There should be at least 50 amendments. Don't do exact
274 // comparison to avoid maintenance as more amendments are added in
275 // the future.
276 BEAST_EXPECT(result[jss::features].size() >= 50);
277 for (auto it = result[jss::features].begin();
278 it != result[jss::features].end();
279 ++it)
280 {
281 uint256 id;
282 (void)id.parseHex(it.key().asString().c_str());
283 if (!BEAST_EXPECT((*it).isMember(jss::name)))
284 return;
285 bool expectEnabled =
286 env.app().getAmendmentTable().isEnabled(id);
287 bool expectSupported =
288 env.app().getAmendmentTable().isSupported(id);
289 BEAST_EXPECTS(
290 (*it).isMember(jss::enabled) &&
291 (*it)[jss::enabled].asBool() == expectEnabled,
292 (*it)[jss::name].asString() + " enabled");
293 BEAST_EXPECTS(
294 (*it).isMember(jss::supported) &&
295 (*it)[jss::supported].asBool() == expectSupported,
296 (*it)[jss::name].asString() + " supported");
297 BEAST_EXPECT(!(*it).isMember(jss::vetoed));
298 BEAST_EXPECT(!(*it).isMember(jss::majority));
299 BEAST_EXPECT(!(*it).isMember(jss::count));
300 BEAST_EXPECT(!(*it).isMember(jss::validations));
301 BEAST_EXPECT(!(*it).isMember(jss::threshold));
302 }
303 }
304
305 {
306 Json::Value params;
307 // invalid feature
308 params[jss::feature] =
309 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCD"
310 "EF";
311 auto const result = env.rpc(
312 "json",
313 "feature",
314 boost::lexical_cast<std::string>(params))[jss::result];
315 BEAST_EXPECTS(
316 result[jss::error] == "badFeature", result.toStyledString());
317 BEAST_EXPECT(
318 result[jss::error_message] == "Feature unknown or invalid.");
319 }
320
321 {
322 Json::Value params;
323 params[jss::feature] =
324 "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515"
325 "A7";
326 // invalid param
327 params[jss::vetoed] = true;
328 auto const result = env.rpc(
329 "json",
330 "feature",
331 boost::lexical_cast<std::string>(params))[jss::result];
332 BEAST_EXPECTS(
333 result[jss::error] == "noPermission",
334 result[jss::error].asString());
335 BEAST_EXPECT(
336 result[jss::error_message] ==
337 "You don't have permission for this command.");
338 }
339
340 {
341 std::string const feature =
342 "C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD"
343 "37";
344 Json::Value params;
345 params[jss::feature] = feature;
346 auto const result = env.rpc(
347 "json",
348 "feature",
349 boost::lexical_cast<std::string>(params))[jss::result];
350 BEAST_EXPECT(result.isMember(feature));
351 auto const amendmentResult = result[feature];
352 BEAST_EXPECT(amendmentResult[jss::enabled].asBool() == false);
353 BEAST_EXPECT(amendmentResult[jss::supported].asBool() == true);
354 BEAST_EXPECT(
355 amendmentResult[jss::name].asString() ==
356 "fixMasterKeyAsRegularKey");
357 }
358 }
359
360 void
362 {
363 testcase("No Params, Some Enabled");
364
365 using namespace test::jtx;
366 Env env{
367 *this, FeatureBitset(featureDepositAuth, featureDepositPreauth)};
368
371
372 auto jrr = env.rpc("feature")[jss::result];
373 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
374 return;
375 for (auto it = jrr[jss::features].begin();
376 it != jrr[jss::features].end();
377 ++it)
378 {
379 uint256 id;
380 (void)id.parseHex(it.key().asString().c_str());
381 if (!BEAST_EXPECT((*it).isMember(jss::name)))
382 return;
383 bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
384 bool expectSupported =
385 env.app().getAmendmentTable().isSupported(id);
386 bool expectVeto =
387 (votes.at((*it)[jss::name].asString()) ==
389 bool expectObsolete =
390 (votes.at((*it)[jss::name].asString()) ==
392 BEAST_EXPECTS(
393 (*it).isMember(jss::enabled) &&
394 (*it)[jss::enabled].asBool() == expectEnabled,
395 (*it)[jss::name].asString() + " enabled");
396 if (expectEnabled)
397 BEAST_EXPECTS(
398 !(*it).isMember(jss::vetoed),
399 (*it)[jss::name].asString() + " vetoed");
400 else
401 BEAST_EXPECTS(
402 (*it).isMember(jss::vetoed) &&
403 (*it)[jss::vetoed].isBool() == !expectObsolete &&
404 (!(*it)[jss::vetoed].isBool() ||
405 (*it)[jss::vetoed].asBool() == expectVeto) &&
406 ((*it)[jss::vetoed].isBool() ||
407 (*it)[jss::vetoed].asString() == "Obsolete"),
408 (*it)[jss::name].asString() + " vetoed");
409 BEAST_EXPECTS(
410 (*it).isMember(jss::supported) &&
411 (*it)[jss::supported].asBool() == expectSupported,
412 (*it)[jss::name].asString() + " supported");
413 }
414 }
415
416 void
418 {
419 testcase("With Majorities");
420
421 using namespace test::jtx;
422 Env env{*this, envconfig(validator, "")};
423
424 auto jrr = env.rpc("feature")[jss::result];
425 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
426 return;
427
428 // at this point, there are no majorities so no fields related to
429 // amendment voting
430 for (auto const& feature : jrr[jss::features])
431 {
432 if (!BEAST_EXPECT(feature.isMember(jss::name)))
433 return;
434 BEAST_EXPECTS(
435 !feature.isMember(jss::majority),
436 feature[jss::name].asString() + " majority");
437 BEAST_EXPECTS(
438 !feature.isMember(jss::count),
439 feature[jss::name].asString() + " count");
440 BEAST_EXPECTS(
441 !feature.isMember(jss::threshold),
442 feature[jss::name].asString() + " threshold");
443 BEAST_EXPECTS(
444 !feature.isMember(jss::validations),
445 feature[jss::name].asString() + " validations");
446 BEAST_EXPECTS(
447 !feature.isMember(jss::vote),
448 feature[jss::name].asString() + " vote");
449 }
450
451 auto majorities = getMajorityAmendments(*env.closed());
452 if (!BEAST_EXPECT(majorities.empty()))
453 return;
454
455 // close ledgers until the amendments show up.
456 for (auto i = 0; i <= 256; ++i)
457 {
458 env.close();
459 majorities = getMajorityAmendments(*env.closed());
460 if (!majorities.empty())
461 break;
462 }
463
464 // There should be at least 5 amendments. Don't do exact comparison
465 // to avoid maintenance as more amendments are added in the future.
466 BEAST_EXPECT(majorities.size() >= 5);
469
470 jrr = env.rpc("feature")[jss::result];
471 if (!BEAST_EXPECT(jrr.isMember(jss::features)))
472 return;
473 for (auto const& feature : jrr[jss::features])
474 {
475 if (!BEAST_EXPECT(feature.isMember(jss::name)))
476 return;
477 bool expectVeto =
478 (votes.at(feature[jss::name].asString()) ==
480 bool expectObsolete =
481 (votes.at(feature[jss::name].asString()) ==
483 BEAST_EXPECTS(
484 (expectVeto || expectObsolete) ^
485 feature.isMember(jss::majority),
486 feature[jss::name].asString() + " majority");
487 BEAST_EXPECTS(
488 feature.isMember(jss::vetoed) &&
489 feature[jss::vetoed].isBool() == !expectObsolete &&
490 (!feature[jss::vetoed].isBool() ||
491 feature[jss::vetoed].asBool() == expectVeto) &&
492 (feature[jss::vetoed].isBool() ||
493 feature[jss::vetoed].asString() == "Obsolete"),
494 feature[jss::name].asString() + " vetoed");
495 BEAST_EXPECTS(
496 feature.isMember(jss::count),
497 feature[jss::name].asString() + " count");
498 BEAST_EXPECTS(
499 feature.isMember(jss::threshold),
500 feature[jss::name].asString() + " threshold");
501 BEAST_EXPECTS(
502 feature.isMember(jss::validations),
503 feature[jss::name].asString() + " validations");
504 BEAST_EXPECT(
505 feature[jss::count] ==
506 ((expectVeto || expectObsolete) ? 0 : 1));
507 BEAST_EXPECT(feature[jss::threshold] == 1);
508 BEAST_EXPECT(feature[jss::validations] == 1);
509 BEAST_EXPECTS(
510 expectVeto || expectObsolete || feature[jss::majority] == 2540,
511 "Majority: " + feature[jss::majority].asString());
512 }
513 }
514
515 void
517 {
518 testcase("Veto");
519
520 using namespace test::jtx;
521 Env env{*this, FeatureBitset(featureMultiSignReserve)};
522 constexpr char const* featureName = "MultiSignReserve";
523
524 auto jrr = env.rpc("feature", featureName)[jss::result];
525 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
526 return;
527 jrr.removeMember(jss::status);
528 if (!BEAST_EXPECT(jrr.size() == 1))
529 return;
530 auto feature = *(jrr.begin());
531 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
532 BEAST_EXPECTS(
533 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
534 "vetoed");
535
536 jrr = env.rpc("feature", featureName, "reject")[jss::result];
537 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
538 return;
539 jrr.removeMember(jss::status);
540 if (!BEAST_EXPECT(jrr.size() == 1))
541 return;
542 feature = *(jrr.begin());
543 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
544 BEAST_EXPECTS(
545 feature[jss::vetoed].isBool() && feature[jss::vetoed].asBool(),
546 "vetoed");
547
548 jrr = env.rpc("feature", featureName, "accept")[jss::result];
549 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
550 return;
551 jrr.removeMember(jss::status);
552 if (!BEAST_EXPECT(jrr.size() == 1))
553 return;
554 feature = *(jrr.begin());
555 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
556 BEAST_EXPECTS(
557 feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(),
558 "vetoed");
559
560 // anything other than accept or reject is an error
561 jrr = env.rpc("feature", featureName, "maybe");
562 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
563 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
564 }
565
566 void
568 {
569 testcase("Obsolete");
570
571 using namespace test::jtx;
572 Env env{*this};
573 constexpr char const* featureName = "NonFungibleTokensV1";
574
575 auto jrr = env.rpc("feature", featureName)[jss::result];
576 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
577 return;
578 jrr.removeMember(jss::status);
579 if (!BEAST_EXPECT(jrr.size() == 1))
580 return;
581 auto feature = *(jrr.begin());
582 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
583 BEAST_EXPECTS(
584 feature[jss::vetoed].isString() &&
585 feature[jss::vetoed].asString() == "Obsolete",
586 "vetoed");
587
588 jrr = env.rpc("feature", featureName, "reject")[jss::result];
589 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
590 return;
591 jrr.removeMember(jss::status);
592 if (!BEAST_EXPECT(jrr.size() == 1))
593 return;
594 feature = *(jrr.begin());
595 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
596 BEAST_EXPECTS(
597 feature[jss::vetoed].isString() &&
598 feature[jss::vetoed].asString() == "Obsolete",
599 "vetoed");
600
601 jrr = env.rpc("feature", featureName, "accept")[jss::result];
602 if (!BEAST_EXPECTS(jrr[jss::status] == jss::success, "status"))
603 return;
604 jrr.removeMember(jss::status);
605 if (!BEAST_EXPECT(jrr.size() == 1))
606 return;
607 feature = *(jrr.begin());
608 BEAST_EXPECTS(feature[jss::name] == featureName, "name");
609 BEAST_EXPECTS(
610 feature[jss::vetoed].isString() &&
611 feature[jss::vetoed].asString() == "Obsolete",
612 "vetoed");
613
614 // anything other than accept or reject is an error
615 jrr = env.rpc("feature", featureName, "maybe");
616 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
617 BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
618 }
619
620public:
621 void
622 run() override
623 {
626 testNoParams();
629 testNonAdmin();
632 testVeto();
633 testObsolete();
634 }
635};
636
637BEAST_DEFINE_TESTSUITE(Feature, rpc, ripple);
638
639} // namespace ripple
T at(T... args)
Represents a JSON value.
Definition: json_value.h:150
Value removeMember(char const *key)
Remove and return the named member.
Definition: json_value.cpp:935
A testsuite class.
Definition: suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:533
void run() override
Runs the suite.
A transaction testing environment.
Definition: Env.h:121
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:770
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: rpc.h:35
@ nullValue
'null' value
Definition: json_value.h:39
@ arrayValue
array value (ordered list)
Definition: json_value.h:45
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:46
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
Definition: Feature.cpp:374
std::size_t numDownVotedAmendments()
Amendments that this server won't vote for by default.
Definition: Feature.cpp:367
std::map< std::string, VoteBehavior > const & supportedAmendments()
Amendments that this server supports and the default voting behavior.
Definition: Feature.cpp:360
std::unique_ptr< Config > validator(std::unique_ptr< Config >, std::string const &)
adjust configuration with params needed to be a validator
Definition: envconfig.cpp:113
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
uint256 bitsetIndexToFeature(size_t i)
Definition: Feature.cpp:415
size_t featureToBitsetIndex(uint256 const &f)
Definition: Feature.cpp:409
std::map< std::string, AmendmentSupport > const & allAmendments()
All amendments libxrpl knows about.
Definition: Feature.cpp:351
std::string featureToName(uint256 const &f)
Definition: Feature.cpp:421
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:382
majorityAmendments_t getMajorityAmendments(ReadView const &view)
Definition: View.cpp:925
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630