mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 18:45:52 +00:00
Add Loan Broker and Loan ledger objects:
- Also add new SFields, Keylet functions, and an Invariant to verify no illegal field modification
This commit is contained in:
@@ -339,6 +339,24 @@ vault(uint256 const& vaultKey)
|
||||
return {ltVAULT, vaultKey};
|
||||
}
|
||||
|
||||
Keylet
|
||||
loanbroker(AccountID const& owner, std::uint32_t seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
loanbroker(uint256 const& vaultKey)
|
||||
{
|
||||
return {ltLOAN_BROKER, vaultKey};
|
||||
}
|
||||
|
||||
Keylet
|
||||
loan(AccountID const& owner, uint256 loanBrokerID, std::uint32_t seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
loan(uint256 const& vaultKey)
|
||||
{
|
||||
return {ltLOAN, vaultKey};
|
||||
}
|
||||
|
||||
Keylet
|
||||
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ class STLedgerEntry final : public STObject, public CountedObject<STLedgerEntry>
|
||||
public:
|
||||
using pointer = std::shared_ptr<STLedgerEntry>;
|
||||
using ref = const std::shared_ptr<STLedgerEntry>&;
|
||||
using const_pointer = std::shared_ptr<STLedgerEntry const>;
|
||||
using const_ref = const std::shared_ptr<STLedgerEntry const>&;
|
||||
|
||||
/** Create an empty object with the given key and type. */
|
||||
explicit STLedgerEntry(Keylet const& k);
|
||||
|
||||
@@ -167,6 +167,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
|
||||
{sfFirstNFTokenSequence, soeOPTIONAL},
|
||||
{sfAMMID, soeOPTIONAL}, // pseudo-account designator
|
||||
{sfVaultID, soeOPTIONAL}, // pseudo-account designator
|
||||
{sfLoanBrokerID, soeOPTIONAL}, // pseudo-account designator
|
||||
}))
|
||||
|
||||
/** A ledger object which contains a list of object identifiers.
|
||||
@@ -485,6 +486,60 @@ LEDGER_ENTRY(ltVAULT, 0x0083, Vault, vault, ({
|
||||
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
|
||||
}))
|
||||
|
||||
/** A ledger object representing a loan broker
|
||||
|
||||
\sa keylet::loanbroker
|
||||
*/
|
||||
LEDGER_ENTRY(ltLOAN_BROKER, 0x0084, LoanBroker, loan_broker, ({
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfSequence, soeREQUIRED},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfVaultNode, soeREQUIRED},
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfData, soeDEFAULT},
|
||||
{sfManagementFeeRate, soeDEFAULT},
|
||||
{sfOwnerCount, soeREQUIRED},
|
||||
{sfDebtTotal, soeREQUIRED},
|
||||
{sfDebtMaximum, soeREQUIRED},
|
||||
{sfCoverAvailable, soeREQUIRED},
|
||||
{sfCoverRateMinimum, soeREQUIRED},
|
||||
{sfCoverRateLiquidation, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object representing a loan between a Borrower and a Loan Broker
|
||||
|
||||
\sa keylet::loan
|
||||
*/
|
||||
LEDGER_ENTRY(ltLOAN, 0x0085, Loan, loan, ({
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfSequence, soeREQUIRED},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfLoanBrokerNode, soeREQUIRED},
|
||||
{sfLoanBrokerID, soeREQUIRED},
|
||||
{sfBorrower, soeREQUIRED},
|
||||
{sfLoanOriginationFee, soeREQUIRED},
|
||||
{sfLoanServiceFee, soeREQUIRED},
|
||||
{sfLatePaymentFee, soeREQUIRED},
|
||||
{sfClosePaymentFee, soeREQUIRED},
|
||||
{sfOverpaymentFee, soeREQUIRED},
|
||||
{sfInterestRate, soeREQUIRED},
|
||||
{sfLateInterestRate, soeREQUIRED},
|
||||
{sfCloseInterestRate, soeREQUIRED},
|
||||
{sfOverpaymentInterestRate, soeREQUIRED},
|
||||
{sfStartDate, soeREQUIRED},
|
||||
{sfPaymentInterval, soeREQUIRED},
|
||||
{sfGracePeriod, soeREQUIRED},
|
||||
{sfPreviousPaymentDate, soeREQUIRED},
|
||||
{sfNextPaymentDueDate, soeREQUIRED},
|
||||
{sfPaymentRemaining, soeREQUIRED},
|
||||
{sfAssetAvailable, soeREQUIRED},
|
||||
{sfPrincipalOutstanding, soeREQUIRED},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
|
||||
@@ -59,6 +59,13 @@ TYPED_SFIELD(sfHookEmitCount, UINT16, 18)
|
||||
TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
|
||||
TYPED_SFIELD(sfHookApiVersion, UINT16, 20)
|
||||
TYPED_SFIELD(sfLedgerFixType, UINT16, 21)
|
||||
TYPED_SFIELD(sfManagementFeeRate, UINT16, 22)
|
||||
TYPED_SFIELD(sfCoverRateMinimum, UINT16, 23)
|
||||
TYPED_SFIELD(sfCoverRateLiquidation, UINT16, 24)
|
||||
TYPED_SFIELD(sfInterestRate, UINT16, 25)
|
||||
TYPED_SFIELD(sfLateInterestRate, UINT16, 26)
|
||||
TYPED_SFIELD(sfCloseInterestRate, UINT16, 27)
|
||||
TYPED_SFIELD(sfOverpaymentInterestRate, UINT16, 28)
|
||||
|
||||
// 32-bit integers (common)
|
||||
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
||||
@@ -113,6 +120,12 @@ TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
|
||||
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
TYPED_SFIELD(sfStartDate, UINT32, 52)
|
||||
TYPED_SFIELD(sfPaymentInterval, UINT32, 53)
|
||||
TYPED_SFIELD(sfGracePeriod, UINT32, 54)
|
||||
TYPED_SFIELD(sfPreviousPaymentDate, UINT32, 55)
|
||||
TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 56)
|
||||
TYPED_SFIELD(sfPaymentRemaining, UINT32, 57)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -143,6 +156,8 @@ TYPED_SFIELD(sfOutstandingAmount, UINT64, 25, SField::sMD_BaseTen|SFie
|
||||
TYPED_SFIELD(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SField::sMD_Default)
|
||||
TYPED_SFIELD(sfIssuerNode, UINT64, 27)
|
||||
TYPED_SFIELD(sfSubjectNode, UINT64, 28)
|
||||
TYPED_SFIELD(sfVaultNode, UINT64, 29)
|
||||
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 30)
|
||||
|
||||
// 128-bit
|
||||
TYPED_SFIELD(sfEmailHash, UINT128, 1)
|
||||
@@ -194,6 +209,7 @@ TYPED_SFIELD(sfHookNamespace, UINT256, 32)
|
||||
TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
|
||||
TYPED_SFIELD(sfDomainID, UINT256, 34)
|
||||
TYPED_SFIELD(sfVaultID, UINT256, 35)
|
||||
TYPED_SFIELD(sfLoanBrokerID, UINT256, 35)
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
@@ -201,6 +217,15 @@ TYPED_SFIELD(sfAssetsAvailable, NUMBER, 2)
|
||||
TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3)
|
||||
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4)
|
||||
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5)
|
||||
TYPED_SFIELD(sfDebtTotal, NUMBER, 6)
|
||||
TYPED_SFIELD(sfDebtMaximum, NUMBER, 7)
|
||||
TYPED_SFIELD(sfCoverAvailable, NUMBER, 8)
|
||||
TYPED_SFIELD(sfLoanOriginationFee, NUMBER, 9)
|
||||
TYPED_SFIELD(sfLoanServiceFee, NUMBER, 9)
|
||||
TYPED_SFIELD(sfLatePaymentFee, NUMBER, 10)
|
||||
TYPED_SFIELD(sfClosePaymentFee, NUMBER, 11)
|
||||
TYPED_SFIELD(sfOverpaymentFee, NUMBER, 12)
|
||||
TYPED_SFIELD(sfPrincipalOutstanding, NUMBER, 13)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
@@ -295,6 +320,7 @@ TYPED_SFIELD(sfAttestationRewardAccount, ACCOUNT, 21)
|
||||
TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22)
|
||||
TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
|
||||
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
|
||||
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
|
||||
|
||||
// vector of 256-bit
|
||||
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never)
|
||||
|
||||
@@ -95,6 +95,8 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
CREDENTIAL = 'D',
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
VAULT = 'V',
|
||||
LOAN_BROKER = 'l', // lower-case L
|
||||
LOAN = 'L',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -550,6 +552,18 @@ vault(AccountID const& owner, std::uint32_t seq) noexcept
|
||||
return vault(indexHash(LedgerNameSpace::VAULT, owner, seq));
|
||||
}
|
||||
|
||||
Keylet
|
||||
loanbroker(AccountID const& owner, std::uint32_t seq) noexcept
|
||||
{
|
||||
return loanbroker(indexHash(LedgerNameSpace::LOAN_BROKER, owner, seq));
|
||||
}
|
||||
|
||||
Keylet
|
||||
loan(AccountID const& owner, uint256 loanBrokerID, std::uint32_t seq) noexcept
|
||||
{
|
||||
return loan(indexHash(LedgerNameSpace::LOAN, loanBrokerID, seq));
|
||||
}
|
||||
|
||||
Keylet
|
||||
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept
|
||||
{
|
||||
|
||||
@@ -1578,4 +1578,101 @@ ValidPermissionedDomain::finalize(
|
||||
(sleStatus_[1] ? check(*sleStatus_[1], j) : true);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
NoModifiedUnmodifiableFields::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (isDelete || !before)
|
||||
// Creation and deletion are ignored
|
||||
return;
|
||||
|
||||
auto const type = after->getType();
|
||||
if (knownTypes_.contains(type))
|
||||
{
|
||||
changedEntries_.emplace(before, after);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
NoModifiedUnmodifiableFields::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
static auto const fieldChanged = [](auto const& before,
|
||||
auto const& after,
|
||||
auto const& field) {
|
||||
return before->isFieldPresent(field) != after->isFieldPresent(field) ||
|
||||
before->at(field) != after->at(field);
|
||||
};
|
||||
for (auto const& slePair : changedEntries_)
|
||||
{
|
||||
auto const& before = slePair.first;
|
||||
auto const& after = slePair.second;
|
||||
auto const type = after->getType();
|
||||
bool bad = false;
|
||||
[[maybe_unused]] bool enforce = false;
|
||||
switch (type)
|
||||
{
|
||||
case ltLOAN_BROKER:
|
||||
/*
|
||||
* We check this invariant regardless of lending protocol
|
||||
* amendment status, allowing for detection and logging of
|
||||
* potential issues even when the amendment is disabled.
|
||||
*/
|
||||
enforce = view.rules().enabled(featureLendingProtocol);
|
||||
bad = fieldChanged(before, after, sfVaultID) ||
|
||||
fieldChanged(before, after, sfAccount) ||
|
||||
fieldChanged(before, after, sfOwner) ||
|
||||
fieldChanged(before, after, sfManagementFeeRate) ||
|
||||
fieldChanged(before, after, sfCoverRateMinimum) ||
|
||||
fieldChanged(before, after, sfCoverRateLiquidation);
|
||||
break;
|
||||
case ltLOAN:
|
||||
/*
|
||||
* We check this invariant regardless of lending protocol
|
||||
* amendment status, allowing for detection and logging of
|
||||
* potential issues even when the amendment is disabled.
|
||||
*/
|
||||
enforce = view.rules().enabled(featureLendingProtocol);
|
||||
bad = fieldChanged(before, after, sfLoanBrokerID) ||
|
||||
fieldChanged(before, after, sfBorrower) ||
|
||||
fieldChanged(before, after, sfLoanOriginationFee) ||
|
||||
fieldChanged(before, after, sfLoanServiceFee) ||
|
||||
fieldChanged(before, after, sfLatePaymentFee) ||
|
||||
fieldChanged(before, after, sfClosePaymentFee) ||
|
||||
fieldChanged(before, after, sfOverpaymentFee) ||
|
||||
fieldChanged(before, after, sfInterestRate) ||
|
||||
fieldChanged(before, after, sfLateInterestRate) ||
|
||||
fieldChanged(before, after, sfCloseInterestRate) ||
|
||||
fieldChanged(before, after, sfOverpaymentInterestRate) ||
|
||||
fieldChanged(before, after, sfPaymentInterval);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE(
|
||||
"ripple::NoModifiedUnmodifiableFields::finalize : unknown "
|
||||
"SLE type");
|
||||
}
|
||||
XRPL_ASSERT(
|
||||
!bad || enforce,
|
||||
"ripple::NoModifiedUnmodifiableFields::finalize : no bad "
|
||||
"changes or enforce invariant");
|
||||
if (bad)
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: changed an unchangable field for "
|
||||
<< tx.getTransactionID();
|
||||
if (enforce)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -618,6 +618,35 @@ public:
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Some fields are unmodifiable
|
||||
*
|
||||
* Check that any fields specified as unmodifiable are not modified when the
|
||||
* object is modified. Creation and deletion are ignored.
|
||||
*
|
||||
*/
|
||||
class NoModifiedUnmodifiableFields
|
||||
{
|
||||
std::set<LedgerEntryType> const knownTypes_{ltLOAN_BROKER, ltLOAN};
|
||||
|
||||
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
// additional invariant checks can be declared above and then added to this
|
||||
// tuple
|
||||
using InvariantChecks = std::tuple<
|
||||
@@ -637,7 +666,8 @@ using InvariantChecks = std::tuple<
|
||||
NFTokenCountTracking,
|
||||
ValidClawback,
|
||||
ValidMPTIssuance,
|
||||
ValidPermissionedDomain>;
|
||||
ValidPermissionedDomain,
|
||||
NoModifiedUnmodifiableFields>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
Reference in New Issue
Block a user