Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3422c11
adds lending v1.1 fix amendment
Tapanito Feb 4, 2026
5e51893
fixes a typo
Tapanito Feb 4, 2026
d0b5ca9
Merge branch 'develop' into tapanito/lending-fix-amendment
ximinez Feb 4, 2026
c5d7ebe
restores missing linebreak
Tapanito Feb 5, 2026
6674500
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Feb 10, 2026
ba60306
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Feb 11, 2026
1671472
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Feb 12, 2026
74c968d
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Feb 17, 2026
106bf48
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Feb 18, 2026
7459fe4
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Feb 23, 2026
3c3bd75
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Feb 24, 2026
3477308
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Feb 25, 2026
ba53026
adds sfMemoData field to VaultDelete transaction (#6356)
Tapanito Feb 26, 2026
e159d27
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Mar 3, 2026
b322097
fixes formatting errors
Tapanito Mar 3, 2026
feba605
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Mar 3, 2026
ed4330a
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Mar 4, 2026
4067e50
Add rounding to Vault invariants (#6217)
Tapanito Mar 5, 2026
d2f23b2
Merge branch 'develop' into tapanito/lending-fix-amendment
Tapanito Mar 5, 2026
a67da5c
Merge remote-tracking branch 'origin/develop' into tapanito/lending-f…
Tapanito Mar 9, 2026
eb46d9e
Integrate Permissioned Domain & Credential Checks for Lending Protocol
a1q123456 Mar 10, 2026
85ede3e
Add unit tests for invariant checks
a1q123456 Mar 23, 2026
afa94e5
Apply suggestion from @Tapanito
a1q123456 Mar 23, 2026
d908d20
Apply suggestion from @Tapanito
a1q123456 Mar 23, 2026
ee61591
Apply suggestion from @Tapanito
a1q123456 Mar 23, 2026
54da6c1
Apply suggestions from code review
a1q123456 Mar 23, 2026
e708999
Address PR comments
a1q123456 Mar 23, 2026
ef3e625
Address PR comments
a1q123456 Mar 23, 2026
2ad2175
Merge remote-tracking branch 'origin/develop' into a1q123456/adding-p…
a1q123456 Jun 5, 2026
7491a23
Address PR comments
a1q123456 Jun 5, 2026
b766041
Fix clang-tidy error
a1q123456 Jun 6, 2026
710dd81
Update the feature flag
a1q123456 Jun 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion include/xrpl/protocol/LedgerFormats.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ enum LedgerEntryType : std::uint16_t {
LEDGER_OBJECT(Loan, \
LSF_FLAG(lsfLoanDefault, 0x00010000) \
LSF_FLAG(lsfLoanImpaired, 0x00020000) \
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \
\
LEDGER_OBJECT(LoanBroker, \
LSF_FLAG(lsfLoanBrokerPrivate, 0x00010000))

// clang-format on

Expand Down
4 changes: 4 additions & 0 deletions include/xrpl/protocol/TxFlags.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfLoanDefault, 0x00010000) \
TF_FLAG(tfLoanImpair, 0x00020000) \
TF_FLAG(tfLoanUnimpair, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(LoanBrokerSet, \
TF_FLAG(tfLoanBrokerPrivate, 0x00010000), \
MASK_ADJ(0))

// clang-format on
Expand Down
1 change: 1 addition & 0 deletions include/xrpl/protocol/detail/features.macro
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.

XRPL_FEATURE(LendingPermissionedDomain, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)
Expand Down
1 change: 1 addition & 0 deletions include/xrpl/protocol/detail/ledger_entries.macro
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ LEDGER_ENTRY(ltLOAN_BROKER, 0x0088, LoanBroker, loan_broker, ({
{sfCoverAvailable, SoeDefault},
{sfCoverRateMinimum, SoeDefault},
{sfCoverRateLiquidation, SoeDefault},
{sfDomainID, SoeOptional},
}))

/** A ledger object representing a loan between a Borrower and a Loan Broker
Expand Down
1 change: 1 addition & 0 deletions include/xrpl/protocol/detail/transactions.macro
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
{sfDebtMaximum, SoeOptional},
{sfCoverRateMinimum, SoeOptional},
{sfCoverRateLiquidation, SoeOptional},
{sfDomainID, SoeOptional},
}))

/** This transaction deletes a Loan Broker */
Expand Down
35 changes: 35 additions & 0 deletions include/xrpl/protocol_autogen/ledger_entries/LoanBroker.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,30 @@ class LoanBroker : public LedgerEntryBase
{
return this->sle_->isFieldPresent(sfCoverRateLiquidation);
}

/**
* @brief Get sfDomainID (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getDomainID() const
{
if (hasDomainID())
return this->sle_->at(sfDomainID);
return std::nullopt;
}

/**
* @brief Check if sfDomainID is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDomainID() const
{
return this->sle_->isFieldPresent(sfDomainID);
}
};

/**
Expand Down Expand Up @@ -576,6 +600,17 @@ class LoanBrokerBuilder : public LedgerEntryBuilderBase<LoanBrokerBuilder>
return *this;
}

/**
* @brief Set sfDomainID (SoeOptional)
* @return Reference to this builder for method chaining.
*/
LoanBrokerBuilder&
setDomainID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfDomainID] = value;
return *this;
}

/**
* @brief Build and return the completed LoanBroker wrapper.
* @param index The ledger entry index.
Expand Down
37 changes: 37 additions & 0 deletions include/xrpl/protocol_autogen/transactions/LoanBrokerSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,32 @@ class LoanBrokerSet : public TransactionBase
{
return this->tx_->isFieldPresent(sfCoverRateLiquidation);
}

/**
* @brief Get sfDomainID (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getDomainID() const
{
if (hasDomainID())
{
return this->tx_->at(sfDomainID);
}
return std::nullopt;
}

/**
* @brief Check if sfDomainID is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasDomainID() const
{
return this->tx_->isFieldPresent(sfDomainID);
}
};

/**
Expand Down Expand Up @@ -334,6 +360,17 @@ class LoanBrokerSetBuilder : public TransactionBuilderBase<LoanBrokerSetBuilder>
return *this;
}

/**
* @brief Set sfDomainID (SoeOptional)
* @return Reference to this builder for method chaining.
*/
LoanBrokerSetBuilder&
setDomainID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfDomainID] = value;
return *this;
}

