Compare commits

..

26 Commits

Author SHA1 Message Date
Bart
b6a09a4d2c release: Bump version to 3.2.0-b3 2026-04-01 13:27:58 -04:00
Valentin Balaschenko
3adad49c86 fix: Remove fatal assertion on Linux thread name truncation (#6690) 2026-04-01 13:27:23 -04:00
Alex Kremer
f04943fc50 chore: Enable clang-tidy coreguidelines checks (#6698)
Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
2026-04-01 13:27:23 -04:00
Ayaz Salikhov
ab7f683b2f ci: Allow uploading artifacts for XRPLF org (#6702) 2026-04-01 13:27:23 -04:00
Vito Tumas
14749fdfb0 fix: Enforce aggregate MaximumAmount in multi-send MPT (#6644)
Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
2026-04-01 13:27:22 -04:00
Ayaz Salikhov
c6ddf6aa1c chore: Use nudb recipe from the upstream (#6701) 2026-04-01 13:27:22 -04:00
Mayukha Vadari
ee3a08c8d4 fix: Fix previous ledger size typo in RCLConsensus (#6696) 2026-04-01 13:27:22 -04:00
Alex Kremer
2dc705bd99 chore: Enable clang-tidy misc checks (#6655) 2026-04-01 13:27:22 -04:00
Ayaz Salikhov
7315c57edc ci: Use pull_request_target to check for signed commits (#6697) 2026-04-01 13:27:22 -04:00
Bart
5a66086fce chore: Remove unnecessary clang-format off/on directives (#6682)
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com>
2026-04-01 13:27:21 -04:00
Pratik Mankawde
682dda8bfc fix: Fix Workers::stop() race between m_allPaused and m_runningTaskCount (#6574)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 13:27:21 -04:00
Bart
aac17f588a release: Bump version to 3.2.0-b2 2026-03-30 14:52:51 -04:00
Ayaz Salikhov
cbc09b2999 ci: Only publish docs in public repos (#6687) 2026-03-30 14:52:09 -04:00
Alex Kremer
453d94da17 chore: Enable remaining clang-tidy performance checks (#6648)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-30 14:52:09 -04:00
Jingchen
b36aedb4d5 refactor: Address PR comments after the modularisation PRs (#6389)
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
Co-authored-by: Bart <bthomee@users.noreply.github.com>
2026-03-30 14:52:09 -04:00
Alex Kremer
18540c97a5 chore: Fix clang-tidy header filter (#6686) 2026-03-30 14:52:08 -04:00
dependabot[bot]
8e3d87fce3 ci: [DEPENDABOT] bump actions/deploy-pages from 4.0.5 to 5.0.0 (#6684)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 14:52:08 -04:00
dependabot[bot]
b83dc9aa16 ci: [DEPENDABOT] bump codecov/codecov-action from 5.5.3 to 6.0.0 (#6685)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-30 14:52:08 -04:00
Pratik Mankawde
0c76bf991a fix: Guard Coro::resume() against completed coroutines (#6608)
Signed-off-by: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:52:08 -04:00
Vito Tumas
29aba28f5b refactor: Split LoanInvariant into LoanBrokerInvariant and LoanInvariant (#6674) 2026-03-30 14:52:08 -04:00
Ayaz Salikhov
b3f14c4052 ci: Don't publish docs on release branches (#6673) 2026-03-30 14:52:08 -04:00
Jingchen
e26624dcd1 refactor: Make function naming in ServiceRegistry consistent (#6390)
Signed-off-by: JCW <a1q123456@users.noreply.github.com>
Co-authored-by: Ed Hennis <ed@ripple.com>
2026-03-30 14:52:08 -04:00
Valentin Balaschenko
885f7b8c33 chore: Shorten job names to stay within Linux 15-char thread limit (#6669) 2026-03-30 14:52:07 -04:00
Vito Tumas
dcf973bc50 fix: Improve loan invariant message (#6668) 2026-03-30 14:52:07 -04:00
Ayaz Salikhov
68596f60d8 ci: Upload artifacts only in public repositories (#6670) 2026-03-30 14:52:07 -04:00
Bart
aac64d3b85 Update version (#6672) 2026-03-26 09:50:04 -04:00
9 changed files with 42 additions and 171 deletions

View File

@@ -58,8 +58,8 @@ private:
{
explicit Value() = default;
STAmount lowAcctDebits;
STAmount highAcctDebits;
STAmount lowAcctCredits;
STAmount highAcctCredits;
STAmount lowAcctOrigBalance;
};

View File

@@ -503,6 +503,14 @@ public:
{
return cur_.size();
}
void
removeIndex(std::size_t i)
{
if (i >= next_.size())
return;
next_.erase(next_.begin() + i);
}
};
/// @endcond
@@ -627,6 +635,11 @@ flow(
std::optional<BestStrand> best;
if (flowDebugInfo)
flowDebugInfo->newLiquidityPass();
// Index of strand to mark as inactive (remove from the active list) if
// the liquidity is used. This is used for strands that consume too many
// offers Constructed as `false,0` to workaround a gcc warning about
// uninitialized variables
std::optional<std::size_t> markInactiveOnUse;
for (size_t strandIndex = 0, sie = activeStrands.size(); strandIndex != sie; ++strandIndex)
{
Strand const* strand = activeStrands.get(strandIndex);
@@ -690,6 +703,11 @@ flow(
if (best)
{
if (markInactiveOnUse)
{
activeStrands.removeIndex(*markInactiveOnUse);
markInactiveOnUse.reset();
}
savedIns.insert(best->in);
savedOuts.insert(best->out);
remainingOut = outReq - sum(savedOuts);

View File

@@ -37,14 +37,14 @@ DeferredCredits::credit(
if (sender < receiver)
{
v.lowAcctDebits = amount;
v.highAcctDebits = amount.zeroed();
v.highAcctCredits = amount;
v.lowAcctCredits = amount.zeroed();
v.lowAcctOrigBalance = preCreditSenderBalance;
}
else
{
v.lowAcctDebits = amount.zeroed();
v.highAcctDebits = amount;
v.highAcctCredits = amount.zeroed();
v.lowAcctCredits = amount;
v.lowAcctOrigBalance = -preCreditSenderBalance;
}
@@ -56,11 +56,11 @@ DeferredCredits::credit(
auto& v = i->second;
if (sender < receiver)
{
v.lowAcctDebits += amount;
v.highAcctCredits += amount;
}
else
{
v.highAcctDebits += amount;
v.lowAcctCredits += amount;
}
}
}
@@ -104,11 +104,11 @@ DeferredCredits::adjustments(
if (main < other)
{
result.emplace(v.lowAcctDebits, v.highAcctDebits, v.lowAcctOrigBalance);
result.emplace(v.highAcctCredits, v.lowAcctCredits, v.lowAcctOrigBalance);
return result;
}
result.emplace(v.highAcctDebits, v.lowAcctDebits, -v.lowAcctOrigBalance);
result.emplace(v.lowAcctCredits, v.highAcctCredits, -v.lowAcctOrigBalance);
return result;
}
@@ -122,8 +122,8 @@ DeferredCredits::apply(DeferredCredits& to)
{
auto& toVal = r.first->second;
auto const& fromVal = i.second;
toVal.lowAcctDebits += fromVal.lowAcctDebits;
toVal.highAcctDebits += fromVal.highAcctDebits;
toVal.lowAcctCredits += fromVal.lowAcctCredits;
toVal.highAcctCredits += fromVal.highAcctCredits;
// Do not update the orig balance, it's already correct
}
}
@@ -349,14 +349,14 @@ PaymentSandbox::balanceChanges(ReadView const& view) const
auto const cur = newBalance.getCurrency();
result[std::make_tuple(lowID, highID, cur)] = delta;
auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta);
if (!r.second)
if (r.second)
{
r.first->second += delta;
}
delta.negate();
r = result.emplace(std::make_tuple(highID, highID, cur), delta);
if (!r.second)
if (r.second)
{
r.first->second += delta;
}

View File

@@ -69,11 +69,7 @@ transferRate(ReadView const& view, MPTID const& issuanceID)
// which represents 50% of 1,000,000,000
if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
sle && sle->isFieldPresent(sfTransferFee))
{
auto const fee = sle->getFieldU16(sfTransferFee);
XRPL_ASSERT(fee <= maxTransferFee, "xrpl::transferRate : fee is too large");
return Rate{1'000'000'000u + (10'000 * fee)};
}
return Rate{1'000'000'000u + (10'000 * sle->getFieldU16(sfTransferFee))};
return parityRate;
}
@@ -139,7 +135,7 @@ authorizeMPToken(
// When a holder wants to unauthorize/delete a MPT, the ledger must
// - delete mptokenKey from owner directory
// - delete the MPToken
if ((flags & tfMPTUnauthorize) != 0u)
if ((flags & tfMPTUnauthorize) != 0)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const sleMpt = view.peek(mptokenKey);
@@ -219,7 +215,7 @@ authorizeMPToken(
// Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
// their MPToken
if ((flags & tfMPTUnauthorize) != 0u)
if ((flags & tfMPTUnauthorize) != 0)
{
flagsOut &= ~lsfMPTAuthorized;
}

View File

@@ -23,7 +23,7 @@ namespace {
// and follow the format described at http://semver.org/
//------------------------------------------------------------------------------
// clang-format off
char const* const versionString = "3.2.0-b0"
char const* const versionString = "3.2.0-b3"
// clang-format on
;

View File

@@ -564,6 +564,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
return {tecEXPIRED, true};
}
bool const bOpenLedger = sb.open();
bool crossed = false;
if (isTesSuccess(result))
@@ -649,8 +650,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
stream << " out: " << format_amount(place_offer.out);
}
bool const isLedgerOpen = sb.open();
if (result == tecFAILED_PROCESSING && isLedgerOpen)
if (result == tecFAILED_PROCESSING && bOpenLedger)
result = telFAILED_PROCESSING;
if (!isTesSuccess(result))

View File

@@ -388,7 +388,6 @@ Payment::doApply()
sleDst = std::make_shared<SLE>(k);
sleDst->setAccountID(sfAccount, dstAccountID);
sleDst->setFieldU32(sfSequence, view().seq());
sleDst->setFieldAmount(sfBalance, XRPAmount(beast::zero));
view().insert(sleDst);
}

View File

@@ -43,10 +43,10 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
if (!vault)
return tecNO_ENTRY;
auto const amount = ctx.tx[sfAmount];
auto const assets = ctx.tx[sfAmount];
auto const vaultAsset = vault->at(sfAsset);
auto const vaultShare = vault->at(sfShareMPTID);
if (amount.asset() != vaultAsset && amount.asset() != vaultShare)
if (assets.asset() != vaultAsset && assets.asset() != vaultShare)
return tecWRONG_ASSET;
auto const& vaultAccount = vault->at(sfAccount);
@@ -67,53 +67,8 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
// LCOV_EXCL_STOP
}
if (ctx.view.rules().enabled(fixSecurity3_1_3) && amount.asset() == vaultShare)
{
// Post-fixSecurity3_1_3: if the user specified shares, convert
// to the equivalent asset amount before checking withdrawal
// limits. Pre-amendment the limit check was skipped for
// share-denominated withdrawals.
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(vaultShare));
if (!sleIssuance)
{
// LCOV_EXCL_START
JLOG(ctx.j.error()) << "VaultWithdraw: missing issuance of vault shares.";
return tefINTERNAL;
// LCOV_EXCL_STOP
}
try
{
auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, amount);
if (!maybeAssets)
return tefINTERNAL; // LCOV_EXCL_LINE
if (auto const ret = canWithdraw(
ctx.view,
account,
dstAcct,
*maybeAssets,
ctx.tx.isFieldPresent(sfDestinationTag)))
return ret;
}
catch (std::overflow_error const&)
{
// It's easy to hit this exception from Number with large enough Scale
// so we avoid spamming the log and only use debug here.
JLOG(ctx.j.debug()) //
<< "VaultWithdraw: overflow error with"
<< " scale=" << (int)vault->at(sfScale) //
<< ", assetsTotal=" << vault->at(sfAssetsTotal)
<< ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount)
<< ", amount=" << amount.value();
return tecPATH_DRY;
}
}
else
{
if (auto const ret = canWithdraw(ctx.view, ctx.tx))
return ret;
}
if (auto const ret = canWithdraw(ctx.view, ctx.tx))
return ret;
// If sending to Account (i.e. not a transfer), we will also create (only
// if authorized) a trust line or MPToken as needed, in doApply().

View File

@@ -5231,102 +5231,6 @@ class Vault_test : public beast::unit_test::suite
}
}
// Reproduction: canWithdraw IOU limit check bypassed when
// withdrawal amount is specified in shares (MPT) rather than in assets.
void
testBug6_LimitBypassWithShares()
{
using namespace test::jtx;
testcase("Bug6 - limit bypass with share-denominated withdrawal");
auto const allAmendments = testable_amendments() | featureSingleAssetVault;
for (auto const& features : {allAmendments, allAmendments - fixSecurity3_1_3})
{
bool const withFix = features[fixSecurity3_1_3];
Env env{*this, features};
Account const owner{"owner"};
Account const issuer{"issuer"};
Account const depositor{"depositor"};
Account const charlie{"charlie"};
Vault const vault{env};
env.fund(XRP(1000), issuer, owner, depositor, charlie);
env(fset(issuer, asfAllowTrustLineClawback));
env.close();
PrettyAsset const asset = issuer["IOU"];
env.trust(asset(1000), owner);
env.trust(asset(1000), depositor);
env(pay(issuer, owner, asset(200)));
env(pay(issuer, depositor, asset(200)));
env.close();
// Charlie gets a LOW trustline limit of 5
env.trust(asset(5), charlie);
env.close();
auto const [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
auto const depositTx =
vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)});
env(depositTx);
env.close();
// Get the share MPT info
auto const vaultSle = env.le(keylet);
if (!BEAST_EXPECT(vaultSle))
return;
auto const mptIssuanceID = vaultSle->at(sfShareMPTID);
MPTIssue const shares(mptIssuanceID);
PrettyAsset const share(shares);
// CONTROL: Withdraw 10 IOU (asset-denominated) to charlie.
// Charlie's limit is 5, so this should be rejected with tecNO_LINE
// regardless of the amendment.
{
auto withdrawTx =
vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(10)});
withdrawTx[sfDestination] = charlie.human();
env(withdrawTx, ter{tecNO_LINE});
env.close();
}
auto const charlieBalanceBefore = env.balance(charlie, asset.raw().get<Issue>());
// Withdraw the equivalent amount in shares to charlie.
// Post-fix: rejected (tecNO_LINE) because the share amount is
// converted to assets and the trustline limit is checked.
// Pre-fix: succeeds (tesSUCCESS) because the limit check was
// skipped for share-denominated withdrawals.
{
auto withdrawTx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = STAmount(share, 10'000'000)});
withdrawTx[sfDestination] = charlie.human();
env(withdrawTx, ter{withFix ? TER{tecNO_LINE} : TER{tesSUCCESS}});
env.close();
auto const charlieBalanceAfter = env.balance(charlie, asset.raw().get<Issue>());
if (withFix)
{
// Post-fix: charlie's balance is unchanged — the withdrawal
// was correctly rejected despite being share-denominated.
BEAST_EXPECT(charlieBalanceAfter == charlieBalanceBefore);
}
else
{
// Pre-fix: charlie received the assets, bypassing the
// trustline limit.
BEAST_EXPECT(charlieBalanceAfter > charlieBalanceBefore);
}
}
}
}
public:
void
run() override
@@ -5347,7 +5251,6 @@ public:
testVaultClawbackBurnShares();
testVaultClawbackAssets();
testAssetsMaximum();
testBug6_LimitBypassWithShares();
}
};