Skip to content

fix: Serialize MPToken UInt64 amounts as base-10 strings#3139

Open
ckeshava wants to merge 13 commits into
XRPLF:developfrom
ckeshava:updateAmtReprToStr
Open

fix: Serialize MPToken UInt64 amounts as base-10 strings#3139
ckeshava wants to merge 13 commits into
XRPLF:developfrom
ckeshava:updateAmtReprToStr

Conversation

@ckeshava

@ckeshava ckeshava commented Jul 7, 2026

Copy link
Copy Markdown

Summary

The account_mptokens and account_mpt_issuances RPCs emitted MPToken amount fields as JSON numbers. Because many JSON parsers back numbers with IEEE-754 doubles, any value greater than 2^53 was silently rounded, so clients could not recover the exact on-ledger amount.

This change serializes the affected UInt64 amount fields as base-10 JSON strings, matching rippled's STUInt64::getJson behavior so Clio's output is consistent with the reference implementation:

  • account_mptokens: mpt_amount, locked_amount
  • account_mpt_issuances: maximum_amount, outstanding_amount, locked_amount

Changes

  • Serialize the UInt64 amount fields via xrpl::STUInt64{field, value}.getJson(...) instead of inserting the raw integer into the JSON object, so the encoding matches rippled's string representation exactly.
  • Add regression tests in both handlers asserting that large amounts (2^63 - 1, 2^53 + 1, and an odd value > 2^53) round-trip as exact strings.

Test plan

  • New unit tests: RPCAccountMPTokensHandlerTest.LargeAmountsSerializedAsStrings and RPCAccountMPTokenIssuancesHandlerTest.LargeAmountsSerializedAsStrings.
  • Existing MPToken handler tests updated to expect string-typed amount fields.

Notes

mpt_holders already serializes mpt_amount correctly via STUInt64::getJson; all other handlers that expose ledger UInt64 fields (e.g. vault_info, get_aggregate_price, gateway_balances) route through rippled's own serialization or STAmount, so they were already emitting strings and are unaffected.

🤖 Generated with Claude Code

…ision of very large numbers (greater than 2^53). This commit returns a string representation instead of Number representation in the RPC outputs to solve this issue.
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ckeshava

ckeshava commented Jul 8, 2026

Copy link
Copy Markdown
Author

/ai-reviewer

// UInt64 amount fields must be serialized as base-10 strings (matching rippled's
// STUInt64::getJson) so that JSON parsers using IEEE-754 doubles do not silently lose
// precision for values greater than 2^53.
auto const uint64ToString = [](xrpl::SField const& field, std::uint64_t value) {

@PeterChen13579 PeterChen13579 Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to do this this PR, but I think this function along with setUint64IfPresent and in MPTHolders all do the same thing more or less. If this mpt_amount ever changes, there will be 3 places that needs to change. I think worth adding a helper to RPChelpers.cpp: something like:

boost::json::string
toBoostJsonString(xrpl::SField const& field, std::uint64_t value);

And all these 3 places can use it instead

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that makes sense. its good to have one common utility method.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my search, I found only two similar utility methods: setUint64IfPresent (introduced in this PR) and the verbose code in MPTHolders RPC, both of which do identical tasks. I wanted to keep the diff of this PR as limited as possible, but it is a good idea to refactor them if there are more such usages.

PeterChen13579
PeterChen13579 previously approved these changes Jul 14, 2026

@PeterChen13579 PeterChen13579 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just left one comment

@godexsoft godexsoft left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR seems to do what is needed. Let's just polish a bit and don't use obsolete names 👍

Comment thread tests/unit/rpc/handlers/AccountMPTokenIssuancesTests.cpp Outdated
Comment thread tests/unit/rpc/handlers/AccountMPTokenIssuancesTests.cpp Outdated
Comment thread tests/unit/rpc/handlers/AccountMPTokenIssuancesTests.cpp Outdated
Comment thread tests/unit/rpc/handlers/AccountMPTokensTests.cpp Outdated
Comment thread tests/unit/rpc/handlers/AccountMPTokensTests.cpp Outdated
ValuesIn(
std::vector<AccountMPTokensAmountSerializationTestCaseBundle>{
{.testName = "LargeAmounts",
.mptAmount = 9223372036854775807ULL, // 2^63 - 1 (max MPT amount)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, updated in 26e2eae

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for this you used (1ULL << 63) - 1 and stuff like that. It of course works but it's even less readable now 😃
What i meant is use std::pow(2, 63) - 1. Note that they are constexpr only from c++26 so right now this would be a runtime call. Which is fine in tests and will become compile time for free when c++26 is used. Readability is more important than performance in tests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that makes sense, I have updated this code in 8be75fb

@godexsoft godexsoft left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try std::pow etc., otherwise looks good 👍

@ckeshava
ckeshava requested a review from godexsoft July 17, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants