rippled
Loading...
Searching...
No Matches
PermissionedDomains_test.cpp
1#include <test/jtx.h>
2
3#include <xrpl/protocol/Feature.h>
4#include <xrpl/protocol/jss.h>
5#include <xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h>
6
7#include <exception>
8#include <map>
9#include <optional>
10#include <string>
11#include <utility>
12#include <vector>
13
14namespace xrpl {
15namespace test {
16
17using namespace jtx;
18
19static std::string
21{
22 try
23 {
24 env(jv, ter(temMALFORMED));
25 }
26 catch (std::exception const& ex)
27 {
28 return ex.what();
29 }
30 return {};
31}
32
34{
35 FeatureBitset withoutFeature_{testable_amendments() - featurePermissionedDomains};
38 | featurePermissionedDomains | featureCredentials};
39
42 | featurePermissionedDomains | featureCredentials};
43
44 // Verify that each tx type can execute if the feature is enabled.
45 void
47 {
48 testcase("Enabled");
49 Account const alice("alice");
50 Env env(*this, features);
51 env.fund(XRP(1000), alice);
52 pdomain::Credentials const credentials{{alice, "first credential"}};
53 env(pdomain::setTx(alice, credentials));
54 BEAST_EXPECT(env.ownerCount(alice) == 1);
55 auto objects = pdomain::getObjects(alice, env);
56 BEAST_EXPECT(objects.size() == 1);
57 // Test that account_objects is correct without passing it the type
58 BEAST_EXPECT(objects == pdomain::getObjects(alice, env, false));
59 auto const domain = objects.begin()->first;
60 env(pdomain::deleteTx(alice, domain));
61 }
62
63 // Verify that PD cannot be created or updated if credentials are disabled
64 void
66 {
67 auto amendments = testable_amendments();
68 amendments.set(featurePermissionedDomains);
69 amendments.reset(featureCredentials);
70 testcase("Credentials disabled");
71 Account const alice("alice");
72 Env env(*this, amendments);
73 env.fund(XRP(1000), alice);
74 pdomain::Credentials const credentials{{alice, "first credential"}};
75 env(pdomain::setTx(alice, credentials), ter(temDISABLED));
76 }
77
78 // Verify that each tx does not execute if feature is disabled
79 void
81 {
82 testcase("Disabled");
83 Account const alice("alice");
84 Env env(*this, withoutFeature_);
85 env.fund(XRP(1000), alice);
86 pdomain::Credentials const credentials{{alice, "first credential"}};
87 env(pdomain::setTx(alice, credentials), ter(temDISABLED));
88 env(pdomain::deleteTx(alice, uint256(75)), ter(temDISABLED));
89 }
90
91 // Verify that bad inputs fail for each of create new and update
92 // behaviors of PermissionedDomainSet
93 void
95 {
96 Account const alice2("alice2");
97 Account const alice3("alice3");
98 Account const alice4("alice4");
99 Account const alice5("alice5");
100 Account const alice6("alice6");
101 Account const alice7("alice7");
102 Account const alice8("alice8");
103 Account const alice9("alice9");
104 Account const alice10("alice10");
105 Account const alice11("alice11");
106 Account const alice12("alice12");
107 auto const setFee(drops(env.current()->fees().increment));
108
109 // Test empty credentials.
111
112 // Test 11 credentials.
113 pdomain::Credentials const credentials11{
114 {alice2, "credential1"},
115 {alice3, "credential2"},
116 {alice4, "credential3"},
117 {alice5, "credential4"},
118 {alice6, "credential5"},
119 {alice7, "credential6"},
120 {alice8, "credential7"},
121 {alice9, "credential8"},
122 {alice10, "credential9"},
123 {alice11, "credential10"},
124 {alice12, "credential11"}};
125 BEAST_EXPECT(credentials11.size() == maxPermissionedDomainCredentialsArraySize + 1);
126 env(pdomain::setTx(account, credentials11, domain), ter(temARRAY_TOO_LARGE));
127
128 // Test credentials including non-existent issuer.
129 Account const nobody("nobody");
130 pdomain::Credentials const credentialsNon{
131 {alice2, "credential1"},
132 {alice3, "credential2"},
133 {alice4, "credential3"},
134 {nobody, "credential4"},
135 {alice5, "credential5"},
136 {alice6, "credential6"},
137 {alice7, "credential7"}};
138 env(pdomain::setTx(account, credentialsNon, domain), ter(tecNO_ISSUER));
139
140 // Test bad fee
141 env(pdomain::setTx(account, credentials11, domain), fee(1, true), ter(temBAD_FEE));
142
143 pdomain::Credentials const credentials4{
144 {alice2, "credential1"},
145 {alice3, "credential2"},
146 {alice4, "credential3"},
147 {alice5, "credential4"},
148 };
149 auto txJsonMutable = pdomain::setTx(account, credentials4, domain);
150 auto const credentialOrig = txJsonMutable["AcceptedCredentials"][2u];
151
152 // Remove Issuer from a credential and apply.
153 txJsonMutable["AcceptedCredentials"][2u][jss::Credential].removeMember(jss::Issuer);
154 BEAST_EXPECT(exceptionExpected(env, txJsonMutable).starts_with("invalidParams"));
155
156 // Make an empty CredentialType.
157 txJsonMutable["AcceptedCredentials"][2u] = credentialOrig;
158 txJsonMutable["AcceptedCredentials"][2u][jss::Credential]["CredentialType"] = "";
159 env(txJsonMutable, ter(temMALFORMED));
160
161 // Make too long CredentialType.
162 constexpr std::string_view longCredentialType =
163 "Cred0123456789012345678901234567890123456789012345678901234567890";
164 static_assert(longCredentialType.size() == maxCredentialTypeLength + 1);
165 txJsonMutable["AcceptedCredentials"][2u] = credentialOrig;
166 txJsonMutable["AcceptedCredentials"][2u][jss::Credential]["CredentialType"] =
167 std::string(longCredentialType);
168 BEAST_EXPECT(exceptionExpected(env, txJsonMutable).starts_with("invalidParams"));
169
170 // Remove Credentialtype from a credential and apply.
171 txJsonMutable["AcceptedCredentials"][2u][jss::Credential].removeMember("CredentialType");
172 BEAST_EXPECT(exceptionExpected(env, txJsonMutable).starts_with("invalidParams"));
173
174 // Remove both
175 txJsonMutable["AcceptedCredentials"][2u][jss::Credential].removeMember(jss::Issuer);
176 BEAST_EXPECT(exceptionExpected(env, txJsonMutable).starts_with("invalidParams"));
177
178 // Make 2 identical credentials. Duplicates are not supported by
179 // permissioned domains, so transactions should return errors
180 {
181 pdomain::Credentials const credentialsDup{
182 {alice7, "credential6"},
183 {alice2, "credential1"},
184 {alice3, "credential2"},
185 {alice2, "credential1"},
186 {alice5, "credential4"},
187 };
188
190 for (auto const& c : credentialsDup)
191 human2Acc.emplace(c.issuer.human(), c.issuer);
192
193 auto const sorted = pdomain::sortCredentials(credentialsDup);
194 BEAST_EXPECT(sorted.size() == 4);
195 env(pdomain::setTx(account, credentialsDup, domain), ter(temMALFORMED));
196
197 env.close();
198 env(pdomain::setTx(account, sorted, domain));
199
200 uint256 d;
201 if (domain)
202 {
203 d = *domain;
204 }
205 else
206 {
207 d = pdomain::getNewDomain(env.meta());
208 }
209 env.close();
210 auto objects = pdomain::getObjects(account, env);
211 auto const fromObject = pdomain::credentialsFromJson(objects[d], human2Acc);
212 auto const sortedCreds = pdomain::sortCredentials(credentialsDup);
213 BEAST_EXPECT(fromObject == sortedCreds);
214 }
215
216 // Have equal issuers but different credentials and make sure they
217 // sort correctly.
218 {
219 pdomain::Credentials const credentialsSame{
220 {alice2, "credential3"},
221 {alice3, "credential2"},
222 {alice2, "credential9"},
223 {alice5, "credential4"},
224 {alice2, "credential6"},
225 };
227 for (auto const& c : credentialsSame)
228 human2Acc.emplace(c.issuer.human(), c.issuer);
229
230 BEAST_EXPECT(credentialsSame != pdomain::sortCredentials(credentialsSame));
231 env(pdomain::setTx(account, credentialsSame, domain));
232
233 uint256 d;
234 if (domain)
235 {
236 d = *domain;
237 }
238 else
239 {
240 d = pdomain::getNewDomain(env.meta());
241 }
242 env.close();
243 auto objects = pdomain::getObjects(account, env);
244 auto const fromObject = pdomain::credentialsFromJson(objects[d], human2Acc);
245 auto const sortedCreds = pdomain::sortCredentials(credentialsSame);
246 BEAST_EXPECT(fromObject == sortedCreds);
247 }
248 }
249
250 // Test PermissionedDomainSet
251 void
253 {
254 testcase("Set");
255 Env env(*this, features);
257
258 int const accNum = 12;
259 Account const alice[accNum] = {
260 "alice",
261 "alice2",
262 "alice3",
263 "alice4",
264 "alice5",
265 "alice6",
266 "alice7",
267 "alice8",
268 "alice9",
269 "alice10",
270 "alice11",
271 "alice12"};
273 for (auto const& c : alice)
274 human2Acc.emplace(c.human(), c);
275
276 for (int i = 0; i < accNum; ++i)
277 env.fund(XRP(1000), alice[i]);
278
279 // Create new from existing account with a single credential.
280 pdomain::Credentials const credentials1{{alice[2], "credential1"}};
281 {
282 env(pdomain::setTx(alice[0], credentials1));
283 BEAST_EXPECT(env.ownerCount(alice[0]) == 1);
284 auto tx = env.tx()->getJson(JsonOptions::none);
285 BEAST_EXPECT(tx[jss::TransactionType] == "PermissionedDomainSet");
286 BEAST_EXPECT(tx["Account"] == alice[0].human());
287 auto objects = pdomain::getObjects(alice[0], env);
288 auto domain = objects.begin()->first;
289 BEAST_EXPECT(domain.isNonZero());
290 auto object = objects.begin()->second;
291 BEAST_EXPECT(object["LedgerEntryType"] == "PermissionedDomain");
292 BEAST_EXPECT(object["Owner"] == alice[0].human());
293 BEAST_EXPECT(object["Sequence"] == tx["Sequence"]);
294 BEAST_EXPECT(pdomain::credentialsFromJson(object, human2Acc) == credentials1);
295 }
296
297 // Make longest possible CredentialType.
298 {
299 constexpr std::string_view longCredentialType =
300 "Cred0123456789012345678901234567890123456789012345678901234567"
301 "89";
302 static_assert(longCredentialType.size() == maxCredentialTypeLength);
303 pdomain::Credentials const longCredentials{{alice[1], std::string(longCredentialType)}};
304
305 env(pdomain::setTx(alice[0], longCredentials));
306
307 // One account can create multiple domains
308 BEAST_EXPECT(env.ownerCount(alice[0]) == 2);
309
310 auto tx = env.tx()->getJson(JsonOptions::none);
311 BEAST_EXPECT(tx[jss::TransactionType] == "PermissionedDomainSet");
312 BEAST_EXPECT(tx["Account"] == alice[0].human());
313
314 bool findSeq = false;
315 for (auto const& [domain, object] : pdomain::getObjects(alice[0], env))
316 {
317 findSeq = object["Sequence"] == tx["Sequence"];
318 if (findSeq)
319 {
320 BEAST_EXPECT(domain.isNonZero());
321 BEAST_EXPECT(object["LedgerEntryType"] == "PermissionedDomain");
322 BEAST_EXPECT(object["Owner"] == alice[0].human());
323 BEAST_EXPECT(
324 pdomain::credentialsFromJson(object, human2Acc) == longCredentials);
325 break;
326 }
327 }
328 BEAST_EXPECT(findSeq);
329 }
330
331 // Create new from existing account with 10 credentials.
332 // Last credential describe domain owner itself
333 pdomain::Credentials const credentials10{
334 {alice[2], "credential1"},
335 {alice[3], "credential2"},
336 {alice[4], "credential3"},
337 {alice[5], "credential4"},
338 {alice[6], "credential5"},
339 {alice[7], "credential6"},
340 {alice[8], "credential7"},
341 {alice[9], "credential8"},
342 {alice[10], "credential9"},
343 {alice[0], "credential10"},
344 };
345 uint256 domain2;
346 {
347 BEAST_EXPECT(credentials10.size() == maxPermissionedDomainCredentialsArraySize);
348 BEAST_EXPECT(credentials10 != pdomain::sortCredentials(credentials10));
349 env(pdomain::setTx(alice[0], credentials10));
350 auto tx = env.tx()->getJson(JsonOptions::none);
351 domain2 = pdomain::getNewDomain(env.meta());
352 auto objects = pdomain::getObjects(alice[0], env);
353 auto object = objects[domain2];
354 BEAST_EXPECT(
355 pdomain::credentialsFromJson(object, human2Acc) ==
356 pdomain::sortCredentials(credentials10));
357 }
358
359 // Update with 1 credential.
360 env(pdomain::setTx(alice[0], credentials1, domain2));
361 BEAST_EXPECT(
362 pdomain::credentialsFromJson(pdomain::getObjects(alice[0], env)[domain2], human2Acc) ==
363 credentials1);
364
365 // Update with 10 credentials.
366 env(pdomain::setTx(alice[0], credentials10, domain2));
367 env.close();
368 BEAST_EXPECT(
369 pdomain::credentialsFromJson(pdomain::getObjects(alice[0], env)[domain2], human2Acc) ==
370 pdomain::sortCredentials(credentials10));
371
372 // Update from the wrong owner.
373 env(pdomain::setTx(alice[2], credentials1, domain2), ter(tecNO_PERMISSION));
374
375 // Update a uint256(0) domain
376 env(pdomain::setTx(alice[0], credentials1, uint256(0)), ter(temMALFORMED));
377
378 // Update non-existent domain
379 env(pdomain::setTx(alice[0], credentials1, uint256(75)), ter(tecNO_ENTRY));
380
381 // Wrong flag
382 env(pdomain::setTx(alice[0], credentials1), txflags(tfClawTwoAssets), ter(temINVALID_FLAG));
383
384 // Test bad data when creating a domain.
385 testBadData(alice[0], env);
386 // Test bad data when updating a domain.
387 testBadData(alice[0], env, domain2);
388
389 // Try to delete the account with domains.
390 auto const acctDelFee(drops(env.current()->fees().increment));
391 constexpr std::size_t deleteDelta = 255;
392 {
393 // Close enough ledgers to make it potentially deletable if empty.
394 std::size_t const ownerSeq = env.seq(alice[0]);
395 while (deleteDelta + ownerSeq > env.current()->seq())
396 env.close();
397 env(acctdelete(alice[0], alice[2]), fee(acctDelFee), ter(tecHAS_OBLIGATIONS));
398 }
399
400 {
401 // Delete the domains and then the owner account.
402 for (auto const& objs : pdomain::getObjects(alice[0], env))
403 env(pdomain::deleteTx(alice[0], objs.first));
404 env.close();
405 std::size_t const ownerSeq = env.seq(alice[0]);
406 while (deleteDelta + ownerSeq > env.current()->seq())
407 env.close();
408 env(acctdelete(alice[0], alice[2]), fee(acctDelFee));
409 }
410 }
411
412 // Test PermissionedDomainDelete
413 void
415 {
416 testcase("Delete");
417 Env env(*this, features);
418 Account const alice("alice");
419
420 env.fund(XRP(1000), alice);
421 auto const setFee(drops(env.current()->fees().increment));
422
423 pdomain::Credentials const credentials{{alice, "first credential"}};
424 env(pdomain::setTx(alice, credentials));
425 env.close();
426
427 auto objects = pdomain::getObjects(alice, env);
428 BEAST_EXPECT(objects.size() == 1);
429 auto const domain = objects.begin()->first;
430
431 // Delete a domain that doesn't belong to the account.
432 Account const bob("bob");
433 env.fund(XRP(1000), bob);
435
436 // Delete a non-existent domain.
437 env(pdomain::deleteTx(alice, uint256(75)), ter(tecNO_ENTRY));
438
439 // Test bad fee
440 env(pdomain::deleteTx(alice, uint256(75)), ter(temBAD_FEE), fee(1, true));
441
442 // Wrong flag
443 env(pdomain::deleteTx(alice, domain), ter(temINVALID_FLAG), txflags(tfClawTwoAssets));
444
445 // Delete a zero domain.
446 env(pdomain::deleteTx(alice, uint256(0)), ter(temMALFORMED));
447
448 // Make sure owner count reflects the existing domain.
449 BEAST_EXPECT(env.ownerCount(alice) == 1);
450 auto const objID = pdomain::getObjects(alice, env).begin()->first;
451 BEAST_EXPECT(pdomain::objectExists(objID, env));
452
453 // Delete domain that belongs to user.
454 env(pdomain::deleteTx(alice, domain));
455 auto const tx = env.tx()->getJson(JsonOptions::none);
456 BEAST_EXPECT(tx[jss::TransactionType] == "PermissionedDomainDelete");
457
458 // Make sure the owner count goes back to 0.
459 BEAST_EXPECT(env.ownerCount(alice) == 0);
460
461 // The object needs to be gone.
462 BEAST_EXPECT(pdomain::getObjects(alice, env).empty());
463 BEAST_EXPECT(!pdomain::objectExists(objID, env));
464 }
465
466 void
468 {
469 // Verify that the reserve behaves as expected for creating.
470 testcase("Account Reserve");
471
472 using namespace test::jtx;
473
474 Env env(*this, features);
475 Account const alice("alice");
476
477 // Fund alice enough to exist, but not enough to meet
478 // the reserve.
479 auto const acctReserve = env.current()->fees().reserve;
480 auto const incReserve = env.current()->fees().increment;
481 env.fund(acctReserve, alice);
482 env.close();
483 BEAST_EXPECT(env.balance(alice) == acctReserve);
484 BEAST_EXPECT(env.ownerCount(alice) == 0);
485
486 // alice does not have enough XRP to cover the reserve.
487 pdomain::Credentials const credentials{{alice, "first credential"}};
488 env(pdomain::setTx(alice, credentials), ter(tecINSUFFICIENT_RESERVE));
489 BEAST_EXPECT(env.ownerCount(alice) == 0);
490 BEAST_EXPECT(pdomain::getObjects(alice, env).empty());
491 env.close();
492
493 auto const baseFee = env.current()->fees().base.drops();
494
495 // Pay alice almost enough to make the reserve.
496 env(pay(env.master, alice, incReserve + drops(2 * baseFee) - drops(1)));
497 BEAST_EXPECT(env.balance(alice) == acctReserve + incReserve + drops(baseFee) - drops(1));
498 env.close();
499
500 // alice still does not have enough XRP for the reserve.
501 env(pdomain::setTx(alice, credentials), ter(tecINSUFFICIENT_RESERVE));
502 env.close();
503 BEAST_EXPECT(env.ownerCount(alice) == 0);
504
505 // Pay alice enough to make the reserve.
506 env(pay(env.master, alice, drops(baseFee) + drops(1)));
507 env.close();
508
509 // Now alice can create a PermissionedDomain.
510 env(pdomain::setTx(alice, credentials));
511 env.close();
512 BEAST_EXPECT(env.ownerCount(alice) == 1);
513 }
514
515public:
516 void
530};
531
532BEAST_DEFINE_TESTSUITE(PermissionedDomains, app, xrpl);
533
534} // namespace test
535} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
void testBadData(Account const &account, Env &env, std::optional< uint256 > domain=std::nullopt)
Immutable cryptographic account descriptor.
Definition Account.h:19
A transaction testing environment.
Definition Env.h:122
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
Definition Env.cpp:240
void set_parse_failure_expected(bool b)
Definition Env.h:456
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:249
Account const & master
Definition Env.h:126
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:168
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition Env.cpp:483
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition Env.cpp:505
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:329
Set the domain on a JTx.
Definition domain.h:11
Set the fee on a JTx.
Definition fee.h:17
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:15
Set the flags on a JTx.
Definition txflags.h:11
T emplace(T... args)
T is_same_v
Credentials credentialsFromJson(Json::Value const &object, std::unordered_map< std::string, Account > const &human2Acc)
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
Json::Value deleteTx(AccountID const &account, uint256 const &domain)
uint256 getNewDomain(std::shared_ptr< STObject const > const &meta)
Credentials sortCredentials(Credentials const &input)
std::map< uint256, Json::Value > getObjects(Account const &account, Env &env, bool withType)
bool objectExists(uint256 const &objID, Env &env)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:95
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
FeatureBitset testable_amendments()
Definition Env.h:78
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
static std::string exceptionExpected(Env &env, Json::Value const &jv)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
base_uint< 256 > uint256
Definition base_uint.h:531
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:228
@ temARRAY_TOO_LARGE
Definition TER.h:121
@ temBAD_FEE
Definition TER.h:72
@ temINVALID_FLAG
Definition TER.h:91
@ temMALFORMED
Definition TER.h:67
@ temARRAY_EMPTY
Definition TER.h:120
@ temDISABLED
Definition TER.h:94
@ tecNO_ENTRY
Definition TER.h:287
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
@ tecNO_PERMISSION
Definition TER.h:286
@ tecNO_ISSUER
Definition TER.h:280
@ tecHAS_OBLIGATIONS
Definition TER.h:298
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:221
T size(T... args)
T what(T... args)