Compare commits

...

5 Commits

Author SHA1 Message Date
Vito
a43f5abede fixes returned error code 2026-02-10 11:44:10 +01:00
Vito
c5d7ebe93d restores missing linebreak 2026-02-05 10:24:14 +01:00
Ed Hennis
d0b5ca9dab Merge branch 'develop' into tapanito/lending-fix-amendment 2026-02-04 18:21:55 -04:00
Vito
5e51893e9b fixes a typo 2026-02-04 11:31:58 +01:00
Vito
3422c11d02 adds lending v1.1 fix amendment 2026-02-04 11:30:41 +01:00
14 changed files with 58 additions and 42 deletions

View File

@@ -16,6 +16,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FIX (LendingProtocolV1_1, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -1701,10 +1701,21 @@ class LoanBroker_test : public beast::unit_test::suite
testRIPD4274MPT();
}
void
testFixAmendmentEnabled()
{
using namespace jtx;
testcase("testFixAmendmentEnabled");
Env env{*this};
BEAST_EXPECT(env.enabled(fixLendingProtocolV1_1));
}
public:
void
run() override
{
testFixAmendmentEnabled();
testLoanBrokerSetDebtMaximum();
testLoanBrokerCoverDepositNullVault();

View File

@@ -11,6 +11,9 @@ struct PreflightContext;
bool
checkLendingProtocolDependencies(PreflightContext const& ctx);
NotTEC
temInvalidToMalformed(PreflightContext const& ctx);
static constexpr std::uint32_t secondsInYear = 365 * 24 * 60 * 60;
Number

View File

@@ -10,6 +10,12 @@ checkLendingProtocolDependencies(PreflightContext const& ctx)
return ctx.rules.enabled(featureSingleAssetVault) && VaultCreate::checkExtraFeatures(ctx);
}
NotTEC
temInvalidToMalformed(PreflightContext const& ctx)
{
return ctx.rules.enabled(fixLendingProtocolV1_1) ? temMALFORMED : temINVALID;
}
LoanPaymentParts&
LoanPaymentParts::operator+=(LoanPaymentParts const& other)
{

View File

@@ -33,14 +33,9 @@ DIDSet::preflight(PreflightContext const& ctx)
ctx.tx[sfDIDDocument].empty() && ctx.tx.isFieldPresent(sfData) && ctx.tx[sfData].empty())
return temEMPTY_DID;
auto isTooLong = [&](auto const& sField, std::size_t length) -> bool {
if (auto field = ctx.tx[~sField])
return field->length() > length;
return false;
};
if (isTooLong(sfURI, maxDIDURILength) || isTooLong(sfDIDDocument, maxDIDDocumentLength) ||
isTooLong(sfData, maxDIDAttestationLength))
if (!validDataLength(ctx.tx[~sfURI], maxDIDURILength) ||
!validDataLength(ctx.tx[~sfDIDDocument], maxDIDDocumentLength) ||
!validDataLength(ctx.tx[~sfData], maxDIDAttestationLength))
return temMALFORMED;
return tesSUCCESS;

View File

