Skip to content

fix(adl): compute dominantSide and use it as buildAdlTransaction's default target - #345

Open
Morenikeoa wants to merge 1 commit into
dcccrypto:mainfrom
Morenikeoa:fix/adl-dominantside-upstream
Open

fix(adl): compute dominantSide and use it as buildAdlTransaction's default target#345
Morenikeoa wants to merge 1 commit into
dcccrypto:mainfrom
Morenikeoa:fix/adl-dominantside-upstream

Conversation

@Morenikeoa

@Morenikeoa Morenikeoa commented Jun 27, 2026

Copy link
Copy Markdown

Summary

The ADL module's own docs state "The most profitable positions on the dominant side are deleveraged first" and "the position at rank 0 of the dominant side is deleveraged first," but rankAdlPositions never computed which side (long/short) actually has greater net open interest. buildAdlTransaction, when called without an explicit preferSide, fell through to ranking.ranked[0] — the single best PnL% position merged across BOTH sides, not the dominant side's top position.

Per the on-chain devnet log format ("net_long_oi=500000 net_short_oi=500000 target_side=long candidates=0"), the on-chain ADL engine gates eligibility by net-OI dominant side and resolves ties to "long". A caller relying on the SDK's documented default behavior without manually passing preferSide could have the SDK pick a position on the minority side, which the program would reject.

Note on real-world impact: encodeExecuteAdl/buildAdlInstruction currently always throw removedInstruction() (ExecuteAdl was removed from the v17 wrapper, see test/adl.test.ts), so buildAdlTransaction's instruction-build path is not reachable end-to-end today. The read-side ranking functions (rankAdlPositions, fetchAdlRankedPositions) remain exported, though, and could mislead an external keeper/dashboard that trusts ranking.ranked[0] as "the next ADL victim" without separately checking OI dominance.

Fix

