Skip to content

fix(checkStablecoin): don't flag pufETH/LanternSOL/SUNOLD as stablecoins (substring false-match)#2822

Open
Himess wants to merge 3 commits into
DefiLlama:masterfrom
Himess:fix/stablecoin-substring
Open

fix(checkStablecoin): don't flag pufETH/LanternSOL/SUNOLD as stablecoins (substring false-match)#2822
Himess wants to merge 3 commits into
DefiLlama:masterfrom
Himess:fix/stablecoin-substring

Conversation

@Himess

@Himess Himess commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

checkStablecoin sets the flag by substring-matching the pool symbol against the ~410 symbol list from https://stablecoins.llama.fi:

stable = stablecoins.some((x) =>
  x.length === 1 ? tokenClean === x : tokenClean.includes(x)
);

The list contains short, word-like symbols (feth, ern, uno, usc, usn, and others), so a volatile token that merely contains one as a substring gets flagged stable.

'pufeth'.includes('feth') is true, so Puffer's liquid restaking token is classified as a stablecoin. It trades around $3k and is ETH-correlated.

TVL project symbol matched via what it actually is
$46,603,577 puffer-stake PUFETH 'pufeth'.includes('feth') Puffer liquid-restaking ETH
$1,211,491 lantern-staked-sol LANTERNSOL 'lanternsol'.includes('ern') a Solana LST
$261,714 justlend-v1 SUNOLD 'sunold'.includes('uno') TRON SUN

(plus smaller pufETH pools on compound-v3, pendle and euler-v2.)

$47.5m of this is puffer-stake pufETH. Someone filtering the yields page for stablecoin yield sees a restaked-ETH position as stable.

Every other ETH LST/LRT in the data is correctly flagged false. pufETH is the same asset class and gets the opposite answer, purely because of the substring collision:

token stablecoin top TVL
STETH false $17.6B
WEETH false $3.28B
RETH false $2.60B
WSTETH false $2.35B
RSETH false $991M
LSETH false $613M
METH false $492M
CBETH false $305M
OSETH false $261M
EZETH false $90M
SFRXETH false $72M
SWETH false $30M
ANKRETH false $16M
FRXETH false $19k
PUFETH true $47M

All 14 siblings are false; pufETH is the only ETH LST in the dataset flagged true.

 const checkStablecoin = (el, stablecoins) => {
   let tokens = el.symbol.split('-').map((el) => el.toLowerCase());
   const symbolLC = el.symbol.toLowerCase();

+  // tokens that merely CONTAIN a stablecoin symbol substring but are NOT stablecoins.
+  // e.g. 'pufeth'.includes('feth') or 'lanternsol'.includes('ern') otherwise flag these
+  // volatile assets (restaked ETH, a SOL LST, TRON SUN) as stablecoin: true.
+  const notStable = ['pufeth', 'lanternsol', 'sunold'];
+  if (tokens.some((t) => notStable.includes(t))) return false;
+
   let stable;

Tested against the live pool set: exactly 7 pools flip true to false (PUFETH x4, LANTERNSOL x2, SUNOLD x1). 0 flip false to true. The change is one-directional by construction: forcing a known-non-stable token to false can only remove wrong stable flags, it cannot introduce a new false true. The genuine cases (SUSDE, STEAKUSDC, SDAI, SGHO all really wrap the stablecoin they match) are unchanged.

I checked how wide this collision class is: about 25 distinct symbols are affected (cash, hai, vai, use, xy, gai, ern, volt, mod, feth, uno). The dollar impact is concentrated: pufETH alone is $47.5m of roughly $50m. This patch covers the material cases. The tail is about 22 small pools totalling $1.2m.

An ends-with rule would not help here, since 'pufeth' ends with 'feth'. The symbols themselves are the problem.

This has the same root cause as #2821 (checkIlRisk, where 'ethfi'.includes('eth') mislabels ETHFI-ETH pools). Both classify tokens by symbol substring against a hand-curated list, and both exception fixes are whack-a-mole. The proper fix is one shared registry keyed on the pool's underlyingTokens addresses, which the data already carries, instead of symbol matching. That would retire both defect classes at once. If you would rather have that than two exception lists, I am happy to close both and do the rewrite instead. Just say the word.

Summary by CodeRabbit

  • Bug Fixes
    • Improved stablecoin detection by excluding token symbols associated with volatile assets.
    • Reduced the risk of incorrectly classifying non-stable tokens as stablecoins.
    • Updated adaptor processing to prevent internal validation logic from being included in generated file lists.

checkStablecoin substring-matches a single token against the stablecoins
list, so 'pufeth'.includes('feth'), 'lanternsol'.includes('ern') and
'sunold'.includes('uno') flag these volatile assets (restaked ETH, a SOL
LST, TRON SUN) as stablecoin: true. Add a notStable exception so they don't
match. 7 live pools (~$49M) flip to the correct false; 0 regressions.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

checkStablecoin rejects selected volatile-looking token parts before existing detection rules, and adaptor file-list generation excludes checkStablecoin.js.

Changes

Stablecoin detection and adaptor filtering

Layer / File(s) Summary
Add early non-stable filter
src/adaptors/checkStablecoin.js
Adds a notStable list and returns false when any lowercased token part matches an excluded entry.
Exclude checker from adaptor listing
.github/workflows/getFileList.js
Excludes checkStablecoin.js from the computed adaptor fileSet.

Estimated code review effort: 1 (Trivial) | ~5 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: preventing false stablecoin matches for specific volatile tokens.
✨ 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
Contributor

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/adaptors/checkStablecoin.js`:
- Around line 8-9: Normalize each token by removing the same parenthetical
annotations used by the later stablecoin match before applying the notStable
guard in checkStablecoin. Ensure annotated pufeth, lanternsol, and sunold values
are excluded, and add regression coverage for these annotated forms.
🪄 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: b5b1f2bf-369c-49e3-aabf-c5daf9b21926

📥 Commits

Reviewing files that changed from the base of the PR and between 732246c and 52742ee.

📒 Files selected for processing (1)
  • src/adaptors/checkStablecoin.js

Comment thread src/adaptors/checkStablecoin.js Outdated
Himess added 2 commits July 16, 2026 11:40
checkStablecoin.js is a shared helper (like utils.js), not a project adapter,
so 'npm run test --adapter=checkStablecoin.js' fails with 'module.apy is not a
function'. Exclude it from getFileList.js the same way utils.js/test.js are.
… guard

Match the cleanup the later stablecoin check already applies, so annotated
forms like 'pufeth (...)' also hit the exclusion.
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