mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
[WIP] Start implementing LoanSet transactor
- Add some more values and functions to make it easier to work with basis point values / bips. - Fix several earlier mistakes.
This commit is contained in:
@@ -82,30 +82,86 @@ std::size_t constexpr maxDeletableTokenOfferEntries = 500;
|
|||||||
*/
|
*/
|
||||||
std::uint16_t constexpr maxTransferFee = 50000;
|
std::uint16_t constexpr maxTransferFee = 50000;
|
||||||
|
|
||||||
/** The maximum management fee rate allowed in lending.
|
/** There are 10,000 basis points (bips) in 100%.
|
||||||
|
*
|
||||||
TODO: Is this a good name?
|
* Basis points represent 0.01%.
|
||||||
|
*
|
||||||
Valid values for the the management fee charged by the Lending Protocol are
|
* Given a value X, to find the amount for B bps,
|
||||||
between 0 and 10000 inclusive. A value of 1 is equivalent to 1/10 basis
|
* use X * B / bipsPerUnity
|
||||||
point fee or 0.001%.
|
*
|
||||||
|
* Example: If a loan broker has 999 XRP of debt, and must maintain 1,000 bps of
|
||||||
|
* that debt as cover (10%), then the minimum cover amount is 999,000,000 drops
|
||||||
|
* * 1000 / bipsPerUnity = 99,900,00 drops or 99.9 XRP.
|
||||||
|
*
|
||||||
|
* Given a percentage P, to find the number of bps that percentage represents,
|
||||||
|
* use P * bipsPerUnity.
|
||||||
|
*
|
||||||
|
* Example: 50% is 0.50 * bipsPerUnity = 5,000 bps.
|
||||||
*/
|
*/
|
||||||
std::uint16_t constexpr maxFeeRate = 10'000;
|
std::uint32_t constexpr bipsPerUnity = 100 * 100;
|
||||||
|
std::uint32_t constexpr tenthBipsPerUnity = bipsPerUnity * 10;
|
||||||
|
|
||||||
/** The maximum coverage rate allowed in lending.
|
constexpr std::uint32_t
|
||||||
|
percentageToBips(std::uint32_t percentage)
|
||||||
|
{
|
||||||
|
return percentage * bipsPerUnity / 100;
|
||||||
|
}
|
||||||
|
constexpr std::uint32_t
|
||||||
|
percentageToTenthBips(std::uint32_t percentage)
|
||||||
|
{
|
||||||
|
return percentage * tenthBipsPerUnity / 100;
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
constexpr T
|
||||||
|
bipsOfValue(T value, std::uint32_t bips)
|
||||||
|
{
|
||||||
|
return value * bips / bipsPerUnity;
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
constexpr T
|
||||||
|
tenthBipsOfValue(T value, std::uint32_t bips)
|
||||||
|
{
|
||||||
|
return value * bips / tenthBipsPerUnity;
|
||||||
|
}
|
||||||
|
|
||||||
TODO: Is this a good name?
|
/** The maximum management fee rate allowed by a loan broker in 1/10 bips.
|
||||||
|
|
||||||
Valid values for the coverage rate charged by the Lending Protocol for first
|
Valid values are between 0 and 10% inclusive.
|
||||||
loss capital operations are between 0 and 100000 inclusive. A value of 1 is
|
|
||||||
equivalent to 1/10 bps or 0.001%.
|
|
||||||
*/
|
*/
|
||||||
std::uint32_t constexpr maxCoverRate = 100'000;
|
std::uint16_t constexpr maxManagementFeeRate = percentageToTenthBips(10);
|
||||||
|
static_assert(maxManagementFeeRate == 10'000);
|
||||||
|
|
||||||
/** Basis points (bps) represent 0.01% of a thing. Given a value X, to find the
|
/** The maximum coverage rate required of a loan broker in 1/10 bips.
|
||||||
* amount for B bps, use X * B / bpsPerOne
|
|
||||||
|
Valid values are between 0 and 100% inclusive.
|
||||||
*/
|
*/
|
||||||
std::uint32_t constexpr bpsPerOne = 10'000;
|
std::uint32_t constexpr maxCoverRate = percentageToTenthBips(100);
|
||||||
|
static_assert(maxCoverRate == 100'000);
|
||||||
|
|
||||||
|
/** The maximum overpayment fee on a loan in 1/10 bips.
|
||||||
|
*
|
||||||
|
Valid values are between 0 and 100% inclusive.
|
||||||
|
*/
|
||||||
|
std::uint32_t constexpr maxOverpaymentFee = percentageToTenthBips(100);
|
||||||
|
|
||||||
|
/** The maximum premium added to the interest rate for late payments on a loan
|
||||||
|
* in 1/10 bips.
|
||||||
|
*
|
||||||
|
* Valid values are between 0 and 100% inclusive.
|
||||||
|
*/
|
||||||
|
std::uint32_t constexpr maxLateInterestRate = percentageToTenthBips(100);
|
||||||
|
|
||||||
|
/** The maximum interest rate charged for repaying a loan early in 1/10 bips.
|
||||||
|
*
|
||||||
|
* Valid values are between 0 and 100% inclusive.
|
||||||
|
*/
|
||||||
|
std::uint32_t constexpr maxCloseInterestRate = percentageToTenthBips(100);
|
||||||
|
|
||||||
|
/** The maximum interest rate charged on loan overpayments in 1/10 bips.
|
||||||
|
*
|
||||||
|
* Valid values are between 0 and 100% inclusive.
|
||||||
|
*/
|
||||||
|
std::uint32_t constexpr maxOverpaymentInterestRate = percentageToTenthBips(100);
|
||||||
|
|
||||||
/** The maximum length of a URI inside an NFT */
|
/** The maximum length of a URI inside an NFT */
|
||||||
std::size_t constexpr maxTokenURILength = 256;
|
std::size_t constexpr maxTokenURILength = 256;
|
||||||
|
|||||||
@@ -234,14 +234,14 @@ constexpr std::uint32_t const tfVaultShareNonTransferable = 0x00020000;
|
|||||||
constexpr std::uint32_t const tfVaultCreateMask = ~(tfUniversal | tfVaultPrivate | tfVaultShareNonTransferable);
|
constexpr std::uint32_t const tfVaultCreateMask = ~(tfUniversal | tfVaultPrivate | tfVaultShareNonTransferable);
|
||||||
|
|
||||||
// LoanSet flags:
|
// LoanSet flags:
|
||||||
// True, indicates the load supports overpayments
|
// True, indicates the loan supports overpayments
|
||||||
constexpr std::uint32_t const tfLoanOverpayment = 0x00010000;
|
constexpr std::uint32_t const tfLoanOverpayment = 0x00010000;
|
||||||
constexpr std::uint32_t const tfLoanSetMask = ~(tfUniversal | tfLoanOverpayment);
|
constexpr std::uint32_t const tfLoanSetMask = ~(tfUniversal | tfLoanOverpayment);
|
||||||
|
|
||||||
// LoanManage flags:
|
// LoanManage flags:
|
||||||
constexpr std::uint32_t const tfLoanDefault = 0x00010000;
|
constexpr std::uint32_t const tfLoanDefault = 0x00010000;
|
||||||
constexpr std::uint32_t const tfLoanImpair = 0x00010000;
|
constexpr std::uint32_t const tfLoanImpair = 0x00020000;
|
||||||
constexpr std::uint32_t const tfLoanUnimpair = 0x00010000;
|
constexpr std::uint32_t const tfLoanUnimpair = 0x00040000;
|
||||||
constexpr std::uint32_t const tfLoanManageMask = ~(tfUniversal | tfLoanDefault | tfLoanImpair | tfLoanUnimpair);
|
constexpr std::uint32_t const tfLoanManageMask = ~(tfUniversal | tfLoanDefault | tfLoanImpair | tfLoanUnimpair);
|
||||||
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#error "undefined macro: TYPED_SFIELD"
|
#error "undefined macro: TYPED_SFIELD"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
// untyped
|
// untyped
|
||||||
UNTYPED_SFIELD(sfLedgerEntry, LEDGERENTRY, 257)
|
UNTYPED_SFIELD(sfLedgerEntry, LEDGERENTRY, 257)
|
||||||
UNTYPED_SFIELD(sfTransaction, TRANSACTION, 257)
|
UNTYPED_SFIELD(sfTransaction, TRANSACTION, 257)
|
||||||
@@ -60,10 +62,6 @@ TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
|
|||||||
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
|
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
|
||||||
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
|
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
|
||||||
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22)
|
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22)
|
||||||
TYPED_SFIELD(sfInterestRate, UINT16, 25)
|
|
||||||
TYPED_SFIELD(sfLateInterestRate, UINT16, 26)
|
|
||||||
TYPED_SFIELD(sfCloseInterestRate, UINT16, 27)
|
|
||||||
TYPED_SFIELD(sfOverpaymentInterestRate, UINT16, 28)
|
|
||||||
|
|
||||||
// 32-bit integers (common)
|
// 32-bit integers (common)
|
||||||
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
||||||
@@ -127,6 +125,10 @@ TYPED_SFIELD(sfPaymentRemaining, UINT32, 57)
|
|||||||
TYPED_SFIELD(sfPaymentTotal, UINT32, 58)
|
TYPED_SFIELD(sfPaymentTotal, UINT32, 58)
|
||||||
TYPED_SFIELD(sfCoverRateMinimum, UINT32, 59)
|
TYPED_SFIELD(sfCoverRateMinimum, UINT32, 59)
|
||||||
TYPED_SFIELD(sfCoverRateLiquidation, UINT32, 60)
|
TYPED_SFIELD(sfCoverRateLiquidation, UINT32, 60)
|
||||||
|
TYPED_SFIELD(sfInterestRate, UINT32, 61)
|
||||||
|
TYPED_SFIELD(sfLateInterestRate, UINT32, 62)
|
||||||
|
TYPED_SFIELD(sfCloseInterestRate, UINT32, 63)
|
||||||
|
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 64)
|
||||||
|
|
||||||
// 64-bit integers (common)
|
// 64-bit integers (common)
|
||||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||||
@@ -418,3 +420,5 @@ UNTYPED_SFIELD(sfAuthAccounts, ARRAY, 25)
|
|||||||
UNTYPED_SFIELD(sfAuthorizeCredentials, ARRAY, 26)
|
UNTYPED_SFIELD(sfAuthorizeCredentials, ARRAY, 26)
|
||||||
UNTYPED_SFIELD(sfUnauthorizeCredentials, ARRAY, 27)
|
UNTYPED_SFIELD(sfUnauthorizeCredentials, ARRAY, 27)
|
||||||
UNTYPED_SFIELD(sfAcceptedCredentials, ARRAY, 28)
|
UNTYPED_SFIELD(sfAcceptedCredentials, ARRAY, 28)
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
@@ -726,7 +726,6 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, noPriv, (
|
|||||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
#if 0
|
|
||||||
/** This transaction creates a Loan */
|
/** This transaction creates a Loan */
|
||||||
#if TRANSACTION_INCLUDE
|
#if TRANSACTION_INCLUDE
|
||||||
# include <xrpld/app/tx/detail/LoanSet.h>
|
# include <xrpld/app/tx/detail/LoanSet.h>
|
||||||
@@ -750,6 +749,7 @@ TRANSACTION(ttLOAN_SET, 78, LoanSet, noPriv, ({
|
|||||||
{sfGracePeriod, soeOPTIONAL},
|
{sfGracePeriod, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
#if 0
|
||||||
/** This transaction deletes an existing Loan */
|
/** This transaction deletes an existing Loan */
|
||||||
#if TRANSACTION_INCLUDE
|
#if TRANSACTION_INCLUDE
|
||||||
# include <xrpld/app/tx/detail/LoanDelete.h>
|
# include <xrpld/app/tx/detail/LoanDelete.h>
|
||||||
|
|||||||
@@ -28,12 +28,6 @@ InnerObjectFormats::InnerObjectFormats()
|
|||||||
// inner objects with the default fields have to be
|
// inner objects with the default fields have to be
|
||||||
// constructed with STObject::makeInnerObject()
|
// constructed with STObject::makeInnerObject()
|
||||||
|
|
||||||
std::initializer_list<SOElement> const signingFields = {
|
|
||||||
{sfAccount, soeREQUIRED},
|
|
||||||
{sfSigningPubKey, soeREQUIRED},
|
|
||||||
{sfTxnSignature, soeREQUIRED},
|
|
||||||
};
|
|
||||||
|
|
||||||
add(sfSignerEntry.jsonName,
|
add(sfSignerEntry.jsonName,
|
||||||
sfSignerEntry.getCode(),
|
sfSignerEntry.getCode(),
|
||||||
{
|
{
|
||||||
@@ -42,7 +36,13 @@ InnerObjectFormats::InnerObjectFormats()
|
|||||||
{sfWalletLocator, soeOPTIONAL},
|
{sfWalletLocator, soeOPTIONAL},
|
||||||
});
|
});
|
||||||
|
|
||||||
add(sfSigner.jsonName, sfSigner.getCode(), signingFields);
|
add(sfSigner.jsonName,
|
||||||
|
sfSigner.getCode(),
|
||||||
|
{
|
||||||
|
{sfAccount, soeREQUIRED},
|
||||||
|
{sfSigningPubKey, soeREQUIRED},
|
||||||
|
{sfTxnSignature, soeREQUIRED},
|
||||||
|
});
|
||||||
|
|
||||||
add(sfMajority.jsonName,
|
add(sfMajority.jsonName,
|
||||||
sfMajority.getCode(),
|
sfMajority.getCode(),
|
||||||
@@ -157,7 +157,11 @@ InnerObjectFormats::InnerObjectFormats()
|
|||||||
|
|
||||||
add(sfCounterpartySignature.jsonName,
|
add(sfCounterpartySignature.jsonName,
|
||||||
sfCounterpartySignature.getCode(),
|
sfCounterpartySignature.getCode(),
|
||||||
signingFields);
|
{
|
||||||
|
{sfSigningPubKey, soeREQUIRED},
|
||||||
|
{sfTxnSignature, soeOPTIONAL},
|
||||||
|
{sfSigners, soeOPTIONAL},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerObjectFormats const&
|
InnerObjectFormats const&
|
||||||
|
|||||||
@@ -416,12 +416,12 @@ class LoanBroker_test : public beast::unit_test::suite
|
|||||||
ter(temINVALID));
|
ter(temINVALID));
|
||||||
// sfManagementFeeRate: good value, bad account
|
// sfManagementFeeRate: good value, bad account
|
||||||
env(set(evan, vault.vaultID),
|
env(set(evan, vault.vaultID),
|
||||||
managementFeeRate(maxFeeRate),
|
managementFeeRate(maxManagementFeeRate),
|
||||||
fee(increment),
|
fee(increment),
|
||||||
ter(tecNO_PERMISSION));
|
ter(tecNO_PERMISSION));
|
||||||
// sfManagementFeeRate: too big
|
// sfManagementFeeRate: too big
|
||||||
env(set(evan, vault.vaultID),
|
env(set(evan, vault.vaultID),
|
||||||
managementFeeRate(maxFeeRate + 1),
|
managementFeeRate(maxManagementFeeRate + 1),
|
||||||
fee(increment),
|
fee(increment),
|
||||||
ter(temINVALID));
|
ter(temINVALID));
|
||||||
// sfCoverRateMinimum: good value, bad account
|
// sfCoverRateMinimum: good value, bad account
|
||||||
@@ -504,17 +504,17 @@ class LoanBroker_test : public beast::unit_test::suite
|
|||||||
// ManagementFeeRate
|
// ManagementFeeRate
|
||||||
env(set(alice, vault.vaultID),
|
env(set(alice, vault.vaultID),
|
||||||
loanBrokerID(broker->key()),
|
loanBrokerID(broker->key()),
|
||||||
managementFeeRate(maxFeeRate),
|
managementFeeRate(maxManagementFeeRate),
|
||||||
ter(temINVALID));
|
ter(temINVALID));
|
||||||
// CoverRateMinimum
|
// CoverRateMinimum
|
||||||
env(set(alice, vault.vaultID),
|
env(set(alice, vault.vaultID),
|
||||||
loanBrokerID(broker->key()),
|
loanBrokerID(broker->key()),
|
||||||
coverRateMinimum(maxFeeRate),
|
coverRateMinimum(maxManagementFeeRate),
|
||||||
ter(temINVALID));
|
ter(temINVALID));
|
||||||
// CoverRateLiquidation
|
// CoverRateLiquidation
|
||||||
env(set(alice, vault.vaultID),
|
env(set(alice, vault.vaultID),
|
||||||
loanBrokerID(broker->key()),
|
loanBrokerID(broker->key()),
|
||||||
coverRateLiquidation(maxFeeRate),
|
coverRateLiquidation(maxManagementFeeRate),
|
||||||
ter(temINVALID));
|
ter(temINVALID));
|
||||||
|
|
||||||
// fields that can be changed
|
// fields that can be changed
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto const coverAvail = sleBroker->at(sfCoverAvailable);
|
auto const coverAvail = sleBroker->at(sfCoverAvailable);
|
||||||
// Cover Rate is in 1/10 bps units
|
// Cover Rate is in 1/10 bips units
|
||||||
auto const minimumCover = sleBroker->at(sfDebtTotal) *
|
auto const minimumCover = tenthBipsOfValue(
|
||||||
sleBroker->at(sfCoverRateMinimum) / bpsPerOne / 10;
|
sleBroker->at(sfDebtTotal), sleBroker->at(sfCoverRateMinimum));
|
||||||
if (coverAvail < amount)
|
if (coverAvail < amount)
|
||||||
return tecINSUFFICIENT_FUNDS;
|
return tecINSUFFICIENT_FUNDS;
|
||||||
if ((coverAvail - amount) < minimumCover)
|
if ((coverAvail - amount) < minimumCover)
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ LoanBrokerSet::doPreflight(PreflightContext const& ctx)
|
|||||||
if (auto const data = tx[~sfData]; data && !data->empty() &&
|
if (auto const data = tx[~sfData]; data && !data->empty() &&
|
||||||
!validDataLength(tx[~sfData], maxDataPayloadLength))
|
!validDataLength(tx[~sfData], maxDataPayloadLength))
|
||||||
return temINVALID;
|
return temINVALID;
|
||||||
if (!validNumericRange(tx[~sfManagementFeeRate], maxFeeRate))
|
if (!validNumericRange(tx[~sfManagementFeeRate], maxManagementFeeRate))
|
||||||
return temINVALID;
|
return temINVALID;
|
||||||
if (!validNumericRange(tx[~sfCoverRateMinimum], maxCoverRate))
|
if (!validNumericRange(tx[~sfCoverRateMinimum], maxCoverRate))
|
||||||
return temINVALID;
|
return temINVALID;
|
||||||
|
|||||||
213
src/xrpld/app/tx/detail/LoanSet.cpp
Normal file
213
src/xrpld/app/tx/detail/LoanSet.cpp
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2025 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <xrpld/app/tx/detail/LoanSet.h>
|
||||||
|
//
|
||||||
|
#include <xrpld/app/tx/detail/LoanBrokerSet.h>
|
||||||
|
#include <xrpld/app/tx/detail/SignerEntries.h>
|
||||||
|
#include <xrpld/app/tx/detail/VaultCreate.h>
|
||||||
|
#include <xrpld/ledger/ApplyView.h>
|
||||||
|
#include <xrpld/ledger/View.h>
|
||||||
|
|
||||||
|
#include <xrpl/basics/Log.h>
|
||||||
|
#include <xrpl/basics/Number.h>
|
||||||
|
#include <xrpl/basics/chrono.h>
|
||||||
|
#include <xrpl/beast/utility/Journal.h>
|
||||||
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
|
#include <xrpl/protocol/AccountID.h>
|
||||||
|
#include <xrpl/protocol/Feature.h>
|
||||||
|
#include <xrpl/protocol/Indexes.h>
|
||||||
|
#include <xrpl/protocol/PublicKey.h>
|
||||||
|
#include <xrpl/protocol/SField.h>
|
||||||
|
#include <xrpl/protocol/STAmount.h>
|
||||||
|
#include <xrpl/protocol/STNumber.h>
|
||||||
|
#include <xrpl/protocol/STObject.h>
|
||||||
|
#include <xrpl/protocol/STXChainBridge.h>
|
||||||
|
#include <xrpl/protocol/TER.h>
|
||||||
|
#include <xrpl/protocol/TxFlags.h>
|
||||||
|
#include <xrpl/protocol/XRPAmount.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
bool
|
||||||
|
LoanSet::isEnabled(PreflightContext const& ctx)
|
||||||
|
{
|
||||||
|
return lendingProtocolEnabled(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t
|
||||||
|
LoanSet::getFlagsMask(PreflightContext const& ctx)
|
||||||
|
{
|
||||||
|
return tfLoanSetMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotTEC
|
||||||
|
LoanSet::doPreflight(PreflightContext const& ctx)
|
||||||
|
{
|
||||||
|
auto const& tx = ctx.tx;
|
||||||
|
if (auto const data = tx[~sfData]; data && !data->empty() &&
|
||||||
|
!validDataLength(tx[~sfData], maxDataPayloadLength))
|
||||||
|
return temINVALID;
|
||||||
|
if (!validNumericRange(tx[~sfLateInterestRate], maxLateInterestRate))
|
||||||
|
return temINVALID;
|
||||||
|
if (!validNumericRange(tx[~sfCloseInterestRate], maxCloseInterestRate))
|
||||||
|
return temINVALID;
|
||||||
|
if (!validNumericRange(
|
||||||
|
tx[~sfOverpaymentInterestRate], maxOverpaymentInterestRate))
|
||||||
|
return temINVALID;
|
||||||
|
|
||||||
|
if (auto const paymentTotal = tx[~sfPaymentTotal];
|
||||||
|
paymentTotal && *paymentTotal == 0)
|
||||||
|
return temINVALID;
|
||||||
|
if (auto const paymentInterval =
|
||||||
|
tx[~sfPaymentInterval].value_or(LoanSet::defaultPaymentInterval);
|
||||||
|
paymentInterval < LoanSet::defaultPaymentInterval)
|
||||||
|
return temINVALID;
|
||||||
|
else if (auto const gracePeriod =
|
||||||
|
tx[~sfGracePeriod].value_or(LoanSet::defaultGracePeriod);
|
||||||
|
gracePeriod > paymentInterval)
|
||||||
|
return temINVALID;
|
||||||
|
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
LoanSet::preclaim(PreclaimContext const& ctx)
|
||||||
|
{
|
||||||
|
return temDISABLED;
|
||||||
|
|
||||||
|
auto const& tx = ctx.tx;
|
||||||
|
|
||||||
|
auto const account = tx[sfAccount];
|
||||||
|
if (auto const ID = tx[~sfLoanID])
|
||||||
|
{
|
||||||
|
auto const sle = ctx.view.read(keylet::loan(*ID));
|
||||||
|
if (!sle)
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.warn()) << "Loan does not exist.";
|
||||||
|
return tecNO_ENTRY;
|
||||||
|
}
|
||||||
|
if (tx[sfVaultID] != sle->at(sfVaultID))
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.warn()) << "Can not change VaultID on an existing Loan.";
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
|
if (account != sle->at(sfOwner))
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.warn()) << "Account is not the owner of the Loan.";
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto const vaultID = tx[sfVaultID];
|
||||||
|
auto const sleVault = ctx.view.read(keylet::vault(vaultID));
|
||||||
|
if (!sleVault)
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.warn()) << "Vault does not exist.";
|
||||||
|
return tecNO_ENTRY;
|
||||||
|
}
|
||||||
|
if (account != sleVault->at(sfOwner))
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.warn()) << "Account is not the owner of the Vault.";
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
TER
|
||||||
|
LoanSet::doApply()
|
||||||
|
{
|
||||||
|
return temDISABLED;
|
||||||
|
|
||||||
|
auto const& tx = ctx_.tx;
|
||||||
|
auto& view = ctx_.view();
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (auto const ID = tx[~sfLoanID])
|
||||||
|
{
|
||||||
|
// Modify an existing Loan
|
||||||
|
auto loan = view.peek(keylet::loan(*ID));
|
||||||
|
|
||||||
|
if (auto const data = tx[~sfData])
|
||||||
|
loan->at(sfData) = *data;
|
||||||
|
if (auto const debtMax = tx[~sfDebtMaximum])
|
||||||
|
loan->at(sfDebtMaximum) = *debtMax;
|
||||||
|
|
||||||
|
view.update();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create a new Loan pointing back to the given Vault
|
||||||
|
auto const vaultID = tx[sfVaultID];
|
||||||
|
auto const sleVault = view.read(keylet::vault(vaultID));
|
||||||
|
auto const vaultPseudoID = sleVault->at(sfAccount);
|
||||||
|
auto const sequence = tx.getSeqValue();
|
||||||
|
|
||||||
|
auto owner = view.peek(keylet::account(account_));
|
||||||
|
auto loan = std::make_shared<SLE>(keylet::loan(account_, sequence));
|
||||||
|
|
||||||
|
if (auto const ter = dirLink(view, account_, ))
|
||||||
|
return ter;
|
||||||
|
if (auto const ter = dirLink(view, vaultPseudoID, , sfVaultNode))
|
||||||
|
return ter;
|
||||||
|
|
||||||
|
adjustOwnerCount(view, owner, 1, j_);
|
||||||
|
auto ownerCount = owner->at(sfOwnerCount);
|
||||||
|
if (mPriorBalance < view.fees().accountReserve(ownerCount))
|
||||||
|
return tecINSUFFICIENT_RESERVE;
|
||||||
|
|
||||||
|
auto maybePseudo =
|
||||||
|
createPseudoAccount(view, loan->key(), PseudoAccountOwnerType::Loan);
|
||||||
|
if (!maybePseudo)
|
||||||
|
return maybePseudo.error();
|
||||||
|
auto& pseudo = *maybePseudo;
|
||||||
|
auto pseudoId = pseudo->at(sfAccount);
|
||||||
|
|
||||||
|
if (auto ter = addEmptyHolding(
|
||||||
|
view, pseudoId, mPriorBalance, sleVault->at(sfAsset), j_))
|
||||||
|
return ter;
|
||||||
|
|
||||||
|
// Initialize data fields:
|
||||||
|
loan->at(sfSequence) = sequence;
|
||||||
|
loan->at(sfVaultID) = vaultID;
|
||||||
|
loan->at(sfOwner) = account_;
|
||||||
|
loan->at(sfAccount) = pseudoId;
|
||||||
|
if (auto const data = tx[~sfData])
|
||||||
|
loan->at(sfData) = *data;
|
||||||
|
if (auto const rate = tx[~sfManagementFeeRate])
|
||||||
|
loan->at(sfManagementFeeRate) = *rate;
|
||||||
|
if (auto const debtMax = tx[~sfDebtMaximum])
|
||||||
|
loan->at(sfDebtMaximum) = *debtMax;
|
||||||
|
if (auto const coverMin = tx[~sfCoverRateMinimum])
|
||||||
|
loan->at(sfCoverRateMinimum) = *coverMin;
|
||||||
|
if (auto const coverLiq = tx[~sfCoverRateLiquidation])
|
||||||
|
loan->at(sfCoverRateLiquidation) = *coverLiq;
|
||||||
|
|
||||||
|
view.insert(loan);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
} // namespace ripple
|
||||||
61
src/xrpld/app/tx/detail/LoanSet.h
Normal file
61
src/xrpld/app/tx/detail/LoanSet.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2025 Ripple Labs Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#ifndef RIPPLE_TX_LOANSET_H_INCLUDED
|
||||||
|
#define RIPPLE_TX_LOANSET_H_INCLUDED
|
||||||
|
|
||||||
|
#include <xrpld/app/tx/detail/Transactor.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
|
||||||
|
class LoanSet : public Transactor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||||
|
|
||||||
|
explicit LoanSet(ApplyContext& ctx) : Transactor(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
isEnabled(PreflightContext const& ctx);
|
||||||
|
|
||||||
|
static std::uint32_t
|
||||||
|
getFlagsMask(PreflightContext const& ctx);
|
||||||
|
|
||||||
|
static NotTEC
|
||||||
|
doPreflight(PreflightContext const& ctx);
|
||||||
|
|
||||||
|
static TER
|
||||||
|
preclaim(PreclaimContext const& ctx);
|
||||||
|
|
||||||
|
TER
|
||||||
|
doApply() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::uint32_t constexpr defaultPaymentTotal = 1;
|
||||||
|
static std::uint32_t constexpr defaultPaymentInterval = 60;
|
||||||
|
static std::uint32_t constexpr defaultGracePeriod = 60;
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
} // namespace ripple
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user