rippled
Loading...
Searching...
No Matches
LoanBroker_test.cpp
1#include <test/jtx.h>
2
3#include <xrpld/app/tx/detail/LoanBrokerCoverDeposit.h>
4
5#include <xrpl/beast/unit_test/suite.h>
6
7namespace xrpl {
8namespace test {
9
11{
12 // Ensure that all the features needed for Lending Protocol are included,
13 // even if they are set to unsupported.
15 jtx::testable_amendments() | featureMPTokensV1 | featureSingleAssetVault | featureLendingProtocol};
16
17 void
19 {
20 testcase("Disabled");
21 // Lending Protocol depends on Single Asset Vault (SAV). Test
22 // combinations of the two amendments.
23 // Single Asset Vault depends on MPTokensV1, but don't test every combo
24 // of that.
25 using namespace jtx;
26 auto failAll = [this](FeatureBitset features, bool goodVault = false) {
27 Env env(*this, features);
28
29 Account const alice{"alice"};
30 env.fund(XRP(10000), alice);
31
32 // Try to create a vault
33 PrettyAsset const asset{xrpIssue(), 1'000'000};
34 Vault vault{env};
35 auto const [tx, keylet] = vault.create({.owner = alice, .asset = asset});
36 env(tx, ter(goodVault ? ter(tesSUCCESS) : ter(temDISABLED)));
37 env.close();
38 BEAST_EXPECT(static_cast<bool>(env.le(keylet)) == goodVault);
39
40 using namespace loanBroker;
41 // Can't create a loan broker regardless of whether the vault exists
42 env(set(alice, keylet.key), ter(temDISABLED));
43 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
44 // Other LoanBroker transactions are disabled, too.
45 // 1. LoanBrokerCoverDeposit
46 env(coverDeposit(alice, brokerKeylet.key, asset(1000)), ter(temDISABLED));
47 // 2. LoanBrokerCoverWithdraw
48 env(coverWithdraw(alice, brokerKeylet.key, asset(1000)), ter(temDISABLED));
49 // 3. LoanBrokerCoverClawback
50 env(coverClawback(alice), ter(temDISABLED));
51 env(coverClawback(alice), loanBrokerID(brokerKeylet.key), ter(temDISABLED));
52 env(coverClawback(alice), amount(asset(0)), ter(temDISABLED));
53 env(coverClawback(alice), loanBrokerID(brokerKeylet.key), amount(asset(1000)), ter(temDISABLED));
54 // 4. LoanBrokerDelete
55 env(del(alice, brokerKeylet.key), ter(temDISABLED));
56 };
57 failAll(all - featureMPTokensV1);
58 failAll(all - featureSingleAssetVault - featureLendingProtocol);
59 failAll(all - featureSingleAssetVault);
60 failAll(all - featureLendingProtocol, true);
61 }
62
63 struct VaultInfo
64 {
68 VaultInfo(jtx::PrettyAsset const& asset_, uint256 const& vaultID_, AccountID const& pseudo)
69 : asset(asset_), vaultID(vaultID_), pseudoAccount("vault", pseudo)
70 {
71 }
72 };
73
74 void
76 char const* label,
77 jtx::Env& env,
78 jtx::Account const& issuer,
79 jtx::Account const& alice,
80 jtx::Account const& evan,
81 jtx::Account const& bystander,
82 VaultInfo const& vault,
83 VaultInfo const& badVault,
84 std::function<jtx::JTx(jtx::JTx const&)> modifyJTx,
85 std::function<void(SLE::const_ref)> checkBroker,
86 std::function<void(SLE::const_ref)> changeBroker,
87 std::function<void(SLE::const_ref)> checkChangedBroker)
88 {
89 {
90 auto const& asset = vault.asset.raw();
91 testcase << "Lifecycle: "
92 << (asset.native() ? "XRP "
93 : asset.holds<Issue>() ? "IOU "
94 : asset.holds<MPTIssue>() ? "MPT "
95 : "Unknown ")
96 << label;
97 }
98
99 using namespace jtx;
100 using namespace loanBroker;
101
102 // Bogus assets to use in test cases
103 static PrettyAsset const badMptAsset = [&]() {
104 MPTTester badMptt{env, evan, mptInitNoFund};
106 env.close();
107 return badMptt["BAD"];
108 }();
109 static PrettyAsset const badIouAsset = evan["BAD"];
110 static Account const nonExistent{"NonExistent"};
111 static PrettyAsset const ghostIouAsset = nonExistent["GST"];
112 PrettyAsset const vaultPseudoIouAsset = vault.pseudoAccount["PSD"];
113
114 auto const badKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
115 env(set(alice, badVault.vaultID));
116 env.close();
117 auto const badBrokerPseudo = [&]() {
118 if (auto const le = env.le(badKeylet); BEAST_EXPECT(le))
119 {
120 return Account{"Bad Broker pseudo-account", le->at(sfAccount)};
121 }
122 // Just to make the build work
123 return vault.pseudoAccount;
124 }();
125 PrettyAsset const badBrokerPseudoIouAsset = badBrokerPseudo["WAT"];
126
127 auto const keylet = keylet::loanbroker(alice.id(), env.seq(alice));
128 {
129 // Start with default values
130 auto jtx = env.jt(set(alice, vault.vaultID));
131 // Modify as desired
132 if (modifyJTx)
133 jtx = modifyJTx(jtx);
134 // Successfully create a Loan Broker
135 env(jtx);
136 }
137
138 env.close();
139 if (auto broker = env.le(keylet); BEAST_EXPECT(broker))
140 {
141 // log << "Broker after create: " << to_string(broker->getJson())
142 // << std::endl;
143 BEAST_EXPECT(broker->at(sfVaultID) == vault.vaultID);
144 BEAST_EXPECT(broker->at(sfAccount) != alice.id());
145 BEAST_EXPECT(broker->at(sfOwner) == alice.id());
146 BEAST_EXPECT(broker->at(sfFlags) == 0);
147 BEAST_EXPECT(broker->at(sfSequence) == env.seq(alice) - 1);
148 BEAST_EXPECT(broker->at(sfOwnerCount) == 0);
149 BEAST_EXPECT(broker->at(sfLoanSequence) == 1);
150 BEAST_EXPECT(broker->at(sfDebtTotal) == 0);
151 BEAST_EXPECT(broker->at(sfCoverAvailable) == 0);
152 if (checkBroker)
153 checkBroker(broker);
154
155 // if (auto const vaultSLE = env.le(keylet::vault(vault.vaultID)))
156 //{
157 // log << "Vault: " << to_string(vaultSLE->getJson()) <<
158 // std::endl;
159 // }
160 // Load the pseudo-account
161 Account const pseudoAccount{"Broker pseudo-account", broker->at(sfAccount)};
162
163 auto const pseudoKeylet = keylet::account(pseudoAccount);
164 if (auto const pseudo = env.le(pseudoKeylet); BEAST_EXPECT(pseudo))
165 {
166 // log << "Pseudo-account after create: "
167 // << to_string(pseudo->getJson()) << std::endl
168 // << std::endl;
169 BEAST_EXPECT(pseudo->at(sfFlags) == (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth));
170 BEAST_EXPECT(pseudo->at(sfSequence) == 0);
171 BEAST_EXPECT(pseudo->at(sfBalance) == beast::zero);
172 BEAST_EXPECT(pseudo->at(sfOwnerCount) == (vault.asset.raw().native() ? 0 : 1));
173 BEAST_EXPECT(!pseudo->isFieldPresent(sfAccountTxnID));
174 BEAST_EXPECT(!pseudo->isFieldPresent(sfRegularKey));
175 BEAST_EXPECT(!pseudo->isFieldPresent(sfEmailHash));
176 BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletLocator));
177 BEAST_EXPECT(!pseudo->isFieldPresent(sfWalletSize));
178 BEAST_EXPECT(!pseudo->isFieldPresent(sfMessageKey));
179 BEAST_EXPECT(!pseudo->isFieldPresent(sfTransferRate));
180 BEAST_EXPECT(!pseudo->isFieldPresent(sfDomain));
181 BEAST_EXPECT(!pseudo->isFieldPresent(sfTickSize));
182 BEAST_EXPECT(!pseudo->isFieldPresent(sfTicketCount));
183 BEAST_EXPECT(!pseudo->isFieldPresent(sfNFTokenMinter));
184 BEAST_EXPECT(!pseudo->isFieldPresent(sfMintedNFTokens));
185 BEAST_EXPECT(!pseudo->isFieldPresent(sfBurnedNFTokens));
186 BEAST_EXPECT(!pseudo->isFieldPresent(sfFirstNFTokenSequence));
187 BEAST_EXPECT(!pseudo->isFieldPresent(sfAMMID));
188 BEAST_EXPECT(!pseudo->isFieldPresent(sfVaultID));
189 BEAST_EXPECT(pseudo->at(sfLoanBrokerID) == keylet.key);
190 }
191
192 {
193 // Get the AccountInfo RPC result for the broker pseudo-account
194 std::string const pseudoStr = to_string(pseudoAccount.id());
195 auto const accountInfo = env.rpc("account_info", pseudoStr);
196 if (BEAST_EXPECT(accountInfo.isObject()))
197 {
198 auto const& accountData = accountInfo[jss::result][jss::account_data];
199 if (BEAST_EXPECT(accountData.isObject()))
200 {
201 BEAST_EXPECT(accountData[jss::Account] == pseudoStr);
202 BEAST_EXPECT(accountData[sfLoanBrokerID] == to_string(keylet.key));
203 }
204 auto const& pseudoInfo = accountInfo[jss::result][jss::pseudo_account];
205 if (BEAST_EXPECT(pseudoInfo.isObject()))
206 {
207 BEAST_EXPECT(pseudoInfo[jss::type] == "LoanBroker");
208 }
209 }
210 }
211
212 auto verifyCoverAmount = [&env, &vault, &pseudoAccount, &broker, &keylet, this](auto n) {
213 using namespace jtx;
214
215 if (BEAST_EXPECT(broker = env.le(keylet)))
216 {
217 auto const amount = vault.asset(n);
218 BEAST_EXPECT(broker->at(sfCoverAvailable) == amount.number());
219 env.require(balance(pseudoAccount, amount));
220 }
221 };
222
223 // Test Cover funding before allowing alterations
224 env(coverDeposit(alice, uint256(0), vault.asset(10)), ter(temINVALID));
225 env(coverDeposit(evan, keylet.key, vault.asset(10)), ter(tecNO_PERMISSION));
226 env(coverDeposit(evan, keylet.key, vault.asset(0)), ter(temBAD_AMOUNT));
227 env(coverDeposit(evan, keylet.key, vault.asset(-10)), ter(temBAD_AMOUNT));
228 env(coverDeposit(alice, vault.vaultID, vault.asset(10)), ter(tecNO_ENTRY));
229
230 verifyCoverAmount(0);
231
232 // Test cover clawback failure cases BEFORE depositing any cover
233 // Need one of brokerID or amount
234 env(coverClawback(alice), ter(temINVALID));
235 env(coverClawback(alice), loanBrokerID(uint256(0)), ter(temINVALID));
236 env(coverClawback(alice), amount(XRP(1000)), ter(temBAD_AMOUNT));
237 env(coverClawback(alice), amount(vault.asset(-10)), ter(temBAD_AMOUNT));
238 // Clawbacks with an MPT need to specify the broker ID
239 env(coverClawback(alice), amount(badMptAsset(1)), ter(temINVALID));
240 env(coverClawback(evan), loanBrokerID(vault.vaultID), ter(tecNO_ENTRY));
241 // Only the issuer can clawback
242 env(coverClawback(alice), loanBrokerID(keylet.key), ter(tecNO_PERMISSION));
243 if (vault.asset.raw().native())
244 {
245 // Can not clawback XRP under any circumstances
246 env(coverClawback(issuer), loanBrokerID(keylet.key), ter(tecNO_PERMISSION));
247 }
248 else
249 {
250 if (vault.asset.raw().holds<Issue>())
251 {
252 // Clawbacks without a loanBrokerID need to specify an IOU
253 // with the broker's pseudo-account as the issuer
254 env(coverClawback(alice), amount(ghostIouAsset(1)), ter(tecNO_ENTRY));
255 env(coverClawback(alice), amount(badIouAsset(1)), ter(tecOBJECT_NOT_FOUND));
256 // Pseudo-account is not for a broker
257 env(coverClawback(alice), amount(vaultPseudoIouAsset(1)), ter(tecOBJECT_NOT_FOUND));
258 // If we specify a pseudo-account as the IOU amount, it
259 // needs to match the loan broker
260 env(coverClawback(issuer),
261 loanBrokerID(keylet.key),
262 amount(badBrokerPseudoIouAsset(10)),
264 PrettyAsset const brokerWrongCurrencyAsset = pseudoAccount["WAT"];
265 env(coverClawback(issuer),
266 loanBrokerID(keylet.key),
267 amount(brokerWrongCurrencyAsset(10)),
269 }
270 else
271 {
272 // Clawbacks with an MPT need to specify the broker ID, even
273 // if the asset is valid
274 BEAST_EXPECT(vault.asset.raw().holds<MPTIssue>());
275 env(coverClawback(alice), amount(vault.asset(10)), ter(temINVALID));
276 }
277 // Since no cover has been deposited, there's nothing to claw
278 // back
279 env(coverClawback(issuer),
280 loanBrokerID(keylet.key),
281 amount(vault.asset(10)),
283 }
284 env.close();
285
286 // Fund the cover deposit
287 env(coverDeposit(alice, keylet.key, vault.asset(10)));
288 env.close();
289 verifyCoverAmount(10);
290
291 // Test withdrawal failure cases
292 env(coverWithdraw(alice, uint256(0), vault.asset(10)), ter(temINVALID));
293 env(coverWithdraw(evan, keylet.key, vault.asset(10)), ter(tecNO_PERMISSION));
294 env(coverWithdraw(evan, keylet.key, vault.asset(0)), ter(temBAD_AMOUNT));
295 env(coverWithdraw(evan, keylet.key, vault.asset(-10)), ter(temBAD_AMOUNT));
296 env(coverWithdraw(alice, vault.vaultID, vault.asset(10)), ter(tecNO_ENTRY));
297 env(coverWithdraw(alice, keylet.key, vault.asset(900)), ter(tecINSUFFICIENT_FUNDS));
298
299 // Skip this test for XRP, because that can always be sent
300 if (!vault.asset.raw().native())
301 {
302 TER const expected = vault.asset.raw().holds<MPTIssue>() ? tecNO_AUTH : tecNO_LINE;
303 env(coverWithdraw(alice, keylet.key, vault.asset(1)), destination(bystander), ter(expected));
304 }
305
306 // Can not withdraw to the zero address
307 env(coverWithdraw(alice, keylet.key, vault.asset(1)), destination(AccountID{}), ter(temMALFORMED));
308
309 // Withdraw some of the cover amount
310 env(coverWithdraw(alice, keylet.key, vault.asset(7)));
311 env.close();
312 verifyCoverAmount(3);
313
314 // Add some more cover
315 env(coverDeposit(alice, keylet.key, vault.asset(5)));
316 env.close();
317 verifyCoverAmount(8);
318
319 // Withdraw some more. Send it to Evan. Very generous, considering
320 // how much trouble he's been.
321 env(coverWithdraw(alice, keylet.key, vault.asset(1)), destination(evan));
322 env.close();
323 verifyCoverAmount(7);
324
325 // Withdraw some more. Send it to Evan. Very generous, considering
326 // how much trouble he's been.
327 env(coverWithdraw(alice, keylet.key, vault.asset(1)), destination(evan), dtag(3));
328 env.close();
329 verifyCoverAmount(6);
330
331 if (!vault.asset.raw().native())
332 {
333 // Issuer claws back some of the cover
334 env(coverClawback(issuer), loanBrokerID(keylet.key), amount(vault.asset(2)));
335 env.close();
336 verifyCoverAmount(4);
337
338 // Deposit some back
339 env(coverDeposit(alice, keylet.key, vault.asset(5)));
340 env.close();
341 verifyCoverAmount(9);
342
343 // Issuer claws it all back in various different ways
344 for (auto const& tx : {
345 // defer autofills until submission time
346 env.json(coverClawback(issuer), loanBrokerID(keylet.key), fee(none), seq(none), sig(none)),
347 env.json(
348 coverClawback(issuer),
349 loanBrokerID(keylet.key),
350 amount(vault.asset(0)),
351 fee(none),
352 seq(none),
353 sig(none)),
354 env.json(
355 coverClawback(issuer),
356 loanBrokerID(keylet.key),
357 amount(vault.asset(6)),
358 fee(none),
359 seq(none),
360 sig(none)),
361 // amount will be truncated to what's available
362 env.json(
363 coverClawback(issuer),
364 loanBrokerID(keylet.key),
365 amount(vault.asset(100)),
366 fee(none),
367 seq(none),
368 sig(none)),
369 })
370 {
371 // Issuer claws it all back
372 env(tx);
373 env.close();
374 verifyCoverAmount(0);
375
376 // Deposit some back
377 env(coverDeposit(alice, keylet.key, vault.asset(6)));
378 env.close();
379 verifyCoverAmount(6);
380 }
381 }
382
383 // no-op
384 env(set(alice, vault.vaultID), loanBrokerID(keylet.key));
385 env.close();
386
387 // Make modifications to the broker
388 if (changeBroker)
389 changeBroker(broker);
390
391 env.close();
392
393 // Check the results of modifications
394 if (BEAST_EXPECT(broker = env.le(keylet)) && checkChangedBroker)
395 checkChangedBroker(broker);
396
397 // Verify that fields get removed when set to default values
398 // Debt maximum: explicit 0
399 // Data: explicit empty
400 env(set(alice, vault.vaultID), loanBrokerID(broker->key()), debtMaximum(Number(0)), data(""));
401 env.close();
402
403 // Check the updated fields
404 if (BEAST_EXPECT(broker = env.le(keylet)))
405 {
406 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
407 BEAST_EXPECT(!broker->isFieldPresent(sfData));
408 }
409
411 // try to delete the wrong broker object
412 env(del(alice, vault.vaultID), ter(tecNO_ENTRY));
413 // evan tries to delete the broker
414 env(del(evan, keylet.key), ter(tecNO_PERMISSION));
415
416 // Get the "bad" broker out of the way
417 env(del(alice, badKeylet.key));
418 env.close();
419
420 // Note alice's balance of the asset and the broker account's cover
421 // funds
422 auto const aliceBalance = env.balance(alice, vault.asset);
423 auto const coverFunds = env.balance(pseudoAccount, vault.asset);
424 BEAST_EXPECT(coverFunds.number() == broker->at(sfCoverAvailable));
425 BEAST_EXPECT(coverFunds != beast::zero);
426 verifyCoverAmount(6);
427
428 // delete the broker
429 // log << "Broker before delete: " << to_string(broker->getJson())
430 // << std::endl;
431 // if (auto const pseudo = env.le(pseudoKeylet);
432 // BEAST_EXPECT(pseudo))
433 //{
434 // log << "Pseudo-account before delete: "
435 // << to_string(pseudo->getJson()) << std::endl
436 // << std::endl;
437 //}
438
439 env(del(alice, keylet.key));
440 env.close();
441 {
442 broker = env.le(keylet);
443 BEAST_EXPECT(!broker);
444 auto pseudo = env.le(pseudoKeylet);
445 BEAST_EXPECT(!pseudo);
446 }
447 auto const expectedBalance = aliceBalance + coverFunds -
448 (aliceBalance.value().native() ? STAmount(env.current()->fees().base.value()) : vault.asset(0));
449 env.require(balance(alice, expectedBalance));
450 env.require(balance(pseudoAccount, vault.asset(none)));
451 }
452 }
453
454 void
456 {
457 testcase("Lifecycle");
458 using namespace jtx;
459
460 // Create 3 loan brokers: one for XRP, one for an IOU, and one for an
461 // MPT. That'll require three corresponding SAVs.
462 Env env(*this, all);
463
464 Account issuer{"issuer"};
465 // For simplicity, alice will be the sole actor for the vault & brokers.
466 Account alice{"alice"};
467 // Evan will attempt to be naughty
468 Account evan{"evan"};
469 // Bystander doesn't have anything to do with the SAV or Broker, or any
470 // of the relevant tokens
471 Account bystander{"bystander"};
472 Vault vault{env};
473
474 // Fund the accounts and trust lines with the same amount so that tests
475 // can use the same values regardless of the asset.
476 env.fund(XRP(100'000), issuer, noripple(alice, evan, bystander));
477 env.close();
478
479 env(fset(issuer, asfAllowTrustLineClawback));
480 env.close();
481
482 // Create assets
483 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
484 PrettyAsset const iouAsset = issuer["IOU"];
485 env(trust(alice, iouAsset(1'000'000)));
486 env(trust(evan, iouAsset(1'000'000)));
487 env.close();
488 env(pay(issuer, evan, iouAsset(100'000)));
489 env(pay(issuer, alice, iouAsset(100'000)));
490 env.close();
491
492 MPTTester mptt{env, issuer, mptInitNoFund};
494 env.close();
495 PrettyAsset const mptAsset = mptt["MPT"];
496 mptt.authorize({.account = alice});
497 mptt.authorize({.account = evan});
498 env.close();
499 env(pay(issuer, alice, mptAsset(100'000)));
500 env(pay(issuer, evan, mptAsset(100'000)));
501 env.close();
502
503 std::array const assets{xrpAsset, iouAsset, mptAsset};
504
505 // Create vaults
507 for (auto const& asset : assets)
508 {
509 auto [tx, keylet] = vault.create({.owner = alice, .asset = asset});
510 env(tx);
511 env.close();
512 if (auto const le = env.le(keylet); BEAST_EXPECT(env.le(keylet)))
513 {
514 vaults.emplace_back(asset, keylet.key, le->at(sfAccount));
515 }
516
517 env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(50)}));
518 env.close();
519 }
520 VaultInfo const badVault = [&]() -> VaultInfo {
521 auto [tx, keylet] = vault.create({.owner = alice, .asset = iouAsset});
522 env(tx);
523 env.close();
524 if (auto const le = env.le(keylet); BEAST_EXPECT(env.le(keylet)))
525 {
526 return {iouAsset, keylet.key, le->at(sfAccount)};
527 }
528 // This should never happen
529 return {iouAsset, keylet.key, evan.id()};
530 }();
531
532 auto const aliceOriginalCount = env.ownerCount(alice);
533
534 // Create and update Loan Brokers
535 for (auto const& vault : vaults)
536 {
537 {
538 // Get the AccountInfo RPC result for the vault pseudo-account
539 std::string const pseudoStr = to_string(vault.pseudoAccount.id());
540 auto const accountInfo = env.rpc("account_info", pseudoStr);
541 if (BEAST_EXPECT(accountInfo.isObject()))
542 {
543 auto const& accountData = accountInfo[jss::result][jss::account_data];
544 if (BEAST_EXPECT(accountData.isObject()))
545 {
546 BEAST_EXPECT(accountData[jss::Account] == pseudoStr);
547 BEAST_EXPECT(accountData[sfVaultID] == to_string(vault.vaultID));
548 }
549 auto const& pseudoInfo = accountInfo[jss::result][jss::pseudo_account];
550 if (BEAST_EXPECT(pseudoInfo.isObject()))
551 {
552 BEAST_EXPECT(pseudoInfo[jss::type] == "Vault");
553 }
554 }
555 }
556
557 using namespace loanBroker;
558 using namespace xrpl::Lending;
559
560 TenthBips32 const tenthBipsZero{0};
561
562 auto badKeylet = keylet::vault(alice.id(), env.seq(alice));
563 // Try some failure cases
564 // not the vault owner
565 env(set(evan, vault.vaultID), ter(tecNO_PERMISSION));
566 // not a vault
567 env(set(alice, badKeylet.key), ter(tecNO_ENTRY));
568 // flags are checked first
569 env(set(evan, vault.vaultID, ~tfUniversal), ter(temINVALID_FLAG));
570 // field length validation
571 // sfData: good length, bad account
572 env(set(evan, vault.vaultID), data(std::string(maxDataPayloadLength, 'X')), ter(tecNO_PERMISSION));
573 // sfData: too long
574 env(set(evan, vault.vaultID), data(std::string(maxDataPayloadLength + 1, 'Y')), ter(temINVALID));
575 // sfManagementFeeRate: good value, bad account
576 env(set(evan, vault.vaultID), managementFeeRate(maxManagementFeeRate), ter(tecNO_PERMISSION));
577 // sfManagementFeeRate: too big
578 env(set(evan, vault.vaultID), managementFeeRate(maxManagementFeeRate + TenthBips16(10)), ter(temINVALID));
579 // sfCoverRateMinimum and sfCoverRateLiquidation are linked
580 // Cover: good value, bad account
581 env(set(evan, vault.vaultID),
582 coverRateMinimum(maxCoverRate),
583 coverRateLiquidation(maxCoverRate),
585 // CoverMinimum: too big
586 env(set(evan, vault.vaultID),
587 coverRateMinimum(maxCoverRate + 1),
588 coverRateLiquidation(maxCoverRate + 1),
589 ter(temINVALID));
590 // CoverLiquidation: too big
591 env(set(evan, vault.vaultID),
592 coverRateMinimum(maxCoverRate / 2),
593 coverRateLiquidation(maxCoverRate + 1),
594 ter(temINVALID));
595 // Cover: zero min, non-zero liquidation - implicit and
596 // explicit zero values.
597 env(set(evan, vault.vaultID), coverRateLiquidation(maxCoverRate), ter(temINVALID));
598 env(set(evan, vault.vaultID),
599 coverRateMinimum(tenthBipsZero),
600 coverRateLiquidation(maxCoverRate),
601 ter(temINVALID));
602 // Cover: non-zero min, zero liquidation - implicit and
603 // explicit zero values.
604 env(set(evan, vault.vaultID), coverRateMinimum(maxCoverRate), ter(temINVALID));
605 env(set(evan, vault.vaultID),
606 coverRateMinimum(maxCoverRate),
607 coverRateLiquidation(tenthBipsZero),
608 ter(temINVALID));
609 // sfDebtMaximum: good value, bad account
610 env(set(evan, vault.vaultID), debtMaximum(Number(0)), ter(tecNO_PERMISSION));
611 // sfDebtMaximum: overflow
612 env(set(evan, vault.vaultID), debtMaximum(Number(1, 100)), ter(temINVALID));
613 // sfDebtMaximum: negative
614 env(set(evan, vault.vaultID), debtMaximum(Number(-1)), ter(temINVALID));
615
616 std::string testData;
617 lifecycle(
618 "default fields",
619 env,
620 issuer,
621 alice,
622 evan,
623 bystander,
624 vault,
625 badVault,
626 // No modifications
627 {},
628 [&](SLE::const_ref broker) {
629 // Extra checks
630 BEAST_EXPECT(!broker->isFieldPresent(sfManagementFeeRate));
631 BEAST_EXPECT(!broker->isFieldPresent(sfCoverRateMinimum));
632 BEAST_EXPECT(!broker->isFieldPresent(sfCoverRateLiquidation));
633 BEAST_EXPECT(!broker->isFieldPresent(sfData));
634 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
635 BEAST_EXPECT(broker->at(sfDebtMaximum) == 0);
636 BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 0);
637 BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 0);
638
639 BEAST_EXPECT(env.ownerCount(alice) == aliceOriginalCount + 4);
640 },
641 [&](SLE::const_ref broker) {
642 // Modifications
643
644 // Update the fields
645 auto const nextKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
646
647 // fields that can't be changed
648 // LoanBrokerID
649 env(set(alice, vault.vaultID), loanBrokerID(nextKeylet.key), ter(tecNO_ENTRY), THISLINE);
650 // VaultID
651 env(set(alice, nextKeylet.key), loanBrokerID(broker->key()), ter(tecNO_ENTRY), THISLINE);
652 // Owner
653 env(set(evan, vault.vaultID), loanBrokerID(broker->key()), ter(tecNO_PERMISSION), THISLINE);
654 // ManagementFeeRate
655 env(set(alice, vault.vaultID),
656 loanBrokerID(broker->key()),
657 managementFeeRate(maxManagementFeeRate),
659 THISLINE);
660 // CoverRateMinimum
661 env(set(alice, vault.vaultID),
662 loanBrokerID(broker->key()),
663 coverRateMinimum(maxManagementFeeRate),
665 THISLINE);
666 // CoverRateLiquidation
667 env(set(alice, vault.vaultID),
668 loanBrokerID(broker->key()),
669 coverRateLiquidation(maxManagementFeeRate),
671 THISLINE);
672
673 // fields that can be changed
674 testData = "Test Data 1234";
675 // Bad data: too long
676 env(set(alice, vault.vaultID),
677 loanBrokerID(broker->key()),
680 THISLINE);
681
682 // Bad debt maximum
683 env(set(alice, vault.vaultID),
684 loanBrokerID(broker->key()),
685 debtMaximum(Number(-175, -1)),
687 THISLINE);
688 Number debtMax{175, -1};
689 if (vault.asset.integral())
690 {
691 env(set(alice, vault.vaultID),
692 loanBrokerID(broker->key()),
693 data(testData),
694 debtMaximum(debtMax),
696 THISLINE);
697 roundToAsset(vault.asset, debtMax);
698 }
699 // Data & Debt maximum
700 env(set(alice, vault.vaultID),
701 loanBrokerID(broker->key()),
702 data(testData),
703 debtMaximum(debtMax),
704 THISLINE);
705 },
706 [&](SLE::const_ref broker) {
707 // Check the updated fields
708 BEAST_EXPECT(checkVL(broker->at(sfData), testData));
709 Number const expected = STAmount{vault.asset, Number(175, -1)};
710 auto const actual = broker->at(sfDebtMaximum);
711 BEAST_EXPECTS(
712 actual == expected, "Expected: " + to_string(expected) + ", Actual: " + to_string(actual));
713 });
714
715 lifecycle(
716 "non-default fields",
717 env,
718 issuer,
719 alice,
720 evan,
721 bystander,
722 vault,
723 badVault,
724 [&](jtx::JTx const& jv) {
725 testData = "spam spam spam spam";
726 // Finally, create another Loan Broker with none of the
727 // values at default
728 return env.jt(
729 jv,
730 data(testData),
731 managementFeeRate(TenthBips16(123)),
732 debtMaximum(Number(9)),
733 coverRateMinimum(TenthBips32(100)),
734 coverRateLiquidation(TenthBips32(200)));
735 },
736 [&](SLE::const_ref broker) {
737 // Extra checks
738 BEAST_EXPECT(broker->at(sfManagementFeeRate) == 123);
739 BEAST_EXPECT(broker->at(sfCoverRateMinimum) == 100);
740 BEAST_EXPECT(broker->at(sfCoverRateLiquidation) == 200);
741 BEAST_EXPECT(broker->at(sfDebtMaximum) == Number(9));
742 BEAST_EXPECT(checkVL(broker->at(sfData), testData));
743 },
744 [&](SLE::const_ref broker) {
745 // Reset Data & Debt maximum to default values
746 env(set(alice, vault.vaultID), loanBrokerID(broker->key()), data(""), debtMaximum(Number(0)));
747 },
748 [&](SLE::const_ref broker) {
749 // Check the updated fields
750 BEAST_EXPECT(!broker->isFieldPresent(sfData));
751 BEAST_EXPECT(!broker->isFieldPresent(sfDebtMaximum));
752 });
753 }
754
755 BEAST_EXPECT(env.ownerCount(alice) == aliceOriginalCount);
756 }
757
759
760 void
763 LoanBrokerTest brokerTest)
764 {
765 using namespace jtx;
766 using namespace loanBroker;
767 Account const issuer{"issuer"};
768 Account const alice{"alice"};
769 Env env(*this);
770 Vault vault{env};
771
772 env.fund(XRP(100'000), issuer, alice);
773 env.close();
774
775 PrettyAsset const asset = [&]() {
776 if (getAsset)
777 return getAsset(env, issuer, alice);
778 env(trust(alice, issuer["IOU"](1'000'000)), THISLINE);
779 env.close();
780 return PrettyAsset(issuer["IOU"]);
781 }();
782
783 env(pay(issuer, alice, asset(100'000)), THISLINE);
784 env.close();
785
786 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
787 env(tx, THISLINE);
788 env.close();
789 auto const le = env.le(vaultKeylet);
790 VaultInfo vaultInfo = [&]() {
791 if (BEAST_EXPECT(le))
792 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
793 return VaultInfo{asset, {}, {}};
794 }();
795 if (vaultInfo.vaultID == uint256{})
796 return;
797
798 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(50)}), THISLINE);
799 env.close();
800
801 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
802 env(set(alice, vaultInfo.vaultID), THISLINE);
803 env.close();
804
805 auto broker = env.le(brokerKeylet);
806 if (!BEAST_EXPECT(broker))
807 return;
808
809 auto testZeroBrokerID = [&](auto&& getTxJv) {
810 auto jv = getTxJv();
811 // empty broker ID
812 jv[sfLoanBrokerID] = "";
813 env(jv, ter(temINVALID), THISLINE);
814 // zero broker ID
815 jv[sfLoanBrokerID] = to_string(uint256{});
816 // needs a flag to distinguish the parsed STTx from the prior
817 // test
818 env(jv, txflags(tfFullyCanonicalSig), ter(temINVALID), THISLINE);
819 };
820 auto testZeroVaultID = [&](auto&& getTxJv) {
821 auto jv = getTxJv();
822 // empty broker ID
823 jv[sfVaultID] = "";
824 env(jv, ter(temINVALID), THISLINE);
825 // zero broker ID
826 jv[sfVaultID] = to_string(uint256{});
827 // needs a flag to distinguish the parsed STTx from the prior
828 // test
829 env(jv, txflags(tfFullyCanonicalSig), ter(temINVALID), THISLINE);
830 };
831
832 if (brokerTest == CoverDeposit)
833 {
834 // preflight: temINVALID (empty/zero broker id)
835 testZeroBrokerID([&]() { return coverDeposit(alice, brokerKeylet.key, asset(10)); });
836
837 // preclaim: tecWRONG_ASSET
838 env(coverDeposit(alice, brokerKeylet.key, issuer["BAD"](10)), ter(tecWRONG_ASSET), THISLINE);
839
840 // preclaim: tecINSUFFICIENT_FUNDS
841 env(pay(alice, issuer, asset(100'000 - 50)), THISLINE);
842 env.close();
843 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)), ter(tecINSUFFICIENT_FUNDS));
844
845 // preclaim: tecFROZEN
846 env(fset(issuer, asfGlobalFreeze), THISLINE);
847 env.close();
848 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)), ter(tecFROZEN), THISLINE);
849 }
850 else
851 // Fund the cover deposit
852 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)), THISLINE);
853 env.close();
854
855 if (brokerTest == CoverWithdraw)
856 {
857 // preflight: temINVALID (empty/zero broker id)
858 testZeroBrokerID([&]() { return coverWithdraw(alice, brokerKeylet.key, asset(10)); });
859
860 // preclaim: tecWRONG_ASSET
861 env(coverWithdraw(alice, brokerKeylet.key, issuer["BAD"](10)), ter(tecWRONG_ASSET), THISLINE);
862
863 // preclaim: tecNO_DST
864 Account const bogus{"bogus"};
865 env(coverWithdraw(alice, brokerKeylet.key, asset(10)), destination(bogus), ter(tecNO_DST), THISLINE);
866
867 // preclaim: tecDST_TAG_NEEDED
868 Account const dest{"dest"};
869 env.fund(XRP(1'000), dest);
870 env(fset(dest, asfRequireDest), THISLINE);
871 env.close();
872 env(coverWithdraw(alice, brokerKeylet.key, asset(10)), destination(dest), ter(tecDST_TAG_NEEDED), THISLINE);
873
874 // preclaim: tecNO_PERMISSION
875 env(fclear(dest, asfRequireDest), THISLINE);
876 env(fset(dest, asfDepositAuth), THISLINE);
877 env.close();
878 env(coverWithdraw(alice, brokerKeylet.key, asset(10)), destination(dest), ter(tecNO_PERMISSION), THISLINE);
879
880 // preclaim: tecFROZEN
881 env(trust(dest, asset(1'000)), THISLINE);
882 env(fclear(dest, asfDepositAuth), THISLINE);
883 env(fset(issuer, asfGlobalFreeze), THISLINE);
884 env.close();
885 env(coverWithdraw(alice, brokerKeylet.key, asset(10)), destination(dest), ter(tecFROZEN), THISLINE);
886
887 // preclaim:: tecFROZEN (deep frozen)
888 env(fclear(issuer, asfGlobalFreeze), THISLINE);
889 env(trust(issuer, asset(1'000), dest, tfSetFreeze | tfSetDeepFreeze), THISLINE);
890 env(coverWithdraw(alice, brokerKeylet.key, asset(10)), destination(dest), ter(tecFROZEN), THISLINE);
891
892 // preclaim: tecPSEUDO_ACCOUNT
893 env(coverWithdraw(alice, brokerKeylet.key, asset(10)),
894 destination(vaultInfo.pseudoAccount),
896 THISLINE);
897 }
898
899 if (brokerTest == CoverClawback)
900 {
901 // preflight: temINVALID (empty/zero broker id)
902 testZeroBrokerID([&]() {
903 return env.json(coverClawback(alice), loanBrokerID(brokerKeylet.key), amount(vaultInfo.asset(2)));
904 });
905
906 if (asset.holds<Issue>())
907 {
908 // preclaim: AllowTrustLineClawback is not set
909 env(coverClawback(issuer),
910 loanBrokerID(brokerKeylet.key),
911 amount(vaultInfo.asset(2)),
912 ter(tecNO_PERMISSION),
913 THISLINE);
914
915 // preclaim: NoFreeze is set
916 env(fset(issuer, asfAllowTrustLineClawback | asfNoFreeze), THISLINE);
917 env.close();
918 env(coverClawback(issuer),
919 loanBrokerID(brokerKeylet.key),
920 amount(vaultInfo.asset(2)),
921 ter(tecNO_PERMISSION),
922 THISLINE);
923 }
924 else
925 {
926 // preclaim: MPTCanClawback is not set or MPTCanLock is not set
927 env(coverClawback(issuer),
928 loanBrokerID(brokerKeylet.key),
929 amount(vaultInfo.asset(2)),
930 ter(tecNO_PERMISSION),
931 THISLINE);
932 }
933 env.close();
934 }
935
936 if (brokerTest == Delete)
937 {
938 Account const borrower{"borrower"};
939 env.fund(XRP(1'000), borrower);
940 env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
941 sig(sfCounterpartySignature, alice),
942 fee(env.current()->fees().base * 2),
943 THISLINE);
944
945 // preflight: temINVALID (empty/zero broker id)
946 testZeroBrokerID([&]() { return del(alice, brokerKeylet.key); });
947
948 // preclaim: tecHAS_OBLIGATIONS
949 env(del(alice, brokerKeylet.key), ter(tecHAS_OBLIGATIONS), THISLINE);
950
951 // Repay and delete the loan
952 auto const loanKeylet = keylet::loan(brokerKeylet.key, 1);
953 env(loan::pay(borrower, loanKeylet.key, asset(50).value()), THISLINE);
954 env(loan::del(alice, loanKeylet.key), THISLINE);
955
956 env(trust(issuer, asset(0), alice, tfSetFreeze | tfSetDeepFreeze), THISLINE);
957 // preclaim: tecFROZEN (deep frozen)
958 env(del(alice, brokerKeylet.key), ter(tecFROZEN), THISLINE);
959 env(trust(issuer, asset(0), alice, tfClearFreeze | tfClearDeepFreeze), THISLINE);
960
961 // successful delete the loan broker object
962 env(del(alice, brokerKeylet.key), ter(tesSUCCESS), THISLINE);
963 }
964 else
965 env(del(alice, brokerKeylet.key), THISLINE);
966
967 if (brokerTest == Set)
968 {
969 // preflight: temINVALID (empty/zero broker id)
970 testZeroBrokerID([&]() { return env.json(set(alice, vaultInfo.vaultID), loanBrokerID(brokerKeylet.key)); });
971 // preflight: temINVALID (empty/zero vault id)
972 testZeroVaultID([&]() { return env.json(set(alice, vaultInfo.vaultID), loanBrokerID(brokerKeylet.key)); });
973
974 if (asset.holds<Issue>())
975 {
976 env(fclear(issuer, asfDefaultRipple), THISLINE);
977 env.close();
978 // preclaim: DefaultRipple is not set
979 env(set(alice, vaultInfo.vaultID), ter(terNO_RIPPLE), THISLINE);
980
981 env(fset(issuer, asfDefaultRipple), THISLINE);
982 env.close();
983 }
984
985 auto const amt = env.balance(alice) - env.current()->fees().accountReserve(env.ownerCount(alice));
986 env(pay(alice, issuer, amt), THISLINE);
987
988 // preclaim:: tecINSUFFICIENT_RESERVE
989 env(set(alice, vaultInfo.vaultID), ter(tecINSUFFICIENT_RESERVE), THISLINE);
990 }
991 }
992
993 void
995 {
996 testcase("Invalid LoanBrokerCoverClawback");
997 using namespace jtx;
998 using namespace loanBroker;
999
1000 // preflight
1001 {
1002 Account const alice{"alice"};
1003 Account const issuer{"issuer"};
1004 auto const USD = alice["USD"];
1005 Env env(*this);
1006 env.fund(XRP(100'000), alice);
1007 env.close();
1008
1009 auto jtx = env.jt(coverClawback(alice), amount(USD(100)));
1010
1011 // holder == account
1012 env(jtx, ter(temINVALID), THISLINE);
1013
1014 // holder == beast::zero
1015 STAmount bad(Issue{USD.currency, beast::zero}, 100);
1016 jtx.jv[sfAmount] = bad.getJson();
1017 jtx.stx = env.ust(jtx);
1018 Serializer s;
1019 jtx.stx->add(s);
1020 auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result];
1021 // fails in doSubmit() on STTx construction
1022 BEAST_EXPECT(jrr[jss::error] == "invalidTransaction");
1023 BEAST_EXPECT(jrr[jss::error_exception] == "invalid native account");
1024 }
1025
1026 // preclaim
1027
1028 // Issue:
1029 // AllowTrustLineClawback is not set or NoFreeze is set
1030 testLoanBroker({}, CoverClawback);
1031
1032 // MPTIssue:
1033 // MPTCanClawback is not set
1034 testLoanBroker(
1035 [&](Env& env, Account const& issuer, Account const& alice) -> MPT {
1036 MPTTester mpt({.env = env, .issuer = issuer, .holders = {alice}});
1037 return mpt;
1038 },
1039 CoverClawback);
1040 }
1041
1042 void
1044 {
1045 testcase("Invalid LoanBrokerCoverDeposit");
1046 using namespace jtx;
1047
1048 // preclaim:
1049 // tecWRONG_ASSET, tecINSUFFICIENT_FUNDS, frozen asset
1050 testLoanBroker({}, CoverDeposit);
1051 }
1052
1053 void
1055 {
1056 testcase("Invalid LoanBrokerCoverWithdraw");
1057 using namespace jtx;
1058
1059 /*
1060 preflight: illegal net
1061 isLegalNet() check is probably redundant. STAmount parsing
1062 should throw an exception on deserialize
1063
1064 preclaim: tecWRONG_ASSET, tecNO_DST, tecDST_TAG_NEEDED,
1065 tecNO_PERMISSION, checkFrozen failure, checkDeepFrozenFailure,
1066 second+third tecINSUFFICIENT_FUNDS (can this happen)?
1067 doApply: tecPATH_DRY (can it happen, funds already checked?)
1068 */
1069 testLoanBroker({}, CoverWithdraw);
1070 }
1071
1072 void
1074 {
1075 using namespace jtx;
1076 testcase("Invalid LoanBrokerDelete");
1077 /*
1078 preclaim: tecHAS_OBLIGATIONS
1079 doApply:
1080 accountSend failure, removeEmptyHolding failure,
1081 all tecHAS_OBLIGATIONS (can any of these happen?)
1082 */
1083 testLoanBroker({}, Delete);
1084 }
1085
1086 void
1088 {
1089 using namespace jtx;
1090 testcase("Invalid LoanBrokerSet");
1091
1092 /*preclaim: canAddHolding failure (can it happen with MPT?
1093 can't create Vault if CanTransfer is not enabled.)
1094 doApply:
1095 first+second dirLink failure, createPseudoAccount failure,
1096 addEmptyHolding failure
1097 can any of these happen?
1098 */
1099 testLoanBroker({}, Set);
1100 }
1101
1102 void
1104 {
1105 // This test is lifted directly from
1106 // https://bugs.immunefi.com/dashboard/submission/57808
1107 using namespace jtx;
1108 Env env(*this);
1109
1110 Account const alice{"alice"};
1111 env.fund(XRP(10000), alice);
1112 env.close();
1113
1114 // Create a Vault owned by alice with an XRP asset
1115 PrettyAsset const asset{xrpIssue(), 1};
1116 Vault vault{env};
1117 auto const [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
1118 env(createTx);
1119 env.close();
1120
1121 // Predict LoanBroker key using alice's current sequence BEFORE submit
1122 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
1123
1124 // Create LoanBroker pointing to the vault
1125 env(loanBroker::set(alice, vaultKeylet.key));
1126 env.close();
1127
1128 // Build the CoverDeposit STTx directly
1129 STTx tx{ttLOAN_BROKER_COVER_DEPOSIT, [](STObject&) {}};
1130 tx.setAccountID(sfAccount, alice.id());
1131 tx.setFieldH256(sfLoanBrokerID, brokerKeylet.key);
1132 tx.setFieldAmount(sfAmount, asset(1));
1133
1134 // Create a writable view cloned from the current ledger and remove the
1135 // vault SLE
1136 OpenView ov{*env.current()};
1138 beast::Journal jlog{sink};
1139 ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, tapNONE, jlog};
1140
1141 if (auto sleBroker = ac.view().peek(keylet::loanbroker(brokerKeylet.key)))
1142 {
1143 auto const vaultID = (*sleBroker)[sfVaultID];
1144 if (auto sleVault = ac.view().peek(keylet::vault(vaultID)))
1145 {
1146 ac.view().erase(sleVault);
1147 }
1148 }
1149
1150 // Invoke preclaim against the mutated (ApplyView) view; triggers
1151 // nullptr deref
1152 PreclaimContext pctx{env.app(), ac.view(), tesSUCCESS, tx, tapNONE, jlog};
1154 }
1155
1156 void
1158 {
1159 testcase("Require Auth - Implicit Pseudo-account authorization");
1160 using namespace jtx;
1161 using namespace loanBroker;
1162
1163 Account const issuer{"issuer"};
1164 Account const alice{"alice"};
1165 Env env(*this);
1166 Vault vault{env};
1167
1168 env.fund(XRP(100'000), issuer, alice);
1169 env.close();
1170
1171 auto asset = MPTTester({
1172 .env = env,
1173 .issuer = issuer,
1174 .holders = {alice},
1176 .authHolder = true,
1177 });
1178
1179 env(pay(issuer, alice, asset(100'000)));
1180 env.close();
1181
1182 // Alice is not authorized, can still create the vault
1183 asset.authorize({.account = issuer, .holder = alice, .flags = tfMPTUnauthorize});
1184 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
1185 env(tx);
1186 env.close();
1187
1188 auto const le = env.le(vaultKeylet);
1189 VaultInfo vaultInfo = [&]() {
1190 if (BEAST_EXPECT(le))
1191 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
1192 return VaultInfo{asset, {}, {}};
1193 }();
1194 if (vaultInfo.vaultID == uint256{})
1195 return;
1196
1197 // Can't unauthorize Vault pseudo-account
1198 asset.authorize(
1199 {.account = issuer, .holder = vaultInfo.pseudoAccount, .flags = tfMPTUnauthorize, .err = tecNO_PERMISSION});
1200
1201 auto forUnauthAuth = [&](auto&& doTx) {
1202 for (auto const flag : {tfMPTUnauthorize, 0u})
1203 {
1204 asset.authorize({.account = issuer, .holder = alice, .flags = flag});
1205 env.close();
1206 doTx(flag == 0);
1207 env.close();
1208 }
1209 };
1210
1211 // Can't deposit into Vault if the vault owner is not authorized
1212 forUnauthAuth([&](bool authorized) {
1213 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1214 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(51)}), err);
1215 });
1216
1217 // Can't withdraw from Vault if the vault owner is not authorized
1218 forUnauthAuth([&](bool authorized) {
1219 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1220 env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = asset(1)}), err);
1221 });
1222
1223 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
1224 // Can create LoanBroker if the vault owner is not authorized
1225 forUnauthAuth([&](auto) { env(set(alice, vaultInfo.vaultID)); });
1226
1227 auto const broker = env.le(brokerKeylet);
1228 if (!BEAST_EXPECT(broker))
1229 return;
1230 Account brokerPseudo("pseudo", broker->at(sfAccount));
1231
1232 // Can't unauthorize LoanBroker pseudo-account
1233 asset.authorize(
1234 {.account = issuer, .holder = brokerPseudo, .flags = tfMPTUnauthorize, .err = tecNO_PERMISSION});
1235
1236 // Can't cover deposit into Vault if the vault owner is not authorized
1237 forUnauthAuth([&](bool authorized) {
1238 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1239 env(coverDeposit(alice, brokerKeylet.key, vaultInfo.asset(10)), err);
1240 });
1241
1242 // Can't cover withdraw from Vault if the vault owner is not authorized
1243 forUnauthAuth([&](bool authorized) {
1244 auto const err = !authorized ? ter(tecNO_AUTH) : ter(tesSUCCESS);
1245 env(coverWithdraw(alice, brokerKeylet.key, vaultInfo.asset(5)), err);
1246 });
1247
1248 // Issuer can always cover clawback. The holder authorization is n/a.
1249 forUnauthAuth(
1250 [&](bool) { env(coverClawback(issuer), loanBrokerID(brokerKeylet.key), amount(vaultInfo.asset(1))); });
1251 }
1252
1253 void
1255 {
1256 testcase("testLoanBrokerSetDebtMaximum");
1257 using namespace jtx;
1258 using namespace loanBroker;
1259 Account const issuer{"issuer"};
1260 Account const alice{"alice"};
1261 Env env(*this);
1262 Vault vault{env};
1263
1264 env.fund(XRP(100'000), issuer, alice);
1265 env.close();
1266
1267 PrettyAsset const asset = [&]() {
1268 MPTTester mptt{env, issuer, mptInitNoFund};
1269 mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
1270 env.close();
1271 PrettyAsset const mptAsset = mptt["MPT"];
1272 mptt.authorize({.account = alice});
1273 env.close();
1274 return mptAsset;
1275 }();
1276
1277 env(pay(issuer, alice, asset(100'000)), THISLINE);
1278 env.close();
1279
1280 auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
1281 env(tx, THISLINE);
1282 env.close();
1283 auto const le = env.le(vaultKeylet);
1284 VaultInfo vaultInfo = [&]() {
1285 if (BEAST_EXPECT(le))
1286 return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)};
1287 return VaultInfo{asset, {}, {}};
1288 }();
1289 if (vaultInfo.vaultID == uint256{})
1290 return;
1291
1292 env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(50)}), THISLINE);
1293 env.close();
1294
1295 auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice));
1296 env(set(alice, vaultInfo.vaultID), THISLINE);
1297 env.close();
1298
1299 Account const borrower{"borrower"};
1300 env.fund(XRP(1'000), borrower);
1301 env(loan::set(borrower, brokerKeylet.key, asset(50).value()),
1302 sig(sfCounterpartySignature, alice),
1303 fee(env.current()->fees().base * 2),
1304 THISLINE);
1305 auto const broker = env.le(brokerKeylet);
1306 if (!BEAST_EXPECT(broker))
1307 return;
1308
1309 BEAST_EXPECT(broker->at(sfDebtTotal) == 50);
1310 auto debtTotal = broker->at(sfDebtTotal);
1311
1312 auto tx2 = set(alice, vaultInfo.vaultID);
1313 tx2[sfLoanBrokerID] = to_string(brokerKeylet.key);
1314 tx2[sfDebtMaximum] = debtTotal - 1;
1315 env(tx2, ter(tecLIMIT_EXCEEDED), THISLINE);
1316
1317 tx2[sfDebtMaximum] = debtTotal + 1;
1318 env(tx2, ter(tesSUCCESS), THISLINE);
1319
1320 tx2[sfDebtMaximum] = 0;
1321 env(tx2, ter(tesSUCCESS), THISLINE);
1322
1323 tx2[sfDebtMaximum] = Json::Value::maxInt;
1324 env(tx2, ter(tesSUCCESS), THISLINE);
1325
1326 {
1327 auto const dm = power(2, 64) - 1;
1328 BEAST_EXPECT(dm > maxMPTokenAmount);
1329 tx2[sfDebtMaximum] = dm;
1330 env(tx2, ter(temINVALID), THISLINE);
1331 }
1332
1333 {
1334 auto const dm = power(2, 63) - 1;
1335 BEAST_EXPECTS(dm > maxMPTokenAmount, to_string(dm));
1336 tx2[sfDebtMaximum] = dm;
1337 env(tx2, ter(temINVALID), THISLINE);
1338 }
1339
1340 {
1341 auto const dm = power(2, 63) - 3;
1342 BEAST_EXPECTS(dm == maxMPTokenAmount, to_string(dm));
1343 tx2[sfDebtMaximum] = dm;
1344 env(tx2, ter(tesSUCCESS), THISLINE);
1345 }
1346
1347 {
1348 auto const dm = 2 * (power(2, 62) - 1) + 1;
1349 BEAST_EXPECTS(dm == maxMPTokenAmount, to_string(dm));
1350 tx2[sfDebtMaximum] = dm;
1351 env(tx2, ter(tesSUCCESS), THISLINE);
1352 }
1353
1354 tx2[sfDebtMaximum] = Number{9223372036854775807, 0};
1355 env(tx2, ter(tesSUCCESS), THISLINE);
1356 }
1357
1358 void
1360 {
1361 testcase << "RIPD-4323";
1362 using namespace jtx;
1363 Account const issuer("issuer");
1364 Account const holder("holder");
1365 Account const& broker = issuer;
1366
1367 auto test = [&](auto&& getToken) {
1368 Env env(*this);
1369
1370 env.fund(XRP(1'000), issuer, holder);
1371 env.close();
1372
1373 auto const [token, deposit, err] = getToken(env);
1374
1375 Vault vault(env);
1376 auto const [tx, keylet] = vault.create({.owner = broker, .asset = token.asset()});
1377 env(tx);
1378 env.close();
1379
1380 env(vault.deposit({.depositor = broker, .id = keylet.key, .amount = deposit}), ter(err));
1381 env.close();
1382
1383 auto const brokerKeylet = keylet::loanbroker(broker, env.seq(broker));
1384
1385 env(loanBroker::set(broker, keylet.key));
1386 env.close();
1387
1388 env(loanBroker::coverDeposit(broker, brokerKeylet.key, deposit), ter(err));
1389 env.close();
1390 };
1391
1392 test([&](Env&) {
1393 // issuer can issue any amount
1394 auto const token = issuer["IOU"];
1395 return std::make_tuple(token, token(1'000), tesSUCCESS);
1396 });
1398 std::uint64_t, // pay to holder
1399 std::optional<std::uint64_t>, // max amount
1400 std::uint64_t, // deposit amount
1401 TER>> // expected error
1402 mptTests = {
1403 // issuer can issue up to 2'000 tokens
1404 {2'000, 4'000, 1'000, tesSUCCESS},
1405 // issuer can issue 500 tokens (250 VaultDeposit +
1406 // 250 LoanBrokerCoverDeposit)
1407 {2'000, 2'500, 250, tesSUCCESS},
1408 // issuer can issue 500 tokens (250 VaultDeposit +
1409 // 250 LoanBrokerCoverDeposit). MaximumAmount is default.
1411 // issuer can issue 500, and fails on depositing 1'000
1412 {2'000, 2'500, 1'000, tecINSUFFICIENT_FUNDS},
1413 // issuer has already issued MaximumAmount
1414 {2'000, 2'000, 1'000, tecINSUFFICIENT_FUNDS},
1415 // issuer has already issued MaximumAmount. MaximumAmount is
1416 // default.
1418 };
1419 for (auto const& [pay, max, deposit, err] : mptTests)
1420 {
1421 test([&](Env& env) -> std::tuple<MPT, PrettyAmount, TER> {
1422 MPT const token = MPTTester(
1423 {.env = env,
1424 .issuer = issuer,
1425 .holders = {holder},
1426 .pay = pay,
1427 .flags = MPTDEXFlags,
1428 .maxAmt = max});
1429 return std::make_tuple(token, token(deposit), err);
1430 });
1431 }
1432 }
1433
1434 void
1436 {
1437 testcase << "RIPD-4466 - LoanBrokerSet disallows frozen vaults";
1438 using namespace jtx;
1439 Env env(*this);
1440
1441 Account const issuer{"issuer"}, lender{"lender"}, borrower{"borrower"};
1442 env.fund(XRP(20'000), issuer, lender, borrower);
1443 auto const IOU = issuer["IOU"];
1444
1445 Vault vault{env};
1446 auto [tx, vaultKeylet] = vault.create({.owner = lender, .asset = IOU.asset()});
1447 env(tx);
1448 env.close();
1449
1450 // Get vault pseudo-account and FREEZE it
1451 auto const vaultSle = env.le(vaultKeylet);
1452 auto const vaultPseudo = vaultSle->at(sfAccount);
1453 auto const vaultPseudoAcct = Account("VaultPseudo", vaultPseudo);
1454 env(trust(issuer, vaultPseudoAcct["IOU"](0), tfSetFreeze));
1455
1456 env(loanBroker::set(lender, vaultKeylet.key), ter(tecFROZEN));
1457 }
1458
1459 void
1461 {
1462 using namespace jtx;
1463 Account issuer("broker");
1464 Account broker("issuer");
1465 Account dest("destination");
1466 auto const token = issuer["IOU"];
1467
1468 enum TrustState {
1469 RequireAuth,
1470 ZeroLimit,
1471 ReachedLimit,
1472 NearLimit,
1473 NoTrustLine,
1474 };
1475
1476 auto test = [&](TrustState trustState) {
1477 Env env(*this);
1478
1479 testcase << "RIPD-4274 IOU with state: " << static_cast<int>(trustState);
1480
1481 auto setTrustLine = [&](Account const& acct, TrustState state) {
1482 switch (state)
1483 {
1484 case RequireAuth:
1485 env(trust(issuer, token(0), acct, tfSetfAuth));
1486 break;
1487 case ZeroLimit: {
1488 auto jv = trust(acct, token(0));
1489 // set QualityIn so that the trustline is not
1490 // auto-deleted
1491 jv[sfQualityIn] = 10'000'000;
1492 env(jv);
1493 }
1494 break;
1495 case ReachedLimit: {
1496 env(trust(acct, token(1'000)));
1497 env(pay(issuer, acct, token(1'000)));
1498 env.close();
1499 }
1500 break;
1501 case NearLimit: {
1502 env(trust(acct, token(1'000)));
1503 env(pay(issuer, acct, token(950)));
1504 env.close();
1505 }
1506 break;
1507 case NoTrustLine:
1508 // don't create a trustline
1509 break;
1510 default:
1511 BEAST_EXPECT(false);
1512 }
1513 env.close();
1514 };
1515
1516 env.fund(XRP(1'000), issuer, broker, dest);
1517 env.close();
1518
1519 if (trustState == RequireAuth)
1520 {
1521 env(fset(issuer, asfRequireAuth));
1522 env.close();
1523
1524 setTrustLine(broker, RequireAuth);
1525 }
1526
1527 setTrustLine(dest, trustState);
1528
1529 env(trust(broker, token(2'000), 0));
1530 env(pay(issuer, broker, token(2'000)));
1531 env.close();
1532
1533 Vault vault(env);
1534 auto const [tx, keylet] = vault.create({.owner = broker, .asset = token.asset()});
1535 env(tx);
1536 env.close();
1537
1538 // Test Vault withdraw
1539 env(vault.deposit({.depositor = broker, .id = keylet.key, .amount = token(1'000)}));
1540 env.close();
1541
1542 env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}),
1543 loanBroker::destination(dest),
1544 ter(std::ignore));
1545 BEAST_EXPECT(env.ter() == tecNO_LINE);
1546 env.close();
1547
1548 env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}));
1549
1550 // Test LoanBroker withdraw
1551 auto const brokerKeylet = keylet::loanbroker(broker, env.seq(broker));
1552
1553 env(loanBroker::set(broker, keylet.key));
1554 env.close();
1555
1556 env(loanBroker::coverDeposit(broker, brokerKeylet.key, token(1'000)));
1557 env.close();
1558
1559 env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
1560 loanBroker::destination(dest),
1561 ter(std::ignore));
1562 BEAST_EXPECT(env.ter() == tecNO_LINE);
1563 env.close();
1564
1565 // Clearing RequireAuth shouldn't change the result
1566 if (trustState == RequireAuth)
1567 {
1568 env(fclear(issuer, asfRequireAuth));
1569 env.close();
1570
1571 env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
1572 loanBroker::destination(dest),
1573 ter(std::ignore));
1574 BEAST_EXPECT(env.ter() == tecNO_LINE);
1575 env.close();
1576 }
1577 };
1578
1579 test(RequireAuth);
1580 test(ZeroLimit);
1581 test(ReachedLimit);
1582 test(NearLimit);
1583 test(NoTrustLine);
1584 }
1585
1586 void
1588 {
1589 using namespace jtx;
1590 Account issuer("broker");
1591 Account broker("issuer");
1592 Account dest("destination");
1593
1594 enum MPTState {
1595 RequireAuth,
1596 ReachedMAX,
1597 NoMPT,
1598 };
1599
1600 auto test = [&](MPTState MPTState) {
1601 Env env(*this);
1602
1603 testcase << "RIPD-4274 MPT with state: " << static_cast<int>(MPTState);
1604
1605 env.fund(XRP(1'000), issuer, broker, dest);
1606 env.close();
1607
1608 auto const maybeToken = [&]() -> std::optional<MPT> {
1609 switch (MPTState)
1610 {
1611 case RequireAuth: {
1612 auto tester = MPTTester(
1613 {.env = env,
1614 .issuer = issuer,
1615 .holders = {broker, dest},
1616 .pay = 2'000,
1617 .flags = MPTDEXFlags | tfMPTRequireAuth,
1618 .authHolder = true,
1619 .maxAmt = 5'000});
1620 // unauthorize dest
1621 tester.authorize({.account = issuer, .holder = dest, .flags = tfMPTUnauthorize});
1622 return tester;
1623 }
1624 case ReachedMAX: {
1625 auto tester = MPTTester(
1626 {.env = env,
1627 .issuer = issuer,
1628 .holders = {broker, dest},
1629 .pay = 2'000,
1630 .flags = MPTDEXFlags,
1631 .maxAmt = 4'000});
1632 BEAST_EXPECT(env.balance(issuer, tester) == tester(-4'000));
1633 return tester;
1634 }
1635 case NoMPT: {
1636 return MPTTester(
1637 {.env = env,
1638 .issuer = issuer,
1639 .holders = {broker},
1640 .pay = 2'000,
1641 .flags = MPTDEXFlags,
1642 .maxAmt = 4'000});
1643 }
1644 default:
1645 return std::nullopt;
1646 }
1647 }();
1648 if (!BEAST_EXPECT(maybeToken))
1649 return;
1650
1651 auto const& token = *maybeToken;
1652
1653 Vault vault(env);
1654 auto const [tx, keylet] = vault.create({.owner = broker, .asset = token.asset()});
1655 env(tx);
1656 env.close();
1657
1658 // Test Vault withdraw
1659 env(vault.deposit({.depositor = broker, .id = keylet.key, .amount = token(1'000)}));
1660 env.close();
1661
1662 env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}),
1663 loanBroker::destination(dest),
1664 ter(std::ignore));
1665
1666 // Shouldn't fail if at MaximumAmount since no new tokens are issued
1667 TER const err = MPTState == ReachedMAX ? TER(tesSUCCESS) : tecNO_AUTH;
1668 BEAST_EXPECT(env.ter() == err);
1669 env.close();
1670
1671 if (err != tesSUCCESS)
1672 {
1673 env(vault.withdraw({.depositor = broker, .id = keylet.key, .amount = token(1'000)}));
1674 }
1675
1676 // Test LoanBroker withdraw
1677 auto const brokerKeylet = keylet::loanbroker(broker, env.seq(broker));
1678
1679 env(loanBroker::set(broker, keylet.key));
1680 env.close();
1681
1682 env(loanBroker::coverDeposit(broker, brokerKeylet.key, token(1'000)));
1683 env.close();
1684
1685 env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)),
1686 loanBroker::destination(dest),
1687 ter(std::ignore));
1688 BEAST_EXPECT(env.ter() == err);
1689 env.close();
1690 };
1691
1692 test(RequireAuth);
1693 test(ReachedMAX);
1694 test(NoMPT);
1695 }
1696
1697 void
1699 {
1700 testRIPD4274IOU();
1701 testRIPD4274MPT();
1702 }
1703
1704public:
1705 void
1706 run() override
1707 {
1708 testLoanBrokerSetDebtMaximum();
1709 testLoanBrokerCoverDepositNullVault();
1710
1711 testDisabled();
1712 testLifecycle();
1713 testInvalidLoanBrokerCoverClawback();
1714 testInvalidLoanBrokerCoverDeposit();
1715 testInvalidLoanBrokerCoverWithdraw();
1716 testInvalidLoanBrokerDelete();
1717 testInvalidLoanBrokerSet();
1718 testRequireAuth();
1719
1720 testRIPD4323();
1721 testAMB06_VaultFreezeCheckMissing();
1722
1723 testRIPD4274();
1724
1725 // TODO: Write clawback failure tests with an issuer / MPT that doesn't
1726 // have the right flags set.
1727 }
1728};
1729
1730BEAST_DEFINE_TESTSUITE(LoanBroker, tx, xrpl);
1731
1732} // namespace test
1733} // namespace xrpl
static constexpr Int maxInt
Definition json_value.h:143
A generic endpoint for log messages.
Definition Journal.h:40
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
State information when applying a tx.
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
static TER preclaim(PreclaimContext const &ctx)
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:45
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:721
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:754
Slice slice() const noexcept
Definition Serializer.h:44
void lifecycle(char const *label, jtx::Env &env, jtx::Account const &issuer, jtx::Account const &alice, jtx::Account const &evan, jtx::Account const &bystander, VaultInfo const &vault, VaultInfo const &badVault, std::function< jtx::JTx(jtx::JTx const &)> modifyJTx, std::function< void(SLE::const_ref)> checkBroker, std::function< void(SLE::const_ref)> changeBroker, std::function< void(SLE::const_ref)> checkChangedBroker)
void run() override
Runs the suite.
void testLoanBroker(std::function< jtx::PrettyAsset(jtx::Env &, jtx::Account const &, jtx::Account const &)> getAsset, LoanBrokerTest brokerTest)
Immutable cryptographic account descriptor.
Definition Account.h:19
AccountID id() const
Returns the Account ID.
Definition Account.h:87
A transaction testing environment.
Definition Env.h:119
Application & app()
Definition Env.h:251
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:98
TER ter() const
Return the TER for the last JTx.
Definition Env.h:603
std::uint32_t ownerCount(Account const &account) const
Return the number of objects owned by an account.
Definition Env.cpp:231
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:249
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:261
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:240
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition Env.h:494
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:158
Json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
Definition Env.h:520
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:792
std::shared_ptr< STTx const > ust(JTx const &jt)
Create a STTx from a JTx without sanitizing Use to inject bogus values into test transactions by firs...
Definition Env.cpp:574
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:533
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:319
Converts to IOU Issue or STAmount.
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:228
Converts to MPT Issue or STAmount.
A balance matches.
Definition balance.h:19
Set the fee on a JTx.
Definition fee.h:17
Set the regular signature on a JTx.
Definition sig.h:15
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:15
T emplace_back(T... args)
T is_same_v
T make_tuple(T... args)
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:498
Keylet loan(uint256 const &loanBrokerID, std::uint32_t loanSeq) noexcept
Definition Indexes.cpp:504
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:492
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Json::Value del(jtx::Account const &account)
Definition dids.cpp:33
Json::Value coverDeposit(AccountID const &account, uint256 const &brokerID, STAmount const &amount, uint32_t flags)
Json::Value coverWithdraw(AccountID const &account, uint256 const &brokerID, STAmount const &amount, uint32_t flags)
Json::Value set(AccountID const &account, uint256 const &vaultId, uint32_t flags)
Json::Value coverClawback(AccountID const &account, std::uint32_t flags)
Json::Value pay(AccountID const &account, uint256 const &loanID, STAmount const &amount, 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)
static MPTInit const mptInitNoFund
Definition mpt.h:103
auto const data
General field definitions, or fields used in multiple transaction namespaces.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:101
static none_t const none
Definition tags.h:14
FeatureBitset testable_amendments()
Definition Env.h:76
auto const amount
auto const MPTDEXFlags
Definition mpt.h:16
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
bool checkVL(Slice const &result, std::string const &expected)
STTx createTx(bool disabling, LedgerIndex seq, PublicKey const &txKey)
Create ttUNL_MODIFY Tx.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::uint32_t asfAllowTrustLineClawback
Definition TxFlags.h:74
@ terNO_RIPPLE
Definition TER.h:204
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:63
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:57
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:132
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:129
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:233
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:237
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:65
Number power(Number const &f, unsigned n)
Definition Number.cpp:915
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:429
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:128
base_uint< 256 > uint256
Definition base_uint.h:526
constexpr std::uint32_t asfDefaultRipple
Definition TxFlags.h:64
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:133
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:99
constexpr std::uint32_t tfFullyCanonicalSig
Transaction flags.
Definition TxFlags.h:40
constexpr std::uint32_t tfClearDeepFreeze
Definition TxFlags.h:101
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:100
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:152
constexpr std::uint32_t tfUniversal
Definition TxFlags.h:42
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:95
void roundToAsset(A const &asset, Number &value)
Round an arbitrary precision Number IN PLACE to the precision of a given Asset.
Definition STAmount.h:674
@ tapNONE
Definition ApplyView.h:11
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:428
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:58
constexpr std::uint32_t asfNoFreeze
Definition TxFlags.h:62
@ temINVALID
Definition TER.h:90
@ temINVALID_FLAG
Definition TER.h:91
@ temMALFORMED
Definition TER.h:67
@ temDISABLED
Definition TER.h:94
@ temBAD_AMOUNT
Definition TER.h:69
@ tecWRONG_ASSET
Definition TER.h:341
@ tecPSEUDO_ACCOUNT
Definition TER.h:343
@ tecNO_ENTRY
Definition TER.h:287
@ tecOBJECT_NOT_FOUND
Definition TER.h:307
@ tecNO_AUTH
Definition TER.h:281
@ tecFROZEN
Definition TER.h:284
@ tecINSUFFICIENT_FUNDS
Definition TER.h:306
@ tecNO_LINE
Definition TER.h:282
@ tecPRECISION_LOSS
Definition TER.h:344
@ tecINSUFFICIENT_RESERVE
Definition TER.h:288
@ tecLIMIT_EXCEEDED
Definition TER.h:342
@ tecNO_PERMISSION
Definition TER.h:286
@ tecDST_TAG_NEEDED
Definition TER.h:290
@ tecHAS_OBLIGATIONS
Definition TER.h:298
@ tecNO_DST
Definition TER.h:271
@ lsfDepositAuth
@ lsfDefaultRipple
@ lsfDisableMaster
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:98
@ tesSUCCESS
Definition TER.h:225
uint256 key
Definition Keylet.h:20
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:53
VaultInfo(jtx::PrettyAsset const &asset_, uint256 const &vaultID_, AccountID const &pseudo)
Execution context for applying a JSON transaction.
Definition JTx.h:25
std::optional< MPTCreate > create
Definition mpt.h:101
Set the destination tag on a JTx.
Definition tag.h:12
Set the sequence number on a JTx.
Definition seq.h:14