mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 02:55:50 +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};
|
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
|
Keylet
|
||||||
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
|
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class STLedgerEntry final : public STObject, public CountedObject<STLedgerEntry>
|
|||||||
public:
|
public:
|
||||||
using pointer = std::shared_ptr<STLedgerEntry>;
|
using pointer = std::shared_ptr<STLedgerEntry>;
|
||||||
using ref = const 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. */
|
/** Create an empty object with the given key and type. */
|
||||||
explicit STLedgerEntry(Keylet const& k);
|
explicit STLedgerEntry(Keylet const& k);
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
|
|||||||
{sfFirstNFTokenSequence, soeOPTIONAL},
|
{sfFirstNFTokenSequence, soeOPTIONAL},
|
||||||
{sfAMMID, soeOPTIONAL}, // pseudo-account designator
|
{sfAMMID, soeOPTIONAL}, // pseudo-account designator
|
||||||
{sfVaultID, soeOPTIONAL}, // pseudo-account designator
|
{sfVaultID, soeOPTIONAL}, // pseudo-account designator
|
||||||
|
{sfLoanBrokerID, soeOPTIONAL}, // pseudo-account designator
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** A ledger object which contains a list of object identifiers.
|
/** 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)
|
// 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 EXPAND
|
||||||
#undef LEDGER_ENTRY_DUPLICATE
|
#undef LEDGER_ENTRY_DUPLICATE
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,13 @@ TYPED_SFIELD(sfHookEmitCount, UINT16, 18)
|
|||||||
TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19)
|
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(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)
|
// 32-bit integers (common)
|
||||||
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
TYPED_SFIELD(sfNetworkID, UINT32, 1)
|
||||||
@@ -113,6 +120,12 @@ TYPED_SFIELD(sfEmitGeneration, UINT32, 46)
|
|||||||
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
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)
|
// 64-bit integers (common)
|
||||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
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(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SField::sMD_Default)
|
||||||
TYPED_SFIELD(sfIssuerNode, UINT64, 27)
|
TYPED_SFIELD(sfIssuerNode, UINT64, 27)
|
||||||
TYPED_SFIELD(sfSubjectNode, UINT64, 28)
|
TYPED_SFIELD(sfSubjectNode, UINT64, 28)
|
||||||
|
TYPED_SFIELD(sfVaultNode, UINT64, 29)
|
||||||
|
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 30)
|
||||||
|
|
||||||
// 128-bit
|
// 128-bit
|
||||||
TYPED_SFIELD(sfEmailHash, UINT128, 1)
|
TYPED_SFIELD(sfEmailHash, UINT128, 1)
|
||||||
@@ -194,6 +209,7 @@ TYPED_SFIELD(sfHookNamespace, UINT256, 32)
|
|||||||
TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
|
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, 35)
|
||||||
|
|
||||||
// number (common)
|
// number (common)
|
||||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||||
@@ -201,6 +217,15 @@ TYPED_SFIELD(sfAssetsAvailable, NUMBER, 2)
|
|||||||
TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3)
|
TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3)
|
||||||
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4)
|
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4)
|
||||||
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5)
|
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)
|
// currency amount (common)
|
||||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||||
@@ -295,6 +320,7 @@ TYPED_SFIELD(sfAttestationRewardAccount, ACCOUNT, 21)
|
|||||||
TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22)
|
TYPED_SFIELD(sfLockingChainDoor, ACCOUNT, 22)
|
||||||
TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
|
TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
|
||||||
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
|
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
|
||||||
|
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
|
||||||
|
|
||||||
// vector of 256-bit
|
// vector of 256-bit
|
||||||
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never)
|
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::sMD_Never)
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ enum class LedgerNameSpace : std::uint16_t {
|
|||||||
CREDENTIAL = 'D',
|
CREDENTIAL = 'D',
|
||||||
PERMISSIONED_DOMAIN = 'm',
|
PERMISSIONED_DOMAIN = 'm',
|
||||||
VAULT = 'V',
|
VAULT = 'V',
|
||||||
|
LOAN_BROKER = 'l', // lower-case L
|
||||||
|
LOAN = 'L',
|
||||||
|
|
||||||
// No longer used or supported. Left here to reserve the space
|
// No longer used or supported. Left here to reserve the space
|
||||||
// to avoid accidental reuse.
|
// to avoid accidental reuse.
|
||||||
@@ -550,6 +552,18 @@ vault(AccountID const& owner, std::uint32_t seq) noexcept
|
|||||||
return vault(indexHash(LedgerNameSpace::VAULT, owner, seq));
|
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
|
Keylet
|
||||||
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept
|
permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1578,4 +1578,101 @@ ValidPermissionedDomain::finalize(
|
|||||||
(sleStatus_[1] ? check(*sleStatus_[1], j) : true);
|
(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
|
} // namespace ripple
|
||||||
|
|||||||
@@ -618,6 +618,35 @@ public:
|
|||||||
beast::Journal const&);
|
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
|
// additional invariant checks can be declared above and then added to this
|
||||||
// tuple
|
// tuple
|
||||||
using InvariantChecks = std::tuple<
|
using InvariantChecks = std::tuple<
|
||||||
@@ -637,7 +666,8 @@ using InvariantChecks = std::tuple<
|
|||||||
NFTokenCountTracking,
|
NFTokenCountTracking,
|
||||||
ValidClawback,
|
ValidClawback,
|
||||||
ValidMPTIssuance,
|
ValidMPTIssuance,
|
||||||
ValidPermissionedDomain>;
|
ValidPermissionedDomain,
|
||||||
|
NoModifiedUnmodifiableFields>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief get a tuple of all invariant checks
|
* @brief get a tuple of all invariant checks
|
||||||
|
|||||||
Reference in New Issue
Block a user