1#include <test/app/lending/LoanTestBase.h>
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/fee.h>
7#include <test/jtx/flags.h>
8#include <test/jtx/jtx_json.h>
9#include <test/jtx/mpt.h>
10#include <test/jtx/pay.h>
11#include <test/jtx/ter.h>
12#include <test/jtx/trust.h>
13#include <test/jtx/txflags.h>
14#include <test/jtx/vault.h>
16#include <xrpl/basics/Number.h>
17#include <xrpl/beast/unit_test/suite.h>
18#include <xrpl/beast/utility/Zero.h>
19#include <xrpl/json/json_value.h>
20#include <xrpl/ledger/helpers/LendingHelpers.h>
21#include <xrpl/protocol/Asset.h>
22#include <xrpl/protocol/Feature.h>
23#include <xrpl/protocol/Indexes.h>
24#include <xrpl/protocol/Issue.h>
25#include <xrpl/protocol/Keylet.h>
26#include <xrpl/protocol/Protocol.h>
27#include <xrpl/protocol/SField.h>
28#include <xrpl/protocol/STAmount.h>
29#include <xrpl/protocol/SeqProxy.h>
30#include <xrpl/protocol/TER.h>
31#include <xrpl/protocol/TxFlags.h>
32#include <xrpl/protocol/Units.h>
53 using namespace std::chrono_literals;
54 Env env{*
this, features};
59 Account const borrower{
"borrower"};
62 env.
fund(
XRP(1'000'000'00), issuer, lender, borrower, victim);
66 auto asset = issuer[
"USD"];
67 env(
trust(lender, asset(100000)));
68 env(
trust(borrower, asset(100000)));
69 env(
trust(victim, asset(100000)));
70 env(
pay(issuer, lender, asset(50000)));
71 env(
pay(issuer, borrower, asset(50000)));
72 env(
pay(issuer, victim, asset(50000)));
76 .vaultDeposit = 10000,
85 if (!BEAST_EXPECT(brokerSle))
89 BEAST_EXPECT(brokerSle->at(sfOwnerCount) == 0);
93 auto const loanSequence = brokerSle->at(sfLoanSequence);
99 auto const& vaultKeylet = broker.vaultKeylet();
102 auto const vaultSle = env.
le(vaultKeylet);
103 Number const assetsTotal = vaultSle->at(sfAssetsTotal);
104 Number const assetsAvail = vaultSle->at(sfAssetsAvailable);
108 log <<
" AssetsAvailable: " << assetsAvail <<
std::endl;
109 log <<
" Difference: " << (assetsTotal - assetsAvail) <<
std::endl;
112 BEAST_EXPECT(assetsAvail == assetsTotal);
113 BEAST_EXPECT(assetsAvail == broker.asset(brokerParams.
vaultDeposit).number());
116 Keylet const& loanKeylet = *loanKeyletOpt;
121 .principalRequest =
Number{100},
124 .payInterval = 86400 * 6,
125 .gracePd = 86400 * 5,
128 env(loanParams(env, broker));
135 auto const vaultSle = env.
le(vaultKeylet);
136 Number const assetsTotal = vaultSle->at(sfAssetsTotal);
137 Number const assetsAvail = vaultSle->at(sfAssetsAvailable);
141 log <<
" AssetsAvailable: " << assetsAvail <<
std::endl;
142 log <<
" Difference: " << (assetsTotal - assetsAvail) <<
std::endl;
144 auto const loanSle = env.
le(loanKeylet);
145 if (!BEAST_EXPECT(loanSle))
150 log <<
" ValueOutstanding: " << state.valueOutstanding <<
std::endl;
151 log <<
" PrincipalOutstanding: " << state.principalOutstanding <<
std::endl;
152 log <<
" InterestOutstanding: " << state.interestOutstanding() <<
std::endl;
153 log <<
" InterestDue: " << state.interestDue <<
std::endl;
154 log <<
" FeeDue: " << state.managementFeeDue <<
std::endl;
158 BEAST_EXPECT(assetsAvail < assetsTotal);
164 broker.asset(brokerParams.
vaultDeposit + state.interestDue).number());
173 auto const vaultSle2 = env.
le(vaultKeylet);
174 Number const assetsTotal2 = vaultSle2->at(sfAssetsTotal);
175 Number const assetsAvail2 = vaultSle2->at(sfAssetsAvailable);
179 log <<
" AssetsAvailable: " << assetsAvail2 <<
std::endl;
180 log <<
" Difference: " << (assetsTotal2 - assetsAvail2) <<
std::endl;
183 BEAST_EXPECT(assetsAvail2 == assetsTotal2);
190 testcase(
"Minimum cover rounding allows undercoverage (XRP)");
195 Env env{*
this, features};
197 Account const lender{
"lender"};
198 Account const borrower{
"borrower"};
200 env.
fund(
XRP(200'000), lender, borrower);
204 Vault const vault{env};
205 auto [vaultCreate, vaultKeylet] = vault.create({.owner = lender, .asset =
xrpIssue()});
208 BEAST_EXPECT(env.
le(vaultKeylet));
214 .vaultDeposit = 1'000,
223 env(
loan::set(borrower, brokerInfo.brokerID, xrpAsset(804).value()),
225 Sig(sfCounterpartySignature, lender),
232 BEAST_EXPECT(brokerSle))
235 BEAST_EXPECT(brokerSle->at(sfDebtTotal) ==
Number(804));
240 env(coverWithdraw(lender, brokerInfo.brokerID, xrpAsset(2).value()),
247 env(coverWithdraw(lender, brokerInfo.brokerID, xrpAsset(1).value()));
253 BEAST_EXPECT(brokerSle))
256 BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == xrpAsset(81).value());
257 BEAST_EXPECT(brokerSle->at(sfDebtTotal) ==
Number(804));
261 log <<
"Theoretical min cover: " << theoreticalMin <<
std::endl;
262 BEAST_EXPECT(
Number(804, -1) == theoreticalMin);
269 testcase(
"Rounding manipulation does not permit yield theft");
271 using namespace loan;
275 Account const issuer{
"issuer"};
276 Account const lender{
"lender"};
277 Account const borrower{
"borrower"};
279 env.
fund(
XRP(1000), issuer, lender, borrower);
284 env(
trust(lender, iou(100'000'000)));
285 env(
trust(borrower, iou(100'000'000)));
286 env(
pay(issuer, lender, iou(100'000'000)));
287 env(
pay(issuer, borrower, iou(100'000'000)));
296 .vaultDeposit = 5'000'000,
297 .debtMax =
Number{100'000'000},
298 .coverDeposit = 500'000,
300 auto const [currentSeq, vaultKeylet] = [&]() {
302 if (!BEAST_EXPECT(brokerSle))
304 auto const currentSeq = brokerSle->at(sfLoanSequence);
305 auto const vaultKeylet =
keylet::vault(brokerSle->at(sfVaultID));
310 Number const principal = 1'000'000;
315 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
316 env(
set(borrower, brokerInfo.brokerID, iou(principal).value(), flags),
317 Sig(sfCounterpartySignature, lender),
326 auto borrowerBalance = [&]() {
return env.
balance(borrower, iou); };
327 auto const borrowerScale =
static_cast<STAmount const&
>(borrowerBalance()).exponent();
329 auto const loanKeylet =
332 auto const loanSle = env.
le(loanKeylet);
333 if (!BEAST_EXPECT(loanSle))
336 return STAmount{iou, loanSle->at(sfPeriodicPayment)};
338 if (!maybePeriodicPayment)
340 auto const periodicPayment = *maybePeriodicPayment;
341 auto const roundedPayment =
346 STAmount const attackPayment = periodicPayment + paymentBuffer;
349 auto const vault = env.
le(vaultKeylet);
350 if (!BEAST_EXPECT(vault))
352 return vault->at(sfAssetsTotal);
354 if (!maybeInitialVaultAssets)
356 auto const initialVaultAssets = *maybeInitialVaultAssets;
359 int yieldTheftCount = 0;
360 auto previousAssetsTotal = initialVaultAssets;
362 for (
int i = 0; i < 100; ++i)
364 auto const balanceBefore = borrowerBalance();
365 env(
pay(borrower, loanKeylet.key, attackPayment, flags));
367 auto const borrowerDelta = balanceBefore - borrowerBalance();
368 BEAST_EXPECT(borrowerDelta.signum() == roundedPayment.signum());
370 auto const loanSle = env.
le(loanKeylet);
371 if (!BEAST_EXPECT(loanSle))
373 auto const updatedPayment =
STAmount{iou, loanSle->at(sfPeriodicPayment)};
378 (updatedPayment == periodicPayment) ||
379 (flags == tfLoanOverpayment && i >= 2 && updatedPayment < periodicPayment));
381 auto const currentVaultSle = env.
le(vaultKeylet);
382 if (!BEAST_EXPECT(currentVaultSle))
385 auto const currentAssetsTotal = currentVaultSle->at(sfAssetsTotal);
386 auto const delta = currentAssetsTotal - previousAssetsTotal;
389 (delta ==
beast::kZero && borrowerDelta <= roundedPayment) ||
390 (delta >
beast::kZero && borrowerDelta > roundedPayment));
394 if (delta ==
beast::kZero && borrowerDelta > roundedPayment)
399 previousAssetsTotal = currentAssetsTotal;
402 BEAST_EXPECTS(yieldTheftCount == 0,
std::to_string(yieldTheftCount));
423 if (!features[fixCleanup3_2_0])
426 testcase(
"edge: integer MPT principal stuck mid-loan completes via final");
429 Env env(*
this, features);
431 Account const issuer{
"issuer"};
432 Account const lender{
"lender"};
433 Account const borrower{
"borrower"};
435 env.
fund(
XRP(100'000), issuer, lender, borrower);
439 mptt.
create({.maxAmt = 100'000, .flags = tfMPTCanTransfer});
445 env(
pay(issuer, lender, asset(10'000)));
446 env(
pay(issuer, borrower, asset(10'000)));
449 Vault const vault{env};
450 auto [vaultTx, vaultKeylet] = vault.create({.owner = lender, .asset = asset});
454 env(vault.deposit({.depositor = lender, .id = vaultKeylet.key, .amount = asset(5'000)}));
457 auto const brokerKeylet =
464 auto const brokerStateBefore = env.
le(brokerKeylet);
465 if (!BEAST_EXPECT(brokerStateBefore))
467 auto const loanSequence = brokerStateBefore->at(sfLoanSequence);
471 Sig(sfCounterpartySignature, lender),
478 auto const borrowerStart = env.
balance(borrower, asset).
value();
491 for (
int i = 0; i < 3; ++i)
496 auto const sle = env.
le(loanKeylet);
497 if (!BEAST_EXPECT(sle))
499 BEAST_EXPECT(sle->at(sfPrincipalOutstanding) == expectedPO[i]);
500 BEAST_EXPECT(sle->at(sfTotalValueOutstanding) == expectedTVO[i]);
501 BEAST_EXPECT(sle->at(sfPaymentRemaining) == expectedRemaining[i]);
506 auto const borrowerEnd = env.
balance(borrower, asset).
value();
507 BEAST_EXPECT(borrowerStart - borrowerEnd == asset(3).value());
514 auto testLoanCoverMinimumRoundingExploit = [&,
this](
Number const& principalRequest) {
515 testcase <<
"LoanBrokerCoverClawback drains cover via rounding"
516 <<
" principalRequested=" <<
to_string(principalRequest);
519 using namespace loan;
522 Env env(*
this, features);
524 Account const issuer{
"issuer"};
525 Account const lender{
"lender"};
526 Account const borrower{
"borrower"};
528 env.fund(
XRP(1'000'000'000), issuer, lender, borrower);
531 env(
fset(issuer, asfAllowTrustLineClawback));
534 PrettyAsset
const asset = issuer[iouCurrency];
535 env(
trust(lender, asset(2'000'0000)));
536 env(
trust(borrower, asset(2'000'0000)));
539 env(
pay(issuer, lender, asset(2'000'0000)));
545 auto const loanSetFee = Fee(env.current()->fees().base * 2);
547 set(borrower, broker.brokerID, principalRequest),
548 Sig(sfCounterpartySignature, lender),
557 BEAST_EXPECT(brokerBefore);
561 Number
const debtOutstanding = brokerBefore->at(sfDebtTotal);
562 Number
const coverAvailableBefore = brokerBefore->at(sfCoverAvailable);
564 BEAST_EXPECT(debtOutstanding > Number{});
565 BEAST_EXPECT(coverAvailableBefore > Number{});
568 <<
" cover_available=" <<
to_string(coverAvailableBefore);
570 env(
coverClawback(issuer, 0), loanBrokerID(broker.brokerID));
574 BEAST_EXPECT(brokerAfter);
578 Number
const debtAfter = brokerAfter->at(sfDebtTotal);
580 BEAST_EXPECT(debtAfter == debtOutstanding);
582 Number
const coverAvailableAfter = brokerAfter->at(sfCoverAvailable);
586 BEAST_EXPECT(coverAvailableAfter != Number{});
590 testLoanCoverMinimumRoundingExploit(Number{1, -30});
591 testLoanCoverMinimumRoundingExploit(Number{1, -20});
592 testLoanCoverMinimumRoundingExploit(Number{1, -10});
593 testLoanCoverMinimumRoundingExploit(Number{1, 1});
631 testcase(
"bug: doOverpayment asserts 'principal change agrees'");
634 using namespace loan;
652 Number principalOutstanding;
653 Number expectedNewPrincipal;
654 Number managementFeeChange;
659 Env env(*
this, features);
661 Account const issuer{
"issuer"};
662 Account const lender{
"vaultOwner"};
663 Account const borrower{
"borrower"};
672 {.vaultDeposit = 900'000,
674 .managementFeeRate = p.managementFeeRate,
675 .vaultScale = p.vaultScale});
677 auto const brokerSle = env.
le(broker.brokerKeylet());
678 BEAST_EXPECT(brokerSle);
679 auto const loanSequence = brokerSle ? brokerSle->at(sfLoanSequence) : 0;
680 auto const loanKeylet =
683 env(
set(borrower, broker.brokerID,
Number{p.principal}, tfLoanOverpayment),
684 Sig(sfCounterpartySignature, lender),
685 kInterestRate(p.interestRate),
686 kPaymentTotal(p.paymentTotal),
687 kPaymentInterval(p.paymentInterval),
688 kGracePeriod(p.paymentInterval),
689 kOverpaymentFee(p.overpaymentFeeRate),
690 kOverpaymentInterestRate(p.overpaymentInterestRate),
699 auto const periodicRate =
loanPeriodicRate(s.interestRate, s.paymentInterval);
705 s.principalOutstanding,
706 s.managementFeeOutstanding,
710 p.managementFeeRate);
724 p.overpaymentInterestRate,
725 p.overpaymentFeeRate,
726 p.managementFeeRate);
727 Number const expectedNewPrincipal = s.principalOutstanding -
728 onePeriod.trackedPrincipalDelta - overpaymentComponents.trackedPrincipalDelta;
730 Number const managementFeeBefore = s.managementFeeOutstanding;
732 STAmount const payAmount{asset, onePeriod.trackedValueDelta + p.overpayment};
733 env(
pay(borrower, loanKeylet.key, payAmount),
738 auto const loanSle = env.
le(loanKeylet);
739 BEAST_EXPECT(loanSle);
742 .principalOutstanding = loanSle ?
Number{loanSle->at(sfPrincipalOutstanding)} : 0,
743 .expectedNewPrincipal = expectedNewPrincipal,
744 .managementFeeChange =
745 (loanSle ?
Number{loanSle->at(sfManagementFeeOutstanding)} :
Number{0}) -
753 Params
const principalCase{
757 .paymentInterval = 60,
759 .overpayment =
Number{49999998, -9},
772 fixed.principalOutstanding ==
fixed.expectedNewPrincipal,
773 "fixed principal " +
to_string(
fixed.principalOutstanding) +
" != expected " +
780 Result const legacy = runScenario(
all_ - fixCleanup3_2_0, principalCase);
782 legacy.principalOutstanding == legacy.expectedNewPrincipal,
783 "legacy principal " +
to_string(legacy.principalOutstanding) +
" != expected " +
791 Params
const feeCase{
795 .paymentInterval = 30u * 24 * 60 * 60,
797 .overpayment =
Number{214367363, -10},
800 .vaultScale = std::nullopt};
802 Result const feeFixed = runScenario(
all_, feeCase);
803 Result const feeLegacy = runScenario(
all_ - fixCleanup3_2_0, feeCase);
808 feeFixed.principalOutstanding == feeFixed.expectedNewPrincipal,
809 "fee-case fixed principal " +
to_string(feeFixed.principalOutstanding) +
810 " != expected " +
to_string(feeFixed.expectedNewPrincipal));
812 feeLegacy.principalOutstanding == feeLegacy.expectedNewPrincipal + feeLegacy.unit,
813 "fee-case legacy principal " +
to_string(feeLegacy.principalOutstanding) +
814 " != expected " +
to_string(feeLegacy.expectedNewPrincipal + feeLegacy.unit));
828 BEAST_EXPECT(feeFixed.managementFeeChange == feeLegacy.managementFeeChange);
830 (feeFixed.managementFeeChange ==
Number{-8219709543, -10}),
831 "fee-case mgmt fee change " +
to_string(feeFixed.managementFeeChange));
844 testcase(
"bug: computeOverpaymentComponents isRounded assertion");
847 using namespace loan;
850 Account const issuer{
"issuer"};
851 Account const lender{
"vaultOwner"};
852 Account const borrower{
"borrower"};
860 {.vaultDeposit = 100'000,
865 auto const sleBroker = env.
le(broker.brokerKeylet());
866 if (!BEAST_EXPECT(sleBroker))
868 auto const loanSequence = sleBroker->at(sfLoanSequence);
871 using namespace loan;
872 env(
set(borrower, broker.brokerID,
Number{1000}, tfLoanOverpayment),
873 Sig(sfCounterpartySignature, lender),
876 kPaymentInterval(60),
901 testcase(
"bug: LoanPay asserts 'interest due delta' on near-zero rate");
904 using namespace std::chrono_literals;
907 Account const issuer{
"issuer"};
908 Account const lender{
"lender"};
909 Account const borrower{
"borrower"};
911 env.
fund(
XRP(1'000'000), issuer, lender, borrower);
913 env(
fset(issuer, asfDefaultRipple));
917 env(
trust(lender, iouAsset(1'000'000'000)));
918 env(
trust(borrower, iouAsset(1'000'000'000)));
919 env(
pay(issuer, lender, iouAsset(5'000'000)));
920 env(
pay(issuer, borrower, iouAsset(5'000'000)));
924 .vaultDeposit = 1'000'000,
925 .debtMax = 1'000'000,
933 using namespace loan;
935 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
936 Number const principalRequest{100};
938 auto createJson = env.
json(
943 createJson[
"InterestRate"] = 1;
944 createJson[
"PaymentTotal"] = 3;
945 createJson[
"PaymentInterval"] = 600;
949 createJson = env.
json(createJson,
Sig(sfCounterpartySignature, lender));
965 for (
auto const flags : {0u, tfLoanOverpayment})
980 testLoanCoverMinimumRoundingExploit(features);
990 {fixCleanup3_1_3, fixCleanup3_2_0, featureMPTokensV2},
all_))
LogOs< char > log
Logging output stream.
TestcaseT testcase
Memberspace for declaring test cases.
Number is a floating point type that can represent a wide range of values.
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
void testYieldTheftRounding(std::uint32_t flags)
void testBugInterestDueDeltaCrash()
void testDustManipulation(FeatureBitset features)
void testBugOverpayUnroundedAmount()
void testRoundingAllowsUndercoverage(FeatureBitset features)
void runAmendmentSensitive(FeatureBitset features)
void runAmendmentIndependent()
void testIntegerScalePrincipalSticks(FeatureBitset features)
void run() override
Runs the suite.
void testBugOverpaymentPrincipalChange()
BrokerInfo createVaultAndBroker(jtx::Env &env, jtx::PrettyAsset const &asset, jtx::Account const &lender, BrokerParameters const ¶ms=BrokerParameters::defaults())
LoanState getCurrentState(jtx::Env const &env, BrokerInfo const &broker, Keylet const &loanKeylet)
Get the state without checking anything.
Keylet nextLoanKeylet(jtx::Env const &env, BrokerInfo const &broker)
static jtx::PrettyAsset createFundedRippleIouAsset(jtx::Env &env, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower, Number const &lenderPay=1 '000 '000, Number const &borrowerPay=1 '000 '000)
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
TER ter() const
Return the TER for the last JTx.
json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
SLE::const_pointer le(Account const &account) const
Return an account root.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
void create(MPTCreate const &arg=MPTCreate{})
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
MPTID const & issuanceID() const
Set the regular signature on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
@ Object
object value (collection of name/value pairs).
ExtendedPaymentComponents computeOverpaymentComponents(Rules const &rules, Asset const &asset, int32_t const loanScale, Number const &overpayment, TenthBips32 const overpaymentInterestRate, TenthBips32 const overpaymentFeeRate, TenthBips16 const managementFeeRate)
PaymentComponents computePaymentComponents(Rules const &rules, Asset const &asset, std::int32_t scale, Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding, Number const &periodicPayment, Number const &periodicRate, std::uint32_t paymentRemaining, TenthBips16 managementFeeRate)
Keylet computation functions.
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Keylet loan(uint256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
json::Value set(AccountID const &account, uint256 const &vaultId, uint32_t flags)
json::Value coverClawback(AccountID const &account, std::uint32_t flags)
json::Value set(AccountID const &account, uint256 const &loanBrokerID, Number principalRequested, std::uint32_t flags)
auto const kPaymentInterval
json::Value manage(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)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
std::vector< FeatureBitset > amendmentCombinations(std::initializer_list< uint256 > features, FeatureBitset seed)
Returns all 2^N permutations of a seed FeatureBitset with each subset of the given features excluded.
XrpT const XRP
Converts to XRP Issue or STAmount.
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
static MPTInit const kMptInitNoFund
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
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.
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Number loanPeriodicRate(TenthBips32 interestRate, std::uint32_t paymentInterval)
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
TenthBips< std::uint32_t > TenthBips32
static FunctionType fixed(Keylet const &keylet)
TenthBips< std::uint16_t > TenthBips16
boost::outcome_v2::result< T, std::error_code > Result
std::string to_string(BaseUInt< Bits, Tag > const &a)
STAmount roundToScale(STAmount const &value, std::int32_t scale, Number::RoundingMode rounding=Number::getround())
Round an arbitrary precision Amount to the precision of an STAmount that has a given exponent.
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
A pair of SHAMap key and LedgerEntryType.
STAmount const & value() const
Asset const & raw() const