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