rankAdlPositions now computes engine.longOi vs engine.shortOi and exposes the result as AdlRankingResult.dominantSide ("long" | "short" | nullnull only when engine state can't be parsed at all). Ties resolve to "long" to match the on-chain convention. buildAdlTransaction's default branch (no preferSide) now targets ranking.dominantSide's top position, falling back to the global ranked[0] only when dominantSide is null.

Test plan

  • Added regression tests in test/adl.test.ts: dominantSide is "long"/"short" correctly from engine.longOi/engine.shortOi, ties resolve to "long", and dominantSide is null when engine parsing fails entirely (bad magic). Also added a pure-logic test for buildAdlTransaction's target-side branch selection.
  • Confirmed all new tests fail against the prior code and pass against this fix.
  • Full suite green: 857 passed, 31 skipped (888 total, +6 new tests).

Summary by CodeRabbit

  • New Features

    • Added smarter ADL position selection based on the market’s dominant side, helping transaction creation pick the most relevant target by default.
  • Bug Fixes

    • Improved ADL ranking behavior when market data is incomplete or invalid.
    • Tie cases now resolve consistently, and fallback selection works when no dominant side can be determined.

…fault target

The ADL module's own docs state "The most profitable positions on the
dominant side are deleveraged first" and "the position at rank 0 of
the dominant side is deleveraged first," but rankAdlPositions never
computed which side (long/short) actually has greater net open
interest. buildAdlTransaction, when called without an explicit
preferSide, fell through to ranking.ranked[0] — the single best PnL%
position merged across BOTH sides, not the dominant side's top
position.

Per the on-chain devnet log format ("net_long_oi=500000
net_short_oi=500000 target_side=long candidates=0"), the on-chain ADL
engine gates eligibility by net-OI dominant side and resolves ties to
"long". A caller relying on the SDK's documented default behavior
without manually passing preferSide could have the SDK pick a
position on the minority side, which the program would reject.

Note on real-world impact: encodeExecuteAdl/buildAdlInstruction
currently always throw removedInstruction() (ExecuteAdl was removed
from the v17 wrapper, see test/adl.test.ts), so buildAdlTransaction's
instruction-build path is not reachable end-to-end today. The read-side
ranking functions (rankAdlPositions, fetchAdlRankedPositions) remain
exported, though, and could mislead an external keeper/dashboard that
trusts ranking.ranked[0] as "the next ADL victim" without separately
checking OI dominance.

Fix: rankAdlPositions now computes engine.longOi vs engine.shortOi and
exposes the result as AdlRankingResult.dominantSide ("long" | "short" |
null — null only when engine state can't be parsed at all). Ties
resolve to "long" to match the on-chain convention.
buildAdlTransaction's default branch (no preferSide) now targets
ranking.dominantSide's top position, falling back to the global
ranked[0] only when dominantSide is null.

Added regression tests in test/adl.test.ts: dominantSide is "long"/
"short" correctly from engine.longOi/shortOi, ties resolve to "long",
and dominantSide is null when engine parsing fails entirely (bad
magic). Also added a pure-logic test for buildAdlTransaction's
target-side branch selection. Confirmed all new tests fail against
the prior code and pass against this fix.

Full suite: 857 passed | 31 skipped (882 -> 888, +6 new tests).
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a dominantSide field to ADL ranking results, derives it from engine open interest, and updates ADL transaction target selection to use that side when no preferred side is provided. Tests cover the new ranking value and selection fallback.

Changes

ADL dominant-side flow

Layer / File(s) Summary
Ranking contract and derivation
src/solana/adl.ts, test/adl.test.ts
AdlRankingResult now includes dominantSide; rankAdlPositions computes it from engine open interest and returns it; tests cover long, short, tie, and parse-failure outcomes.
Default target selection
src/solana/adl.ts, test/adl.test.ts
buildAdlTransaction uses dominantSide when preferSide is omitted, falls back to ranked[0] when dominantSide is null, and tests cover both selection paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through the ADL trail so bright,
With long and short in balanced light.
When one side leads, I nibble and choose,
When tied or unclear, I still won’t lose.
Boing! The ranking now feels just right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main ADL change: computing dominantSide and using it as the default buildAdlTransaction target.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/solana/adl.ts`:
- Around line 205-210: The dominant-side detection in
parseEngine()/buildAdlTransaction is treating missing per-side OI as a real tie
and defaulting to "long", which incorrectly overrides the ranking.ranked[0]
fallback. Update the dominantSide assignment so it remains null unless the
detected slab layout actually includes both longOi and shortOi fields; only
derive "long"/"short" when those offsets are present, and keep the existing
fallback behavior unchanged for older layouts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c0351665-3fbb-45ed-9f5e-f908ac3996ca

📥 Commits

Reviewing files that changed from the base of the PR and between 673bc47 and b735c58.

📒 Files selected for processing (2)
  • src/solana/adl.ts
  • test/adl.test.ts

Comment thread src/solana/adl.ts
Comment on lines +205 to +210
let dominantSide: AdlSide | null = null;
try {
const engine = parseEngine(slabData);
pnlPosTot = engine.pnlPosTot;
// Ties resolve to "long" to match the on-chain target_side log convention.
dominantSide = engine.shortOi > engine.longOi ? "short" : "long";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't derive a real dominant side from missing per-side OI.

parseEngine() succeeds on some older layouts by filling engine.longOi / engine.shortOi with 0n when those offsets do not exist. This branch then turns that “unknown” state into "long", so buildAdlTransaction() stops using the ranking.ranked[0] fallback and instead biases to ranking.longs[0]. Please leave dominantSide as null unless the detected layout actually exposes both per-side OI fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/solana/adl.ts` around lines 205 - 210, The dominant-side detection in
parseEngine()/buildAdlTransaction is treating missing per-side OI as a real tie
and defaulting to "long", which incorrectly overrides the ranking.ranked[0]
fallback. Update the dominantSide assignment so it remains null unless the
detected slab layout actually includes both longOi and shortOi fields; only
derive "long"/"short" when those offsets are present, and keep the existing
fallback behavior unchanged for older layouts.

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.

1 participant