Summary
When cashing a Check for XRP using an exact Amount (as opposed to DeliverMin), the CheckCash transactor does not call ctx_.deliver(), which means the DeliveredAmount and delivered_amount fields are absent from the transaction metadata. This is inconsistent with all other CheckCash code paths (XRP with DeliverMin, and all IOU paths), which do set DeliveredAmount.
Severity
Low. This is a metadata inconsistency only — the correct amount of XRP is transferred between accounts, so there is no financial impact. However, downstream consumers (explorers, wallets, analytics tools) that rely on DeliveredAmount metadata to determine the amount delivered by a transaction will not find it for this specific transaction type/path combination, potentially causing incorrect display or accounting.
Root Cause
In src/libxrpl/tx/transactors/check/CheckCash.cpp, the XRP code path (starting around line 240) only calls ctx_.deliver() inside the if (optDeliverMin) branch (lines 269–273):
if (optDeliverMin)
{
// Set the DeliveredAmount metadata.
ctx_.deliver(xrpDeliver);
}
When the transaction specifies an exact Amount instead of DeliverMin, optDeliverMin is std::nullopt, so ctx_.deliver() is never called. The XRP is still correctly transferred via transferXRP() on line 276, but the metadata is not set.
By contrast, the IOU code path (starting around line 284) unconditionally calls ctx_.deliver() on line 415:
// Set the delivered amount metadata in all cases, not just
// for DeliverMin.
ctx_.deliver(result.actualAmountOut);
The comment on line 413 ("in all cases, not just for DeliverMin") explicitly acknowledges this should be done for both paths — but the XRP path was not updated to match.
Expected Fix
After the if (optDeliverMin) block and the transferXRP() call in the XRP path, add an unconditional ctx_.deliver(xrpDeliver) call (or move the existing one outside the if). For example, change:
if (optDeliverMin)
{
// Set the DeliveredAmount metadata.
ctx_.deliver(xrpDeliver);
}
to:
// Set the DeliveredAmount metadata for both exact Amount
// and DeliverMin paths.
ctx_.deliver(xrpDeliver);
Reproduction Unit Test
The following test can be added to src/test/app/Check_test.cpp and confirms the bug (passes with 0 failures — the !hasDeliveredAmount branch is taken):
void
testCheckCashXRPMissingDeliveredAmount(FeatureBitset features)
{
testcase("CheckCash XRP missing DeliveredAmount");
using namespace test::jtx;
Account const alice{"alice"};
Account const bob{"bob"};
Env env{*this, features};
env.fund(XRP(10000), alice, bob);
env.close();
// Create a check for exact XRP amount.
uint256 const chkId{getCheckIndex(alice, env.seq(alice))};
env(check::create(alice, bob, XRP(100)));
env.close();
// Cash the check using exact Amount (not DeliverMin).
env(check::cash(bob, chkId, XRP(100)));
// Get the hash for the most recent transaction.
std::string const txHash{
env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
env.close();
Json::Value const meta =
env.rpc("tx", txHash)[jss::result][jss::meta];
// BUG: DeliveredAmount is NOT present for exact-amount XRP
// CheckCash, but it IS present for DeliverMin XRP CheckCash
// and for all IOU CheckCash paths.
STAmount const expected{XRP(100)};
bool const hasDeliveredAmount =
meta.isMember(sfDeliveredAmount.jsonName);
// Currently fails — documenting the missing metadata.
if (hasDeliveredAmount)
{
BEAST_EXPECT(
meta[sfDeliveredAmount.jsonName] ==
expected.getJson(JsonOptions::none));
BEAST_EXPECT(
meta[jss::delivered_amount] ==
expected.getJson(JsonOptions::none));
}
else
{
// Bug confirmed: DeliveredAmount is missing.
BEAST_EXPECT(!hasDeliveredAmount);
}
}
Run with: .build/xrpld --unittest xrpl.app.Check
Summary
When cashing a Check for XRP using an exact
Amount(as opposed toDeliverMin), theCheckCashtransactor does not callctx_.deliver(), which means theDeliveredAmountanddelivered_amountfields are absent from the transaction metadata. This is inconsistent with all other CheckCash code paths (XRP withDeliverMin, and all IOU paths), which do setDeliveredAmount.Severity
Low. This is a metadata inconsistency only — the correct amount of XRP is transferred between accounts, so there is no financial impact. However, downstream consumers (explorers, wallets, analytics tools) that rely on
DeliveredAmountmetadata to determine the amount delivered by a transaction will not find it for this specific transaction type/path combination, potentially causing incorrect display or accounting.Root Cause
In
src/libxrpl/tx/transactors/check/CheckCash.cpp, the XRP code path (starting around line 240) only callsctx_.deliver()inside theif (optDeliverMin)branch (lines 269–273):When the transaction specifies an exact
Amountinstead ofDeliverMin,optDeliverMinisstd::nullopt, soctx_.deliver()is never called. The XRP is still correctly transferred viatransferXRP()on line 276, but the metadata is not set.By contrast, the IOU code path (starting around line 284) unconditionally calls
ctx_.deliver()on line 415:The comment on line 413 ("in all cases, not just for DeliverMin") explicitly acknowledges this should be done for both paths — but the XRP path was not updated to match.
Expected Fix
After the
if (optDeliverMin)block and thetransferXRP()call in the XRP path, add an unconditionalctx_.deliver(xrpDeliver)call (or move the existing one outside theif). For example, change:to:
Reproduction Unit Test
The following test can be added to
src/test/app/Check_test.cppand confirms the bug (passes with 0 failures — the!hasDeliveredAmountbranch is taken):Run with:
.build/xrpld --unittest xrpl.app.Check