@@ -19,10 +19,10 @@ LoanBrokerCoverClawback::preflight(PreflightContext const& ctx)
auto const amount = ctx.tx[~sfAmount];
if (!brokerID && !amount)
return temINVALID;
return temInvalidToMalformed(ctx);
if (brokerID && *brokerID == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
if (amount)
{
@@ -41,7 +41,7 @@ LoanBrokerCoverClawback::preflight(PreflightContext const& ctx)
if (!brokerID)
{
if (amount->holds<MPTIssue>())
return temINVALID;
return temInvalidToMalformed(ctx);
auto const account = ctx.tx[sfAccount];
// Since we don't have a LoanBrokerID, holder _should_ be the loan
@@ -49,7 +49,7 @@ LoanBrokerCoverClawback::preflight(PreflightContext const& ctx)
// use a generic placeholder name.
auto const holder = amount->getIssuer();
if (holder == account || holder == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
}
}

View File

@@ -16,7 +16,7 @@ NotTEC
LoanBrokerCoverDeposit::preflight(PreflightContext const& ctx)
{
if (ctx.tx[sfLoanBrokerID] == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
auto const dstAmount = ctx.tx[sfAmount];
if (dstAmount <= beast::zero)

View File

@@ -18,7 +18,7 @@ NotTEC
LoanBrokerCoverWithdraw::preflight(PreflightContext const& ctx)
{
if (ctx.tx[sfLoanBrokerID] == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
auto const dstAmount = ctx.tx[sfAmount];
if (dstAmount <= beast::zero)

View File

@@ -16,7 +16,7 @@ NotTEC
LoanBrokerDelete::preflight(PreflightContext const& ctx)
{
if (ctx.tx[sfLoanBrokerID] == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
return tesSUCCESS;
}

View File

@@ -18,16 +18,16 @@ LoanBrokerSet::preflight(PreflightContext const& ctx)
using namespace Lending;
auto const& tx = ctx.tx;
if (auto const data = tx[~sfData]; data && !data->empty() && !validDataLength(tx[~sfData], maxDataPayloadLength))
return temINVALID;
if (auto const data = tx[~sfData]; data && !validDataLength(tx[~sfData], maxDataPayloadLength))
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfManagementFeeRate], maxManagementFeeRate))
return temINVALID;
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfCoverRateMinimum], maxCoverRate))
return temINVALID;
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfCoverRateLiquidation], maxCoverRate))
return temINVALID;
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfDebtMaximum], Number(maxMPTokenAmount), Number(0)))
return temINVALID;
return temInvalidToMalformed(ctx);
if (tx.isFieldPresent(sfLoanBrokerID))
{
@@ -35,16 +35,16 @@ LoanBrokerSet::preflight(PreflightContext const& ctx)
// LoanBroker Object
if (tx.isFieldPresent(sfManagementFeeRate) || tx.isFieldPresent(sfCoverRateMinimum) ||
tx.isFieldPresent(sfCoverRateLiquidation))
return temINVALID;
return temInvalidToMalformed(ctx);
if (tx[sfLoanBrokerID] == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
}
if (auto const vaultID = tx.at(~sfVaultID))
{
if (*vaultID == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
}
{
@@ -53,7 +53,7 @@ LoanBrokerSet::preflight(PreflightContext const& ctx)
// Both must be zero or non-zero.
if (minimumZero != liquidationZero)
{
return temINVALID;
return temInvalidToMalformed(ctx);
}
}

View File

@@ -16,7 +16,7 @@ NotTEC
LoanDelete::preflight(PreflightContext const& ctx)
{
if (ctx.tx[sfLoanID] == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
return tesSUCCESS;
}

View File

@@ -23,7 +23,7 @@ NotTEC
LoanManage::preflight(PreflightContext const& ctx)
{
if (ctx.tx[sfLoanID] == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
// Flags are mutually exclusive
if (auto const flagField = ctx.tx[~sfFlags]; flagField && *flagField)

View File

@@ -28,7 +28,7 @@ NotTEC
LoanPay::preflight(PreflightContext const& ctx)
{
if (ctx.tx[sfLoanID] == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
if (ctx.tx[sfAmount] <= beast::zero)
return temBAD_AMOUNT;

View File

@@ -53,40 +53,40 @@ LoanSet::preflight(PreflightContext const& ctx)
return ret;
}
if (auto const data = tx[~sfData]; data && !data->empty() && !validDataLength(tx[~sfData], maxDataPayloadLength))
return temINVALID;
if (auto const data = tx[~sfData]; data && !validDataLength(tx[~sfData], maxDataPayloadLength))
return temInvalidToMalformed(ctx);
for (auto const& field : {&sfLoanServiceFee, &sfLatePaymentFee, &sfClosePaymentFee})
{
if (!validNumericMinimum(tx[~*field]))
return temINVALID;
return temInvalidToMalformed(ctx);
}
// Principal Requested is required
if (auto const p = tx[sfPrincipalRequested]; p <= 0)
return temINVALID;
return temInvalidToMalformed(ctx);
else if (!validNumericRange(tx[~sfLoanOriginationFee], p))
return temINVALID;
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfInterestRate], maxInterestRate))
return temINVALID;
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfOverpaymentFee], maxOverpaymentFee))
return temINVALID;
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfLateInterestRate], maxLateInterestRate))
return temINVALID;
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfCloseInterestRate], maxCloseInterestRate))
return temINVALID;
return temInvalidToMalformed(ctx);
if (!validNumericRange(tx[~sfOverpaymentInterestRate], maxOverpaymentInterestRate))
return temINVALID;
return temInvalidToMalformed(ctx);
if (auto const paymentTotal = tx[~sfPaymentTotal]; paymentTotal && *paymentTotal <= 0)
return temINVALID;
return temInvalidToMalformed(ctx);
if (auto const paymentInterval = tx[~sfPaymentInterval];
!validNumericMinimum(paymentInterval, LoanSet::minPaymentInterval))
return temINVALID;
return temInvalidToMalformed(ctx);
// Grace period is between min default value and payment interval
else if (auto const gracePeriod = tx[~sfGracePeriod]; //
!validNumericRange(
gracePeriod, paymentInterval.value_or(LoanSet::defaultPaymentInterval), defaultGracePeriod))
return temINVALID;
return temInvalidToMalformed(ctx);
// Copied from preflight2
if (counterPartySig)
@@ -96,7 +96,7 @@ LoanSet::preflight(PreflightContext const& ctx)
}
if (auto const brokerID = ctx.tx[~sfLoanBrokerID]; brokerID && *brokerID == beast::zero)
return temINVALID;
return temInvalidToMalformed(ctx);
return tesSUCCESS;
}