Compare commits

...

10 Commits

Author SHA1 Message Date
Vito
a2198146a8 adds BLockDeposit flagsto VaultSet 2026-02-12 17:42:27 +01:00
Vito
c808c46049 adds flag definitions 2026-02-12 15:27:19 +01:00
Vito
167147281c Merge branch 'develop' into tapanito/lending-fix-amendment 2026-02-12 15:22:30 +01:00
Pratik Mankawde
11e8d1f8a2 chore: Fix gcov lib coverage build failure on macOS (#6350)
For coverage builds, we try to link against the `gcov` library (specific to the environment). But as macOS doesn't have this library and thus doesn't have the coverage tools to generate reports, the coverage builds on that platform were failing on linking.

We actually don't need to explicitly force this linking, as the `CodeCoverage` file already has correct detection logic (currently on lines 177-193), which is invoked when the `--coverage` flag is provided:
* AppleClang: Uses `xcrun -f llvm-cov` to set `GCOV_TOOL="llvm-cov gcov"`.
* Clang: Finds `llvm-cov` to set `GCOV_TOOL="llvm-cov gcov"`.
* GCC: Finds `gcov` to set `GCOV_TOOL="gcov"`.
The `GCOV_TOOL` is then passed to `gcovr` on line 416, so the correct tool is used for processing coverage data.

This change therefore removes the `gcov` suffix from lines 473 and 475 in the `CodeCoverage.cmake` file.
2026-02-12 06:11:26 -05:00
Vito Tumas
ba60306610 Merge branch 'develop' into tapanito/lending-fix-amendment 2026-02-11 17:46:20 +01:00
Vito Tumas
6674500896 Merge branch 'develop' into tapanito/lending-fix-amendment 2026-02-10 11:48:23 +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
10 changed files with 177 additions and 8 deletions

View File

@@ -466,11 +466,6 @@ function (add_code_coverage_to_target name scope)
target_compile_options(${name} ${scope} $<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_CXX_COMPILER_FLAGS}>
$<$<COMPILE_LANGUAGE:C>:${COVERAGE_C_COMPILER_FLAGS}>)
target_link_libraries(
${name}
${scope}
$<$<LINK_LANGUAGE:CXX>:${COVERAGE_CXX_LINKER_FLAGS}
gcov>
$<$<LINK_LANGUAGE:C>:${COVERAGE_C_LINKER_FLAGS}
gcov>)
target_link_libraries(${name} ${scope} $<$<LINK_LANGUAGE:CXX>:${COVERAGE_CXX_LINKER_FLAGS}>
$<$<LINK_LANGUAGE:C>:${COVERAGE_C_LINKER_FLAGS}>)
endfunction () # add_code_coverage_to_target

View File

@@ -185,6 +185,7 @@ enum LedgerSpecificFlags {
// ltVAULT
lsfVaultPrivate = 0x00010000,
lsfVaultDepositBlocked = 0x00020000, // True, vault deposit is blocked
// ltLOAN
lsfLoanDefault = 0x00010000,

View File

@@ -270,11 +270,13 @@ constexpr std::uint32_t const tfBatchMask =
// LoanPay: True, indicates any excess in this payment can be used
// as an overpayment. False, no overpayments will be taken.
constexpr std::uint32_t const tfLoanOverpayment = 0x00010000;
// LoanPay exclusive flags:
// tfLoanFullPayment: True, indicates that the payment is an early
// full payment. It must pay the entire loan including close
// interest and fees, or it will fail. False: Not a full payment.
constexpr std::uint32_t const tfLoanFullPayment = 0x00020000;
// tfLoanLatePayment: True, indicates that the payment is late,
// and includes late interest and fees. If the loan is not late,
// it will fail. False: not a late payment. If the current payment
@@ -291,6 +293,11 @@ constexpr std::uint32_t const tfLoanImpair = 0x00020000;
constexpr std::uint32_t const tfLoanUnimpair = 0x00040000;
constexpr std::uint32_t const tfLoanManageMask = ~(tfUniversal | tfLoanDefault | tfLoanImpair | tfLoanUnimpair);
// VaultSet flags:
constexpr std::uint32_t const tfVaultDepositBlock = 0x00010000;
constexpr std::uint32_t const tfVaultDepositUnblock = 0x00020000;
constexpr std::uint32_t const tfVaultSetMask = ~(tfUniversal | tfVaultDepositBlock | tfVaultDepositUnblock);
// clang-format on
} // namespace xrpl

View File

@@ -15,6 +15,8 @@
// 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 (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, 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

