Skip to content

[ANCHOR-1186] Fix MEMO_ID validation to support full Stellar uint64 range#1918

Merged
JiahuiWho merged 4 commits intodevelopfrom
anchor-1186-unit64-memo-id
Apr 8, 2026
Merged

[ANCHOR-1186] Fix MEMO_ID validation to support full Stellar uint64 range#1918
JiahuiWho merged 4 commits intodevelopfrom
anchor-1186-unit64-memo-id

Conversation

@JiahuiWho
Copy link
Copy Markdown
Contributor

@JiahuiWho JiahuiWho commented Apr 8, 2026

Description

  • Fix MEMO_ID validation to support the full Stellar uint64 range (0 to 18,446,744,073,709,551,615)
  • Replace Long.parseLong and Long.longValue() with BigInteger for MEMO_ID parsing and conversion
  • Consolidate memo ID creation into a shared MemoHelper.makeMemoId() method

Context

Partner reported that SEP-24 requests with refund_memo 11872666534918305457 were rejected with "Invalid Memo" due to this refund_memo value above Java's Long.MAX_VALUE (9,223,372,036,854,775,807)

Stellar protocol defines MEMO_ID as uint64, but the platform was using Java's signed long for parsing, which only supports half the range.

The same issue existed in SEP-10 memo validation, xdrMemoToString (used by the payment observer for memo matching), and muxed account memo handling in DefaultPaymentListener

Testing

  • ./gradlew test
  • Added new unit tests to cover all cases

Documentation

N/A

Known limitations

N/A

@JiahuiWho JiahuiWho marked this pull request as ready for review April 8, 2026 19:12
Copilot AI review requested due to automatic review settings April 8, 2026 19:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates memo parsing/serialization to correctly support Stellar MEMO_ID values across the full uint64 space by moving off Java signed long parsing/formatting and centralizing memo-id creation in MemoHelper.

Changes:

  • Introduces MemoHelper.makeMemoId() using BigInteger and updates memo parsing to use it.
  • Updates XDR memo-id string conversion to avoid longValue() truncation for values above Long.MAX_VALUE.
  • Updates muxed-account memo handling and adds unit tests for uint64 memo-id round-trips and boundary values.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
platform/src/main/java/org/stellar/anchor/platform/observer/stellar/DefaultPaymentListener.java Uses muxed-id memo creation without narrowing to signed long.
core/src/main/java/org/stellar/anchor/util/MemoHelper.java Adds BigInteger-based memo-id parsing and fixes XDR memo-id string conversion.
core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java Reuses shared memo-id parsing for SEP-10 memo validation.
core/src/test/kotlin/org/stellar/anchor/util/MemoHelperTest.kt Adds tests for uint64 memo-id boundaries and XDR round-trip behavior.
Comments suppressed due to low confidence (1)

core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java:213

  • validateChallengeRequestMemo() catches NumberFormatException for both non-numeric input and out-of-range/invalid numeric values (e.g., <=0 or > uint64 max), but the log/error message says "invalid memo format". Consider changing the message to "Invalid memo value" (or including the allowed range) so callers get an accurate error for numeric-but-invalid memos.
  public Memo validateChallengeRequestMemo(ChallengeRequest request) throws SepException {
    // Validate memo. It should be a positive uint64 integer if not null.
    try {
      if (request.getMemo() != null) {
        return makeMemoId(request.getMemo());
      } else {
        return null;
      }
    } catch (NumberFormatException e) {
      infoF("invalid memo format: {}. Only MEMO_ID is supported", request.getMemo());
      throw new SepValidationException(format("Invalid memo format: %s", request.getMemo()));
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@marwen-abid marwen-abid left a comment

Choose a reason for hiding this comment

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

LGTM

public static MemoId makeMemoId(String memo) {
try {
BigInteger memoId = new BigInteger(memo);
if (memoId.compareTo(BigInteger.ZERO) <= 0) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should probably also catch the upper bound, otherwise new MemoId(memoId) can throw an illegal argument exception. One option would be :

 if (memoId.compareTo(XdrUnsignedHyperInteger.MIN_VALUE) <= 0
              || memoId.compareTo(XdrUnsignedHyperInteger.MAX_VALUE) > 0) {
        throw new NumberFormatException("MEMO_ID must be between 0 and 2^64-1");
      }

Copy link
Copy Markdown
Contributor Author

@JiahuiWho JiahuiWho Apr 8, 2026

Choose a reason for hiding this comment

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

Good point on the upper bound!

I looked into this and the SDK's MemoId(BigInteger) constructor already validates the exact same range (0 to 2^64-1) using XdrUnsignedHyperInteger.MIN_VALUE and MAX_VALUE. So I decided to delegate to the constructor and just wrap the IllegalArgumentException into NumberFormatException (since the caller catches NumberFormatException). This way if the SDK ever adjusts its validation, we inherit the change automatically.

@JiahuiWho JiahuiWho merged commit a1d7906 into develop Apr 8, 2026
11 checks passed
@JiahuiWho JiahuiWho deleted the anchor-1186-unit64-memo-id branch April 8, 2026 21:14
@JiahuiWho JiahuiWho changed the title [ACNHOT-1186] Fix MEMO_ID validation to support full Stellar uint64 range [ANCHOR-1186] Fix MEMO_ID validation to support full Stellar uint64 range Apr 8, 2026
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