-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add LazyUSD yield adapter #2266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
LazyUSD is a delta-neutral yield vault on Ethereum that deploys USDC across lending protocols, DEXs, and derivatives platforms to generate sustainable yields while maintaining full USDC backing. The adapter calculates APY based on share price changes over a 7-day window, falling back to accumulated yield for new vaults.
📝 WalkthroughWalkthroughAdds a new LazyUSD adapter that reads a Vault contract on Ethereum to compute TVL and APY (7-day comparison with 1-day fallback) and returns a standardized pool object with fields like pool, chain, project, symbol, tvlUsd, apyBase, underlyingTokens, and url. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller
participant Module as LazyUSD Module
participant RPC as Ethereum RPC
participant SDK as DefiLlama SDK
Caller->>Module: apy()
Module->>RPC: read current sharePrice & totalAssets
RPC-->>Module: current data
Module->>RPC: read historical block (7d ago)
alt historical data present
RPC-->>Module: historical sharePrice
Module->>Module: compute APY from price change (annualize)
else historical data missing
RPC-->>Module: no historical price
Module->>RPC: fetch recent accumulatedYield / deposits
RPC-->>Module: accumulated data
Module->>Module: compute fallback APY from observed change
end
Module->>Caller: return array with pool object (tvlUsd, apyBase, etc.)
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)src/adaptors/lazyusd/index.js (1)
🔇 Additional comments (4)
Comment |
|
Error while running lazyusd adapter: Test Suites: 1 failed, 1 total |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In @src/adaptors/lazyusd/index.js:
- Around line 40-42: Prevent division by zero when computing APY: before
calculating priceChange and apyBase, check if historicalSharePrice is 0 or falsy
and handle that case (e.g., set apyBase = 0 or null or skip the computation)
instead of performing (currentSharePrice - historicalSharePrice) /
historicalSharePrice; update the logic around priceChange, historicalSharePrice,
currentSharePrice and apyBase to only compute priceChange and apyBase when
historicalSharePrice is nonzero, otherwise assign a safe default.
- Around line 61-64: The APY fallback uses a hardcoded daysLive = 6 and divides
by totalDeposited, which will become incorrect as the vault ages and can divide
by zero; replace the hardcoded value by computing daysLive from the vault
launch/deployment timestamp (e.g., fetch a deployment timestamp from the
contract or vault metadata and compute (now - launchTimestamp)/86400) and add
defensive checks: only compute yieldRate/apyBase when totalDeposited > 0, and
ensure daysLive is at least 1 (or use a minimum clamp) to avoid division by
zero; update the calculation around the variables daysLive, yieldRate,
accumulatedYield, totalDeposited, and apyBase accordingly and default apyBase to
0 when data is insufficient.
🧹 Nitpick comments (1)
src/adaptors/lazyusd/index.js (1)
43-51: Catch block swallows all errors, not just "vault didn't exist".Network failures, RPC errors, or other transient issues would trigger the fallback calculation, potentially returning incorrect APY data. Consider checking the error type or logging for observability.
♻️ Suggested improvement
} catch (e) { - // Vault might not have existed 7 days ago, use accumulated yield instead + // Vault might not have existed 7 days ago, use accumulated yield instead + console.log(`Historical share price unavailable, using fallback: ${e.message}`); const accumulatedYield = (
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/adaptors/lazyusd/index.js
🧰 Additional context used
🧬 Code graph analysis (1)
src/adaptors/lazyusd/index.js (1)
src/adaptors/beforeTests.js (1)
module(55-55)
🔇 Additional comments (4)
src/adaptors/lazyusd/index.js (4)
1-4: LGTM!Constants are well-defined with clear naming. The contract addresses match the expected format.
6-23: LGTM!The share price and TVL fetching logic is clean and correctly handles USDC's 6 decimal places.
67-76: LGTM!The pool structure follows the expected DefiLlama adapter format with proper lowercase pool ID construction.
79-83: Thetimetravel: falsesetting is correct.The adapter does not accept a
timestampparameter for external historical queries. While it internally uses a fixed historical block (7 days ago) for APY calculation, thetimetravelflag indicates whether the adapter function accepts an external timestamp parameter—which lazyusd does not. Adapters withtimetravel: true(like smardex-usdn) accept a timestamp parameter and can be queried at arbitrary historical points.
src/adaptors/lazyusd/index.js
Outdated
| // Calculate 7-day APY | ||
| const priceChange = (currentSharePrice - historicalSharePrice) / historicalSharePrice; | ||
| apyBase = (priceChange / 7) * 365 * 100; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential division by zero if historicalSharePrice is 0.
If the vault's historical share price was 0 (edge case during initialization), this would produce Infinity or NaN for the APY.
🛡️ Suggested defensive check
// Calculate 7-day APY
+ if (historicalSharePrice === 0) {
+ throw new Error('Historical share price is zero');
+ }
const priceChange = (currentSharePrice - historicalSharePrice) / historicalSharePrice;
apyBase = (priceChange / 7) * 365 * 100;🤖 Prompt for AI Agents
In @src/adaptors/lazyusd/index.js around lines 40 - 42, Prevent division by zero
when computing APY: before calculating priceChange and apyBase, check if
historicalSharePrice is 0 or falsy and handle that case (e.g., set apyBase = 0
or null or skip the computation) instead of performing (currentSharePrice -
historicalSharePrice) / historicalSharePrice; update the logic around
priceChange, historicalSharePrice, currentSharePrice and apyBase to only compute
priceChange and apyBase when historicalSharePrice is nonzero, otherwise assign a
safe default.
- Use vault deploy block to calculate available history - Try 7-day window first, fall back to 1-day if vault is newer - Remove hardcoded daysLive that would become stale
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @src/adaptors/lazyusd/index.js:
- Around line 48-49: The calculation for apyBase uses historicalSharePrice
without guarding against zero, which will produce Infinity; update the logic in
the block that computes priceChange and apyBase to check historicalSharePrice
(and possibly very small epsilon) before dividing: if historicalSharePrice is
zero or nearly zero, set apyBase to 0 (or skip the calculation) instead of
performing the division; adjust references in this change to the variables
priceChange, historicalSharePrice, daysForCalc, and apyBase so callers receive a
safe numeric value.
🧹 Nitpick comments (2)
src/adaptors/lazyusd/index.js (2)
11-25: Consider parallelizing independent RPC calls.The
currentSharePriceandtotalAssetscalls are independent and could be fetched concurrently usingPromise.allto reduce latency.♻️ Suggested refactor
- const currentSharePrice = ( - await sdk.api.abi.call({ + const [sharePriceResult, totalAssetsResult] = await Promise.all([ + sdk.api.abi.call({ target: VAULT, abi: 'function sharePrice() view returns (uint256)', chain: 'ethereum', - }) - ).output / 1e6; - - const totalAssets = ( - await sdk.api.abi.call({ + }), + sdk.api.abi.call({ target: VAULT, abi: 'function totalAssets() view returns (uint256)', chain: 'ethereum', - }) - ).output / 1e6; + }), + ]); + + const currentSharePrice = sharePriceResult.output / 1e6; + const totalAssets = totalAssetsResult.output / 1e6;
32-36: Edge case: Vault less than 1 day old returns zero APY.If the vault is less than 1 day old, the 1-day fallback will still be before
VAULT_DEPLOY_BLOCK, causingapyBaseto remain 0. Consider using the maximum available history from the deploy block instead:♻️ Suggested improvement to use all available history
// Fall back to 1-day if vault is less than 7 days old if (historicalBlock < VAULT_DEPLOY_BLOCK) { - historicalBlock = latestBlock.number - blocksPerDay; - daysForCalc = 1; + historicalBlock = VAULT_DEPLOY_BLOCK; + const blocksElapsed = latestBlock.number - VAULT_DEPLOY_BLOCK; + daysForCalc = blocksElapsed / blocksPerDay; }This ensures the adapter uses all available history rather than defaulting to fixed windows, providing better APY estimates for vaults between 1-7 days old.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/adaptors/lazyusd/index.js
🧰 Additional context used
🧬 Code graph analysis (1)
src/adaptors/lazyusd/index.js (1)
src/adaptors/beforeTests.js (1)
module(55-55)
🔇 Additional comments (3)
src/adaptors/lazyusd/index.js (3)
1-5: LGTM!Constants are appropriately defined with checksummed addresses. The deploy block constant enables proper historical data bounds checking.
64-68: LGTM!Module exports are correctly structured with
timetravel: false(appropriate since this adapter doesn't support historical queries via the standard API), theapyfunction, and the project URL.
52-61: Pool structure is correct and ready; protocol slug registration pending TVL adapter PR.The return object follows the DefiLlama adapter format correctly. The
project: 'lazyusd'slug is not yet registered in the DefiLlama protocol registry, but this is expected and depends on PR #17673 being merged to add the TVL adapter first. No changes needed in this code.
| const priceChange = (currentSharePrice - historicalSharePrice) / historicalSharePrice; | ||
| apyBase = (priceChange / daysForCalc) * 365 * 100; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential division by zero if historical share price is zero.
If historicalSharePrice is 0 (unlikely but possible during vault initialization), line 48 will produce Infinity. Consider adding a guard:
🛡️ Suggested guard
+ if (historicalSharePrice === 0) {
+ return [{ /* ... pool object with apyBase: 0 ... */ }];
+ }
const priceChange = (currentSharePrice - historicalSharePrice) / historicalSharePrice;
apyBase = (priceChange / daysForCalc) * 365 * 100;🤖 Prompt for AI Agents
In @src/adaptors/lazyusd/index.js around lines 48 - 49, The calculation for
apyBase uses historicalSharePrice without guarding against zero, which will
produce Infinity; update the logic in the block that computes priceChange and
apyBase to check historicalSharePrice (and possibly very small epsilon) before
dividing: if historicalSharePrice is zero or nearly zero, set apyBase to 0 (or
skip the calculation) instead of performing the division; adjust references in
this change to the variables priceChange, historicalSharePrice, daysForCalc, and
apyBase so callers receive a safe numeric value.
- Corrected VAULT_DEPLOY_BLOCK from 21763550 to 24181000 (Jan 7, 2026) - Use ethers.js directly instead of SDK for more reliable ABI parsing - Calculate APY using 7-day window (falls back to 1-day for new vaults)
|
Error while running lazyusd adapter: Test Suites: 1 failed, 1 total |
Summary
Test output
Test plan
npm run test --adapter=lazyusd- all relevant tests passSummary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.