xrpld
Loading...
Searching...
No Matches
Delegate_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/CaptureLogs.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/acctdelete.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/balance.h>
8#include <test/jtx/batch.h>
9#include <test/jtx/delegate.h>
10#include <test/jtx/delivermin.h>
11#include <test/jtx/did.h>
12#include <test/jtx/domain.h>
13#include <test/jtx/fee.h>
14#include <test/jtx/flags.h>
15#include <test/jtx/mpt.h>
16#include <test/jtx/multisign.h>
17#include <test/jtx/noop.h>
18#include <test/jtx/offer.h>
19#include <test/jtx/paths.h>
20#include <test/jtx/pay.h>
21#include <test/jtx/permissioned_domains.h>
22#include <test/jtx/rate.h>
23#include <test/jtx/regkey.h>
24#include <test/jtx/sendmax.h>
25#include <test/jtx/sig.h>
26#include <test/jtx/sponsor.h>
27#include <test/jtx/ter.h>
28#include <test/jtx/trust.h>
29#include <test/jtx/txflags.h>
30#include <test/jtx/vault.h>
31
32#include <xrpl/basics/Number.h>
33#include <xrpl/basics/Slice.h>
34#include <xrpl/basics/base_uint.h>
35#include <xrpl/basics/strHex.h>
36#include <xrpl/beast/unit_test/suite.h>
37#include <xrpl/json/json_value.h>
38#include <xrpl/json/to_string.h>
39#include <xrpl/ledger/Dir.h>
40#include <xrpl/ledger/helpers/DelegateHelpers.h>
41#include <xrpl/protocol/Feature.h>
42#include <xrpl/protocol/Indexes.h>
43#include <xrpl/protocol/Issue.h>
44#include <xrpl/protocol/KeyType.h>
45#include <xrpl/protocol/Permissions.h>
46#include <xrpl/protocol/SField.h>
47#include <xrpl/protocol/STObject.h>
48#include <xrpl/protocol/STTx.h>
49#include <xrpl/protocol/SecretKey.h>
50#include <xrpl/protocol/TER.h>
51#include <xrpl/protocol/TxFlags.h>
52#include <xrpl/protocol/TxSettings.h>
53#include <xrpl/protocol/XRPAmount.h>
54#include <xrpl/protocol/jss.h>
55
56#include <algorithm>
57#include <cstddef>
58#include <cstdint>
59#include <limits>
60#include <memory>
61#include <string>
62#include <tuple>
63#include <unordered_map>
64#include <unordered_set>
65#include <vector>
66
67namespace xrpl::test {
69{
70 void
72 {
73 testcase("test feature not enabled");
74 using namespace jtx;
75
76 Env env{*this, features};
77 Account const gw{"gateway"};
78 Account const alice{"alice"};
79 Account const bob{"bob"};
80 env.fund(XRP(1000000), gw, alice, bob);
81 env.close();
82
83 auto res = features[featurePermissionDelegationV1_1] ? Ter(tesSUCCESS) : Ter(temDISABLED);
84
85 // can not set Delegate when feature disabled
86 env(delegate::set(gw, alice, {"Payment"}), res);
87 env.close();
88
89 // can not send delegating transaction when feature disabled
90 env(pay(gw, bob, XRP(100)), delegate::As(alice), res);
91 }
92
93 void
95 {
96 testcase("test valid request creating, updating, deleting permissions");
97 using namespace jtx;
98
99 Env env(*this);
100 Account const gw{"gateway"};
101 Account const alice{"alice"};
102 env.fund(XRP(100000), gw, alice);
103 env.close();
104
105 // delegating an empty permission list when the delegate ledger object
106 // does not exist is not allowed
107 env(delegate::set(gw, alice, {}), Ter(tecNO_ENTRY));
108 env.close();
109
110 auto const permissions = std::vector<std::string>{
111 "Payment", "EscrowCreate", "EscrowFinish", "TrustlineAuthorize", "CheckCreate"};
112 env(delegate::set(gw, alice, permissions));
113 env.close();
114
115 // this lambda function is used to compare the json value of ledger
116 // entry response with the given vector of permissions.
117 auto comparePermissions = [&](json::Value const& jle,
118 std::vector<std::string> const& permissions,
119 Account const& account,
120 Account const& authorize) {
121 BEAST_EXPECT(
122 !jle[jss::result].isMember(jss::error) && jle[jss::result].isMember(jss::node));
123 BEAST_EXPECT(jle[jss::result][jss::node]["LedgerEntryType"] == jss::Delegate);
124 BEAST_EXPECT(jle[jss::result][jss::node][jss::Account] == account.human());
125 BEAST_EXPECT(jle[jss::result][jss::node][sfAuthorize.jsonName] == authorize.human());
126
127 auto const& jPermissions = jle[jss::result][jss::node][sfPermissions.jsonName];
128 unsigned i = 0;
129 for (auto const& permission : permissions)
130 {
131 BEAST_EXPECT(
132 jPermissions[i][sfPermission.jsonName][sfPermissionValue.jsonName] ==
133 permission);
134 i++;
135 }
136 };
137
138 // get ledger entry with valid parameter
139 comparePermissions(delegate::entry(env, gw, alice), permissions, gw, alice);
140
141 // gw updates permission
142 auto const newPermissions =
143 std::vector<std::string>{"Payment", "AMMCreate", "AMMDeposit", "AMMWithdraw"};
144 env(delegate::set(gw, alice, newPermissions));
145 env.close();
146
147 // get ledger entry again, permissions should be updated to
148 // newPermissions
149 comparePermissions(delegate::entry(env, gw, alice), newPermissions, gw, alice);
150
151 // gw deletes all permissions delegated to alice, this will delete the ledger entry
152 env(delegate::set(gw, alice, {}));
153 env.close();
154 auto const jle = delegate::entry(env, gw, alice);
155 BEAST_EXPECT(jle[jss::result][jss::error] == "entryNotFound");
156
157 // alice can delegate permissions to gw as well
158 env(delegate::set(alice, gw, permissions));
159 env.close();
160 comparePermissions(delegate::entry(env, alice, gw), permissions, alice, gw);
161 auto const response = delegate::entry(env, gw, alice);
162 // alice has not been granted any permissions by gw
163 BEAST_EXPECT(response[jss::result][jss::error] == "entryNotFound");
164 }
165
166 void
168 {
169 testcase("test invalid DelegateSet");
170 using namespace jtx;
171
172 Env env(*this, features);
173 Account const gw{"gateway"};
174 Account const alice{"alice"};
175 Account const bob{"bob"};
176 env.fund(XRP(100000), gw, alice, bob);
177 env.close();
178
179 // when permissions size exceeds the limit 10, should return
180 // temARRAY_TOO_LARGE
181 {
182 env(delegate::set(
183 gw,
184 alice,
185 {"Payment",
186 "EscrowCreate",
187 "EscrowFinish",
188 "EscrowCancel",
189 "CheckCreate",
190 "CheckCash",
191 "CheckCancel",
192 "DepositPreauth",
193 "TrustSet",
194 "NFTokenMint",
195 "NFTokenBurn"}),
197 }
198
199 // alice can not authorize herself
200 {
201 env(delegate::set(alice, alice, {"Payment"}), Ter(temMALFORMED));
202 }
203
204 // bad fee
205 {
206 json::Value jv;
207 jv[jss::TransactionType] = jss::DelegateSet;
208 jv[jss::Account] = gw.human();
209 jv[sfAuthorize.jsonName] = alice.human();
210 json::Value permissionsJson(json::ValueType::Array);
211 json::Value permissionValue;
212 permissionValue[sfPermissionValue.jsonName] = "Payment";
213 json::Value permissionObj;
214 permissionObj[sfPermission.jsonName] = permissionValue;
215 permissionsJson.append(permissionObj);
216 jv[sfPermissions.jsonName] = permissionsJson;
217 jv[sfFee.jsonName] = -1;
218 env(jv, Ter(temBAD_FEE));
219 }
220
221 // when provided permissions contains duplicate values, should return
222 // temMALFORMED
223 {
224 env(delegate::set(
225 gw,
226 alice,
227 {"Payment",
228 "EscrowCreate",
229 "EscrowFinish",
230 "TrustlineAuthorize",
231 "CheckCreate",
232 "TrustlineAuthorize"}),
234 }
235
236 // when authorizing account which does not exist, should return
237 // tecNO_TARGET
238 {
239 env(delegate::set(gw, Account("unknown"), {"Payment"}), Ter(tecNO_TARGET));
240 }
241
242 // Delegating to a pseudo-account is not allowed, should return tecPSEUDO_ACCOUNT
243 {
244 Vault const vault{env};
245 auto [tx, keylet] = vault.create({.owner = gw, .asset = xrpIssue()});
246 env(tx);
247 env.close();
248
249 auto const sleVault = env.le(keylet);
250 BEAST_EXPECT(sleVault);
251 Account const vaultPseudo{"vault", sleVault->at(sfAccount)};
252 env(delegate::set(gw, vaultPseudo, {"Payment"}), Ter(tecPSEUDO_ACCOUNT));
253 }
254
255 // non-delegable transaction
256 {
257 env(delegate::set(gw, alice, {"SetRegularKey"}), Ter(temMALFORMED));
258 env(delegate::set(gw, alice, {"AccountSet"}), Ter(temMALFORMED));
259 env(delegate::set(gw, alice, {"SignerListSet"}), Ter(temMALFORMED));
260 env(delegate::set(gw, alice, {"DelegateSet"}), Ter(temMALFORMED));
261 env(delegate::set(gw, alice, {"EnableAmendment"}), Ter(temMALFORMED));
262 env(delegate::set(gw, alice, {"UNLModify"}), Ter(temMALFORMED));
263 env(delegate::set(gw, alice, {"SetFee"}), Ter(temMALFORMED));
264 env(delegate::set(gw, alice, {"Batch"}), Ter(temMALFORMED));
265 }
266 }
267
268 void
270 {
271 testcase("test reserve");
272 using namespace jtx;
273
274 // reserve requirement not met
275 {
276 Env env(*this);
277 Account const alice{"alice"};
278 Account const bob{"bob"};
279
280 auto const txFee = env.current()->fees().base;
281 env.fund(env.current()->fees().accountReserve(0, 1) + txFee, alice);
282 env.fund(XRP(100000), bob);
283 env.close();
284
285 // alice does not have enough reserve to create Delegate
286 env(delegate::set(alice, bob, {"Payment"}), Ter(tecINSUFFICIENT_RESERVE));
287 }
288
289 // reserve recovered after deleting delegation object
290 {
291 Env env(*this);
292 Account const bob{"bob"};
293 Account const alice{"alice"};
294 Account const carol{"carol"};
295
296 auto const txFee = env.current()->fees().base;
297
298 env.fund(env.current()->fees().accountReserve(1, 1) + (txFee * 4), alice);
299 env.fund(XRP(100000), bob, carol);
300 env.close();
301
302 // alice consumes 1 txFee and requires 1 object reserve
303 env(delegate::set(alice, bob, {"Payment"}));
304 env.close();
305
306 // alice does not have enough reserve to create another delegation object
307 env(delegate::set(alice, carol, {"Payment"}), Ter(tecINSUFFICIENT_RESERVE));
308 env.close();
309
310 // deleting delegation object recovers 1 reserve
311 env(delegate::set(alice, bob, {}));
312 env.close();
313
314 // now alice can delegate again
315 env(delegate::set(alice, carol, {"Payment"}));
316 }
317
318 // test reserve when sending transaction on behalf of other account
319 {
320 Env env(*this);
321 Account const alice{"alice"};
322 Account const bob{"bob"};
323
324 env.fund(drops(env.current()->fees().accountReserve(1, 1)), alice);
325 env.fund(drops(env.current()->fees().accountReserve(2, 1)), bob);
326 env.close();
327
328 // alice gives bob permission
329 env(delegate::set(alice, bob, {"DIDSet", "DIDDelete"}));
330 env.close();
331
332 // bob set DID on behalf of alice, but alice does not have enough
333 // reserve
334 env(did::set(alice), did::Uri("uri"), delegate::As(bob), Ter(tecINSUFFICIENT_RESERVE));
335
336 // bob can set DID for himself because he has enough reserve
337 env(did::set(bob), did::Uri("uri"));
338 env.close();
339 }
340 }
341
342 void
344 {
345 testcase("test fee");
346 using namespace jtx;
347
348 // Common setup: fund alice, bob, carol with 1000 XRP.
349 auto setup = [&](Env& env) {
350 Account const alice{"alice"};
351 Account const bob{"bob"};
352 Account const carol{"carol"};
353 env.fund(XRP(1000), alice, bob, carol);
354 env.close();
355 return std::make_tuple(alice, bob, carol);
356 };
357
358 // No fee deduction for terNO_DELEGATE_PERMISSION.
359 {
360 Env env(*this);
361 auto [alice, bob, carol] = setup(env);
362
363 auto const aliceBalance = env.balance(alice);
364 auto const bobBalance = env.balance(bob);
365 auto const carolBalance = env.balance(carol);
366
367 env(pay(alice, carol, XRP(100)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
368 env.close();
369 BEAST_EXPECT(env.balance(alice) == aliceBalance);
370 BEAST_EXPECT(env.balance(bob) == bobBalance);
371 BEAST_EXPECT(env.balance(carol) == carolBalance);
372 }
373
374 // Delegate pays the fee successfully.
375 {
376 Env env(*this);
377 auto [alice, bob, carol] = setup(env);
378 env(delegate::set(alice, bob, {"Payment"}));
379 env.close();
380
381 auto const aliceBalance = env.balance(alice);
382 auto const bobBalance = env.balance(bob);
383 auto const carolBalance = env.balance(carol);
384
385 auto const sendAmt = XRP(100);
386 auto const feeAmt = XRP(10);
387 env(pay(alice, carol, sendAmt), Fee(feeAmt), delegate::As(bob));
388 env.close();
389 BEAST_EXPECT(env.balance(alice) == aliceBalance - sendAmt);
390 BEAST_EXPECT(env.balance(bob) == bobBalance - feeAmt);
391 BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt);
392 }
393
394 // Bob has insufficient balance to pay the fee, will get terINSUF_FEE_B.
395 {
396 Env env(*this);
397 auto [alice, bob, carol] = setup(env);
398 env(delegate::set(alice, bob, {"Payment"}));
399 env.close();
400
401 auto const aliceBalance = env.balance(alice);
402 auto const bobBalance = env.balance(bob);
403 auto const carolBalance = env.balance(carol);
404
405 env(pay(alice, carol, XRP(100)),
406 Fee(XRP(2000)),
407 delegate::As(bob),
409 env.close();
410 BEAST_EXPECT(env.balance(alice) == aliceBalance);
411 BEAST_EXPECT(env.balance(bob) == bobBalance);
412 BEAST_EXPECT(env.balance(carol) == carolBalance);
413 }
414
415 // The delegated account has enough balance to pay and delegator has enough reserve
416 {
417 // Common setup: fund accounts and grant Bob permission to pay on Alice's behalf.
418 // Alice is funded with exactly (paymentAmount + reserve + baseFee): baseFee covers
419 // the DelegateSet tx cost, leaving Alice with exactly (paymentAmount + reserve).
420 // highFee = reserve + baseFee, strictly greater than reserve, so that
421 // max(reserve, highFee) = highFee — making the direct payment check fail.
422 auto setup = [&](Env& env) {
423 Account const alice{"alice"};
424 Account const bob{"bob"};
425 Account const carol{"carol"};
426
427 auto const baseFee = env.current()->fees().base;
428 auto const reserve = env.current()->fees().accountReserve(1, 1);
429 auto const paymentAmount = XRP(1);
430 auto const highFee = reserve + baseFee;
431 BEAST_EXPECT(highFee > reserve);
432
433 env.fund(paymentAmount + reserve + baseFee, alice);
434 env.fund(XRP(1000), bob);
435 env.fund(XRP(1000), carol);
436 env.close();
437
438 env(delegate::set(alice, bob, {"Payment"}));
439 env.close();
440
441 env.require(Balance(alice, paymentAmount + reserve));
442
443 return std::make_tuple(alice, bob, carol, paymentAmount, highFee, reserve);
444 };
445
446 // Alice's balance (paymentAmount + reserve) is insufficient to cover both
447 // the payment and highFee directly. Even though fees are allowed to dip
448 // below reserve, when Alice pays the fee herself the required funds =
449 // paymentAmount + max(reserve, highFee) = paymentAmount + highFee
450 // (since highFee > reserve), which still exceeds her balance.
451 // tec: highFee is consumed from Alice's balance.
452 {
453 Env env(*this);
454 auto [alice, bob, carol, paymentAmount, highFee, reserve] = setup(env);
455 auto const aliceBalance = env.balance(alice);
456 auto const bobBalance = env.balance(bob);
457 auto const carolBalance = env.balance(carol);
458
459 env(pay(alice, carol, paymentAmount), Fee(highFee), Ter(tecUNFUNDED_PAYMENT));
460
461 // tec consumes the fee from Alice; carol and bob are unaffected.
462 BEAST_EXPECT(env.balance(alice) == aliceBalance - highFee);
463 BEAST_EXPECT(env.balance(bob) == bobBalance);
464 BEAST_EXPECT(env.balance(carol) == carolBalance);
465 }
466
467 // The payment succeeds because the delegated account pays the fee.
468 // Alice only needs (paymentAmount + reserve).
469 {
470 Env env(*this);
471 auto [alice, bob, carol, paymentAmount, highFee, reserve] = setup(env);
472
473 auto const alicePrePay = env.balance(alice, XRP);
474 auto const bobPrePay = env.balance(bob, XRP);
475 auto const carolPrePay = env.balance(carol, XRP);
476
477 env(pay(alice, carol, paymentAmount), delegate::As(bob), Fee(highFee));
478 env.close();
479
480 env.require(Balance(alice, alicePrePay - paymentAmount));
481 env.require(Balance(bob, bobPrePay - highFee));
482 env.require(Balance(carol, carolPrePay + paymentAmount));
483 }
484 }
485
486 // Delegated account can pay the fee even if it dips below reserve.
487 {
488 Env env(*this);
489 Account const alice{"alice"};
490 Account const bob{"bob"};
491 Account const carol{"carol"};
492
493 auto const baseFee = env.current()->fees().base;
494 auto const baseReserve = env.current()->fees().accountReserve(0, 1);
495
496 env.fund(env.current()->fees().accountReserve(1, 1) + baseFee + XRP(1), alice);
497 env.fund(baseReserve, bob);
498 env.fund(XRP(1000), carol);
499 env.close();
500
501 env(delegate::set(alice, bob, {"Payment"}));
502 env.close();
503
504 auto const alicePreTx = env.balance(alice, XRP);
505 auto const bobPreTx = env.balance(bob, XRP);
506
507 // After paying for this transaction, bob's balance will
508 // dip below the base reserve
509 env(pay(alice, carol, XRP(1)), delegate::As(bob));
510 env.close();
511
512 // Bob's balance is now less than the base reserve.
513 BEAST_EXPECT(env.balance(bob, XRP) < baseReserve);
514 env.require(Balance(bob, bobPreTx - drops(baseFee)));
515
516 // Alice's balance only decreased by the 1.0 XRP she sent.
517 env.require(Balance(alice, alicePreTx - XRP(1)));
518 }
519
520 // The delegated account has enough balance for the fee, but delegator
521 // runs into tecUNFUNDED_PAYMENT.
522 {
523 Env env(*this);
524 Account const alice{"alice"};
525 Account const bob{"bob"};
526 Account const carol{"carol"};
527
528 auto const baseFee = env.current()->fees().base;
529 auto const reserve = env.current()->fees().accountReserve(1, 1);
530
531 // Alice is funded with (reserve + baseFee): after DelegateSet she has
532 // exactly 'reserve', which is insufficient to send XRP(10) while keeping
533 // reserve. Bob has plenty to pay the fee.
534 env.fund(reserve + baseFee, alice);
535 env.fund(XRP(1000), bob);
536 env.fund(XRP(1000), carol);
537 env.close();
538
539 env(delegate::set(alice, bob, {"Payment"}));
540 env.close();
541
542 auto const alicePrePay = env.balance(alice, XRP);
543 auto const bobPrePay = env.balance(bob, XRP);
544 auto const carolPrePay = env.balance(carol, XRP);
545
546 // Bob pays the fee, but Alice has insufficient balance to send XRP(10).
547 env(pay(alice, carol, XRP(10)), delegate::As(bob), Ter(tecUNFUNDED_PAYMENT));
548
549 env.require(Balance(alice, alicePrePay));
550 env.require(Balance(bob, bobPrePay - drops(baseFee)));
551 env.require(Balance(carol, carolPrePay));
552 }
553 }
554
555 void
557 {
558 testcase("test sequence");
559 using namespace jtx;
560
561 Env env(*this);
562 Account const alice{"alice"};
563 Account const bob{"bob"};
564 Account const carol{"carol"};
565 env.fund(XRP(10000), alice, bob, carol);
566 env.close();
567
568 auto aliceSeq = env.seq(alice);
569 auto bobSeq = env.seq(bob);
570 env(delegate::set(alice, bob, {"Payment"}));
571 env(delegate::set(bob, alice, {"Payment"}));
572 env.close();
573 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
574 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
575 aliceSeq = env.seq(alice);
576 bobSeq = env.seq(bob);
577
578 for (auto i = 0; i < 20; ++i)
579 {
580 // bob is the delegated account, his sequence won't kIncrement
581 env(pay(alice, carol, XRP(10)), Fee(XRP(10)), delegate::As(bob));
582 env.close();
583 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
584 BEAST_EXPECT(env.seq(bob) == bobSeq);
585 aliceSeq = env.seq(alice);
586
587 // bob sends payment for himself, his sequence will kIncrement
588 env(pay(bob, carol, XRP(10)), Fee(XRP(10)));
589 BEAST_EXPECT(env.seq(alice) == aliceSeq);
590 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
591 bobSeq = env.seq(bob);
592
593 // alice is the delegated account, her sequence won't kIncrement
594 env(pay(bob, carol, XRP(10)), Fee(XRP(10)), delegate::As(alice));
595 env.close();
596 BEAST_EXPECT(env.seq(alice) == aliceSeq);
597 BEAST_EXPECT(env.seq(bob) == bobSeq + 1);
598 bobSeq = env.seq(bob);
599
600 // alice sends payment for herself, her sequence will kIncrement
601 env(pay(alice, carol, XRP(10)), Fee(XRP(10)));
602 BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
603 BEAST_EXPECT(env.seq(bob) == bobSeq);
604 aliceSeq = env.seq(alice);
605 }
606 }
607
608 void
610 {
611 testcase("test deleting account");
612 using namespace jtx;
613
614 // Delegator (alice) deletes account: Delegate object is cleaned up from
615 // both alice's and bob's owner directories.
616 {
617 Env env(*this);
618 Account const alice{"alice"};
619 Account const bob{"bob"};
620 Account const carol{"carol"};
621 env.fund(XRP(100000), alice, bob, carol);
622 env.close();
623
624 env(delegate::set(alice, bob, {"Payment"}));
625 env.close();
626
627 auto const delegateKey = keylet::delegate(alice.id(), bob.id());
628 BEAST_EXPECT(env.closed()->exists(delegateKey));
629
630 auto hasKey = [](xrpl::Dir const& dir, UInt256 const& key) {
631 return std::any_of( // NOLINT(modernize-use-ranges)
632 dir.begin(), dir.end(), [&](auto const& sle) { return sle->key() == key; });
633 };
634
635 // Delegate object should appear in both alice's and bob's directories
636 BEAST_EXPECT(
637 hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(alice.id())), delegateKey.key));
638 BEAST_EXPECT(
639 hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(bob.id())), delegateKey.key));
640
641 for (std::uint32_t i = 0; i < 256; ++i)
642 env.close();
643
644 auto const aliceBalance = env.balance(alice);
645 auto const carolBalance = env.balance(carol);
646
647 // alice deletes account, this will remove the Delegate object from
648 // both alice's and bob's owner directories
649 auto const deleteFee = drops(env.current()->fees().increment);
650 env(acctdelete(alice, carol), Fee(deleteFee));
651 env.close();
652
653 BEAST_EXPECT(!env.closed()->exists(keylet::account(alice.id())));
654 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(alice.id())));
655 BEAST_EXPECT(!env.closed()->exists(delegateKey));
656 // bob's directory should no longer reference the Delegate object
657 BEAST_EXPECT(
658 !hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(bob.id())), delegateKey.key));
659 BEAST_EXPECT(env.balance(carol) == carolBalance + aliceBalance - deleteFee);
660 }
661
662 // Delegatee (bob) deletes account: Delegate object is cleaned up from
663 // both alice's and bob's owner directories, freeing alice's reserve so
664 // she can subsequently delete her own account.
665 {
666 Env env(*this);
667 Account const alice{"alice"};
668 Account const bob{"bob"};
669 Account const carol{"carol"};
670 env.fund(XRP(100000), alice, bob, carol);
671 env.close();
672
673 env(delegate::set(alice, bob, {"Payment"}));
674 env.close();
675
676 auto const delegateKey = keylet::delegate(alice.id(), bob.id());
677 BEAST_EXPECT(env.closed()->exists(delegateKey));
678
679 auto hasKey = [](xrpl::Dir const& dir, UInt256 const& key) {
680 return std::any_of( // NOLINT(modernize-use-ranges)
681 dir.begin(), dir.end(), [&](auto const& sle) { return sle->key() == key; });
682 };
683
684 BEAST_EXPECT(
685 hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(alice.id())), delegateKey.key));
686 BEAST_EXPECT(
687 hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(bob.id())), delegateKey.key));
688
689 // The Delegate entry counts against alice's ownerCount.
690 auto const sleAlice = env.closed()->read(keylet::account(alice.id()));
691 BEAST_EXPECT(sleAlice);
692 BEAST_EXPECT(sleAlice->getFieldU32(sfOwnerCount) == 1);
693
694 for (std::uint32_t i = 0; i < 256; ++i)
695 env.close();
696
697 auto const bobBalance = env.balance(bob);
698 auto const carolBalance = env.balance(carol);
699
700 // bob (the authorized/delegatee account) deletes his account.
701 // This must clean up the Delegate object from both alice's and
702 // bob's owner directories so alice's delegation does not survive
703 // a potential account resurrection.
704 auto const deleteFee = drops(env.current()->fees().increment);
705 env(acctdelete(bob, carol), Fee(deleteFee));
706 env.close();
707
708 BEAST_EXPECT(!env.closed()->exists(keylet::account(bob.id())));
709 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(bob.id())));
710 BEAST_EXPECT(!env.closed()->exists(delegateKey));
711 // alice's directory should no longer reference the Delegate object
712 BEAST_EXPECT(
713 !hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(alice.id())), delegateKey.key));
714 BEAST_EXPECT(env.balance(carol) == carolBalance + bobBalance - deleteFee);
715
716 // alice's ownerCount is now 0; she can delete her own account.
717 auto const sleAlice2 = env.closed()->read(keylet::account(alice.id()));
718 BEAST_EXPECT(sleAlice2);
719 BEAST_EXPECT(sleAlice2->getFieldU32(sfOwnerCount) == 0);
720
721 auto const aliceDeleteFee = drops(env.current()->fees().increment);
722 env(acctdelete(alice, carol), Fee(aliceDeleteFee));
723 env.close();
724
725 BEAST_EXPECT(!env.closed()->exists(keylet::account(alice.id())));
726 }
727
728 // Multiple delegators -> same delegatee: when the delegatee (bob)
729 // deletes his account, ALL Delegate objects (from alice and carol)
730 // must be cleaned up from every delegator's directory.
731 {
732 Env env(*this);
733 Account const alice{"alice"};
734 Account const bob{"bob"};
735 Account const carol{"carol"};
736 Account const dave{"dave"};
737 env.fund(XRP(100000), alice, bob, carol, dave);
738 env.close();
739
740 // Both alice and carol delegate to bob
741 env(delegate::set(alice, bob, {"Payment"}));
742 env(delegate::set(carol, bob, {"EscrowCreate"}));
743 env.close();
744
745 auto const aliceBobKey = keylet::delegate(alice.id(), bob.id());
746 auto const carolBobKey = keylet::delegate(carol.id(), bob.id());
747
748 auto hasKey = [](xrpl::Dir const& dir, UInt256 const& key) {
749 return std::any_of( // NOLINT(modernize-use-ranges)
750 dir.begin(), dir.end(), [&](auto const& sle) { return sle->key() == key; });
751 };
752
753 // Both Delegate objects exist and are in bob's directory
754 BEAST_EXPECT(env.closed()->exists(aliceBobKey));
755 BEAST_EXPECT(env.closed()->exists(carolBobKey));
756 BEAST_EXPECT(
757 hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(bob.id())), aliceBobKey.key));
758 BEAST_EXPECT(
759 hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(bob.id())), carolBobKey.key));
760
761 for (std::uint32_t i = 0; i < 256; ++i)
762 env.close();
763
764 auto const bobBalance = env.balance(bob);
765 auto const daveBalance = env.balance(dave);
766
767 auto const deleteFee = drops(env.current()->fees().increment);
768 env(acctdelete(bob, dave), Fee(deleteFee));
769 env.close();
770
771 // bob's account and directory are gone
772 BEAST_EXPECT(!env.closed()->exists(keylet::account(bob.id())));
773 BEAST_EXPECT(!env.closed()->exists(keylet::ownerDir(bob.id())));
774
775 // Both Delegate objects are erased
776 BEAST_EXPECT(!env.closed()->exists(aliceBobKey));
777 BEAST_EXPECT(!env.closed()->exists(carolBobKey));
778
779 // alice's and carol's directories no longer reference the objects
780 BEAST_EXPECT(
781 !hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(alice.id())), aliceBobKey.key));
782 BEAST_EXPECT(
783 !hasKey(xrpl::Dir(*env.closed(), keylet::ownerDir(carol.id())), carolBobKey.key));
784
785 BEAST_EXPECT(env.balance(dave) == daveBalance + bobBalance - deleteFee);
786 }
787 }
788
789 void
791 {
792 testcase("test delegate transaction");
793 using namespace jtx;
794
795 Env env(*this);
796 Account const alice{"alice"};
797 Account const bob{"bob"};
798 Account const carol{"carol"};
799
800 XRPAmount const baseFee{env.current()->fees().base};
801
802 // use different initial amount to distinguish the source balance
803 env.fund(XRP(10000), alice);
804 env.fund(XRP(20000), bob);
805 env.fund(XRP(30000), carol);
806 env.close();
807
808 auto aliceBalance = env.balance(alice, XRP);
809 auto bobBalance = env.balance(bob, XRP);
810 auto carolBalance = env.balance(carol, XRP);
811
812 // can not send transaction on one's own behalf
813 env(pay(alice, bob, XRP(50)), delegate::As(alice), Ter(temBAD_SIGNER));
814 env.require(Balance(alice, aliceBalance));
815
816 env(delegate::set(alice, bob, {"Payment"}));
817 env.close();
818 env.require(Balance(alice, aliceBalance - drops(baseFee)));
819 aliceBalance = env.balance(alice, XRP);
820
821 // bob pays 50 XRP to carol on behalf of alice
822 env(pay(alice, carol, XRP(50)), delegate::As(bob));
823 env.close();
824 env.require(Balance(alice, aliceBalance - XRP(50)));
825 env.require(Balance(carol, carolBalance + XRP(50)));
826 // bob pays the fee
827 env.require(Balance(bob, bobBalance - drops(baseFee)));
828 aliceBalance = env.balance(alice, XRP);
829 bobBalance = env.balance(bob, XRP);
830 carolBalance = env.balance(carol, XRP);
831
832 // bob pays 50 XRP to bob self on behalf of alice
833 env(pay(alice, bob, XRP(50)), delegate::As(bob));
834 env.close();
835 env.require(Balance(alice, aliceBalance - XRP(50)));
836 env.require(Balance(bob, bobBalance + XRP(50) - drops(baseFee)));
837 aliceBalance = env.balance(alice, XRP);
838 bobBalance = env.balance(bob, XRP);
839
840 // bob pay 50 XRP to alice herself on behalf of alice
841 env(pay(alice, alice, XRP(50)), delegate::As(bob), Ter(temREDUNDANT));
842 env.close();
843
844 // bob does not have permission to create check
845 env(check::create(alice, bob, XRP(10)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
846
847 // carol does not have permission to create check
848 env(check::create(alice, bob, XRP(10)),
849 delegate::As(carol),
851 }
852
853 void
855 {
856 testcase("test payment granular");
857 using namespace jtx;
858
859 // test PaymentMint and PaymentBurn
860 {
861 Env env(*this);
862 Account const alice{"alice"};
863 Account const bob{"bob"};
864 Account const gw{"gateway"};
865 Account const gw2{"gateway2"};
866 auto const usd = gw["USD"];
867 auto const eur = gw2["EUR"];
868
869 env.fund(XRP(10000), alice);
870 env.fund(XRP(20000), bob);
871 env.fund(XRP(40000), gw, gw2);
872 env.trust(usd(200), alice);
873 env.trust(eur(400), gw);
874 env.close();
875
876 XRPAmount const baseFee{env.current()->fees().base};
877 auto aliceBalance = env.balance(alice, XRP);
878 auto bobBalance = env.balance(bob, XRP);
879 auto gwBalance = env.balance(gw, XRP);
880 auto gw2Balance = env.balance(gw2, XRP);
881
882 // delegate ledger object is not created yet
883 env(pay(gw, alice, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
884 env.require(Balance(bob, bobBalance));
885
886 // gw gives bob burn permission
887 env(delegate::set(gw, bob, {"PaymentBurn"}));
888 env.close();
889 env.require(Balance(gw, gwBalance - drops(baseFee)));
890 gwBalance = env.balance(gw, XRP);
891
892 // bob sends a payment transaction on behalf of gw
893 env(pay(gw, alice, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
894 env.close();
895 env.require(Balance(bob, bobBalance));
896
897 // gw gives bob mint permission, alice gives bob burn permission
898 env(delegate::set(gw, bob, {"PaymentMint"}));
899 env(delegate::set(alice, bob, {"PaymentBurn"}));
900 env.close();
901 env.require(Balance(alice, aliceBalance - drops(baseFee)));
902 env.require(Balance(gw, gwBalance - drops(baseFee)));
903 aliceBalance = env.balance(alice, XRP);
904 gwBalance = env.balance(gw, XRP);
905
906 // can not send XRP
907 env(pay(gw, alice, XRP(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
908 env.close();
909 env.require(Balance(bob, bobBalance));
910
911 // mint 50 USD
912 env(pay(gw, alice, usd(50)), delegate::As(bob));
913 env.close();
914 env.require(Balance(bob, bobBalance - drops(baseFee)));
915 env.require(Balance(gw, gwBalance));
916 env.require(Balance(gw, alice["USD"](-50)));
917 env.require(Balance(alice, usd(50)));
918 BEAST_EXPECT(env.balance(bob, usd) == usd(0));
919 bobBalance = env.balance(bob, XRP);
920
921 // burn 30 USD
922 env(pay(alice, gw, usd(30)), delegate::As(bob));
923 env.close();
924 env.require(Balance(bob, bobBalance - drops(baseFee)));
925 env.require(Balance(gw, gwBalance));
926 env.require(Balance(gw, alice["USD"](-20)));
927 env.require(Balance(alice, usd(20)));
928 BEAST_EXPECT(env.balance(bob, usd) == usd(0));
929 bobBalance = env.balance(bob, XRP);
930
931 // bob has both mint and burn permissions
932 env(delegate::set(gw, bob, {"PaymentMint", "PaymentBurn"}));
933 env.close();
934 env.require(Balance(gw, gwBalance - drops(baseFee)));
935 gwBalance = env.balance(gw, XRP);
936
937 // mint 100 USD for gw
938 env(pay(gw, alice, usd(100)), delegate::As(bob));
939 env.close();
940 env.require(Balance(gw, alice["USD"](-120)));
941 env.require(Balance(alice, usd(120)));
942 env.require(Balance(bob, bobBalance - drops(baseFee)));
943 bobBalance = env.balance(bob, XRP);
944
945 // gw2 pays gw 200 EUR
946 env(pay(gw2, gw, eur(200)));
947 env.close();
948 env.require(Balance(gw2, gw2Balance - drops(baseFee)));
949 gw2Balance = env.balance(gw2, XRP);
950 env.require(Balance(gw2, gw["EUR"](-200)));
951 env.require(Balance(gw, eur(200)));
952
953 // burn 100 EUR for gw
954 env(pay(gw, gw2, eur(100)), delegate::As(bob));
955 env.close();
956 env.require(Balance(gw2, gw["EUR"](-100)));
957 env.require(Balance(gw, eur(100)));
958 env.require(Balance(bob, bobBalance - drops(baseFee)));
959 env.require(Balance(gw, gwBalance));
960 env.require(Balance(gw2, gw2Balance));
961 env.require(Balance(alice, aliceBalance));
962 }
963
964 // test PaymentMint won't affect Payment transaction level delegation.
965 {
966 Env env(*this);
967 Account const alice{"alice"};
968 Account const bob{"bob"};
969 Account const gw{"gateway"};
970 auto const usd = gw["USD"];
971
972 env.fund(XRP(10000), alice);
973 env.fund(XRP(20000), bob);
974 env.fund(XRP(40000), gw);
975 env.trust(usd(200), alice);
976 env.close();
977
978 XRPAmount const baseFee{env.current()->fees().base};
979
980 auto aliceBalance = env.balance(alice, XRP);
981 auto bobBalance = env.balance(bob, XRP);
982 auto gwBalance = env.balance(gw, XRP);
983
984 // gw gives bob PaymentBurn permission
985 env(delegate::set(gw, bob, {"PaymentBurn"}));
986 env.close();
987 env.require(Balance(gw, gwBalance - drops(baseFee)));
988 gwBalance = env.balance(gw, XRP);
989
990 // bob can not mint on behalf of gw because he only has burn
991 // permission
992 env(pay(gw, alice, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
993 env.close();
994 env.require(Balance(bob, bobBalance));
995
996 // gw gives bob Payment permission as well
997 env(delegate::set(gw, bob, {"PaymentBurn", "Payment"}));
998 env.close();
999 env.require(Balance(gw, gwBalance - drops(baseFee)));
1000 gwBalance = env.balance(gw, XRP);
1001
1002 // bob now can mint on behalf of gw
1003 env(pay(gw, alice, usd(50)), delegate::As(bob));
1004 env.close();
1005 env.require(Balance(bob, bobBalance - drops(baseFee)));
1006 env.require(Balance(gw, gwBalance));
1007 env.require(Balance(alice, aliceBalance));
1008 env.require(Balance(gw, alice["USD"](-50)));
1009 env.require(Balance(alice, usd(50)));
1010 BEAST_EXPECT(env.balance(bob, usd) == usd(0));
1011 }
1012
1013 // disallow cross currency payment with only PaymentBurn/PaymentMint
1014 // permission
1015 {
1016 Env env(*this, features);
1017 Account const alice{"alice"};
1018 Account const bob{"bob"};
1019 Account const gw{"gateway"};
1020 Account const carol{"carol"};
1021 auto const usd = gw["USD"];
1022
1023 env.fund(XRP(10000), alice, bob, carol, gw);
1024 env.close();
1025 env.trust(usd(50000), alice);
1026 env.trust(usd(50000), bob);
1027 env.trust(usd(50000), carol);
1028 env(pay(gw, alice, usd(10000)));
1029 env(pay(gw, bob, usd(10000)));
1030 env(pay(gw, carol, usd(10000)));
1031 env.close();
1032
1033 // PaymentMint
1034 {
1035 env(offer(carol, XRP(100), usd(501)));
1036 BEAST_EXPECT(expectOffers(env, carol, 1));
1037 env(delegate::set(gw, bob, {"PaymentMint"}));
1038 env.close();
1039
1040 // Cross-currency sfSendMax without any flag passes the granular
1041 // template and is rejected by the granular semantic check, because
1042 // the sfSendMax asset (XRP) is not the same as the sfAmount asset (USD).
1043 env(pay(gw, alice, usd(500)),
1044 Sendmax(XRP(1001)),
1045 delegate::As(bob),
1047 BEAST_EXPECT(expectOffers(env, carol, 1));
1048
1049 // bob can not send cross currency payment on behalf of the gw,
1050 // even with PaymentMint permission and gw being the issuer.
1051 env(pay(gw, alice, usd(5000)),
1052 Sendmax(XRP(1001)),
1053 Txflags(tfPartialPayment),
1054 delegate::As(bob),
1056 BEAST_EXPECT(expectOffers(env, carol, 1));
1057
1058 env(pay(gw, alice, usd(5000)),
1059 Path(~XRP),
1060 Txflags(tfPartialPayment),
1061 delegate::As(bob),
1063 BEAST_EXPECT(expectOffers(env, carol, 1));
1064
1065 // succeed with direct payment
1066 env(pay(gw, alice, usd(100)), delegate::As(bob));
1067 env.close();
1068 }
1069
1070 // PaymentBurn
1071 {
1072 env(offer(bob, XRP(100), usd(501)));
1073 BEAST_EXPECT(expectOffers(env, bob, 1));
1074 env(delegate::set(alice, bob, {"PaymentBurn"}));
1075 env.close();
1076
1077 // Cross-currency sfSendMax without any flag passes the granular
1078 // template and is rejected by the granular semantic check, because
1079 // the sfSendMax asset (XRP) is not the same as the sfAmount asset (USD).
1080 env(pay(alice, gw, usd(500)),
1081 Sendmax(XRP(1001)),
1082 delegate::As(bob),
1084 BEAST_EXPECT(expectOffers(env, bob, 1));
1085
1086 // bob can not send cross currency payment on behalf of alice,
1087 // even with PaymentBurn permission and gw being the issuer.
1088 env(pay(alice, gw, usd(5000)),
1089 Sendmax(XRP(1001)),
1090 Txflags(tfPartialPayment),
1091 delegate::As(bob),
1093 BEAST_EXPECT(expectOffers(env, bob, 1));
1094
1095 env(pay(alice, gw, usd(5000)),
1096 Path(~XRP),
1097 Txflags(tfPartialPayment),
1098 delegate::As(bob),
1100 BEAST_EXPECT(expectOffers(env, bob, 1));
1101
1102 // succeed with direct payment
1103 env(pay(alice, gw, usd(100)), delegate::As(bob));
1104 env.close();
1105 }
1106 }
1107
1108 // PaymentMint/PaymentBurn with sfSendMax of the same asset is allowed,
1109 // same-asset SendMax is still a direct payment, not cross-currency.
1110 {
1111 Env env(*this, features);
1112 Account const alice{"alice"};
1113 Account const bob{"bob"};
1114 Account const gw{"gw"};
1115 auto const usd = gw["USD"];
1116 env.fund(XRP(10000), alice, bob, gw);
1117 env.trust(usd(200), alice);
1118 env.close();
1119
1120 env(delegate::set(gw, bob, {"PaymentMint"}));
1121 env.close();
1122
1123 // sfSendMax with same asset as sfAmount, still a direct payment
1124 env(pay(gw, alice, usd(50)), Sendmax(usd(50)), delegate::As(bob));
1125 env.require(Balance(alice, usd(50)));
1126
1127 env(delegate::set(alice, bob, {"PaymentBurn"}));
1128 env.close();
1129
1130 env(pay(alice, gw, usd(30)), delegate::As(bob));
1131 env.require(Balance(alice, usd(20)));
1132 }
1133
1134 // PaymentBurn is authorized by balance direction, not trust limit.
1135 // holder is allowed to burn even if trust limit is 0.
1136 {
1137 Env env(*this);
1138 Account const alice{"alice"};
1139 Account const bob{"bob"};
1140 Account const gw{"gateway"};
1141 auto const gwUSD = gw["USD"];
1142 auto const aliceUSD = alice["USD"];
1143
1144 env.fund(XRP(10000), alice, bob, gw);
1145 env.trust(gwUSD(200), alice);
1146 env.close();
1147
1148 env(pay(gw, alice, gwUSD(50)));
1149 env.require(Balance(alice, gwUSD(50)));
1150 env.close();
1151
1152 env(delegate::set(alice, bob, {"PaymentBurn"}));
1153 env.close();
1154
1155 env.trust(gwUSD(0), alice);
1156 env.close();
1157 BEAST_EXPECT(env.limit(alice, gwUSD.issue()) == gwUSD(0));
1158
1159 env(trust(gw, aliceUSD(200)));
1160 env.close();
1161
1162 env(pay(alice, gw, gwUSD(30)), delegate::As(bob));
1163 env.require(Balance(alice, gwUSD(20)));
1164 env.require(Balance(gw, aliceUSD(-20)));
1165 }
1166
1167 // PaymentBurn must not exceed the balance the account holds. Redeeming past
1168 // zero makes the payment engine issue the account's own IOUs, which is a mint.
1169 {
1170 Env env(*this, features);
1171 Account const alice{"alice"};
1172 Account const bob{"bob"};
1173 Account const gw{"gateway"};
1174 auto const gwUSD = gw["USD"];
1175 auto const aliceUSD = alice["USD"];
1176
1177 env.fund(XRP(10000), alice, bob, gw);
1178 env.trust(gwUSD(200), alice);
1179 env.close();
1180
1181 env(pay(gw, alice, gwUSD(50)));
1182 env.close();
1183 env.require(Balance(alice, gwUSD(50)));
1184
1185 // gw accepts alice-issued USD, so the engine has issuing liquidity
1186 // available once the trustline reaches zero.
1187 env(trust(gw, aliceUSD(200)));
1188 env.close();
1189
1190 env(delegate::set(alice, bob, {"PaymentBurn"}));
1191 env.close();
1192
1193 if (!features[fixCleanup3_4_0])
1194 {
1195 // Pre-fixCleanup3_4_0: the balance direction alone authorizes the payment, so it
1196 // redeems alice's 50 and then mints 50 alice-issued USD.
1197 env(pay(alice, gw, gwUSD(100)), delegate::As(bob));
1198 env.require(Balance(alice, gwUSD(-50)));
1199 env.require(Balance(gw, aliceUSD(50)));
1200 }
1201 else
1202 {
1203 // Post-fixCleanup3_4_0: Rejected because it exceeds what alice holds.
1204 env(pay(alice, gw, gwUSD(100)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1205 env.require(Balance(alice, gwUSD(50)));
1206 env.require(Balance(gw, aliceUSD(-50)));
1207
1208 // Allowed because it is less than what alice holds.
1209 env(pay(alice, gw, gwUSD(20)), delegate::As(bob));
1210 env.require(Balance(alice, gwUSD(30)));
1211 env.close();
1212
1213 // Exactly what alice holds: allowed, and settles at zero.
1214 env(pay(alice, gw, gwUSD(30)), delegate::As(bob));
1215 env.require(Balance(alice, gwUSD(0)));
1216 env.close();
1217
1218 // Nothing left to burn: rejected.
1219 env(pay(alice, gw, gwUSD(1)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1220 env.require(Balance(gw, aliceUSD(0)));
1221 }
1222 }
1223
1224 // A delegate holding both PaymentMint and PaymentBurn may cross zero.
1225 {
1226 Env env(*this, features);
1227 Account const alice{"alice"};
1228 Account const bob{"bob"};
1229 Account const gw{"gateway"};
1230 auto const gwUSD = gw["USD"];
1231 auto const aliceUSD = alice["USD"];
1232
1233 env.fund(XRP(10000), alice, bob, gw);
1234 env.trust(gwUSD(200), alice);
1235 env.close();
1236
1237 env(pay(gw, alice, gwUSD(50)));
1238 env(trust(gw, aliceUSD(200)));
1239 env.close();
1240
1241 env(delegate::set(alice, bob, {"PaymentBurn", "PaymentMint"}));
1242 env.close();
1243
1244 env(pay(alice, gw, gwUSD(100)), delegate::As(bob));
1245 env.require(Balance(alice, gwUSD(-50)));
1246 env.require(Balance(gw, aliceUSD(50)));
1247 }
1248
1249 // Test invalid fields or flags not allowed in granular permission template
1250 {
1251 Env env(*this, features);
1252 Account const alice{"alice"};
1253 Account const bob{"bob"};
1254 Account const gw{"gw"};
1255 auto const usd = gw["USD"];
1256 env.fund(XRP(10000), alice, bob, gw);
1257 env.trust(usd(200), alice);
1258 env.close();
1259
1260 env(delegate::set(gw, bob, {"PaymentMint"}));
1261 env(delegate::set(alice, bob, {"PaymentBurn"}));
1262 env.close();
1263
1264 // sfDeliverMin (with tfPartialPayment) is not in the PaymentMint
1265 // or PaymentBurn template.
1266 env(pay(gw, alice, usd(100)),
1267 DeliverMin(usd(50)),
1268 Txflags(tfPartialPayment),
1269 delegate::As(bob),
1271 env(pay(alice, gw, usd(50)),
1272 DeliverMin(usd(25)),
1273 Txflags(tfPartialPayment),
1274 delegate::As(bob),
1276
1277 // sfDomainID is not in the PaymentMint or PaymentBurn template.
1278 env(pay(gw, alice, usd(100)),
1279 Domain(UInt256{1}),
1280 delegate::As(bob),
1282 env(pay(alice, gw, usd(50)),
1283 Domain(UInt256{1}),
1284 delegate::As(bob),
1286
1287 // sfPaths is not in the PaymentMint or PaymentBurn template.
1288 env(pay(gw, alice, usd(100)),
1289 Path(~XRP),
1290 delegate::As(bob),
1292 env(pay(alice, gw, usd(50)),
1293 Path(~XRP),
1294 delegate::As(bob),
1296
1297 // Valid Payment flags that are not in the PaymentMint or PaymentBurn template.
1298 for (auto const flag : {tfPartialPayment, tfNoRippleDirect, tfLimitQuality})
1299 {
1300 env(pay(gw, alice, usd(100)),
1301 Txflags(flag),
1302 delegate::As(bob),
1304 env(pay(alice, gw, usd(50)),
1305 Txflags(flag),
1306 delegate::As(bob),
1308 }
1309
1310 // The same payments without the extra field or flag succeed.
1311 env(pay(gw, alice, usd(100)), delegate::As(bob));
1312 env(pay(alice, gw, usd(50)), delegate::As(bob));
1313 env.require(Balance(alice, usd(50)));
1314 }
1315
1316 // Delegate account holds no granular permissions for the tx type:
1317 // getGranularPermission returns empty set.
1318 {
1319 Env env(*this, features);
1320 Account const alice{"alice"};
1321 Account const bob{"bob"};
1322 Account const gw{"gw"};
1323 auto const usd = gw["USD"];
1324 env.fund(XRP(10000), alice, bob, gw);
1325 env.trust(usd(200), alice);
1326 env.close();
1327
1328 // Bob holds only an AccountSet granular permission.
1329 env(delegate::set(alice, bob, {"AccountDomainSet"}));
1330 env.close();
1331
1332 // Payment has granular permissions defined in permissions.macro,
1333 // but bob only holds AccountSet's granular permission,
1334 // getGranularPermission returns empty.
1335 env(pay(alice, gw, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1336 }
1337
1338 // PaymentMint and PaymentBurn for MPT
1339 {
1340 std::string logs;
1341 Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
1342 Account const alice{"alice"};
1343 Account const bob{"bob"};
1344 Account const gw{"gateway"};
1345
1346 MPTTester mpt(env, gw, {.holders = {alice, bob}});
1347 mpt.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1348
1349 mpt.authorize({.account = alice});
1350 mpt.authorize({.account = bob});
1351
1352 auto const gwMPT = mpt["MPT"];
1353 env(pay(gw, alice, gwMPT(500)));
1354 env(pay(gw, bob, gwMPT(500)));
1355 env.close();
1356 auto aliceMPT = env.balance(alice, gwMPT);
1357 auto bobMPT = env.balance(bob, gwMPT);
1358
1359 // PaymentMint
1360 {
1361 env(delegate::set(gw, bob, {"PaymentMint"}));
1362 env.close();
1363
1364 env(pay(gw, alice, gwMPT(50)), delegate::As(bob));
1365 BEAST_EXPECT(env.balance(alice, gwMPT) == aliceMPT + gwMPT(50));
1366 BEAST_EXPECT(env.balance(bob, gwMPT) == bobMPT);
1367 aliceMPT = env.balance(alice, gwMPT);
1368 }
1369
1370 // PaymentBurn
1371 {
1372 env(delegate::set(alice, bob, {"PaymentBurn"}));
1373 env.close();
1374
1375 env(pay(alice, gw, gwMPT(50)), delegate::As(bob));
1376 BEAST_EXPECT(env.balance(alice, gwMPT) == aliceMPT - gwMPT(50));
1377 BEAST_EXPECT(env.balance(bob, gwMPT) == bobMPT);
1378 aliceMPT = env.balance(alice, gwMPT);
1379 }
1380
1381 // Grant both granular permissions and tx level permission.
1382 {
1383 env(delegate::set(alice, bob, {"PaymentBurn", "PaymentMint", "Payment"}));
1384 env.close();
1385 env(pay(alice, gw, gwMPT(50)), delegate::As(bob));
1386 BEAST_EXPECT(env.balance(alice, gwMPT) == aliceMPT - gwMPT(50));
1387 BEAST_EXPECT(env.balance(bob, gwMPT) == bobMPT);
1388 aliceMPT = env.balance(alice, gwMPT);
1389 env(pay(alice, bob, gwMPT(100)), delegate::As(bob));
1390 BEAST_EXPECT(env.balance(alice, gwMPT) == aliceMPT - gwMPT(100));
1391 BEAST_EXPECT(env.balance(bob, gwMPT) == bobMPT + gwMPT(100));
1392 }
1393 }
1394
1395 // PaymentMint/PaymentBurn must not trust IOU issuer aliases.
1396 // In a direct IOU payment, sfAmount.issuer may be encoded as either
1397 // endpoint, and PaySteps normalizes those aliases to the same execution.
1398 // These cases ensure a delegate cannot flip the encoded issuer to turn a
1399 // mint into an apparent burn, or a burn into an apparent mint.
1400 {
1401 Env env(*this);
1402 Account const alice{"alice"};
1403 Account const bob{"bob"};
1404 Account const gw{"gateway"};
1405 auto const gwUSD = gw["USD"];
1406 auto const aliceUSD = alice["USD"];
1407
1408 env.fund(XRP(10000), alice, bob, gw);
1409 env.trust(gwUSD(200), alice);
1410 env.close();
1411
1412 // Alice holds 100 USD issued by gw.
1413 env(pay(gw, alice, gwUSD(100)));
1414 env.close();
1415 env.require(Balance(alice, gwUSD(100)));
1416
1417 // Delegate with only PaymentBurn tries to mint by encoding
1418 // Amount.issuer as the destination alias, alice. The actual issuer
1419 // is gw, so this requires PaymentMint and must be rejected.
1420 {
1421 env(delegate::set(gw, bob, {"PaymentBurn"}));
1422 env.close();
1423
1424 // Amount.issuer = alice (destination), rejected because gw is
1425 // the actual issuer and PaymentMint is required.
1426 env(pay(gw, alice, aliceUSD(50)),
1427 delegate::As(bob),
1429 env.require(Balance(alice, gwUSD(100)));
1430
1431 // Fails because bob holds PaymentBurn, not PaymentMint.
1432 env(pay(gw, alice, gwUSD(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1433 env.require(Balance(alice, gwUSD(100)));
1434 }
1435
1436 // Delegate with only PaymentMint tries to burn by encoding
1437 // Amount.issuer as the source alias, alice. The actual issuer is
1438 // gw, so this requires PaymentBurn and must be rejected.
1439 {
1440 env(delegate::set(alice, bob, {"PaymentMint"}));
1441 env.close();
1442
1443 // Amount.issuer = alice (account), rejected because gw is the
1444 // actual issuer and PaymentBurn is required.
1445 env(pay(alice, gw, aliceUSD(50)),
1446 delegate::As(bob),
1448 env.require(Balance(alice, gwUSD(100)));
1449
1450 // Fails because bob holds PaymentMint, not PaymentBurn.
1451 env(pay(alice, gw, gwUSD(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1452 env.require(Balance(alice, gwUSD(100)));
1453 }
1454 }
1455
1456 // Neither account nor destination is issuer.
1457 // PaymentMint and PaymentBurn do not authorize these payments.
1458 {
1459 // IOU
1460 {
1461 Env env(*this);
1462 Account const alice{"alice"};
1463 Account const bob{"bob"};
1464 Account const gw{"gateway"};
1465 Account const gw2{"gateway2"};
1466 auto const gwUSD = gw["USD"];
1467
1468 env.fund(XRP(10000), alice, bob, gw, gw2);
1469 env.close();
1470
1471 env.trust(gwUSD(200), alice);
1472 env.close();
1473
1474 env(pay(gw, alice, gwUSD(100)));
1475 env.close();
1476
1477 env(delegate::set(alice, bob, {"PaymentMint", "PaymentBurn"}));
1478 env.close();
1479
1480 env(pay(alice, gw, gw2["USD"](50)),
1481 delegate::As(bob),
1483 }
1484
1485 // MPT
1486 {
1487 Env env(*this, features);
1488 Account const alice{"alice"};
1489 Account const bob{"bob"};
1490 Account const gw{"gateway"};
1491 Account const gw2{"gateway2"};
1492
1493 env.fund(XRP(10000), gw2);
1494 env.close();
1495
1496 MPTTester mpt(env, gw, {.holders = {alice, bob}});
1497 mpt.create({.ownerCount = 1, .flags = tfMPTCanTransfer});
1498
1499 mpt.authorize({.account = alice});
1500 mpt.authorize({.account = bob});
1501
1502 auto const gwMPT = mpt["MPT"];
1503 env(pay(gw, alice, gwMPT(500)));
1504 env(pay(gw, bob, gwMPT(500)));
1505 env.close();
1506
1507 env(delegate::set(alice, bob, {"PaymentMint", "PaymentBurn"}));
1508 env.close();
1509
1510 env(pay(alice, gw2, gwMPT(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1511 }
1512 }
1513
1514 // IOU issuer is an endpoint, but no trustline exists.
1515 {
1516 Env env(*this);
1517 Account const alice{"alice"};
1518 Account const bob{"bob"};
1519 Account const gw{"gateway"};
1520
1521 env.fund(XRP(10000), alice, bob, gw);
1522 env.close();
1523
1524 env(delegate::set(alice, bob, {"PaymentMint", "PaymentBurn"}));
1525 env.close();
1526
1527 env(pay(alice, gw, alice["USD"](50)),
1528 delegate::As(bob),
1530 }
1531
1532 // Both trust limits (who is the designated issuer) and balance direction
1533 // (which way DirectStepI executes) must be checked. Neither alone is sufficient.
1534 {
1535 Env env(*this);
1536 Account const alice{"alice"};
1537 Account const bob{"bob"};
1538 Account const gw{"gateway"};
1539 auto const gwUSD = gw["USD"];
1540 auto const aliceUSD = alice["USD"];
1541
1542 env.fund(XRP(10000), alice, bob, gw);
1543
1544 // Alice trusts gw but holds zero gw-issued USD. With balance == 0,
1545 // DirectStepI would issue rather than redeem, so PaymentBurn must
1546 // be rejected even though Alice's trust limit to gw is positive.
1547 {
1548 env.trust(gwUSD(200), alice);
1549 env.close();
1550
1551 // Alice has nothing to burn.
1552 env(delegate::set(alice, bob, {"PaymentBurn"}));
1553 env.close();
1554
1555 env(pay(alice, gw, gwUSD(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1556 env(pay(alice, gw, aliceUSD(50)),
1557 delegate::As(bob),
1559 }
1560
1561 // Set up a trust line where gw holds alice-issued USD. DirectStepI
1562 // would redeem rather than issue, so PaymentMint must be rejected
1563 // even though the endpoint identity matches.
1564 {
1565 // Gw sets trust to accept alice-issued USD.
1566 env(trust(gw, aliceUSD(200)));
1567 env.close();
1568
1569 // Alice issues her own USD to gw; now gw holds alice's IOUs.
1570 env(pay(alice, gw, aliceUSD(100)));
1571 env.close();
1572
1573 // In gw's view, accountHolds(gw, USD, alice) > 0, so DirectStepI redeems.
1574 // PaymentMint must be rejected because the step would redeem, not issue.
1575 env(delegate::set(gw, bob, {"PaymentMint"}));
1576 env.close();
1577
1578 env(pay(gw, alice, gwUSD(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1579 env(pay(gw, alice, aliceUSD(50)),
1580 delegate::As(bob),
1582 }
1583 }
1584
1585 // Alice trusts gw but gw is not willing to hold alice's IOU (destLimit == 0).
1586 {
1587 Env env(*this);
1588 Account const alice{"alice"};
1589 Account const bob{"bob"};
1590 Account const gw{"gateway"};
1591 env.fund(XRP(10000), alice, bob, gw);
1592 env.trust(gw["USD"](200), alice);
1593 env.close();
1594
1595 env(delegate::set(alice, bob, {"PaymentMint"}));
1596 env.close();
1597
1598 env(pay(alice, gw, gw["USD"](50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1599 env(pay(alice, gw, alice["USD"](50)),
1600 delegate::As(bob),
1602 }
1603
1604 // Verify granular permissions of different tx types in the same SLE are scoped
1605 // correctly. AccountSet permissions don't apply to Payment and vice versa
1606 {
1607 Env env(*this);
1608 Account const alice{"alice"};
1609 Account const bob{"bob"};
1610 Account const gw{"gw"};
1611 auto const usd = gw["USD"];
1612 env.fund(XRP(10000), alice, bob, gw);
1613 env.trust(usd(200), alice);
1614 env.close();
1615
1616 // Alice granted bob with both AccountDomainSet and PaymentMint.
1617 env(delegate::set(alice, bob, {"AccountDomainSet", "PaymentMint"}));
1618 env.close();
1619
1620 // PaymentMint fails at granular semantic check because alice is not the issuer.
1621 env(pay(alice, gw, usd(50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1622
1623 // AccountDomainSet applies correctly to AccountSet
1624 std::string const domain = "example.com";
1625 auto jt = noop(alice);
1626 jt[sfDomain] = strHex(domain);
1627 jt[sfDelegate] = bob.human();
1628 env(jt);
1629 BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
1630
1631 // gw gives bob PaymentMint and bob can mint on gw's behalf
1632 env(delegate::set(gw, bob, {"PaymentMint"}));
1633 env.close();
1634 env(pay(gw, alice, usd(50)), delegate::As(bob));
1635 env.require(Balance(alice, usd(50)));
1636 }
1637 }
1638
1639 void
1641 {
1642 testcase("test TrustSet granular permissions");
1643 using namespace jtx;
1644
1645 // test TrustlineUnfreeze, TrustlineFreeze and TrustlineAuthorize
1646 {
1647 Env env(*this);
1648 Account const gw{"gw"};
1649 Account const alice{"alice"};
1650 Account const bob{"bob"};
1651 env.fund(XRP(10000), gw, alice, bob);
1652 env(fset(gw, asfRequireAuth));
1653 env.close();
1654
1655 env(delegate::set(alice, bob, {"TrustlineUnfreeze"}));
1656 env.close();
1657 // bob can not create trustline on behalf of alice because he only
1658 // has unfreeze permission
1659 env(trust(alice, gw["USD"](50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1660 env.close();
1661
1662 // alice creates trustline by herself
1663 env(trust(alice, gw["USD"](50)));
1664 env.close();
1665
1666 // gw gives bob unfreeze permission
1667 env(delegate::set(gw, bob, {"TrustlineUnfreeze"}));
1668 env.close();
1669
1670 // unsupported flags
1671 env(trust(alice, gw["USD"](50), tfSetNoRipple),
1672 delegate::As(bob),
1674 env(trust(alice, gw["USD"](50), tfClearNoRipple),
1675 delegate::As(bob),
1677 env(trust(gw, gw["USD"](0), alice, tfSetDeepFreeze),
1678 delegate::As(bob),
1680 env(trust(gw, gw["USD"](0), alice, tfClearDeepFreeze),
1681 delegate::As(bob),
1683 env.close();
1684
1685 // supported flags with wrong permission
1686 env(trust(gw, gw["USD"](0), alice, tfSetfAuth),
1687 delegate::As(bob),
1689 env(trust(gw, gw["USD"](0), alice, tfSetFreeze),
1690 delegate::As(bob),
1692 env.close();
1693
1694 env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
1695 env.close();
1696 env(trust(gw, gw["USD"](0), alice, tfClearFreeze),
1697 delegate::As(bob),
1699 env.close();
1700 // although trustline authorize is granted, bob can not change the
1701 // limit number
1702 env(trust(gw, gw["USD"](50), alice, tfSetfAuth),
1703 delegate::As(bob),
1705 env.close();
1706
1707 // supported flags with correct permission
1708 env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob));
1709 env.close();
1710 env(delegate::set(gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
1711 env.close();
1712 env(trust(gw, gw["USD"](0), alice, tfSetFreeze), delegate::As(bob));
1713 env.close();
1714 env(delegate::set(gw, bob, {"TrustlineAuthorize", "TrustlineUnfreeze"}));
1715 env.close();
1716 env(trust(gw, gw["USD"](0), alice, tfClearFreeze), delegate::As(bob));
1717 env.close();
1718 // but bob can not freeze trustline because he no longer has freeze
1719 // permission
1720 env(trust(gw, gw["USD"](0), alice, tfSetFreeze),
1721 delegate::As(bob),
1723
1724 // cannot update LimitAmount with granular permission, both high and
1725 // low account
1726 env(trust(alice, gw["USD"](100)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1727 env(trust(gw, alice["USD"](100)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1728
1729 // can not set QualityIn or QualityOut
1730 auto tx = trust(alice, gw["USD"](50));
1731 tx["QualityIn"] = "1000";
1733 auto tx2 = trust(alice, gw["USD"](50));
1734 tx2["QualityOut"] = "1000";
1736 auto tx3 = trust(gw, alice["USD"](50));
1737 tx3["QualityIn"] = "1000";
1739 auto tx4 = trust(gw, alice["USD"](50));
1740 tx4["QualityOut"] = "1000";
1742
1743 // granting TrustSet can make it work
1744 env(delegate::set(gw, bob, {"TrustSet"}));
1745 env.close();
1746 auto tx5 = trust(gw, alice["USD"](50));
1747 tx5["QualityOut"] = "1000";
1748 env(tx5, delegate::As(bob));
1749 auto tx6 = trust(alice, gw["USD"](50));
1750 tx6["QualityOut"] = "1000";
1752 env(delegate::set(alice, bob, {"TrustSet"}));
1753 env.close();
1754 env(tx6, delegate::As(bob));
1755 }
1756
1757 // test mix of transaction level delegation and granular delegation
1758 {
1759 Env env(*this);
1760 Account const gw{"gw"};
1761 Account const alice{"alice"};
1762 Account const bob{"bob"};
1763 env.fund(XRP(10000), gw, alice, bob);
1764 env(fset(gw, asfRequireAuth));
1765 env.close();
1766
1767 // bob does not have permission
1768 env(trust(alice, gw["USD"](50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1769 env(delegate::set(alice, bob, {"TrustlineUnfreeze", "NFTokenCreateOffer"}));
1770 env.close();
1771 // bob still does not have permission
1772 env(trust(alice, gw["USD"](50)), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1773
1774 // add TrustSet permission and some unrelated permission
1775 env(delegate::set(
1776 alice,
1777 bob,
1778 {"TrustlineUnfreeze", "NFTokenCreateOffer", "TrustSet", "AccountTransferRateSet"}));
1779 env.close();
1780 env(trust(alice, gw["USD"](50)), delegate::As(bob));
1781 env.close();
1782
1783 env(delegate::set(
1784 gw,
1785 bob,
1786 {"TrustlineUnfreeze", "NFTokenCreateOffer", "TrustSet", "AccountTransferRateSet"}));
1787 env.close();
1788
1789 // since bob has TrustSet permission, he does not need
1790 // TrustlineFreeze granular permission to freeze the trustline
1791 env(trust(gw, gw["USD"](0), alice, tfSetFreeze), delegate::As(bob));
1792 env(trust(gw, gw["USD"](0), alice, tfClearFreeze), delegate::As(bob));
1793 // bob can perform all the operations regarding TrustSet
1794 env(trust(gw, gw["USD"](0), alice, tfSetFreeze), delegate::As(bob));
1795 env(trust(gw, gw["USD"](0), alice, tfSetDeepFreeze), delegate::As(bob));
1796 env(trust(gw, gw["USD"](0), alice, tfClearDeepFreeze), delegate::As(bob));
1797 env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob));
1798 env(trust(alice, gw["USD"](50), tfSetNoRipple), delegate::As(bob));
1799 env(trust(alice, gw["USD"](50), tfClearNoRipple), delegate::As(bob));
1800 }
1801
1802 // tfFullyCanonicalSig won't block delegated transaction
1803 {
1804 Env env(*this);
1805 Account const gw{"gw"};
1806 Account const alice{"alice"};
1807 Account const bob{"bob"};
1808 env.fund(XRP(10000), gw, alice, bob);
1809 env(fset(gw, asfRequireAuth));
1810 env.close();
1811 env(trust(alice, gw["USD"](50)));
1812 env.close();
1813
1814 env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
1815 env.close();
1816 env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfFullyCanonicalSig),
1817 delegate::As(bob));
1818 }
1819
1820 {
1821 Env env(*this);
1822 Account const gw{"gw"};
1823 Account const alice{"alice"};
1824 Account const bob{"bob"};
1825 env.fund(XRP(10000), gw, alice, bob);
1826
1827 env(fset(gw, asfRequireAuth));
1828 env.close();
1829 env(trust(alice, gw["USD"](50)));
1830 env.close();
1831 env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
1832 env.close();
1833
1834 env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob));
1835 env.close();
1836
1837 // sfQualityOut is a valid TrustSet field, but not permitted in granular template
1838 json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth);
1839 txJson[sfQualityOut.jsonName] = 100;
1840 env(txJson, delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1841
1842 // tfSetNoRipple is a valid flag for TrustSet, but not permitted in granular template
1843 env(trust(gw, gw["USD"](0), alice, tfSetfAuth | tfSetNoRipple),
1844 delegate::As(bob),
1846 }
1847 }
1848
1849 void
1851 {
1852 testcase("test AccountSet granular permissions");
1853 using namespace jtx;
1854
1855 // test AccountDomainSet, AccountEmailHashSet,
1856 // AccountMessageKeySet,AccountTransferRateSet, and AccountTickSizeSet
1857 // granular permissions
1858 {
1859 Env env(*this);
1860 auto const alice = Account{"alice"};
1861 auto const bob = Account{"bob"};
1862 env.fund(XRP(10000), alice, bob);
1863 env.close();
1864
1865 // alice gives bob some random permission, which is not related to
1866 // the AccountSet transaction
1867 env(delegate::set(alice, bob, {"TrustlineUnfreeze"}));
1868 env.close();
1869
1870 // bob does not have permission to set domain
1871 // on behalf of alice
1872 std::string const domain = "example.com";
1873 auto jt = noop(alice);
1874 jt[sfDomain] = strHex(domain);
1875 jt[sfDelegate] = bob.human();
1876
1877 // add granular permission related to AccountSet but is not the
1878 // correct permission for domain set
1879 env(delegate::set(alice, bob, {"TrustlineUnfreeze", "AccountEmailHashSet"}));
1880 env.close();
1882
1883 // alice give granular permission of AccountDomainSet to bob
1884 env(delegate::set(alice, bob, {"AccountDomainSet"}));
1885 env.close();
1886
1887 // bob set account domain on behalf of alice
1888 env(jt);
1889 BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
1890
1891 // bob can reset domain
1892 jt[sfDomain] = "";
1893 env(jt);
1894 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfDomain));
1895
1896 // bob tries to set unauthorized flag, it will fail
1897 std::string const failDomain = "fail_domain_update";
1898 jt[sfFlags] = tfRequireAuth;
1899 jt[sfDomain] = strHex(failDomain);
1901 // reset flag number
1902 jt[sfFlags] = 0;
1903
1904 // bob tries to update domain and set email hash,
1905 // but he does not have permission to set email hash
1906 jt[sfDomain] = strHex(domain);
1907 std::string const mh("5F31A79367DC3137FADA860C05742EE6");
1908 jt[sfEmailHash] = mh;
1910
1911 // alice give granular permission of AccountEmailHashSet to bob
1912 env(delegate::set(alice, bob, {"AccountDomainSet", "AccountEmailHashSet"}));
1913 env.close();
1914 env(jt);
1915 BEAST_EXPECT(to_string((*env.le(alice))[sfEmailHash]) == mh);
1916 BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
1917
1918 // bob does not have permission to set message key for alice
1919 auto const rkp = randomKeyPair(KeyType::Ed25519);
1920 jt[sfMessageKey] = strHex(rkp.first.slice());
1922
1923 // alice give granular permission of AccountMessageKeySet to bob
1924 env(delegate::set(
1925 alice, bob, {"AccountDomainSet", "AccountEmailHashSet", "AccountMessageKeySet"}));
1926 env.close();
1927
1928 // bob can set message key for alice
1929 env(jt);
1930 BEAST_EXPECT(strHex((*env.le(alice))[sfMessageKey]) == strHex(rkp.first.slice()));
1931 jt[sfMessageKey] = "";
1932 env(jt);
1933 BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfMessageKey));
1934
1935 // bob does not have permission to set transfer rate for alice
1936 env(rate(alice, 2.0), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1937
1938 // alice give granular permission of AccountTransferRateSet to bob
1939 env(delegate::set(
1940 alice,
1941 bob,
1942 {"AccountDomainSet",
1943 "AccountEmailHashSet",
1944 "AccountMessageKeySet",
1945 "AccountTransferRateSet"}));
1946 env.close();
1947 auto jtRate = rate(alice, 2.0);
1948 jtRate[sfDelegate] = bob.human();
1949 env(jtRate, delegate::As(bob));
1950 BEAST_EXPECT((*env.le(alice))[sfTransferRate] == 2000000000);
1951
1952 // bob does not have permission to set ticksize for alice
1953 jt[sfTickSize] = 8;
1955
1956 // alice give granular permission of AccountTickSizeSet to bob
1957 env(delegate::set(
1958 alice,
1959 bob,
1960 {"AccountDomainSet",
1961 "AccountEmailHashSet",
1962 "AccountMessageKeySet",
1963 "AccountTransferRateSet",
1964 "AccountTickSizeSet"}));
1965 env.close();
1966 env(jt);
1967 BEAST_EXPECT((*env.le(alice))[sfTickSize] == 8);
1968
1969 // can not set asfRequireAuth flag for alice
1970 env(fset(alice, asfRequireAuth), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1971
1972 // reset Delegate will delete the Delegate
1973 // object
1974 env(delegate::set(alice, bob, {}));
1975 // bib still does not have permission to set asfRequireAuth for
1976 // alice
1977 env(fset(alice, asfRequireAuth), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
1978 // alice can set for herself
1979 env(fset(alice, asfRequireAuth));
1980 env.require(Flags(alice, asfRequireAuth));
1981 env.close();
1982
1983 // can not update tick size because bob no longer has permission
1984 jt[sfTickSize] = 7;
1986
1987 env(delegate::set(
1988 alice, bob, {"AccountDomainSet", "AccountEmailHashSet", "AccountMessageKeySet"}));
1989 env.close();
1990
1991 // bob does not have permission to set wallet locater for alice
1992 std::string const locator =
1993 "9633EC8AF54F16B5286DB1D7B519EF49EEFC050C0C8AC4384F1D88ACD1BFDF"
1994 "05";
1995 auto jv2 = noop(alice);
1996 jv2[sfDomain] = strHex(domain);
1997 jv2[sfDelegate] = bob.human();
1998 jv2[sfWalletLocator] = locator;
1999 env(jv2, Ter(terNO_DELEGATE_PERMISSION));
2000 }
2001
2002 // can not set AccountSet flags on behalf of other account,
2003 // in permissions.macro, the template for AccountSet does
2004 // not allow any flag set or clear.
2005 {
2006 Env env(*this);
2007 auto const alice = Account{"alice"};
2008 auto const bob = Account{"bob"};
2009 env.fund(XRP(10000), alice, bob);
2010 env.close();
2011
2012 auto testSetClearFlag = [&](std::uint32_t flag) {
2013 // bob can not set flag on behalf of alice
2014 env(fset(alice, flag), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
2015 // alice set by herself
2016 env(fset(alice, flag));
2017 env.close();
2018 env.require(Flags(alice, flag));
2019 // bob can not clear on behalf of alice
2020 env(fclear(alice, flag), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
2021 };
2022
2023 // testSetClearFlag(asfNoFreeze);
2024 testSetClearFlag(asfRequireAuth);
2025 testSetClearFlag(asfAllowTrustLineClawback);
2026
2027 // alice gives some granular permissions to bob
2028 env(delegate::set(
2029 alice, bob, {"AccountDomainSet", "AccountEmailHashSet", "AccountMessageKeySet"}));
2030 env.close();
2031
2032 testSetClearFlag(asfDefaultRipple);
2033 testSetClearFlag(asfDepositAuth);
2034 testSetClearFlag(asfDisallowIncomingCheck);
2035 testSetClearFlag(asfDisallowIncomingNFTokenOffer);
2036 testSetClearFlag(asfDisallowIncomingPayChan);
2037 testSetClearFlag(asfDisallowIncomingTrustline);
2038 testSetClearFlag(asfDisallowXRP);
2039 testSetClearFlag(asfRequireDest);
2040 testSetClearFlag(asfGlobalFreeze);
2041
2042 // bob can not set asfAccountTxnID on behalf of alice
2043 env(fset(alice, asfAccountTxnID), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
2044 env(fset(alice, asfAccountTxnID));
2045 env.close();
2046 BEAST_EXPECT(env.le(alice)->isFieldPresent(sfAccountTxnID));
2047 env(fclear(alice, asfAccountTxnID), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
2048
2049 // bob can not set asfAuthorizedNFTokenMinter on behalf of alice
2050 json::Value jt = fset(alice, asfAuthorizedNFTokenMinter);
2051 jt[sfDelegate] = bob.human();
2052 jt[sfNFTokenMinter] = bob.human();
2054
2055 // bob gives alice some permissions
2056 env(delegate::set(
2057 bob, alice, {"AccountDomainSet", "AccountEmailHashSet", "AccountMessageKeySet"}));
2058 env.close();
2059
2060 // since we can not set asfNoFreeze if asfAllowTrustLineClawback is
2061 // set, which can not be clear either. Test alice set asfNoFreeze on
2062 // behalf of bob.
2063 env(fset(alice, asfNoFreeze), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
2064 env(fset(bob, asfNoFreeze));
2065 env.close();
2066 env.require(Flags(bob, asfNoFreeze));
2067 // alice can not clear on behalf of bob
2068 env(fclear(alice, asfNoFreeze), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
2069
2070 // bob can not set asfDisableMaster on behalf of alice
2071 Account const bobKey{"bobKey", KeyType::Secp256k1};
2072 env(regkey(bob, bobKey));
2073 env.close();
2074 env(fset(alice, asfDisableMaster),
2075 delegate::As(bob),
2076 Sig(bob),
2078 }
2079
2080 // tfFullyCanonicalSig won't block delegated transaction
2081 {
2082 Env env(*this);
2083 Account const alice{"alice"};
2084 Account const bob{"bob"};
2085 env.fund(XRP(10000), alice, bob);
2086 env.close();
2087
2088 env(delegate::set(alice, bob, {"AccountDomainSet", "AccountEmailHashSet"}));
2089 env.close();
2090
2091 std::string const domain = "example.com";
2092 auto jt = noop(alice);
2093 jt[sfDomain] = strHex(domain);
2094 jt[sfDelegate] = bob.human();
2095 jt[sfFlags] = tfFullyCanonicalSig;
2096
2097 env(jt);
2098 BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
2099 }
2100
2101 // setting invalid field not in permissions.macro template will be rejected.
2102 {
2103 Env env(*this);
2104 auto const alice = Account{"alice"};
2105 auto const bob = Account{"bob"};
2106 env.fund(XRP(10000), alice, bob);
2107 env.close();
2108
2109 // Alice gives Bob permission to set her Domain
2110 env(delegate::set(alice, bob, {"AccountDomainSet"}));
2111 env.close();
2112
2113 std::string const domain = "example.com";
2114 auto txJson = noop(alice);
2115 txJson[sfDomain] = strHex(domain);
2116 txJson[sfDelegate] = bob.human();
2117
2118 // sfNFTokenMinter is a valid field in AccountSet tx, but
2119 // it is not permitted for granular template
2120 txJson[sfNFTokenMinter] = bob.human();
2121
2122 env(txJson, Ter(terNO_DELEGATE_PERMISSION));
2123 }
2124
2125 // Delegated AccountSet with no fields and no flags is allowed,
2126 // because it is allowed in the non-delegated case as well.
2127 {
2128 Env env(*this);
2129 Account const alice{"alice"};
2130 Account const bob{"bob"};
2131 env.fund(XRP(10000), alice, bob);
2132 env.close();
2133
2134 env(delegate::set(alice, bob, {"AccountDomainSet"}));
2135 env.close();
2136
2137 auto jt = noop(alice);
2138 jt[sfDelegate] = bob.human();
2139 env(jt);
2140 }
2141
2142 // Revoking all permissions deletes the SLE and subsequent attempts are rejected.
2143 {
2144 Env env(*this);
2145 Account const alice{"alice"};
2146 Account const bob{"bob"};
2147 env.fund(XRP(10000), alice, bob);
2148 env.close();
2149
2150 env(delegate::set(alice, bob, {"AccountDomainSet"}));
2151 env.close();
2152
2153 std::string const domain = "example.com";
2154 auto jt = noop(alice);
2155 jt[sfDomain] = strHex(domain);
2156 jt[sfDelegate] = bob.human();
2157 env(jt);
2158
2159 // empty DelegateSet deletes the SLE
2160 env(delegate::set(alice, bob, {}));
2161 env.close();
2162
2164 }
2165 }
2166
2167 void
2169 {
2170 testcase("test MPTokenIssuanceSet granular");
2171 using namespace jtx;
2172
2173 // test MPTokenIssuanceUnlock and MPTokenIssuanceLock permissions
2174 {
2175 Env env(*this);
2176 Account const alice{"alice"};
2177 Account const bob{"bob"};
2178 env.fund(XRP(100000), alice, bob);
2179 env.close();
2180
2181 MPTTester mpt(env, alice, {.fund = false});
2182 env.close();
2183 mpt.create({.flags = tfMPTCanLock});
2184 env.close();
2185
2186 // delegate ledger object is not created yet
2187 mpt.set(
2188 {.account = alice,
2189 .flags = tfMPTLock,
2190 .delegate = bob,
2192
2193 // alice gives granular permission to bob of MPTokenIssuanceUnlock
2194 env(delegate::set(alice, bob, {"MPTokenIssuanceUnlock"}));
2195 env.close();
2196 // bob does not have lock permission
2197 mpt.set(
2198 {.account = alice,
2199 .flags = tfMPTLock,
2200 .delegate = bob,
2202 // bob now has lock permission, but does not have unlock permission
2203 env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
2204 env.close();
2205 mpt.set({.account = alice, .flags = tfMPTLock, .delegate = bob});
2206 mpt.set(
2207 {.account = alice,
2208 .flags = tfMPTUnlock,
2209 .delegate = bob,
2211
2212 // now bob can lock and unlock
2213 env(delegate::set(alice, bob, {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"}));
2214 env.close();
2215 mpt.set({.account = alice, .flags = tfMPTUnlock, .delegate = bob});
2216 mpt.set({.account = alice, .flags = tfMPTLock, .delegate = bob});
2217 env.close();
2218 }
2219
2220 // test mix of granular and transaction level permission
2221 {
2222 Env env(*this);
2223 Account const alice{"alice"};
2224 Account const bob{"bob"};
2225 env.fund(XRP(100000), alice, bob);
2226 env.close();
2227
2228 MPTTester mpt(env, alice, {.fund = false});
2229 env.close();
2230 mpt.create({.flags = tfMPTCanLock});
2231 env.close();
2232
2233 // alice gives granular permission to bob of MPTokenIssuanceLock
2234 env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
2235 env.close();
2236 mpt.set({.account = alice, .flags = tfMPTLock, .delegate = bob});
2237 // bob does not have unlock permission
2238 mpt.set(
2239 {.account = alice,
2240 .flags = tfMPTUnlock,
2241 .delegate = bob,
2243
2244 // alice gives bob some unrelated permission with
2245 // MPTokenIssuanceLock
2246 env(delegate::set(alice, bob, {"NFTokenMint", "MPTokenIssuanceLock", "NFTokenBurn"}));
2247 env.close();
2248 // bob can not unlock
2249 mpt.set(
2250 {.account = alice,
2251 .flags = tfMPTUnlock,
2252 .delegate = bob,
2254
2255 // alice add MPTokenIssuanceSet to permissions
2256 env(delegate::set(
2257 alice,
2258 bob,
2259 {"NFTokenMint", "MPTokenIssuanceLock", "NFTokenBurn", "MPTokenIssuanceSet"}));
2260 mpt.set({.account = alice, .flags = tfMPTUnlock, .delegate = bob});
2261 // alice can lock by herself
2262 mpt.set({.account = alice, .flags = tfMPTLock});
2263 mpt.set({.account = alice, .flags = tfMPTUnlock, .delegate = bob});
2264 mpt.set({.account = alice, .flags = tfMPTLock, .delegate = bob});
2265 }
2266
2267 // tfFullyCanonicalSig won't block delegated transaction
2268 {
2269 Env env(*this);
2270 Account const alice{"alice"};
2271 Account const bob{"bob"};
2272 env.fund(XRP(100000), alice, bob);
2273 env.close();
2274
2275 MPTTester mpt(env, alice, {.fund = false});
2276 env.close();
2277 mpt.create({.flags = tfMPTCanLock});
2278 env.close();
2279
2280 // alice gives granular permission to bob of MPTokenIssuanceLock
2281 env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
2282 env.close();
2283 mpt.set({.account = alice, .flags = tfMPTLock | tfFullyCanonicalSig, .delegate = bob});
2284 }
2285
2286 // field not permitted to exist in granular delegation
2287 {
2288 Env env(*this);
2289 Account const alice{"alice"};
2290 Account const bob{"bob"};
2291 env.fund(XRP(100000), alice, bob);
2292
2293 MPTTester mpt(env, alice, {.fund = false});
2294 mpt.create({.flags = tfMPTCanLock});
2295 env.close();
2296
2297 // alice gives granular permission to bob for MPTokenIssuanceLock
2298 env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
2299 env.close();
2300
2301 // tfMPTSetCanLock is a valid MPTokenIssuanceSet flag but is not
2302 // covered by the MPTokenIssuanceLock granular permission, so a
2303 // delegate holding only that permission cannot set it.
2304 mpt.set(
2305 {.account = alice,
2306 .flags = tfMPTSetCanLock,
2307 .delegate = bob,
2309
2310 // Notice: flags not defined in permissions.macro are not permitted for delegation.
2311 // Since preflight will check invalid flag for the tx, it is not reachable.
2312 // If any new flag is defined into the transaction in the future,
2313 // but is not allowed for delegation, the transaction will be rejected with
2314 // terNO_DELEGATE_PERMISSION. The set of permitted flags for delegation is defined in
2315 // permissions.macro.
2316 }
2317
2318 // Fields and flags not in permissions.macro template are not permitted for delegation.
2319 {
2320 Env env(*this);
2321 Account const alice{"alice"};
2322 Account const bob{"bob"};
2323 Account const credIssuer{"credIssuer"};
2324 env.fund(XRP(100000), alice, bob, credIssuer);
2325 env.close();
2326
2327 env(pdomain::setTx(credIssuer, {{.issuer = credIssuer, .credType = "credential"}}));
2328 auto const domainID = pdomain::getNewDomain(env.meta());
2329 env.close();
2330
2331 MPTTester mpt(env, alice, {.fund = false});
2332 mpt.create({.flags = tfMPTCanLock | tfMPTCanTransfer});
2333 env.close();
2334
2335 // bob holds every granular permission defined for MPTokenIssuanceSet
2336 env(delegate::set(alice, bob, {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"}));
2337 env.close();
2338
2339 // Any of those fields or flags not defined in permissions.macro are not permitted for
2340 // delegation.
2341 std::vector<MPTSet> const args = {
2342 {.transferFee = 100},
2343 {.metadata = "test"},
2344 {.domainID = domainID},
2345 {.flags = tfMPTSetCanTrade},
2346 {.flags = tfMPTSetCanClawback},
2347 {.immutableFlags = tifMPTTransferFee},
2348 // a permitted flag does not unlock the extra fields
2349 {.flags = tfMPTLock, .domainID = domainID},
2350 };
2351
2352 for (auto arg : args)
2353 {
2354 arg.account = alice;
2355 arg.delegate = bob;
2357 mpt.set(arg);
2358 env.close();
2359 }
2360 }
2361 }
2362
2363 void
2365 {
2366 testcase("test granular permission with sponsor common fields");
2367 using namespace jtx;
2368
2369 Account const alice{"alice"};
2370 Account const bob{"bob"};
2371 Account const gw{"gw"};
2372 Account const sponsor{"sponsor"};
2373 auto const usd = gw["USD"];
2374 auto const feeAmt = XRP(10);
2375
2376 // Sponsor fields are common fields, so they are permitted in every granular template.
2377 // Fee sponsorship is allowed for a granular delegated transaction.
2378 Env env(*this);
2379 env.fund(XRP(10000), alice, bob, gw, sponsor);
2380 env.trust(usd(200), alice);
2381 env.close();
2382
2383 env(delegate::set(gw, bob, {"PaymentMint"}));
2384 env.close();
2385
2386 // Co-signed: the sponsor account pays the fee.
2387 auto const gwBalance = env.balance(gw);
2388 auto const bobBalance = env.balance(bob);
2389 auto const sponsorBalance = env.balance(sponsor);
2390 env(pay(gw, alice, usd(50)),
2391 delegate::As(bob),
2392 Fee(feeAmt),
2394 Sig(sfSponsorSignature, sponsor));
2395 env.close();
2396 env.require(Balance(alice, usd(50)));
2397 BEAST_EXPECT(env.balance(gw) == gwBalance);
2398 BEAST_EXPECT(env.balance(bob) == bobBalance);
2399 BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
2400
2401 // Pre-funded: sponsorship(sponsor, bob) pays the fee, bob is sfDelegate.
2402 env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(bob));
2403 env.close();
2404 auto const sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, bob);
2405 env(pay(gw, alice, usd(50)),
2406 delegate::As(bob),
2407 Fee(feeAmt),
2409 env.close();
2410 env.require(Balance(alice, usd(100)));
2411 BEAST_EXPECT(env.balance(gw) == gwBalance);
2412 BEAST_EXPECT(env.balance(bob) == bobBalance);
2413 BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, bob) == sponsorFee - feeAmt);
2414
2415 // Reserve sponsorship is not tested here: checkSponsor rejects it with temINVALID for any
2416 // delegated transaction before the permission check, regardless of the delegate's
2417 // permissions. It is covered by testDelegateBlockReserveSponsor in Sponsor_test.
2418 }
2419
2420 void
2422 {
2423 testcase("test single sign");
2424 using namespace jtx;
2425
2426 Env env(*this);
2427 Account const alice{"alice"};
2428 Account const bob{"bob"};
2429 Account const carol{"carol"};
2430 env.fund(XRP(100000), alice, bob, carol);
2431 env.close();
2432
2433 env(delegate::set(alice, bob, {"Payment"}));
2434 env.close();
2435
2436 auto aliceBalance = env.balance(alice);
2437 auto bobBalance = env.balance(bob);
2438 auto carolBalance = env.balance(carol);
2439
2440 env(pay(alice, carol, XRP(100)), Fee(XRP(10)), delegate::As(bob), Sig(bob));
2441 env.close();
2442 BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(100));
2443 BEAST_EXPECT(env.balance(bob) == bobBalance - XRP(10));
2444 BEAST_EXPECT(env.balance(carol) == carolBalance + XRP(100));
2445 }
2446
2447 void
2449 {
2450 testcase("test single sign with bad secret");
2451 using namespace jtx;
2452
2453 {
2454 Env env(*this);
2455 Account const alice{"alice"};
2456 Account const bob{"bob"};
2457 Account const carol{"carol"};
2458 env.fund(XRP(100000), alice, bob, carol);
2459 env.close();
2460
2461 env(delegate::set(alice, bob, {"Payment"}));
2462 env.close();
2463
2464 auto aliceBalance = env.balance(alice);
2465 auto bobBalance = env.balance(bob);
2466 auto carolBalance = env.balance(carol);
2467
2468 env(pay(alice, carol, XRP(100)),
2469 Fee(XRP(10)),
2470 delegate::As(bob),
2471 Sig(alice),
2472 Ter(tefBAD_AUTH));
2473 env.close();
2474 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2475 BEAST_EXPECT(env.balance(bob) == bobBalance);
2476 BEAST_EXPECT(env.balance(carol) == carolBalance);
2477 }
2478
2479 {
2480 Env env(*this);
2481 Account const alice{"alice"};
2482 Account const bob{"bob"};
2483 Account const carol{"carol"};
2484 env.fund(XRP(100000), alice, bob, carol);
2485 env.close();
2486
2487 env(delegate::set(alice, bob, {"TrustSet"}));
2488 env.close();
2489
2490 auto aliceBalance = env.balance(alice);
2491 auto bobBalance = env.balance(bob);
2492 auto carolBalance = env.balance(carol);
2493
2494 env(pay(alice, carol, XRP(100)),
2495 Fee(XRP(10)),
2496 delegate::As(bob),
2497 Sig(carol),
2499 env.close();
2500 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2501 BEAST_EXPECT(env.balance(bob) == bobBalance);
2502 BEAST_EXPECT(env.balance(carol) == carolBalance);
2503
2504 env(pay(alice, carol, XRP(100)),
2505 Fee(XRP(10)),
2506 delegate::As(bob),
2507 Sig(alice),
2509 env.close();
2510 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2511 BEAST_EXPECT(env.balance(bob) == bobBalance);
2512 BEAST_EXPECT(env.balance(carol) == carolBalance);
2513 }
2514
2515 {
2516 Env env(*this);
2517 Account const alice{"alice"};
2518 Account const bob{"bob"};
2519 Account const carol{"carol"};
2520 env.fund(XRP(100000), alice, bob, carol);
2521 env.close();
2522
2523 auto aliceBalance = env.balance(alice);
2524 auto bobBalance = env.balance(bob);
2525 auto carolBalance = env.balance(carol);
2526
2527 env(pay(alice, carol, XRP(100)),
2528 Fee(XRP(10)),
2529 delegate::As(bob),
2530 Sig(alice),
2532 env.close();
2533 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2534 BEAST_EXPECT(env.balance(bob) == bobBalance);
2535 BEAST_EXPECT(env.balance(carol) == carolBalance);
2536
2537 env(pay(alice, carol, XRP(100)),
2538 Fee(XRP(10)),
2539 delegate::As(bob),
2540 Sig(carol),
2542 env.close();
2543 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2544 BEAST_EXPECT(env.balance(bob) == bobBalance);
2545 BEAST_EXPECT(env.balance(carol) == carolBalance);
2546 }
2547 }
2548
2549 void
2551 {
2552 testcase("test multi sign");
2553 using namespace jtx;
2554
2555 Env env(*this);
2556 Account const alice{"alice"};
2557 Account const bob{"bob"};
2558 Account const carol{"carol"};
2559 Account const daria{"daria"};
2560 Account const edward{"edward"};
2561 env.fund(XRP(100000), alice, bob, carol, daria, edward);
2562 env.close();
2563
2564 env(signers(bob, 2, {{daria, 1}, {edward, 1}}));
2565 env.close();
2566
2567 env(delegate::set(alice, bob, {"Payment"}));
2568 env.close();
2569
2570 auto aliceBalance = env.balance(alice);
2571 auto bobBalance = env.balance(bob);
2572 auto carolBalance = env.balance(carol);
2573 auto dariaBalance = env.balance(daria);
2574 auto edwardBalance = env.balance(edward);
2575
2576 env(pay(alice, carol, XRP(100)), Fee(XRP(10)), delegate::As(bob), Msig(daria, edward));
2577 env.close();
2578 BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(100));
2579 BEAST_EXPECT(env.balance(bob) == bobBalance - XRP(10));
2580 BEAST_EXPECT(env.balance(carol) == carolBalance + XRP(100));
2581 BEAST_EXPECT(env.balance(daria) == dariaBalance);
2582 BEAST_EXPECT(env.balance(edward) == edwardBalance);
2583 }
2584
2585 void
2587 {
2588 testcase("test multi sign which does not meet quorum");
2589 using namespace jtx;
2590
2591 Env env(*this);
2592 Account const alice{"alice"};
2593 Account const bob{"bob"};
2594 Account const carol{"carol"};
2595 Account const daria{"daria"};
2596 Account const edward{"edward"};
2597 Account const fred{"fred"};
2598 env.fund(XRP(100000), alice, bob, carol, daria, edward, fred);
2599 env.close();
2600
2601 env(signers(bob, 3, {{daria, 1}, {edward, 1}, {fred, 1}}));
2602 env.close();
2603
2604 env(delegate::set(alice, bob, {"Payment"}));
2605 env.close();
2606
2607 auto aliceBalance = env.balance(alice);
2608 auto bobBalance = env.balance(bob);
2609 auto carolBalance = env.balance(carol);
2610 auto dariaBalance = env.balance(daria);
2611 auto edwardBalance = env.balance(edward);
2612
2613 env(pay(alice, carol, XRP(100)),
2614 Fee(XRP(10)),
2615 delegate::As(bob),
2616 Msig(daria, edward),
2618 env.close();
2619 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2620 BEAST_EXPECT(env.balance(bob) == bobBalance);
2621 BEAST_EXPECT(env.balance(carol) == carolBalance);
2622 BEAST_EXPECT(env.balance(daria) == dariaBalance);
2623 BEAST_EXPECT(env.balance(edward) == edwardBalance);
2624 }
2625
2626 void
2628 {
2629 // checkMultiSign disallows the owner of the account to
2630 // be part of the multisigner list. When it is a delegated transaction,
2631 // the delegate account should not be part of the multisigner list.
2632 testcase("test delegator as multisigner in delegate's signer list");
2633 using namespace jtx;
2634
2635 Env env(*this);
2636 Account const alice{"alice"};
2637 Account const bob{"bob"};
2638 Account const carol{"carol"};
2639 Account const daria{"daria"};
2640 env.fund(XRP(100000), alice, bob, carol, daria);
2641 env.close();
2642
2643 env(delegate::set(alice, bob, {"Payment"}));
2644 env.close();
2645
2646 // bob's signer list includes the delegator alice and daria
2647 env(signers(bob, 2, {{alice, 1}, {daria, 1}}));
2648 env.close();
2649
2650 auto aliceBalance = env.balance(alice);
2651 auto bobBalance = env.balance(bob);
2652 auto carolBalance = env.balance(carol);
2653 auto const amt = 100;
2654
2655 // alice can sign as a multisigner for bob
2656 env(pay(alice, carol, XRP(100)), Fee(XRP(10)), delegate::As(bob), Msig(alice, daria));
2657 env.close();
2658
2659 BEAST_EXPECT(env.balance(alice) == aliceBalance - XRP(amt));
2660 BEAST_EXPECT(env.balance(bob) == bobBalance - XRP(10));
2661 BEAST_EXPECT(env.balance(carol) == carolBalance + XRP(amt));
2662
2663 // alice can not sign as a multisigner if she sent the transaction by herself.
2664 env(pay(alice, carol, XRP(100)), Fee(XRP(10)), Msig(alice, daria), Ter(telENV_RPC_FAILED));
2665 env.close();
2666
2667 // Get new balances
2668 aliceBalance = env.balance(alice);
2669 bobBalance = env.balance(bob);
2670 carolBalance = env.balance(carol);
2671
2672 // bob (the delegate) should not appear as a multisigner in his transaction sent on behalf
2673 // of alice. STTx::checkMultiSign catches this at the local-check stage, so the jtx
2674 // framework returns telENV_RPC_FAILED.
2675 env(pay(alice, carol, XRP(50)),
2676 Fee(XRP(10)),
2677 delegate::As(bob),
2678 Msig(alice, bob),
2680 env.close();
2681 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2682 BEAST_EXPECT(env.balance(bob) == bobBalance);
2683 BEAST_EXPECT(env.balance(carol) == carolBalance);
2684 }
2685
2686 void
2688 {
2689 // In sortAndValidateSigners, if it is a delegated transaction, the delegate account is
2690 // the forbidden account from appearing in its own Signers array.
2691 testcase("test sign_for with delegated transaction");
2692 using namespace jtx;
2693
2694 Env env(*this);
2695 Account const alice{"alice"};
2696 Account const bob{"bob"};
2697 Account const carol{"carol"};
2698 Account const daria{"daria"};
2699 env.fund(XRP(100000), alice, bob, carol, daria);
2700 env.close();
2701
2702 env(delegate::set(alice, bob, {"Payment"}));
2703 env.close();
2704
2705 // bob's signer list includes the delegator alice and daria
2706 env(signers(bob, 2, {{alice, 1}, {daria, 1}}));
2707 env.close();
2708
2709 auto const baseFee = env.current()->fees().base;
2710
2711 auto const sendAmt = 1'000'000;
2712 auto makeDelegateTx = [&]() -> json::Value {
2713 json::Value jv;
2714 jv[jss::tx_json][jss::Account] = alice.human();
2715 jv[jss::tx_json][sfDelegate.jsonName] = bob.human();
2716 jv[jss::tx_json][jss::TransactionType] = jss::Payment;
2717 jv[jss::tx_json][jss::Destination] = carol.human();
2718 jv[jss::tx_json][jss::Amount] = sendAmt;
2719 jv[jss::tx_json][jss::Fee] = std::to_string((10 * baseFee).drops());
2720 jv[jss::tx_json][jss::Sequence] = env.seq(alice);
2721 jv[jss::tx_json][jss::SigningPubKey] = "";
2722 return jv;
2723 };
2724
2725 // The delegator alice and daria both sign via sign_for, which is valid
2726 {
2727 auto const aliceBalance = env.balance(alice);
2728 auto const bobBalance = env.balance(bob);
2729 auto const dariaBalance = env.balance(daria);
2730 auto const carolBalance = env.balance(carol);
2731
2732 json::Value jv = makeDelegateTx();
2733 jv[jss::account] = alice.human();
2734 jv[jss::secret] = alice.name();
2735 auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
2736 BEAST_EXPECT(jrr[jss::status] == "success");
2737
2738 json::Value jv2;
2739 jv2[jss::tx_json] = jrr[jss::tx_json];
2740 jv2[jss::account] = daria.human();
2741 jv2[jss::secret] = daria.name();
2742 jrr = env.rpc("json", "sign_for", to_string(jv2))[jss::result];
2743 BEAST_EXPECT(jrr[jss::status] == "success");
2744
2745 json::Value jvSubmit;
2746 jvSubmit[jss::tx_json] = jrr[jss::tx_json];
2747 jrr = env.rpc("json", "submit_multisigned", to_string(jvSubmit))[jss::result];
2748 BEAST_EXPECT(jrr[jss::status] == "success");
2749 env.close();
2750 BEAST_EXPECT(env.balance(alice) == aliceBalance - XRPAmount(sendAmt));
2751 BEAST_EXPECT(env.balance(bob) == bobBalance - (10 * baseFee));
2752 BEAST_EXPECT(env.balance(daria) == dariaBalance);
2753 BEAST_EXPECT(env.balance(carol) == carolBalance + XRPAmount(sendAmt));
2754 }
2755
2756 // The delegated account bob attempts sign_for, will be rejected.
2757 {
2758 json::Value jv = makeDelegateTx();
2759 jv[jss::account] = bob.human();
2760 jv[jss::secret] = bob.name();
2761 auto jrr = env.rpc("json", "sign_for", to_string(jv))[jss::result];
2762 BEAST_EXPECT(jrr[jss::status] == "error");
2763 BEAST_EXPECT(
2764 jrr[jss::error_message].asString().contains(
2765 "A Signer may not be the transaction's Account"));
2766 }
2767 }
2768
2769 void
2771 {
2772 testcase("test permission value");
2773 using namespace jtx;
2774
2775 Env env(*this, features);
2776
2777 Account const alice{"alice"};
2778 Account const bob{"bob"};
2779 env.fund(XRP(100000), alice, bob);
2780 env.close();
2781
2782 auto buildRequest = [&](auto value) -> json::Value {
2783 json::Value jv;
2784 jv[jss::TransactionType] = jss::DelegateSet;
2785 jv[jss::Account] = alice.human();
2786 jv[sfAuthorize.jsonName] = bob.human();
2787
2788 json::Value permissionsJson(json::ValueType::Array);
2789 json::Value permissionValue;
2790 permissionValue[sfPermissionValue.jsonName] = value;
2791 json::Value permissionObj;
2792 permissionObj[sfPermission.jsonName] = permissionValue;
2793 permissionsJson.append(permissionObj);
2794 jv[sfPermissions.jsonName] = permissionsJson;
2795
2796 return jv;
2797 };
2798
2799 // invalid permission value.
2800 // neither granular permission nor transaction level permission
2801 for (auto value : {0, 100000, 54321})
2802 {
2803 auto jv = buildRequest(value);
2804 env(jv, Ter(temMALFORMED));
2805 }
2806 }
2807
2808 void
2810 {
2811 testcase("test delegate disabled tx");
2812 using namespace jtx;
2813
2814 // map of tx and required feature.
2815 // non-delegable tx are not included.
2816 // NFTokenMint, NFTokenBurn, NFTokenCreateOffer, NFTokenCancelOffer,
2817 // NFTokenAcceptOffer are not included, they are tested separately.
2819 {"AMMClawback", featureAMMClawback},
2820 {"AMMCreate", featureAMM},
2821 {"AMMDeposit", featureAMM},
2822 {"AMMWithdraw", featureAMM},
2823 {"AMMVote", featureAMM},
2824 {"AMMBid", featureAMM},
2825 {"AMMDelete", featureAMM},
2826 {"XChainCreateClaimID", featureXChainBridge},
2827 {"XChainCommit", featureXChainBridge},
2828 {"XChainClaim", featureXChainBridge},
2829 {"XChainAccountCreateCommit", featureXChainBridge},
2830 {"XChainAddClaimAttestation", featureXChainBridge},
2831 {"XChainAddAccountCreateAttestation", featureXChainBridge},
2832 {"XChainModifyBridge", featureXChainBridge},
2833 {"XChainCreateBridge", featureXChainBridge},
2834 {"DIDSet", featureDID},
2835 {"DIDDelete", featureDID},
2836 {"OracleSet", featurePriceOracle},
2837 {"OracleDelete", featurePriceOracle},
2838 {"LedgerStateFix", fixNFTokenPageLinks},
2839 {"MPTokenIssuanceCreate", featureMPTokensV1},
2840 {"MPTokenIssuanceDestroy", featureMPTokensV1},
2841 {"MPTokenIssuanceSet", featureMPTokensV1},
2842 {"MPTokenAuthorize", featureMPTokensV1},
2843 {"CredentialCreate", featureCredentials},
2844 {"CredentialAccept", featureCredentials},
2845 {"CredentialDelete", featureCredentials},
2846 {"NFTokenModify", featureDynamicNFT},
2847 {"PermissionedDomainSet", featurePermissionedDomains},
2848 {"PermissionedDomainDelete", featurePermissionedDomains},
2849 {"SponsorshipSet", featureSponsor},
2850 };
2851
2852 // Can not delegate tx if any required feature disabled.
2853 {
2854 auto txAmendmentDisabled = [&](FeatureBitset features, std::string const& tx) {
2855 BEAST_EXPECT(txRequiredFeatures.contains(tx));
2856
2857 Env env(*this, features - txRequiredFeatures[tx]);
2858
2859 Account const alice{"alice"};
2860 Account const bob{"bob"};
2861 env.fund(XRP(100000), alice, bob);
2862 env.close();
2863
2864 env(delegate::set(alice, bob, {tx}), Ter(temMALFORMED));
2865 };
2866
2867 for (auto const& tx : txRequiredFeatures)
2868 txAmendmentDisabled(features, tx.first);
2869 }
2870
2871 // if all the required features in txRequiredFeatures are enabled, will
2872 // succeed
2873 {
2874 auto txAmendmentEnabled = [&](std::string const& tx) {
2875 Env env(*this, features);
2876
2877 Account const alice{"alice"};
2878 Account const bob{"bob"};
2879 env.fund(XRP(100000), alice, bob);
2880 env.close();
2881
2882 env(delegate::set(alice, bob, {tx}));
2883 };
2884
2885 for (auto const& tx : txRequiredFeatures)
2886 txAmendmentEnabled(tx.first);
2887 }
2888
2889 // Granular permissions also require the amendment for their underlying
2890 // transaction type.
2891 {
2892 for (auto const permission : {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"})
2893 {
2894 Env env(*this, features - featureMPTokensV1);
2895
2896 Account const alice{"alice"};
2897 Account const bob{"bob"};
2898 env.fund(XRP(100000), alice, bob);
2899 env.close();
2900
2901 env(delegate::set(alice, bob, {permission}), Ter(temMALFORMED));
2902 }
2903 }
2904 }
2905
2906 void
2908 {
2909 testcase("Make sure GranularSandbox is checked after transaction-level permission");
2910
2911 using namespace jtx;
2912
2913 Env env(*this);
2914 Account const gw{"gw"};
2915 Account const alice{"alice"};
2916 Account const bob{"bob"};
2917 env.fund(XRP(10000), gw, alice, bob);
2918
2919 env(fset(gw, asfRequireAuth));
2920 env.close();
2921 env(trust(alice, gw["USD"](50)));
2922 env.close();
2923 env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
2924 env.close();
2925
2926 env(trust(gw, gw["USD"](0), alice, tfSetfAuth), delegate::As(bob));
2927 env.close();
2928
2929 // sfQualityOut is a valid TrustSet field, but not permitted in granular template
2930 json::Value txJson = trust(gw, gw["USD"](0), alice, tfSetfAuth);
2931 txJson[sfQualityOut.jsonName] = 100;
2932 env(txJson, delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
2933
2934 // Now Alice grants Bob with transaction level permission
2935 env(delegate::set(gw, bob, {"TrustlineAuthorize", "TrustSet"}));
2936 env.close();
2937
2938 // NOTE: This case is to ensure that if a delegate possesses a
2939 // transaction-level permission (e.g., TrustSet), the granular sandbox must not incorrectly
2940 // block the transaction. The function checkGranularSandbox MUST be called after the
2941 // transaction-level permission check. This test case is to avoid future refactor mistakes,
2942 // modifying the order will fail here.
2943 env(txJson, delegate::As(bob));
2944 }
2945
2946 void
2948 {
2949 testcase("Delegable Transactions Completeness");
2950
2951 std::size_t delegableCount = 0;
2952
2953#pragma push_macro("UNWRAP")
2954#undef UNWRAP
2955#pragma push_macro("TRANSACTION")
2956#undef TRANSACTION
2957
2958#define UNWRAP(...) __VA_ARGS__
2959#define TRANSACTION(tag, value, name, settings, ...) \
2960 if ((xrpl::TxSettings UNWRAP settings).delegable == xrpl::Delegation::Delegable) \
2961 { \
2962 delegableCount++; \
2963 }
2964
2965#include <xrpl/protocol/detail/transactions.macro>
2966
2967#undef TRANSACTION
2968#pragma pop_macro("TRANSACTION")
2969#undef UNWRAP
2970#pragma pop_macro("UNWRAP")
2971
2972 // ====================================================================
2973 // IMPORTANT NOTICE:
2974 //
2975 // If this test fails, it indicates that the 'Delegation::delegable' status
2976 // in transactions.macro has been changed. Delegation allows accounts to act
2977 // on behalf of others, significantly increasing the security surface.
2978 //
2979 //
2980 // To ENSURE any added transaction is safe and compatible with delegation:
2981 //
2982 // 1. Verify that the transaction is intended to be delegable.
2983 // 2. Every standard test case for that transaction MUST be
2984 // duplicated and verified for a Delegated context.
2985 // 3. Ensure that Fee, Reserve, and Signing are correctly handled.
2986 //
2987 // DO NOT modify expectedDelegableCount unless all scenarios, including
2988 // edge cases, have been fully tested and verified.
2989 // ====================================================================
2990 std::size_t const expectedDelegableCount = 57;
2991
2992 BEAST_EXPECTS(
2993 delegableCount == expectedDelegableCount,
2994 "\n[SECURITY] New delegable transaction detected!"
2995 "\n Expected: " +
2996 std::to_string(expectedDelegableCount) +
2997 "\n Actual: " + std::to_string(delegableCount) +
2998 "\n Action: Verify security requirements to interact with Delegation feature");
2999 }
3000
3001 void
3003 {
3004 testcase("non-delegable tx with sfDelegate is rejected at preflight");
3005 using namespace jtx;
3006
3007 Env env(*this, features);
3008 Account const alice{"alice"};
3009 Account const bob{"bob"};
3010 env.fund(XRP(10000), alice, bob);
3011 env.close();
3012
3013 // Transactions that are notDelegable and have no granular permissions
3014 // will be rejected with temINVALID at preflight.
3015 // Note: pseudo-transactions (EnableAmendment, SetFee and UNLModify) are also
3016 // notDelegable but are excluded here — passesLocalChecks() blocks them
3017 // before preflight1 is ever reached.
3018 {
3019 // SetRegularKey, SignerListSet, AccountDelete, DelegateSet.
3020 env(regkey(alice, bob), delegate::As(bob), Ter(temINVALID));
3021 env(signers(alice, 1, {{bob, 1}}), delegate::As(bob), Ter(temINVALID));
3022 env(acctdelete(alice, bob), delegate::As(bob), Ter(temINVALID));
3023 env(delegate::set(alice, bob, {"Payment"}), delegate::As(bob), Ter(temINVALID));
3024
3025 // SAV transactions.
3026 {
3027 Vault const vault{env};
3028 auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()});
3029 env(createTx, delegate::As(bob), Ter(temINVALID));
3030
3031 env(vault.set({.owner = alice, .id = keylet.key}),
3032 delegate::As(bob),
3033 Ter(temINVALID));
3034 env(vault.del({.owner = alice, .id = keylet.key}),
3035 delegate::As(bob),
3036 Ter(temINVALID));
3037 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = XRP(1)}),
3038 delegate::As(bob),
3039 Ter(temINVALID));
3040 env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = XRP(1)}),
3041 delegate::As(bob),
3042 Ter(temINVALID));
3043 env(vault.clawback({.issuer = alice, .id = keylet.key, .holder = bob}),
3044 delegate::As(bob),
3045 Ter(temINVALID));
3046 }
3047
3048 // Batch transaction: the outer Batch itself is non-delegable.
3049 {
3050 auto const seq = env.seq(alice);
3051 auto const batchFee = batch::calcBatchFee(env, 0, 1);
3052 env(batch::outer(alice, seq, batchFee, tfAllOrNothing),
3053 batch::Inner(pay(alice, bob, XRP(1)), seq + 1),
3054 delegate::As(bob),
3055 Ter(temINVALID));
3056 }
3057
3058 // Lending protocol transactions
3059 {
3060 Vault const vault{env};
3061 auto [createTx, keylet] = vault.create({.owner = alice, .asset = xrpIssue()});
3062 env(createTx);
3063
3064 env(loan_broker::set(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
3065 env(loan_broker::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
3066 env(loan_broker::coverDeposit(alice, keylet.key, XRP(1)),
3067 delegate::As(bob),
3068 Ter(temINVALID));
3069 env(loan_broker::coverWithdraw(alice, keylet.key, XRP(1)),
3070 delegate::As(bob),
3071 Ter(temINVALID));
3073
3074 env(loan::set(alice, keylet.key, Number(100)), delegate::As(bob), Ter(temINVALID));
3075 env(loan::manage(alice, keylet.key, 0), delegate::As(bob), Ter(temINVALID));
3076 env(loan::del(alice, keylet.key), delegate::As(bob), Ter(temINVALID));
3077 env(loan::pay(alice, keylet.key, XRP(1)), delegate::As(bob), Ter(temINVALID));
3078 }
3079 }
3080
3081 // AccountSet is notDelegable at tx level but has granular permissions,
3082 // so sfDelegate passes preflight and is rejected at invokeCheckPermission with
3083 // terNO_DELEGATE_PERMISSION.
3084 {
3085 env(fset(alice, asfDefaultRipple), delegate::As(bob), Ter(terNO_DELEGATE_PERMISSION));
3086 }
3087 }
3088
3089 void
3091 {
3092 testcase("DelegateUtils nullptr check");
3093
3094 // checkTxPermission nullptr check
3095 STTx const tx{ttPAYMENT, [](STObject&) {}};
3096 BEAST_EXPECT(checkTxPermission(nullptr, tx) == terNO_DELEGATE_PERMISSION);
3097
3098 // getGranularPermission nullptr check
3099 auto const granularPermissions = getGranularPermission(nullptr, ttPAYMENT);
3100 BEAST_EXPECT(granularPermissions.empty());
3101 }
3102
3103 void
3105 {
3106 testcase("test Permission to Tx type");
3107
3108 // 0 is not a valid permission value
3109 BEAST_EXPECT(!Permission::permissionToTxType(0));
3110
3111 // 1 maps to Payment transaction
3112 BEAST_EXPECT(Permission::permissionToTxType(1) == ttPAYMENT);
3113
3114 // UINT16_MAX+1 is the maximum possible tx-level permission value
3115 constexpr uint32_t maxTxPermission = std::numeric_limits<uint16_t>::max() + 1u;
3116 BEAST_EXPECT(Permission::permissionToTxType(maxTxPermission).has_value());
3117
3118 // exceeding maximum value should return nullopt
3119 BEAST_EXPECT(!Permission::permissionToTxType(maxTxPermission + 1));
3120
3121 // All granular permission values should return nullopt since they do not map to a TxType.
3122 for (auto const gp : {
3123#pragma push_macro("GRANULAR_PERMISSION")
3124#undef GRANULAR_PERMISSION
3125#define GRANULAR_PERMISSION(type, txType, value, ...) GranularPermissionType::type,
3126#include <xrpl/protocol/detail/permissions.macro>
3127#undef GRANULAR_PERMISSION
3128#pragma pop_macro("GRANULAR_PERMISSION")
3129 })
3130 {
3131 BEAST_EXPECT(!Permission::permissionToTxType(static_cast<uint32_t>(gp)));
3132 }
3133 }
3134
3135 void
3169};
3171} // namespace xrpl::test
T any_of(T... args)
A testsuite class.
Definition suite.h:52
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
std::string const & arg() const
Return the argument associated with the runner.
Definition suite.h:292
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
A class that simplifies iterating ledger directory pages.
Definition Dir.h:30
ConstIterator begin() const
Definition Dir.cpp:26
ConstIterator end() const
Definition Dir.cpp:44
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
static std::optional< TxType > permissionToTxType(std::uint32_t value)
void testNonDelegableTxWithDelegate(FeatureBitset features)
void testFeatureDisabled(FeatureBitset features)
void testInvalidRequest(FeatureBitset features)
void run() override
Runs the suite.
void testGranularPermissionWithSponsorCommonFields()
void testPaymentGranular(FeatureBitset features)
void testPermissionValue(FeatureBitset features)
void testTxRequireFeatures(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
std::string const & human() const
Returns the human readable public key.
std::string const & name() const
Return the name.
Definition jtx/Account.h:75
AccountID id() const
Returns the Account ID.
Sets the DeliverMin on a JTx.
Definition delivermin.h:16
A transaction testing environment.
Definition Env.h:161
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:311
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:127
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
PrettyAmount limit(Account const &account, Issue const &issue) const
Returns the IOU limit on an account.
Definition Env.cpp:254
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:302
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:1058
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:201
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:354
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition Env.cpp:538
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:766
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
Set the fee on a JTx.
Definition fee.h:20
Match set account flags.
Definition flags.h:119
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:512
void create(MPTCreate const &arg=MPTCreate{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:343
Set a multisignature on a JTx.
Definition multisign.h:53
Add a path.
Definition paths.h:47
Sets the SendMax on a JTx.
Definition sendmax.h:16
Set the regular signature on a JTx.
Definition sig.h:19
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:18
Set the flags on a JTx.
Definition txflags.h:14
Adds an inner Batch transaction to a JTx and autofills it.
Definition batch.h:91
Sets the optional URI on a DIDSet.
Definition did.h:48
T contains(T... args)
T make_tuple(T... args)
T make_unique(T... args)
T max(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
Keylet computation functions.
Definition Indexes.h:40
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:403
Keylet delegate(AccountID const &account, AccountID const &authorizedAccount) noexcept
A keylet for Delegate object.
Definition Indexes.cpp:511
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Build an outer Batch transaction JSON object.
Definition batch.cpp:55
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate the expected outer Batch transaction fee.
Definition batch.cpp:37
json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
json::Value entry(jtx::Env &env, jtx::Account const &account, jtx::Account const &authorize)
Definition delegate.cpp:41
json::Value set(jtx::Account const &account, jtx::Account const &authorize, std::vector< std::string > const &permissions)
Definition delegate.cpp:17
json::Value set(jtx::Account const &account)
Definition dids.cpp:16
json::Value set(AccountID const &account, UInt256 const &vaultId, uint32_t flags)
json::Value coverWithdraw(AccountID const &account, UInt256 const &brokerID, STAmount const &amount, uint32_t flags)
json::Value coverDeposit(AccountID const &account, UInt256 const &brokerID, STAmount const &amount, uint32_t flags)
json::Value del(AccountID const &account, UInt256 const &brokerID, uint32_t flags)
json::Value coverClawback(AccountID const &account, std::uint32_t flags)
json::Value manage(AccountID const &account, UInt256 const &loanID, std::uint32_t flags)
json::Value set(AccountID const &account, UInt256 const &loanBrokerID, Number principalRequested, std::uint32_t flags)
json::Value del(AccountID const &account, UInt256 const &loanID, std::uint32_t flags)
json::Value pay(AccountID const &account, UInt256 const &loanID, STAmount const &amount, std::uint32_t flags)
UInt256 getNewDomain(std::shared_ptr< STObject const > const &meta)
json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< UInt256 > domain)
json::Value set_fee(jtx::Account const &account, std::uint32_t flags, STAmount feeAmountDelta, std::optional< STAmount > maxFee=std::nullopt)
Definition sponsor.h:26
STAmount sponsorshipFeeBalance(jtx::Env &env, jtx::Account const &sponsor, jtx::Account const &sponsee)
Definition sponsor.cpp:93
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
json::Value regkey(Account const &account, DisabledT)
Disable the regular key.
Definition regkey.cpp:13
bool expectOffers(Env &env, AccountID const &account, std::uint16_t size, std::vector< Amounts > const &toMatch)
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
json::Value noop(Account const &account)
The null transaction.
Definition noop.h:14
XRPAmount txFee(Env const &env, std::uint16_t n)
json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:110
FeatureBitset testableAmendments()
Definition Env.h:92
json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition rate.cpp:15
json::Value signers(Account const &account, std::uint32_t quorum, std::vector< Signer > const &v)
Definition multisign.cpp:31
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
STTx createTx(bool disabling, LedgerIndex seq, PublicKey const &txKey)
Create ttUNL_MODIFY Tx.
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr FlagValue spfSponsorFee
Definition TxFlags.h:465
@ telENV_RPC_FAILED
Definition TER.h:54
@ terINSUF_FEE_B
Definition TER.h:217
@ terNO_DELEGATE_PERMISSION
Definition TER.h:231
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:108
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
std::unordered_set< GranularPermissionType > getGranularPermission(SLE::ConstRef delegate, TxType const &type)
Load the granular permissions granted to the delegate account for the specified transaction type.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
BaseUInt< 256 > Domain
Domain is a 256-bit hash representing a specific domain.
Definition UintTypes.h:59
@ tefBAD_QUORUM
Definition TER.h:175
@ tefBAD_AUTH
Definition TER.h:164
constexpr FlagValue tifMPTTransferFee
Definition TxFlags.h:381
NotTEC checkTxPermission(SLE::ConstRef delegate, STTx const &tx)
Check if the delegate account has permission to execute the transaction.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
Slice makeSlice(std::array< T, N > const &a)
Definition Slice.h:228
BaseUInt< 256 > UInt256
Definition base_uint.h:580
@ temARRAY_TOO_LARGE
Definition TER.h:129
@ temBAD_FEE
Definition TER.h:80
@ temINVALID
Definition TER.h:98
@ temMALFORMED
Definition TER.h:75
@ temDISABLED
Definition TER.h:102
@ temREDUNDANT
Definition TER.h:100
@ temBAD_SIGNER
Definition TER.h:103
@ tecPSEUDO_ACCOUNT
Definition TER.h:370
@ tecUNFUNDED_PAYMENT
Definition TER.h:293
@ tecNO_ENTRY
Definition TER.h:314
@ tecNO_TARGET
Definition TER.h:312
@ tecINSUFFICIENT_RESERVE
Definition TER.h:315
@ tesSUCCESS
Definition TER.h:250
constexpr FlagValue tfFullyCanonicalSig
Definition TxFlags.h:43
T to_string(T... args)