mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-25 08:00:22 +00:00
Address PR comments
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
This commit is contained in:
@@ -57,6 +57,32 @@ public:
|
||||
{
|
||||
return this->tx_->at(sfVaultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfMemoData (soeOPTIONAL)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_VL::type::value_type>
|
||||
getMemoData() const
|
||||
{
|
||||
if (hasMemoData())
|
||||
{
|
||||
return this->tx_->at(sfMemoData);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfMemoData is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasMemoData() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfMemoData);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -112,6 +138,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfMemoData (soeOPTIONAL)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
VaultDeleteBuilder&
|
||||
setMemoData(std::decay_t<typename SF_VL::type::value_type> const& value)
|
||||
{
|
||||
object_[sfMemoData] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the VaultDelete wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -140,40 +140,39 @@ LoanManage::defaultLoan(
|
||||
|
||||
// Apply the First-Loss Capital to the Default Amount
|
||||
TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
|
||||
TenthBips32 const coverRateLiquidation{brokerSle->at(sfCoverRateLiquidation)};
|
||||
auto const coverRateLiquidation{brokerSle->at(~sfCoverRateLiquidation)};
|
||||
|
||||
auto const covered = [&]() {
|
||||
auto const defaultCovered = [&]() {
|
||||
// Always round the minimum required up.
|
||||
NumberRoundModeGuard mg(Number::upward);
|
||||
Number covered;
|
||||
|
||||
if (view.rules().enabled(fixLendingProtocolV1_1))
|
||||
if (view.rules().enabled(featureLendingProtocolV1_1) && !coverRateLiquidation)
|
||||
{
|
||||
// New formula: DefaultCovered = min(DefaultAmount × CoverRateMinimum, CoverAvailable)
|
||||
// Round the liquidation amount up
|
||||
return roundToAsset(vaultAsset, tenthBipsOfValue(totalDefaultAmount, coverRateMinimum), loanScale);
|
||||
covered = roundToAsset(
|
||||
vaultAsset, tenthBipsOfValue(totalDefaultAmount, coverRateMinimum), loanScale);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const minimumCover = tenthBipsOfValue(brokerDebtTotalProxy.value(), coverRateMinimum);
|
||||
auto const minimumCover =
|
||||
tenthBipsOfValue(brokerDebtTotalProxy.value(), coverRateMinimum);
|
||||
// Round the liquidation amount up
|
||||
return roundToAsset(
|
||||
covered = roundToAsset(
|
||||
vaultAsset,
|
||||
/*
|
||||
* This formula is from the XLS-66 spec, section 3.2.3.2 (State
|
||||
* Changes), specifically "if the `tfLoanDefault` flag is set" /
|
||||
* "Apply the First-Loss Capital to the Default Amount"
|
||||
*/
|
||||
std::min(tenthBipsOfValue(minimumCover, coverRateLiquidation), totalDefaultAmount),
|
||||
std::min(
|
||||
tenthBipsOfValue(minimumCover, TenthBips32{coverRateLiquidation.value_or(0)}),
|
||||
totalDefaultAmount),
|
||||
loanScale);
|
||||
}
|
||||
}();
|
||||
|
||||
auto const defaultCovered = [&]() {
|
||||
// Always round the minimum required up.
|
||||
NumberRoundModeGuard mg(Number::upward);
|
||||
|
||||
auto const coverAvailable = *brokerSle->at(sfCoverAvailable);
|
||||
|
||||
return std::min(covered, coverAvailable);
|
||||
}();
|
||||
|
||||
|
||||
@@ -262,8 +262,7 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
if (!ctx.rules.enabled(fixLendingProtocolV1_1) &&
|
||||
std::any_of(
|
||||
if (std::any_of(
|
||||
disabledTxTypes.begin(), disabledTxTypes.end(), [txType](auto const& disabled) {
|
||||
return txType == disabled;
|
||||
}))
|
||||
|
||||
@@ -2600,18 +2600,16 @@ class Batch_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("loan");
|
||||
|
||||
bool const lendingBatchEnabled = !std::any_of(
|
||||
Batch::disabledTxTypes.begin(), Batch::disabledTxTypes.end(), [](auto const& disabled) {
|
||||
return disabled == ttLOAN_BROKER_SET;
|
||||
});
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
test::jtx::Env env{
|
||||
*this, features | featureSingleAssetVault | featureLendingProtocol | featureMPTokensV1};
|
||||
|
||||
bool const lendingBatchEnabled =
|
||||
!std::any_of(
|
||||
Batch::disabledTxTypes.begin(),
|
||||
Batch::disabledTxTypes.end(),
|
||||
[](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; }) ||
|
||||
env.enabled(fixLendingProtocolV1_1);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
// For simplicity, lender will be the sole actor for the vault &
|
||||
// brokers.
|
||||
|
||||
@@ -1946,7 +1946,7 @@ protected:
|
||||
NumberRoundModeGuard mg(Number::upward);
|
||||
auto const totalDefaultAmount = state.totalValue - state.managementFeeOutstanding;
|
||||
auto const defaultAmount = [&] {
|
||||
if (env.enabled(fixLendingProtocolV1_1))
|
||||
if (env.enabled(featureLendingProtocolV1_1))
|
||||
{
|
||||
// DefaultCovered = min(DefaultAmount × CoverRateMinimum, CoverAvailable)
|
||||
return roundToAsset(
|
||||
@@ -1957,13 +1957,14 @@ protected:
|
||||
else
|
||||
{
|
||||
// From XLS-66 spec, section 3.2.3.2:
|
||||
// DefaultCovered = min(DebtTotal × CoverRateMinimum × CoverRateLiquidation, DefaultAmount,
|
||||
// CoverAvailable)
|
||||
// DefaultCovered = min(DebtTotal × CoverRateMinimum × CoverRateLiquidation,
|
||||
// DefaultAmount, CoverAvailable)
|
||||
return roundToAsset(
|
||||
broker.asset,
|
||||
std::min(
|
||||
tenthBipsOfValue(
|
||||
tenthBipsOfValue(brokerSle->at(sfDebtTotal), broker.params.coverRateMin),
|
||||
tenthBipsOfValue(
|
||||
brokerSle->at(sfDebtTotal), broker.params.coverRateMin),
|
||||
broker.params.coverRateLiquidation),
|
||||
state.totalValue - state.managementFeeOutstanding),
|
||||
state.loanScale);
|
||||
@@ -3603,23 +3604,15 @@ protected:
|
||||
// From FIND-001
|
||||
testcase << "Batch Bypass Counterparty";
|
||||
|
||||
bool const lendingBatchEnabled = !std::any_of(
|
||||
Batch::disabledTxTypes.begin(), Batch::disabledTxTypes.end(), [](auto const& disabled) {
|
||||
return disabled == ttLOAN_BROKER_SET;
|
||||
});
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
Env env(*this, all);
|
||||
|
||||
bool const lendingBatchEnabled =
|
||||
!std::any_of(
|
||||
Batch::disabledTxTypes.begin(),
|
||||
Batch::disabledTxTypes.end(),
|
||||
[](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; }) ||
|
||||
env.enabled(fixLendingProtocolV1_1);
|
||||
|
||||
if (!lendingBatchEnabled)
|
||||
{
|
||||
pass();
|
||||
return;
|
||||
}
|
||||
|
||||
Account const lender{"lender"};
|
||||
Account const borrower{"borrower"};
|
||||
|
||||
@@ -6986,7 +6979,7 @@ protected:
|
||||
auto const afterFirstDebtTotal = brokerSle->at(sfDebtTotal);
|
||||
auto const afterFirstCoverAvailable = brokerSle->at(sfCoverAvailable);
|
||||
|
||||
if (env.enabled(fixLendingProtocolV1_1))
|
||||
if (env.enabled(featureLendingProtocolV1_1))
|
||||
{
|
||||
// Proportional default cover
|
||||
// Loan 1 Defaults: 20% of Loan A (50,134) = 10,027 seizure
|
||||
@@ -7023,7 +7016,7 @@ protected:
|
||||
// Both scenarios: DebtTotal should be 0 after both loans default
|
||||
BEAST_EXPECT(afterSecondDebtTotal == 0);
|
||||
|
||||
if (env.enabled(fixLendingProtocolV1_1))
|
||||
if (env.enabled(featureLendingProtocolV1_1))
|
||||
{
|
||||
// Proportional default cover
|
||||
// Loan 2 Defaults: 20% of Loan B (50,134) = 10,027 seizure
|
||||
@@ -7226,7 +7219,7 @@ public:
|
||||
testLoanPayBrokerOwnerUnauthorizedMPT();
|
||||
testLoanPayBrokerOwnerNoPermissionedDomainMPT();
|
||||
testLoanSetBrokerOwnerNoPermissionedDomainMPT();
|
||||
testSequentialFLCDepletion(all - fixLendingProtocolV1_1);
|
||||
testSequentialFLCDepletion(all - featureLendingProtocolV1_1);
|
||||
testSequentialFLCDepletion(all);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
auto const memoDataValue = canonical_VL();
|
||||
|
||||
VaultDeleteBuilder builder{
|
||||
accountValue,
|
||||
@@ -39,6 +40,7 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
};
|
||||
|
||||
// Set optional fields
|
||||
builder.setMemoData(memoDataValue);
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
@@ -62,6 +64,14 @@ TEST(TransactionsVaultDeleteTests, BuilderSettersRoundTrip)
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = memoDataValue;
|
||||
auto const actualOpt = tx.getMemoData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMemoData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfMemoData");
|
||||
EXPECT_TRUE(tx.hasMemoData());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper,
|
||||
@@ -79,6 +89,7 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
|
||||
// Transaction-specific field values
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
auto const memoDataValue = canonical_VL();
|
||||
|
||||
// Build an initial transaction
|
||||
VaultDeleteBuilder initialBuilder{
|
||||
@@ -88,6 +99,7 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
feeValue
|
||||
};
|
||||
|
||||
initialBuilder.setMemoData(memoDataValue);
|
||||
|
||||
auto initialTx = initialBuilder.build(publicKey, secretKey);
|
||||
|
||||
@@ -112,6 +124,13 @@ TEST(TransactionsVaultDeleteTests, BuilderFromStTxRoundTrip)
|
||||
}
|
||||
|
||||
// Verify optional fields
|
||||
{
|
||||
auto const& expected = memoDataValue;
|
||||
auto const actualOpt = rebuiltTx.getMemoData();
|
||||
ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfMemoData should be present";
|
||||
expectEqualField(expected, *actualOpt, "sfMemoData");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 3) Verify wrapper throws when constructed from wrong transaction type.
|
||||
@@ -142,5 +161,35 @@ TEST(TransactionsVaultDeleteTests, BuilderThrowsOnWrongTxType)
|
||||
EXPECT_THROW(VaultDeleteBuilder{wrongTx.getSTTx()}, std::runtime_error);
|
||||
}
|
||||
|
||||
// 5) Build with only required fields and verify optional fields return nullopt.
|
||||
TEST(TransactionsVaultDeleteTests, OptionalFieldsReturnNullopt)
|
||||
{
|
||||
// Generate a deterministic keypair for signing
|
||||
auto const [publicKey, secretKey] =
|
||||
generateKeyPair(KeyType::secp256k1, generateSeed("testVaultDeleteNullopt"));
|
||||
|
||||
// Common transaction fields
|
||||
auto const accountValue = calcAccountID(publicKey);
|
||||
std::uint32_t const sequenceValue = 3;
|
||||
auto const feeValue = canonical_AMOUNT();
|
||||
|
||||
// Transaction-specific required field values
|
||||
auto const vaultIDValue = canonical_UINT256();
|
||||
|
||||
VaultDeleteBuilder builder{
|
||||
accountValue,
|
||||
vaultIDValue,
|
||||
sequenceValue,
|
||||
feeValue
|
||||
};
|
||||
|
||||
// Do NOT set optional fields
|
||||
|
||||
auto tx = builder.build(publicKey, secretKey);
|
||||
|
||||
// Verify optional fields are not present
|
||||
EXPECT_FALSE(tx.hasMemoData());
|
||||
EXPECT_FALSE(tx.getMemoData().has_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user