/**
* @brief Build and return the LoanBrokerSet wrapper.
* @param publicKey The public key for signing.
Expand Down
3 changes: 3 additions & 0 deletions include/xrpl/tx/transactors/lending/LoanBrokerSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class LoanBrokerSet : public Transactor
static std::vector<OptionaledField<STNumber>> const&
getValueFields();

static std::uint32_t
getFlagsMask(PreflightContext const& ctx);

static TER
preclaim(PreclaimContext const& ctx);

Expand Down
3 changes: 2 additions & 1 deletion src/libxrpl/tx/invariants/InvariantCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,8 @@ NoModifiedUnmodifiableFields::finalize(
kFieldChanged(before, after, sfOwner) ||
kFieldChanged(before, after, sfManagementFeeRate) ||
kFieldChanged(before, after, sfCoverRateMinimum) ||
kFieldChanged(before, after, sfCoverRateLiquidation);
kFieldChanged(before, after, sfCoverRateLiquidation) ||
kFieldChanged(before, after, sfFlags);
break;
case ltLOAN:
/*
Expand Down
9 changes: 9 additions & 0 deletions src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@ ValidLoanBroker::finalize(

auto const& before = broker.brokerBefore;

if (view.rules().enabled(featureLendingPermissionedDomain))
{
if (after->at(~sfDomainID) && !after->isFlag(lsfLoanBrokerPrivate))
{
JLOG(j.fatal()) << "Invariant failed: DomainID is set on public Loan Broker";
return false;
}
}

// https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants
// If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most
// one node (the root), which will only hold entries for `RippleState`
Expand Down
100 changes: 100 additions & 0 deletions src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/STTakesAsset.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>

#include <cstdint>
#include <memory>
#include <vector>

Expand Down Expand Up @@ -49,8 +53,17 @@ LoanBrokerSet::preflight(PreflightContext const& ctx)
if (!validNumericRange(tx[~sfDebtMaximum], Number(kMaxMpTokenAmount), Number(0)))
return temINVALID;

if (!ctx.rules.enabled(featureLendingPermissionedDomain))
{
if (tx.isFlag(tfLoanBrokerPrivate) || tx.isFieldPresent(sfDomainID))
{
return temDISABLED;
}
}

if (tx.isFieldPresent(sfLoanBrokerID))
{
// We're modifying an existing LoanBroker.
// Fixed fields can not be specified if we're modifying an existing
// LoanBroker Object
if (tx.isFieldPresent(sfManagementFeeRate) || tx.isFieldPresent(sfCoverRateMinimum) ||
Expand All @@ -59,6 +72,36 @@ LoanBrokerSet::preflight(PreflightContext const& ctx)

if (tx[sfLoanBrokerID] == beast::kZero)
return temINVALID;

if (ctx.rules.enabled(featureLendingPermissionedDomain))
{
// Cannot change private flag on existing broker
if (tx.isFlag(tfLoanBrokerPrivate))
{
return temINVALID;
}
Comment thread
a1q123456 marked this conversation as resolved.
}
}
else
{
// We're creating a new LoanBroker.
if (ctx.rules.enabled(featureLendingPermissionedDomain))
{
auto const domainID = tx.at(~sfDomainID);
if (domainID)
Comment thread
a1q123456 marked this conversation as resolved.
{
if (*domainID == beast::kZero)
{
// DomainID must not be zero if provided
return temMALFORMED;
}
if (!tx.isFlag(tfLoanBrokerPrivate))
{
// Public brokers cannot have a DomainID
return temINVALID;
}
}
Comment thread
a1q123456 marked this conversation as resolved.
}
}

if (auto const vaultID = tx.at(~sfVaultID))
Expand Down Expand Up @@ -88,6 +131,16 @@ LoanBrokerSet::getValueFields()
return kValueFields;
}

std::uint32_t
LoanBrokerSet::getFlagsMask(PreflightContext const& ctx)
{
if (ctx.rules.enabled(fixCleanup3_2_0))
{
return tfLoanBrokerSetMask;
}
return tfUniversalMask;
}

TER
LoanBrokerSet::preclaim(PreclaimContext const& ctx)
{
Expand All @@ -110,6 +163,20 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx)
return tecNO_PERMISSION;
Comment thread
a1q123456 marked this conversation as resolved.
}

if (ctx.view.rules().enabled(featureLendingPermissionedDomain))
{
auto const domainID = tx[~sfDomainID];
if (domainID && *domainID != beast::kZero)
{
auto const sleDomain = ctx.view.read(keylet::permissionedDomain(*domainID));
if (!sleDomain)
{
JLOG(ctx.j.warn()) << "Domain does not exist.";
return tecOBJECT_NOT_FOUND;
}
}
}

if (auto const brokerID = tx[~sfLoanBrokerID])
{
// Updating an existing Broker
Expand Down Expand Up @@ -141,6 +208,13 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx)
return tecLIMIT_EXCEEDED;
}
}

if (ctx.view.rules().enabled(featureLendingPermissionedDomain))
{
auto const domainID = tx[~sfDomainID];
if (!sleBroker->isFlag(lsfLoanBrokerPrivate) && domainID)
return tecNO_PERMISSION;
}
}
else
{
Expand Down Expand Up @@ -199,6 +273,22 @@ LoanBrokerSet::doApply()
if (auto const debtMax = tx[~sfDebtMaximum])
broker->at(sfDebtMaximum) = *debtMax;

if (ctx_.view().rules().enabled(featureLendingPermissionedDomain) &&
broker->isFlag(lsfLoanBrokerPrivate))
{
if (auto const domainID = tx[~sfDomainID])
{
if (*domainID != beast::kZero)
{
broker->setFieldH256(sfDomainID, *domainID);
}
else if (broker->isFieldPresent(sfDomainID))
{
broker->makeFieldAbsent(sfDomainID);
}
}
}

view.update(broker);

associateAsset(*broker, vaultAsset);
Expand Down Expand Up @@ -270,6 +360,16 @@ LoanBrokerSet::doApply()
if (auto const coverLiq = tx[~sfCoverRateLiquidation])
broker->at(sfCoverRateLiquidation) = *coverLiq;

if (ctx_.view().rules().enabled(featureLendingPermissionedDomain))
{
if (tx.isFlag(tfLoanBrokerPrivate))
{
broker->setFlag(lsfLoanBrokerPrivate);
if (auto domainID = tx[~sfDomainID])
broker->setFieldH256(sfDomainID, *domainID);
Comment thread
a1q123456 marked this conversation as resolved.
}
}

view.insert(broker);

associateAsset(*broker, vaultAsset);
Expand Down
Loading
Loading