@@ -4990,6 +4990,97 @@ class Vault_test : public beast::unit_test::suite
}
}
void
testVaultSetVaultDepositFlagValidation()
{
using namespace test::jtx;
auto const all = testable_amendments();
{
testcase("VaultSet VaultDepositBlock fixLendingProtocolV1_1 disabled");
Env env{*this, all - fixLendingProtocolV1_1};
Account owner{"owner"};
env.fund(XRP(1'000'000), owner);
env.close();
PrettyAsset asset = xrpIssue();
Vault vault{env};
auto const [tx, keylet] = vault.create({.owner = owner, .asset = asset, .flags = tfVaultPrivate});
env(tx, ter(tesSUCCESS), THISLINE);
env.close();
env(vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock}),
ter(temDISABLED),
THISLINE);
env.close();
}
{
std::string const prefix = "VaultSet(VaultDepositBlock): ";
Env env{*this, all | fixLendingProtocolV1_1};
Account owner{"owner"};
env.fund(XRP(1'000'000), owner);
env.close();
PrettyAsset asset = xrpIssue();
Vault vault{env};
auto const [tx, keylet] = vault.create({.owner = owner, .asset = asset, .flags = tfVaultPrivate});
env(tx, ter(tesSUCCESS), THISLINE);
env.close();
{
testcase(prefix + "invalid flags");
env(vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock | tfVaultDepositUnblock}),
ter(temMALFORMED),
THISLINE);
env.close();
}
{
testcase(prefix + "unblock already unblocked vault");
// Cannot unblock an already unblocked vault
env(vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock}),
ter(tecNO_PERMISSION),
THISLINE);
}
{
testcase(prefix + "set and clear vault flag");
env(vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock}),
ter(tesSUCCESS),
THISLINE);
auto sleVault = env.le(keylet);
if (!BEAST_EXPECT(sleVault))
return;
if (!BEAST_EXPECT(sleVault->isFlag(lsfVaultDepositBlocked)))
return;
// Cannot block an already blocked vault
env(vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositBlock}),
ter(tecNO_PERMISSION),
THISLINE);
// Cannot unblock an already unblocked vault
env(vault.set({.owner = owner, .id = keylet.key, .flags = tfVaultDepositUnblock}),
ter(tesSUCCESS),
THISLINE);
sleVault = env.le(keylet);
if (!BEAST_EXPECT(sleVault))
return;
BEAST_EXPECT(!sleVault->isFlag(lsfVaultDepositBlocked));
}
}
}
public:
void
run() override
@@ -5011,6 +5102,7 @@ public:
testVaultClawbackBurnShares();
testVaultClawbackAssets();
testAssetsMaximum();
testVaultSetVaultDepositFlagValidation();
}
};

View File

@@ -31,6 +31,8 @@ Vault::set(SetArgs const& args)
jv[jss::TransactionType] = jss::VaultSet;
jv[jss::Account] = args.owner.human();
jv[sfVaultID] = to_string(args.id);
if (args.flags)
jv[jss::Flags] = *args.flags;
return jv;
}

View File

@@ -36,6 +36,7 @@ struct Vault
{
Account owner;
uint256 id;
std::optional<std::uint32_t> flags{};
};
Json::Value

View File

@@ -21,9 +21,34 @@ VaultSet::checkExtraFeatures(PreflightContext const& ctx)
return true;
}
std::uint32_t
VaultSet::getFlagsMask(PreflightContext const& ctx)
{
return tfVaultSetMask;
}
static bool
isValidVaultUpdate(PreflightContext const& ctx)
{
auto const checkFlags = ctx.rules.enabled(fixLendingProtocolV1_1);
auto const atLeastOneFieldPresent =
ctx.tx.isFieldPresent(sfDomainID) || ctx.tx.isFieldPresent(sfAssetsMaximum) || ctx.tx.isFieldPresent(sfData);
return atLeastOneFieldPresent ||
(checkFlags && (ctx.tx.isFlag(tfVaultDepositBlock) || ctx.tx.isFlag(tfVaultDepositUnblock)));
}
NotTEC
VaultSet::preflight(PreflightContext const& ctx)
{
if ((ctx.tx.isFlag(tfVaultDepositBlock) || ctx.tx.isFlag(tfVaultDepositUnblock)) &&
!ctx.rules.enabled(fixLendingProtocolV1_1))
{
JLOG(ctx.j.debug()) << "VaultSet: flags not supported without fixLendingProtocolV1_1.";
return temDISABLED;
}
if (ctx.tx[sfVaultID] == beast::zero)
{
JLOG(ctx.j.debug()) << "VaultSet: zero/empty vault ID.";
@@ -48,12 +73,18 @@ VaultSet::preflight(PreflightContext const& ctx)
}
}
if (!ctx.tx.isFieldPresent(sfDomainID) && !ctx.tx.isFieldPresent(sfAssetsMaximum) && !ctx.tx.isFieldPresent(sfData))
if (!isValidVaultUpdate(ctx))
{
JLOG(ctx.j.debug()) << "VaultSet: nothing is being updated.";
return temMALFORMED;
}
if (ctx.tx.isFlag(tfVaultDepositBlock) && ctx.tx.isFlag(tfVaultDepositUnblock))
{
JLOG(ctx.j.debug()) << "VaultSet: cannot set tfVaultDepositBlock and tfVaultDepositUnblock simultaneously.";
return temMALFORMED;
}
return tesSUCCESS;
}
@@ -107,6 +138,21 @@ VaultSet::preclaim(PreclaimContext const& ctx)
}
}
if (ctx.view.rules().enabled(fixLendingProtocolV1_1))
{
if (vault->isFlag(lsfVaultDepositBlocked) && ctx.tx.isFlag(tfVaultDepositBlock))
{
JLOG(ctx.j.debug()) << "VaultSet: vault deposit is already blocked";
return tecNO_PERMISSION;
}
if (!vault->isFlag(lsfVaultDepositBlocked) && ctx.tx.isFlag(tfVaultDepositUnblock))
{
JLOG(ctx.j.debug()) << "VaultSet: vault deposit is already unblocked";
return tecNO_PERMISSION;
}
}
return tesSUCCESS;
}
@@ -164,6 +210,15 @@ VaultSet::doApply()
view().update(sleIssuance);
}
if (view().rules().enabled(fixLendingProtocolV1_1))
{
if (tx.isFlag(tfVaultDepositBlock))
vault->setFlag(lsfVaultDepositBlocked);
if (tx.isFlag(tfVaultDepositUnblock))
vault->clearFlag(lsfVaultDepositBlocked);
}
// Note, we must update Vault object even if only DomainID is being updated
// in Issuance object. Otherwise it's really difficult for Vault invariants
// to verify the operation.

View File

@@ -13,6 +13,9 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);