XLS-82d MPT-DEX support#704
Conversation
…PTs to issues and updated related tests
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #704 +/- ##
============================================
+ Coverage 93.50% 94.19% +0.68%
- Complexity 2606 2657 +51
============================================
Files 486 489 +3
Lines 6578 6681 +103
Branches 565 578 +13
============================================
+ Hits 6151 6293 +142
+ Misses 270 228 -42
- Partials 157 160 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| BookOffersRequestParams params = BookOffersRequestParams.builder() | ||
| .taker(Address.of("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59")) | ||
| .takerGets(Issue.XRP) | ||
| .takerGets(CurrencyIssue.XRP) |
There was a problem hiding this comment.
Hello Cybele, building on Raj's suggestions above:
I feel the following tests in this file could be improved by including an MPT in the input parameters of the following tests: ripplePathFind, bookOffers, ammInfo --> these methods only have XRP/IOU input combinations in the test.
ledgerEntry test could use an amm: <mpt1, mpt2> input, instead of only relying on the 256-byte cryptographic hash of the ledger object.
| .account(Address.of("razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA")) | ||
| .build() | ||
| ).isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessage("mpt_issuance_id is mutually exclusive with account, currency, and issuer in a PathStep."); |
There was a problem hiding this comment.
| .hasMessage("mpt_issuance_id is mutually exclusive with account, currency, and issuer in a PathStep."); | |
| .hasMessage("mpt_issuance_id is mutually exclusive with account and currency in a PathStep."); |
In a PathStep element, mpt_issuance_id can be used in conjunction with an issuer value. However, the issuer value is optional. If provided, the issuer must match the issuer-of-the-mpt_issuance_id
| BookOffersRequestParams params = BookOffersRequestParams.builder() | ||
| .taker(Address.of("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59")) | ||
| .takerGets(Issue.XRP) | ||
| .takerGets(XrpIssue.XRP) |
There was a problem hiding this comment.
This would constitute a breaking-change. Going forwards, users will not be able to use Issue.XRP in their Java workflows. Am I correct?
There was a problem hiding this comment.
Yes this would be a breaking change however this PR already introduces breaking changes related to MPT. These changes are breaking but would add clarity to the code base based on this comment: #704 (comment)
| .issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")) | ||
| .issuingChainIssue( | ||
| Issue.builder() | ||
| IouIssue.builder() |
There was a problem hiding this comment.
It is preferable to allow users to use the existing Issue.builder() methods for the IOU and XRP assets. This PR would necessitate all users to migrate from Issue.builder() -> IouIssue.builder(), am I correct?
There was a problem hiding this comment.
That is correct however I agree with @Patel-Raj11 that since we are already introducing breaking changes, we should include these updates for overall code clarity
| .account(holderKeyPair.publicKey().deriveAddress()) | ||
| .asset(MptIssue.of(mptIssuanceId)) | ||
| .asset2(XrpIssue.XRP) | ||
| .amount(depositXrpAmount) |
There was a problem hiding this comment.
can you update this test to deposit the MPTAsset instead of XRP? MPT deposits are the highlight of this work.
There was a problem hiding this comment.
| * Creates an MPT/XRP AMM, then has a trader bid on the auction slot using LP tokens. | ||
| */ | ||
| @Test | ||
| void mptAmmBidOnAuctionSlot() throws JsonRpcClientErrorException, JsonProcessingException { |
There was a problem hiding this comment.
I believe these integration are not necessary -- at least as a part of this PR mptAmmVoteOnTradingFee and mptAmmBidOnAuctionSlot. The core functionality of bidding and fee-voting mechanisms have not been modified in this amendment.
There was a problem hiding this comment.
Both methods have now been removed from AmmIT.java
| () -> this.getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress()) | ||
| ); | ||
|
|
||
| Payment payment = Payment.builder() |
There was a problem hiding this comment.
You need to specify the discovered path in the Payment transaction. Without an explicit Path specified, this Payment transaction could take up any of the potential paths from the source to destination.
In this case, there is only one path. However, for the purposes of testing, I feel it is more robust to explicitly specify the pathFindResult in the Payment.builder().
There was a problem hiding this comment.
@cybele-ripple Was comparing the binary serialization changes with xrpl.js and looks like we are missing changes in HopObject. Can we double check if we need the changes in xrpl4j?
There was a problem hiding this comment.
@ckeshava Added to this IT to include a test using pathFindResult() and pathsComputed() for more robust coverage.
@Patel-Raj11 now using else if to match the way MPT hops are handled in xrpl.js
|
|
||
| // The path finding may or may not find a path depending on liquidity | ||
| // For this test, we're primarily verifying that the RPC call succeeds with MPT as sendMax | ||
| assertThat(pathFindResult).isNotNull(); |
There was a problem hiding this comment.
If liquidity is desired, then we can create an AMM or an outstanding offer on the DEX. However, we should test for assertThat(pathFindResult.alternatives().length > 0); (I'm not sure of the exact Java syntax)
Users will need to access this alternatives list to grab the necessary paths. This is the most critical aspect of the response that we should test.
There was a problem hiding this comment.
Added a test to ensure the pathFindResult.alternatives() is not empty here
…PathStep issuer, MPT tests
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
Case-insensitive XRP check causes type confusion. XRPL protocol states "Currency codes are case-sensitive" and only uppercase "XRP" is disallowed (source). An IOU with currency "xrp" (lowercase) would be silently deserialized as native XRP, dropping the issuer — potentially leading to incorrect financial transaction construction.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change equalsIgnoreCase to equals on line 63 to perform a case-sensitive comparison. According to the XRPL protocol, currency codes are case-sensitive and only uppercase "XRP" is the disallowed native asset representation. Using case-insensitive matching could cause an IOU with currency "xrp" (or "Xrp", etc.) to be silently deserialized as native XRP, dropping the issuer field. Also update the comment on line 62 to reflect the change.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| // Check if it's XRP (case-insensitive) | |
| if ("XRP".equalsIgnoreCase(currency)) { | |
| // Check if it's XRP (case-sensitive per XRPL protocol) | |
| if ("XRP".equals(currency)) { |
There was a problem hiding this comment.
The equalsIgnoreCase is intentional and has explicit test coverage
| builder.currency(new CurrencyType().fromParser(parser).toJson()); | ||
| } | ||
|
|
||
| if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
In toJson(), TYPE_MPT is checked with an independent if while fromParser() uses else if (mutually exclusive with TYPE_CURRENCY). When both flags are set (0x50) in stored binary data, fromParser stores only 20 currency bytes, but toJson attempts to parse both currency (20B) and MPT (24B) sequentially — causing a buffer overread/exception on crafted path data.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change the independent if at line 131 to else if to match the mutual-exclusion logic used in fromParser(). In fromParser(), TYPE_CURRENCY and TYPE_MPT are handled with if/else if, meaning only one branch is taken when both flags are set. The toJson() method must mirror this behavior; otherwise, when both flags are present (e.g., type=0x50), toJson will attempt to read bytes for both currency (20B) and MPT (24B) sequentially from a buffer that only contains one of them, causing a buffer overread/exception.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| if ((type & TYPE_MPT) > 0) { | |
| } else if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
| String currency = node.get("currency").asText(); | ||
|
|
||
| // Check if it's XRP | ||
| if ("XRP".equals(currency)) { |
There was a problem hiding this comment.
⚪ Severity: LOW
Inconsistent XRP detection: PathCurrencyDeserializer uses case-sensitive equals here, while IssueDeserializer uses case-insensitive equalsIgnoreCase. The same JSON {"currency":"xrp"} deserialized via Issue yields native XRP, but via PathCurrency it falls through to IOU handling — causing divergent behavior in pathfinding vs. other contexts.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change the case-sensitive equals comparison to equalsIgnoreCase on line 68 to match the behavior used in IssueDeserializer. This ensures consistent XRP detection across all deserializers, even for non-canonical casing like "xrp" or "Xrp".
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| if ("XRP".equals(currency)) { | |
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
equalsIgnoreCase is now used which is consistent with IssueDeserializer
- Add tfMptCanTrade(true) to MPT issuance in CheckIT tests; required by canTrade() check in rippled develop's CheckCreate/CheckCash preclaim - Add @DisabledIf(shouldNotRunMptChecks) to skip MPT check tests on testnet/devnet/clio where MPTokensV2 is not yet enabled - Add useDevelop system property support to XrplEnvironment to use local rippleci/xrpld:develop Docker container for integration tests - Rename build_devnet_its CI job to build_develop_its and switch from -DuseDevnet to -DuseDevelop so MPT tests run against develop image - Fix pre-existing checkstyle violations in OfferIT, PathFindIT, CheckIT, and XrplEnvironment
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
XRPL protocol defines currency codes as case-sensitive, and only "XRP" (uppercase) is reserved for the native asset. Using equalsIgnoreCase means a crafted IOU with currency "xrp" (lowercase) — which is a valid IOU currency on-ledger — would be silently misidentified as native XRP, potentially deceiving users about the asset type in a financial transaction.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change equalsIgnoreCase to equals on line 63 to perform a case-sensitive comparison. The XRPL protocol treats currency codes as case-sensitive, and only uppercase "XRP" designates the native asset. Using equalsIgnoreCase would cause a crafted IOU with currency "xrp" (lowercase) to be silently misidentified as native XRP. Also update the comment on line 62 to remove the "(case-insensitive)" note. This is consistent with the sibling PathCurrencyDeserializer which already uses case-sensitive equals("XRP").
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| // Check if it's XRP (case-insensitive) | |
| if ("XRP".equalsIgnoreCase(currency)) { | |
| // Check if it's XRP | |
| if ("XRP".equals(currency)) { |
There was a problem hiding this comment.
equalsIgnoreCase is intentionally used and has explicit test coverage
| builder.currency(new CurrencyType().fromParser(parser).toJson()); | ||
| } | ||
|
|
||
| if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
toJson() uses independent if blocks for TYPE_CURRENCY and TYPE_MPT, while fromParser() uses else if (mutual exclusion). If a crafted binary hop has both flags set (type 0x50), fromParser stores only currency data, but toJson attempts to read both — causing a buffer over-read on the internal BinaryParser and potential data misinterpretation.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change the independent if block for TYPE_MPT on line 131 to else if so that toJson() uses the same mutual exclusion logic as fromParser(). This prevents attempting to read MPT data from the buffer when both TYPE_CURRENCY and TYPE_MPT flags are set, which would cause a buffer over-read since fromParser() only stored currency data in that case.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| if ((type & TYPE_MPT) > 0) { | |
| } else if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
| /** | ||
| * Validate that the currency is not "XRP" (case-insensitive). | ||
| */ | ||
| @Value.Check | ||
| default void checkCurrencyNotXrp() { | ||
| if ("XRP".equalsIgnoreCase(currency())) { |
There was a problem hiding this comment.
⚪ Severity: LOW
Same case-insensitivity issue as in IssueDeserializer. The XRPL protocol treats "xrp" (lowercase) as a valid IOU currency code distinct from native "XRP". This check prevents constructing an IouIssue for the legitimate IOU currency "xrp", breaking the library's ability to represent valid on-ledger assets.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change the case-insensitive comparison "XRP".equalsIgnoreCase(currency()) to a case-sensitive comparison "XRP".equals(currency()). The XRPL protocol treats currency codes case-sensitively: only the exact uppercase string "XRP" is reserved for the native asset. Lowercase variants like "xrp" or "Xrp" are legitimate 3-character IOU currency codes and should not be rejected. Also update the Javadoc comment to reflect the case-sensitive check.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| /** | |
| * Validate that the currency is not "XRP" (case-insensitive). | |
| */ | |
| @Value.Check | |
| default void checkCurrencyNotXrp() { | |
| if ("XRP".equalsIgnoreCase(currency())) { | |
| /** | |
| * Validate that the currency is not "XRP". | |
| */ | |
| @Value.Check | |
| default void checkCurrencyNotXrp() { | |
| if ("XRP".equals(currency())) { | |
| throw new IllegalStateException("IouIssue currency cannot be 'XRP'. Use XrpIssue instead."); | |
| } | |
| } |
There was a problem hiding this comment.
equalsIgnoreCase is intentionally used and has explicit test coverage
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
Changed from case-sensitive equals to equalsIgnoreCase. XRPL protocol treats currency codes case-sensitively; "xrp" (lowercase) is a valid IOU currency code distinct from native "XRP". This silently converts such IOUs to native XRP, dropping the issuer field—potentially causing financial asset misidentification in payment processing.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change "XRP".equalsIgnoreCase(currency) back to "XRP".equals(currency) on line 63. The XRPL protocol treats currency codes case-sensitively: only uppercase "XRP" designates native XRP, while lowercase variants like "xrp" are valid IOU currency codes. Using case-insensitive comparison silently converts such IOUs to native XRP, dropping the issuer field and causing asset misidentification. This also aligns with the case-sensitive comparison used in PathCurrencyDeserializer.java (line 68) and the binary codec layer. Also update the comment on line 62 to remove the "(case-insensitive)" annotation.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| // Check if it's XRP (case-insensitive) | |
| if ("XRP".equalsIgnoreCase(currency)) { | |
| // Check if it's XRP | |
| if ("XRP".equals(currency)) { |
There was a problem hiding this comment.
equalsIgnoreCase is intentionally used and has explicit test coverage
| builder.currency(new CurrencyType().fromParser(parser).toJson()); | ||
| } | ||
|
|
||
| if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
toJson() checks TYPE_CURRENCY and TYPE_MPT with independent if blocks (lines 127, 131), but fromParser() (line 67-70) uses else if for mutual exclusion. When both flags are present in the type byte, toJson() reads beyond stored data, causing incorrect parsing of payment path binary data.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change the independent if on line 131 to else if so that TYPE_CURRENCY and TYPE_MPT are mutually exclusive in toJson(), matching the else if pattern already used in fromParser() (line 69) and fromJson() (line 103). This ensures that when both flags are unexpectedly set in the type byte, toJson() only reads the currency data (which is all fromParser() stored), preventing out-of-bounds reads or data corruption.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| if ((type & TYPE_MPT) > 0) { | |
| } else if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
…kensV2 support Add @DisabledIf(shouldNotRunMptDex) to mptOfferCreateAndVerifyWithBookOffers, mptOfferCrossing, mptOfferCreateWithIouAndVerifyWithBookOffers in OfferIT and ripplePathFindWithMptDestination, ripplePathFindAlternativesNonEmptyWithDexLiquidity in PathFindIT. Tests still run against the local rippleci/xrpld:develop Docker image which has MPTokensV2 enabled; they are skipped only when useTestnet, useClioTestnet, or useDevnet is set. Also adds -DuseDevelop as an explicit alias for the local Docker environment in XrplEnvironment.
…ronments AmmIT mptAmm* tests fail with "Amount can not be MPT." on testnet because MPT AMM support requires MPTokensV2, which is only enabled in the develop rippled image. MpTokenIT.mptIssuanceWithPermissionedDomainSuccessAndFailure and PermissionedDomainIT.testPermissionedDomainCreateUpdateAndDelete fail with temDISABLED on testnet because the PermissionedDomain feature is not yet enabled there. Add @DisabledIf guards following the same pattern used in OfferIT, PathFindIT, and CheckIT to skip these tests when running against testnet/clio/devnet environments.
| } | ||
|
|
||
| if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
toJson() uses independent if blocks for TYPE_CURRENCY (line 127) and TYPE_MPT (line 131), but fromParser() uses else if (line 67-71), treating them as mutually exclusive. When a binary type byte has both flags set (e.g., 0x50), toJson() will attempt to read MPT bytes (24) that were never stored by fromParser(), causing a buffer over-read on the internal BinaryParser — reading garbage data or throwing an exception in this financial codec.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change the independent if block for TYPE_MPT on line 131 to else if to match the mutually exclusive treatment in fromParser() (line 67-71) and fromJson() (line 100-106). This ensures that when both TYPE_CURRENCY and TYPE_MPT flags are set in a malformed type byte, toJson() only reads the currency bytes (which is what fromParser() stored), preventing a buffer over-read on the internal BinaryParser.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| } | |
| if ((type & TYPE_MPT) > 0) { | |
| } else if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
⚪ Severity: LOW
Changed from case-sensitive equals to equalsIgnoreCase for XRP detection. XRPL currency codes are case-sensitive; a hypothetical custom IOU with code "xrp" (lowercase) would now be silently misidentified as native XRP. This is inconsistent with PathCurrencyDeserializer which uses case-sensitive equals, meaning the same JSON can be parsed as different asset types depending on code path — a type-confusion risk in a financial library.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change equalsIgnoreCase back to case-sensitive equals on line 63 to be consistent with XRPL's case-sensitive currency codes and with PathCurrencyDeserializer (which uses "XRP".equals(currency)). Also update the comment on line 62 to remove the "case-insensitive" note.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| // Check if it's XRP (case-insensitive) | |
| if ("XRP".equalsIgnoreCase(currency)) { | |
| // Check if it's XRP | |
| if ("XRP".equals(currency)) { |
There was a problem hiding this comment.
equalsIgnoreCase is intentionally used and has explicit test coverage
…t and JsonNode Implements #773. In AmountType, the IOU encoding path previously used a package-private Amount model (currency + value + issuer as Strings) to round-trip through Jackson. The value field was the only thing actually needed from that deserialization; currency and issuer were read directly from the JsonNode. - fromJson() IOU path: read value directly from JsonNode instead of deserializing into the codec-private Amount class. - toJson() IOU path: build IssuedCurrencyAmount (the model-layer CurrencyAmount subtype) instead of the codec-private Amount, aligning the binary codec output type with the rest of the codebase. - Delete codec/binary/types/Amount.java (no longer referenced anywhere). In IssueType, a package-private Issue model with Optional<JsonNode> fields was used purely to shuttle data between the raw JsonNode and the binary encoding logic. - fromJson(): access mpt_issuance_id, currency, and issuer directly on the JsonNode via has()/get() — no intermediate model needed. - toJson(): build ObjectNode responses directly instead of serializing through the codec-private Issue model. - Delete codec/binary/types/Issue.java (no longer referenced anywhere). Both deleted classes were internal implementation details of the codec with no public API exposure.
Fix CI: the IOU branch of AmountType.toJson() was rewritten in 2542bef to serialize via the model class IssuedCurrencyAmount instead of the codec-private Amount class. IssuedCurrencyAmount declares fields as {value, currency, issuer} while Amount used {currency, value, issuer}, so JSON output ordering flipped and AmountTypeTest.decodeCurrencyAmount, AmountTypeTest.decodeNegativeCurrencyAmount, and XrplBinaryCodecTest.encodeDecodeIssuedCurrency began failing across the unit-test CI matrix. d5d4bfd already reverted the analogous IssueType change but missed AmountType. Restore Amount.java and revert the IOU branches of AmountType.fromJson/toJson to main, mirroring the IssueType revert. Also revert changes that are unrelated to XLS-82d or issue #773: - MpTokenIT: restore class-level @DisabledIf("shouldNotRun") and the shouldNotRun() predicate that were removed. Whether existing MPTokens v1 tests should now run on testnet/clio is independent of XLS-82d. The new mptIssuanceWithPermissionedDomainSuccessAndFailure test keeps its own method-level skip. - PermissionedDomainIT: revert addition of a method-level @DisabledIf("shouldNotRunPermissionedDomain") on testPermissionedDomainCreateUpdateAndDelete. The PermissionedDomain amendment is unrelated to MPT-DEX. - ServerInfoIT: narrow shouldSkipPublicServerTests() to only check useDevelop. Skipping on the local develop Docker job is required (the test hits public XRPL servers); the broader CI/useTestnet/ useClioTestnet/useDevnet conditions were masking behavior on jobs that were green on main. - XrplClient.ledgerEntry, MultiSignedTransaction.builder, SingleSignedTransaction.builder, BatchSigner.checkAndNormalize, and FeeUtils.computeNetworkFees: revert Javadoc-only tag additions on methods that this PR does not otherwise touch. Build succeeds with javadoc generation enabled without them.
Earlier carve-out narrowed shouldSkipPublicServerTests() to only check useDevelop, on the (incorrect) reasoning that the broader predicate was masking pre-existing flakes on jobs that were green on main. In fact, the broader predicate handled a real failure mode: testServerInfoAcrossAllTypes hits public XRPL servers whose TLS certificate chain fails PKIX validation on some Temurin JDK builds (notably 16 and 21) due to cacerts trust-store gaps. It was passing on main as recently as 2026-04-29; this run shows it failing on build_jdk_temurin_other (16) and (21) with: PKIX path building failed: unable to find valid certification path to requested target executing POST https://s.altnet.rippletest.net:51234/ Restore the original predicate (CI env var + the four use* properties) and add a comment explaining why this skip is needed.
Add 8 unit tests to bring four PR-touched files to 100% instruction coverage: - PathCurrencyTest: assert IllegalArgumentException from deprecated PathCurrency.of(String) when the currency is not "XRP", and NullPointerException from PathCurrency.of((Issue) null). - IouIssueTest: assert IllegalStateException from the @Value.Check validator when currency() is "XRP" or "xrp" (case-insensitive). - PathCurrencySerializerTest: cover the deprecated CurrencyIssue branch when issuer is present (happy path and IOException catch), and the XrpIssue IOException catch. - HopTypeTest: cover the TYPE_ACCOUNT branch in HopType.toJson() via a Hop with only an account. xrpl4j-core line coverage: 96.36% -> 96.53%. xrpl4j-core instruction coverage: 95.76% -> 95.95%.
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
The case-insensitive comparison (equalsIgnoreCase) treats IOU tokens with currency codes like "xrp" or "Xrp" as native XRP. Per XRPL docs, currency codes are case-sensitive and only uppercase "XRP" is reserved. An attacker could issue an IOU with currency code "xrp" that this library would misidentify as native XRP, enabling financial manipulation.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Replace equalsIgnoreCase with equals for a case-sensitive comparison of the currency code. Per the XRPL protocol specification, currency codes are case-sensitive and only the exact uppercase string "XRP" is reserved for the native asset. Using equalsIgnoreCase would cause IOU tokens with currency codes like "xrp" or "Xrp" to be misidentified as native XRP. Also update the comment on line 62 to reflect the case-sensitive check. This is consistent with PathCurrencyDeserializer.java:68 which already uses "XRP".equals(currency).
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| // Check if it's XRP (case-insensitive) | |
| if ("XRP".equalsIgnoreCase(currency)) { | |
| // Check if it's XRP (case-sensitive, per XRPL currency format spec) | |
| if ("XRP".equals(currency)) { |
There was a problem hiding this comment.
equalsIgnoreCase is intentionally used and has explicit test coverage
| builder.currency(new CurrencyType().fromParser(parser).toJson()); | ||
| } | ||
|
|
||
| if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
In toJson(), TYPE_CURRENCY and TYPE_MPT are checked with independent if blocks, but fromParser() uses else if (line 69). If binary data has both flags set, toJson() reads currency (20 bytes) and MPT (24 bytes) from a buffer that only stored one, causing a buffer overread into adjacent hop/path data and corrupting the deserialized output.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change the if on line 131 to else if so that TYPE_CURRENCY and TYPE_MPT are treated as mutually exclusive in toJson(), consistent with fromParser() (line 69) and fromJson(). If both flags are set, only the currency branch should execute (matching the backward-compatibility preference stated in the comments). Replace:
if ((type & TYPE_MPT) > 0) {
with:
} else if ((type & TYPE_MPT) > 0) {
This prevents a buffer overread when both TYPE_CURRENCY and TYPE_MPT flags are set simultaneously.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| if ((type & TYPE_MPT) > 0) { | |
| } else if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
| * Validate that the currency is not "XRP" (case-insensitive). | ||
| */ | ||
| @Value.Check | ||
| default void checkCurrencyNotXrp() { | ||
| if ("XRP".equalsIgnoreCase(currency())) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
The case-insensitive check rejects IOU tokens with valid XRPL currency codes like "xrp" or "Xrp". The XRPL protocol only reserves uppercase "XRP" (source). Combined with the IssueDeserializer change, tokens with these valid currency codes are silently converted to native XRP, misrepresenting the actual asset.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Change equalsIgnoreCase to equals on line 68 so that only the exact uppercase string "XRP" is rejected. The XRPL protocol only reserves the exact uppercase "XRP" as the native asset identifier; other casings like "xrp" or "Xrp" are valid 3-character IOU currency codes. Also update the Javadoc comment on line 64 to reflect the case-sensitive check. The same fix should be applied in IssueDeserializer.java at line 63, where equalsIgnoreCase should also be changed to equals.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| * Validate that the currency is not "XRP" (case-insensitive). | |
| */ | |
| @Value.Check | |
| default void checkCurrencyNotXrp() { | |
| if ("XRP".equalsIgnoreCase(currency())) { | |
| * Validate that the currency is not "XRP". | |
| */ | |
| @Value.Check | |
| default void checkCurrencyNotXrp() { | |
| if ("XRP".equals(currency())) { |
There was a problem hiding this comment.
equalsIgnoreCase is intentionally used and has explicit test coverage
…nsitive XRP design
HopType.toJson() used independent if-blocks for TYPE_CURRENCY and TYPE_MPT,
while fromParser/fromJson both used else-if (mutually exclusive). If a binary
hop had both flags set toJson would attempt to read MPT bytes that were never
stored, corrupting output. Changed to else-if to match.
PathCurrencyDeserializer used case-sensitive equals() for XRP detection while
IssueDeserializer uses equalsIgnoreCase(). Same JSON {"currency":"xrp"} produced
XrpIssue via IssueDeserializer but CurrencyIssue("xrp") via PathCurrencyDeserializer.
Changed to equalsIgnoreCase() and added matching test coverage.
The DEX-liquidity test now submits a cross-currency Payment after calling ripple_path_find, using the first alternative's pathsComputed and sourceAmount directly. This proves the discovered path is valid and executable, not just that alternatives is non-empty. Also fixes three double-submit bugs in the same test where xrplClient.submit() was called a second time just to read validatedLedgerIndex.
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 Severity: MEDIUM
Changed from case-sensitive equals to equalsIgnoreCase. Per XRPL protocol docs, "Currency codes are case-sensitive" and only "XRP" (all-uppercase) is reserved. An IOU with currency "xrp" is a valid distinct asset, but this deserializer now silently converts it to native XRP, discarding the issuer — leading to asset type confusion in a financial library.
Helpful? Add 👍 / 👎
💡 Fix Suggestion
Suggestion: Revert equalsIgnoreCase back to equals for the XRP currency check. Per the XRPL protocol specification, currency codes are case-sensitive, and only the exact uppercase string "XRP" is reserved as the native currency identifier. Using case-insensitive comparison would silently convert a valid IOU with a case-variant currency code (e.g., "xrp", "Xrp") to native XRP, discarding the issuer and causing asset type confusion.
⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.
| // Check if it's XRP (case-insensitive) | |
| if ("XRP".equalsIgnoreCase(currency)) { | |
| // Check if it's XRP (case-sensitive per XRPL protocol) | |
| if ("XRP".equals(currency)) { |
There was a problem hiding this comment.
equalsIgnoreCase is intentionally used and has explicit test coverage
AmmIT was missed when PR #787 fixed the same class of flakiness across nine other integration-test files. After scanForFinality returns, a Clio cluster node may not yet have replicated the newly-validated ledger state, causing actNotFound on ammInfo or stale AMM balances on subsequent reads. Apply the same guard introduced by #787: after each scanForFinality, poll getValidatedAccountInfo until the account sequence reflects the expected post-transaction value. This confirms the responding Clio node has indexed the transaction before any AMM-state queries are issued. Specific changes: - createAmm: guard before getAmmInfo (fixes actNotFound on all callers) - depositXrp: guard + getValidatedAccountInfo + CURRENT→VALIDATED for accountLines - depositAndVoteOnTradingFee: guard after vote + CURRENT→VALIDATED for both accountLines calls - depositAndWithdraw: guard after withdraw - depositAndBid: guard after bid
| run: mvn clean install -Dmaven.javadoc.skip=true -Pandroid | ||
|
|
||
| build_devnet_its: | ||
| build_develop_its: |
Added support for XLS-82d