Skip to content

fix: reject unknown dict flags and close LoanSet and VaultCreate validation gaps - #1062

Open
kuan121 wants to merge 1 commit into
mainfrom
fix/dge-8226-loanset-validation-and-flags
Open

kuan121 wants to merge 1 commit into
mainfrom
fix/dge-8226-loanset-validation-and-flags

Conversation

@kuan121

@kuan121 kuan121 commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

High Level Overview of Change

  • Dict-form flags no longer fail open: a flag set to True that the transaction type doesn't define now raises instead of encoding as Flags: 0. The global TF_INNER_BATCH_TXN now works in dict form for every transaction type.
  • LoanSetFlag, LoanManageFlag and AMMClawbackFlag (and their FlagInterfaces) are exported, so their dict-form flags take effect.
  • LoanSet and VaultCreate validation now rejects the values rippled rejects in preflight.

Jira: DGE-8226
Fixes #1016

Context of Change

Dict-form flags (#1016). interface_to_flag_list turned any flag name it didn't recognise into 0, with no error. This dates from #363. The transaction still validated, signed and submitted, but without the flag the caller asked for. Three things led there:

  1. Unexported enums. The flag table is built from the *Flag enums exported by xrpl.models.transactions. LoanSetFlag, LoanManageFlag and AMMClawbackFlag weren't exported, so even the library's own names were dropped. These calls succeed on the ledger but don't do what the caller asked:

    What the caller wrote Sent rippled result
    LoanManage(flags={"TF_LOAN_DEFAULT": True}) Flags: 0 a no-op that returns tesSUCCESS (LoanManage.cpp:414-423)
    LoanSet(flags={"TF_LOAN_OVER_PAYMENT": True}) Flags: 0 a loan without overpayment support
    AMMClawback(flags={"TF_CLAW_TWO_ASSETS": True}) Flags: 0 only one asset clawed back
  2. Global flag lookup. The global TransactionFlag was never consulted. {"TF_INNER_BATCH_TXN": True} encoded as 0 for every type, and Batch.__post_init__ adds exactly that to inner transactions whose flags are a dict.

  3. Unrecognised names. Rippled-style names (tfImmediateOrCancel) and typos also became 0.

The table now includes the global flags for every transaction type. A flag set to True that isn't valid for the type raises an XRPLModelException that lists the valid names. A name set to False changes nothing on the ledger, so it is still ignored. xrpl.js already rejects unknown flag names.

LoanSet validation now follows LoanSet::preflight, which returns temINVALID for each of these:

Field rippled requires xrpl-py checked
grace_period 60 ≤ grace_period ≤ payment_interval (60 if omitted) only grace_period > payment_interval, and only when both were set
payment_total > 0 nothing
principal_requested > 0 nothing
loan_origination_fee 0 ≤ fee ≤ principal_requested nothing
loan_service_fee, late_payment_fee, close_payment_fee ≥ 0 nothing

The amount fields are number strings, so they are compared as Decimal. A value that doesn't parse as a finite number is left to the existing serialization error. Fractional or NaN values in the integer fields need no new check here, because the model's type check already rejects floats. The LoanSet checks date from #866.

VaultCreate accepted any withdrawal_policy. rippled only accepts 1 (vaultStrategyFirstComeFirstServe) and otherwise returns temMALFORMED (VaultCreate.cpp:67-71). True is rejected too, the same way vault_kind already rejects booleans.

The xrpl.js half of DGE-8226 is XRPLF/xrpl.js#3533.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Code that passed an unrecognised flag name set to True used to sign a transaction without that flag; it now raises. test_transaction_has_no_flags asserted the old behaviour (an OfferCancel given PaymentChannelClaim's TF_CLOSE signed with Flags: 0), so it now expects the error. The stricter LoanSet and VaultCreate checks only reject values rippled already rejects.

Did you update CHANGELOG.md?

  • Yes

Test Plan

  • Unit tests
    • test_better_transaction_flags.py:
      • LoanSet, LoanManage and AMMClawback dict flags resolve to their bits.
      • TF_INNER_BATCH_TXN resolves for Payment and AccountDelete.
      • An unknown name set to True raises, both rippled-style and a typo.
      • An unknown name set to False is ignored.
    • test_loan_set.py: each new bound at and past its edge, and grace_period with and without payment_interval.
    • test_vault_create.py: withdrawal_policy of 0, 2 and True is rejected.
    • Every new or changed test, except the one for unknown names set to False, fails on main and passes with the fix.
  • Integration tests: the full integration suite passes against rippleci/xrpld:develop --standalone (xrpld 3.4.0-rc1). So no existing test relies on an unknown flag name or on a value the new checks reject.
  • Other tests: the full unit suite, flake8 and mypy --strict pass.

🤖 Generated with Claude Code

…dation gaps

interface_to_flag_list encoded any flag name it did not recognise as 0.
That included the library's own LoanSetFlag, LoanManageFlag and
AMMClawbackFlag names, whose enums were not exported, and the global
TF_INNER_BATCH_TXN for every transaction type. A dict flag set to True
that the transaction type does not define now raises XRPLModelException
(#1016); the three enums are exported and global flags are looked up
for every type.

LoanSet now rejects the values LoanSet::preflight rejects with
temINVALID: a grace_period outside 60..payment_interval (60 if omitted),
a payment_total or principal_requested of 0 or less, a
loan_origination_fee outside 0..principal_requested, and negative
service, late and close payment fees. VaultCreate rejects a
withdrawal_policy other than 1.

DGE-8226

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 25 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: XRPLF/xrpl-py/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 058d4f69-f488-4fa6-a3a7-94c69a5bafd6
📥 Commits

Reviewing files that changed from the base of the PR and between 7a81134 and ae68b1e.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • tests/unit/models/transactions/test_better_transaction_flags.py
  • tests/unit/models/transactions/test_loan_set.py
  • tests/unit/models/transactions/test_vault_create.py
  • xrpl/models/flags.py
  • xrpl/models/transactions/__init__.py
  • xrpl/models/transactions/loan_set.py
  • xrpl/models/transactions/vault_create.py
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The diff makes dict-form transaction flags fail closed instead of silently encoding to Flags: 0, exports previously-missing Loan/AMMClawback flag enums, and tightens LoanSet/VaultCreate validation to match rippled's preflight checks. The logic was cross-checked against the extensive new unit tests (bounds for grace_period, principal_requested, loan_origination_fee, fee fields, withdrawal_policy, and flag-name rejection) and is internally consistent: Decimal parsing is used correctly for string amount fields, integer fields are compared directly, the global TransactionFlag set is correctly merged with per-type flags before validation, and the exception messages match what the tests assert. No correctness, security, or resource-leak issues were found in the added/changed lines.

This branch has not been deployed

No deployments
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.

Dict-style flags fail open: unrecognized flag names are silently encoded as Flags: 0

1 participant