[WIP] Define remaining transactions, start implementing LoanBrokerSet

- Does not build
- Transactions: LoanDelete, LoanManage, LoanDraw, LoanPay
- LoanBrokerSet creation mostly done. Need update.
- Also added a lookup table for pseudo account fields.
This commit is contained in:
Ed Hennis
2025-03-21 20:03:22 -04:00
parent 009ae8183f
commit 24374c548b
11 changed files with 355 additions and 16 deletions

View File

@@ -82,6 +82,26 @@ 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.
TODO: Is this a good name?
Valid values for the the management fee charged by the Lending Protocol are
between 0 and 10000 inclusive. A value of 1 is equivalent to 1/10 basis
point fee or 0.001%.
*/
std::uint16_t constexpr maxFeeRate = 10'000;
/** The maximum coverage rate allowed in lending.
TODO: Is this a good name?
Valid values for the coverage rate charged by the Lending Protocol for first
loss capital operations are between 0 and 100000 inclusive. A value of 1 is
equivalent to 1/10 bps or 0.001%.
*/
std::uint16_t constexpr maxCoverRate = 100'000;
/** 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;

View File

@@ -237,6 +237,13 @@ constexpr std::uint32_t const tfVaultCreateMask = ~(tfUniversal | tfVaultPrivate
// True, indicates the load supports overpayments // True, indicates the load 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:
constexpr std::uint32_t const tfLoanDefault = 0x00010000;
constexpr std::uint32_t const tfLoanImpair = 0x00010000;
constexpr std::uint32_t const tfLoanUnimpair = 0x00010000;
constexpr std::uint32_t const tfLoanManageMask = ~(tfUniversal | tfLoanDefault | tfLoanImpair | tfLoanUnimpair);
// clang-format on // clang-format on
} // namespace ripple } // namespace ripple

View File

@@ -501,12 +501,12 @@ LEDGER_ENTRY(ltLOAN_BROKER, 0x0084, LoanBroker, loan_broker, ({
{sfOwner, soeREQUIRED}, {sfOwner, soeREQUIRED},
{sfData, soeDEFAULT}, {sfData, soeDEFAULT},
{sfManagementFeeRate, soeDEFAULT}, {sfManagementFeeRate, soeDEFAULT},
{sfOwnerCount, soeREQUIRED}, {sfOwnerCount, soeDEFAULT},
{sfDebtTotal, soeREQUIRED}, {sfDebtTotal, soeDEFAULT},
{sfDebtMaximum, soeREQUIRED}, {sfDebtMaximum, soeDEFAULT},
{sfCoverAvailable, soeREQUIRED}, {sfCoverAvailable, soeDEFAULT},
{sfCoverRateMinimum, soeREQUIRED}, {sfCoverRateMinimum, soeDEFAULT},
{sfCoverRateLiquidation, soeREQUIRED}, {sfCoverRateLiquidation, soeDEFAULT},
})) }))
/** A ledger object representing a loan between a Borrower and a Loan Broker /** A ledger object representing a loan between a Borrower and a Loan Broker

View File

@@ -211,6 +211,7 @@ TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
TYPED_SFIELD(sfDomainID, UINT256, 34) TYPED_SFIELD(sfDomainID, UINT256, 34)
TYPED_SFIELD(sfVaultID, UINT256, 35) TYPED_SFIELD(sfVaultID, UINT256, 35)
TYPED_SFIELD(sfLoanBrokerID, UINT256, 36) TYPED_SFIELD(sfLoanBrokerID, UINT256, 36)
TYPED_SFIELD(sfLoanID, UINT256, 37)
// number (common) // number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1) TYPED_SFIELD(sfNumber, NUMBER, 1)

View File

@@ -508,7 +508,6 @@ TRANSACTION(ttVAULT_CLAWBACK, 69, VaultClawback, ({
{sfAmount, soeOPTIONAL, soeMPTSupported}, {sfAmount, soeOPTIONAL, soeMPTSupported},
})) }))
#if 0
/** This transaction creates and updates a Loan Broker */ /** This transaction creates and updates a Loan Broker */
TRANSACTION(ttLOAN_BROKER_SET, 70, LoanBrokerSet, ({ TRANSACTION(ttLOAN_BROKER_SET, 70, LoanBrokerSet, ({
{sfVaultID, soeREQUIRED}, {sfVaultID, soeREQUIRED},
@@ -520,6 +519,7 @@ TRANSACTION(ttLOAN_BROKER_SET, 70, LoanBrokerSet, ({
{sfCoverRateLiquidation, soeDEFAULT}, {sfCoverRateLiquidation, soeDEFAULT},
})) }))
#if 0
/** This transaction deletes a Loan Broker */ /** This transaction deletes a Loan Broker */
TRANSACTION(ttLOAN_BROKER_DELETE, 71, LoanBrokerDelete, ({ TRANSACTION(ttLOAN_BROKER_DELETE, 71, LoanBrokerDelete, ({
{sfLoanBrokerID, soeREQUIRED}, {sfLoanBrokerID, soeREQUIRED},
@@ -556,6 +556,28 @@ TRANSACTION(ttLOAN_SET, 74, LoanSet, ({
{sfPaymentInterval, soeOPTIONAL}, {sfPaymentInterval, soeOPTIONAL},
{sfGracePeriod, soeOPTIONAL}, {sfGracePeriod, soeOPTIONAL},
})) }))
/** This transaction deletes an existing Loan */
TRANSACTION(ttLOAN_DELETE, 75, LoanDelete, ({
{sfLoanID, soeREQUIRED},
}))
/** This transaction is used to change the delinquency status of an existing Loan */
TRANSACTION(ttLOAN_MANAGE, 76, LoanManage, ({
{sfLoanID, soeREQUIRED},
}))
/** The Borrower uses this transaction to draws funds from the Loan. */
TRANSACTION(ttLOAN_DRAW, 77, LoanDraw, ({
{sfLoanID, soeREQUIRED},
{sfAmount, soeREQUIRED},
}))
/** The Borrower uses this transaction to make a Payment on the Loan. */
TRANSACTION(ttLOAN_PAY, 77, LoanPay, ({
{sfLoanID, soeREQUIRED},
{sfAmount, soeREQUIRED},
}))
#endif #endif
/** This system-generated transaction type is used to update the status of the various amendments. /** This system-generated transaction type is used to update the status of the various amendments.

View File

@@ -0,0 +1,205 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 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/LoanBroker.h>
#include <xrpld/app/tx/detail/SignerEntries.h>
#include <xrpld/app/tx/detail/Transactor.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/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
lendingProtocolEnabled(PreflightContext const& ctx)
{
return ctx.rules.enabled(featureLendingProtocol) &&
VaultCreate::isEnabled(ctx);
}
bool
LoanBrokerSet::isEnabled(PreflightContext const& ctx)
{
return lendingProtocolEnabled(ctx);
}
std::uint32_t
LoanBrokerSet::getFlagsMask(PreflightContext const& ctx)
{
return tfUniversalMask;
}
NotTEC
LoanBrokerSet::doPreflight(PreflightContext const& ctx)
{
auto const& tx = ctx.tx;
if (!validDataLength(tx[~sfData], maxDataPayloadLength))
return temINVALID;
if (!validNumericRange(tx[~sfManagementFeeRate], 0, maxFeeRate))
return temINVALID;
if (!validNumericRange(tx[~sfCoverRateMinimum], 0, maxCoverRate))
return temINVALID;
if (!validNumericRange(tx[~sfCoverRateLiquidation], 0, maxCoverRate))
return temINVALID;
if (tx.isFieldPresent(sfLoanBrokerID))
{
// Fixed fields can not be specified if we're modifying an existing
// LoanBroker Object
if (tx.isFieldPresent(sfManagementFeeRate) ||
tx.isFieldPresent(sfCoverRateMinimum) ||
tx.isFieldPresent(sfCoverRateLiquidation))
return temINVALID;
}
return tesSUCCESS;
}
TER
LoanBrokerSet::preclaim(PreclaimContext const& ctx)
{
auto const& tx = ctx.tx;
auto const account = tx[sfAccount];
if (auto const brokerID = tx[~sfLoanBrokerID])
{
auto const sleBroker = ctx.view.read(keylet::loanbroker(*brokerID));
if (!sleBroker)
{
JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
return tecNO_ENTRY;
}
if (tx[sfVaultID] != sleBroker->at(sfVaultID))
{
JLOG(ctx.j.warn())
<< "Can not change VaultID on an existing LoanBroker.";
return tecNO_PERMISSION;
}
if (account != sleBroker->at(sfOwner))
{
JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker.";
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
LoanBrokerSet::doApply()
{
auto const& tx = ctx_.tx;
auto& view = ctx_.view();
if (auto const brokerID = tx[~sfLoanBrokerID])
{
// Modify an existing LoanBroker
auto const sleBroker = view.read(keylet::loanbroker(*brokerID));
assert(0);
}
else
{
// Create a new LoanBroker 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 broker =
std::make_shared<SLE>(keylet::loanbroker(account_, sequence));
if (auto const ter = dirLink(view, account_, broker))
return ter;
if (auto const ter = dirLink(view, vaultPseudoID, broker))
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, broker->key(), PseudoAccountOwnerType::LoanBroker);
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:
broker->at(sfSequence) = sequence;
broker->at(sfVaultID) = vaultID;
broker->at(sfOwner) = account_;
broker->at(sfAccount) = pseudoId;
if (auto const data = tx[~sfData])
broker->at(sfData) = *data;
if (auto const rate = tx[~sfManagementFeeRate])
broker->at(sfManagementFeeRate) = *rate;
if (auto const debtMax = tx[~sfDebtMaximum]; debtMax && *debtMax)
broker->at(sfDebtMaximum) = *debtMax;
if (auto const coverMin = tx[~sfCoverRateMinimum])
broker->at(sfCoverRateMinimum) = *coverMin;
if (auto const coverLiq = tx[~sfCoverRateLiquidation])
broker->at(sfCoverRateLiquidation) = *coverLiq;
view.insert(broker);
}
return temDISABLED;
}
//------------------------------------------------------------------------------
} // namespace ripple

View File

@@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 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_LOANBROKER_H_INCLUDED
#define RIPPLE_TX_LOANBROKER_H_INCLUDED
#include <xrpld/app/tx/detail/Transactor.h>
namespace ripple {
// Lending protocol has dependencies, so capture them here.
bool
lendingProtocolEnabled(PreflightContext const& ctx);
class LoanBrokerSet : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit LoanBrokerSet(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;
};
//------------------------------------------------------------------------------
} // namespace ripple
#endif

View File

@@ -205,6 +205,16 @@ Transactor::Transactor(ApplyContext& ctx)
{ {
} }
bool
Transactor::validDataLength(
std::optional<Slice> const& slice,
std::size_t maxLength)
{
if (!slice)
return true;
return !slice->empty() && slice->length() <= maxLength;
}
XRPAmount XRPAmount
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx) Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
{ {

View File

@@ -146,10 +146,6 @@ public:
static XRPAmount static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx); calculateBaseFee(ReadView const& view, STTx const& tx);
// Base class always returns true
static bool
isEnabled(PreflightContext const& ctx);
/* Do NOT define a preflight function in a derived class. /* Do NOT define a preflight function in a derived class.
Instead, define Instead, define
@@ -212,6 +208,17 @@ protected:
Fees const& fees, Fees const& fees,
ApplyFlags flags); ApplyFlags flags);
// Base class always returns true
static bool
isEnabled(PreflightContext const& ctx);
static bool
validDataLength(std::optional<Slice> const& slice, std::size_t maxLength);
template <class T>
static bool
validNumericRange(std::optional<T> value, T min, T max);
private: private:
std::pair<TER, XRPAmount> std::pair<TER, XRPAmount>
reset(XRPAmount fee); reset(XRPAmount fee);
@@ -272,6 +279,15 @@ Transactor::preflight(PreflightContext const& ctx)
return detail::preflight2(ctx); return detail::preflight2(ctx);
} }
template <class T>
bool
Transactor::validNumericRange(std::optional<T> value, T min, T max)
{
if (!value)
return true;
return value >= min && value <= max;
}
} // namespace ripple } // namespace ripple
#endif #endif

View File

@@ -53,11 +53,8 @@ VaultCreate::getFlagsMask(PreflightContext const& ctx)
NotTEC NotTEC
VaultCreate::doPreflight(PreflightContext const& ctx) VaultCreate::doPreflight(PreflightContext const& ctx)
{ {
if (auto const data = ctx.tx[~sfData]) if (!validDataLength(ctx.tx[~sfData], maxDataPayloadLength))
{
if (data->empty() || data->length() > maxDataPayloadLength)
return temMALFORMED; return temMALFORMED;
}
if (auto const withdrawalPolicy = ctx.tx[~sfWithdrawalPolicy]) if (auto const withdrawalPolicy = ctx.tx[~sfWithdrawalPolicy])
{ {

View File

@@ -41,6 +41,7 @@
#include <xrpld/app/tx/detail/DepositPreauth.h> #include <xrpld/app/tx/detail/DepositPreauth.h>
#include <xrpld/app/tx/detail/Escrow.h> #include <xrpld/app/tx/detail/Escrow.h>
#include <xrpld/app/tx/detail/LedgerStateFix.h> #include <xrpld/app/tx/detail/LedgerStateFix.h>
#include <xrpld/app/tx/detail/LoanBroker.h>
#include <xrpld/app/tx/detail/MPTokenAuthorize.h> #include <xrpld/app/tx/detail/MPTokenAuthorize.h>
#include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h> #include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
#include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h> #include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>