-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: Olympus DAO comprehensive fees adapter (multi-chain, additive approach) #5706
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
base: master
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughA new Olympus DAO Fees Adapter is introduced that tracks fee and revenue metrics across Ethereum, Base, and Arbitrum. The adapter fetches data from multiple on-chain sources including Cooler loans, ERC-4626 yield contracts, CD Facility, and liquidity pool positions, then aggregates the collected metrics into daily summaries. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 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
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. Comment |
|
The olympus-dao.ts adapter exports: |
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: 3
🤖 Fix all issues with AI agents
In @.ralph/IMPLEMENTATION_PLAN.md:
- Around line 17-18: The implementation plan currently states sUSDe/POL revenues
should go to dailyProtocolRevenue and not HoldersRevenue, but the adapter routes
all revenue to dailyHoldersRevenue; update the IMPLEMENTATION_PLAN.md tasks
(Tasks 7–8) to reflect the actual routing by replacing references to
dailyProtocolRevenue and “not HoldersRevenue” with the implemented
dailyHoldersRevenue behavior, explicitly noting that sUSDe yield and POL
(Uniswap V3) fees are included in dailyFees and aggregated under
dailyHoldersRevenue via the adapter, and mention the adapter as the source of
truth for routing decisions.
In `@fees/olympus-dao.ts`:
- Around line 114-119: The bond deposit handling loop (iterating bondLogs)
currently adds bondAmount to dailyFees and dailyRevenue but omits
dailyHoldersRevenue; update the loop where bondAmount is derived (const
bondAmount = log.amount) to also call dailyHoldersRevenue.add(CONTRACTS.dai,
bondAmount) so Bond revenue is treated as 100% holder-benefiting revenue
consistent with YRF buybacks, POL fees, and sUSDe yield handling.
- Around line 96-103: The repo market handling currently treats RepoMarket
bidAmount as DAI; update the logic to record these amounts as USDS instead:
replace uses of CONTRACTS.dai with CONTRACTS.usds when adding bidAmount from
repoMarketLogs (affecting dailyFees.add, dailyRevenue.add,
dailyHoldersRevenue.add for the loop that reads log.bidAmount). Also review
similar bond depository handling to ensure each market/bond resolves its actual
quote/reserve token rather than assuming DAI — if markets can use different
reserve tokens, switch to resolving per-market reserve token before calling
dailyFees.add/dailyRevenue.add/dailyHoldersRevenue.add.
🧹 Nitpick comments (1)
fees/olympus-dao.ts (1)
141-168: Cache Uniswap V3 position lookups to reduce RPC load.Each Collect log triggers a
positions()call; repeated tokenIds will cause redundant calls and slow daily runs. Cache token0/token1 by tokenId.♻️ Suggested refactor
- // Process each collect event - need to look up token pair for each position - for (const log of treasuryCollects) { - const tokenId = log.tokenId; + // Process each collect event - need to look up token pair for each position + const positionCache = new Map<string, { token0: string; token1: string }>(); + for (const log of treasuryCollects) { + const tokenId = String(log.tokenId); const amount0 = log.amount0; const amount1 = log.amount1; // Get position details to know which tokens were collected try { - const position = await options.api.call({ - abi: ABIS.positions, - target: CONTRACTS.uniswapV3PositionManager, - params: [tokenId], - }); - - const token0 = position.token0; - const token1 = position.token1; + let position = positionCache.get(tokenId); + if (!position) { + const rawPosition = await options.api.call({ + abi: ABIS.positions, + target: CONTRACTS.uniswapV3PositionManager, + params: [tokenId], + }); + position = { token0: rawPosition.token0, token1: rawPosition.token1 }; + positionCache.set(tokenId, position); + } + + const { token0, token1 } = position;
.ralph/IMPLEMENTATION_PLAN.md
Outdated
| - [x] Task 7: Integrate sUSDe yield into dailyFees and dailyProtocolRevenue - Add the calculated sUSDe yield to the fee tracking (not HoldersRevenue) | ||
| - [x] Task 8: Integrate POL fees into dailyFees and dailyProtocolRevenue - Add collected Uniswap V3 fees to protocol revenue tracking |
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.
Implementation plan conflicts with holdersRevenue routing.
Tasks 7–8 say sUSDe/POL should not hit HoldersRevenue and reference dailyProtocolRevenue, but the adapter now routes all revenue to dailyHoldersRevenue. Please update the plan to match the implemented methodology.
🤖 Prompt for AI Agents
In @.ralph/IMPLEMENTATION_PLAN.md around lines 17 - 18, The implementation plan
currently states sUSDe/POL revenues should go to dailyProtocolRevenue and not
HoldersRevenue, but the adapter routes all revenue to dailyHoldersRevenue;
update the IMPLEMENTATION_PLAN.md tasks (Tasks 7–8) to reflect the actual
routing by replacing references to dailyProtocolRevenue and “not HoldersRevenue”
with the implemented dailyHoldersRevenue behavior, explicitly noting that sUSDe
yield and POL (Uniswap V3) fees are included in dailyFees and aggregated under
dailyHoldersRevenue via the adapter, and mention the adapter as the source of
truth for routing decisions.
|
The olympus-dao.ts adapter exports: |
|
The olympus-dao.ts adapter exports: |
|
The olympus-dao.ts adapter exports: |
Implements a multi-chain fees adapter for Olympus DAO tracking all protocol revenue sources using an additive approach. Revenue sources tracked: Ethereum: - Cooler Loan interest (MonoCooler accumulator delta method) - sUSDS yield (ERC-4626 exchange rate method) - sUSDe yield (ERC-4626 exchange rate method) - CD Facility yield (ClaimedYield events) - CD Lending interest (LoanRepaid events) - POL fees from Uniswap V3 positions (OHM/WETH, OHM/sUSDS) Base: - POL fees from Uniswap V3 (OHM/USDC) Arbitrum: - POL fees from Camelot V2 (WETH/OHM swap volume) Key implementation details: - All revenue classified as holders revenue (backs OHM treasury) - Fixes CD Facility/sUSDS yield overlap to prevent ~0.02% double counting - Uses event-based tracking for claimed yields and loan repayments - Supports multiple treasury addresses per chain Co-Authored-By: Claude Opus 4.5 <[email protected]>
f6ae4d5 to
6067e33
Compare
|
The olympus-dao.ts adapter exports: |
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 `@fees/olympus-dao.ts`:
- Around line 436-454: The code assumes Olympus owns ~100% of the Camelot V2 LP
but never validates that, so either document the assumption or adjust fee
calculation by scaling with actual LP ownership; update the block using
FEE_RATE, swapLogs, and fees.add to (a) add a clear comment near FEE_RATE
stating this is a hardcoded assumption and a TODO to validate ownership, or (b)
implement a runtime check that queries the pool for totalSupply and Olympus LP
token balance, compute ownershipShare = olympusBalance / totalSupply and
multiply fee0/fee1 by ownershipShare before calling fees.add (use
CHAIN_CONFIG.arbitrum addresses to identify tokens). Ensure the chosen approach
is clearly noted in comments and that any new async lookup is performed before
iterating swapLogs.
🧹 Nitpick comments (3)
fees/olympus-dao.ts (3)
180-189: Potential timing inconsistency in Cooler interest calculation.The calculation uses
totalDebtat period end (api.call) but measures accumulator delta across the period. If debt changed significantly during the period, this could over/understate interest. For more accurate measurement, consider using average debt or the debt at period start.However, given that debt changes are likely gradual and this is a daily adapter, the impact should be minimal.
214-223: Balance snapshot timing may affect yield accuracy.The function uses
api(end-of-period) for balance but measures rate delta across the period. If the treasury deposited/withdrew during the period, this could slightly over/understate yield. For higher accuracy, consider using average balance or start-of-period balance.This is a minor concern since deposits/withdrawals are infrequent for treasury positions.
365-420: Unused functionfetchCamelotPOLFees.This function is defined but never called anywhere in the adapter. The Arbitrum fetch uses
fetchCamelotV2Feesinstead. If this was intended for Camelot V3/Algebra positions, it should be integrated; otherwise, remove it to reduce dead code.♻️ Suggested action
Either remove the unused function:
-/** - * Fetch Camelot (Algebra) POL fees on Arbitrum - */ -async function fetchCamelotPOLFees( - options: FetchOptions, - nftManager: string, - treasuryAddresses: string[] -) { - const fees = options.createBalances(); - - try { - const collectLogs = await options.getLogs({ - target: nftManager, - eventAbi: EVENTS.algebraCollect, - }); - - // Filter for collects where recipient is a treasury address - const treasuryCollects = collectLogs.filter( - (log: any) => treasuryAddresses.includes(log.recipient.toLowerCase()) - ); - - // Cache position lookups - const positionCache = new Map<string, { token0: string; token1: string }>(); - - for (const log of treasuryCollects) { - const tokenId = String(log.tokenId); - const amount0 = log.amount0; - const amount1 = log.amount1; - - try { - let positionData = positionCache.get(tokenId); - if (!positionData) { - const position = await options.api.call({ - abi: ABIS.algebraPositions, - target: nftManager, - params: [tokenId], - }); - positionData = { token0: position.token0, token1: position.token1 }; - positionCache.set(tokenId, positionData); - } - - const { token0, token1 } = positionData; - - if (BigInt(amount0) > 0) { - fees.add(token0, amount0); - } - if (BigInt(amount1) > 0) { - fees.add(token1, amount1); - } - } catch (e) { - // Position may have been burned - } - } - } catch (e) { - // POL fee tracking may fail in periods with no collects - } - - return fees; -}Or if Camelot V3/Algebra positions exist, integrate it into
fetchArbitrum.
| // Camelot V2 fee is 0.3% (30 bps) of input amounts | ||
| // Since Olympus owns ~100% of LP, they earn ~100% of LP fees | ||
| // LP fees are typically 0.25% (protocol takes 0.05%) | ||
| const FEE_RATE = BigInt(25); // 0.25% = 25 bps | ||
| const BPS = BigInt(10000); | ||
|
|
||
| for (const log of swapLogs) { | ||
| const amount0In = BigInt(log.amount0In); | ||
| const amount1In = BigInt(log.amount1In); | ||
|
|
||
| // Fee is calculated on input amount | ||
| if (amount0In > 0) { | ||
| const fee0 = (amount0In * FEE_RATE) / BPS; | ||
| fees.add(CHAIN_CONFIG.arbitrum.weth, fee0); | ||
| } | ||
| if (amount1In > 0) { | ||
| const fee1 = (amount1In * FEE_RATE) / BPS; | ||
| fees.add(CHAIN_CONFIG.arbitrum.ohm, fee1); | ||
| } |
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.
🧩 Analysis chain
🏁 Script executed:
git ls-files | grep -i olympusRepository: DefiLlama/dimension-adapters
Length of output: 91
🏁 Script executed:
find . -type f -name "*olympus*" -o -name "fees" -type dRepository: DefiLlama/dimension-adapters
Length of output: 100
🏁 Script executed:
fd "olympus" --type fRepository: DefiLlama/dimension-adapters
Length of output: 91
🏁 Script executed:
rg "Camelot.*V2|0x8acd42e4b5a5750b44a28c5fb50906ebff145359" -i --type tsRepository: DefiLlama/dimension-adapters
Length of output: 1279
🏁 Script executed:
rg "fetchCamelotV2Fees|FEE_RATE.*25" --type ts -B 5 -A 10Repository: DefiLlama/dimension-adapters
Length of output: 4466
🏁 Script executed:
cat -n fees/olympus-dao.ts | head -100Repository: DefiLlama/dimension-adapters
Length of output: 4361
🏁 Script executed:
cat -n fees/olympus-dao.ts | tail -100Repository: DefiLlama/dimension-adapters
Length of output: 3909
🏁 Script executed:
wc -l fees/olympus-dao.tsRepository: DefiLlama/dimension-adapters
Length of output: 95
🏁 Script executed:
sed -n '420,470p' fees/olympus-dao.tsRepository: DefiLlama/dimension-adapters
Length of output: 1534
🏁 Script executed:
rg "ownership|totalSupply|balanceOf|LP.*100|Olympus.*own" fees/olympus-dao.ts -B 2 -A 2Repository: DefiLlama/dimension-adapters
Length of output: 919
🏁 Script executed:
rg "0x8acd42e4b5a5750b44a28c5fb50906ebff145359|camelotV2Pool" --type ts -B 3 -A 3Repository: DefiLlama/dimension-adapters
Length of output: 829
🏁 Script executed:
head -150 fees/olympus-dao.tsRepository: DefiLlama/dimension-adapters
Length of output: 6132
🏁 Script executed:
rg "liquidity|LP.*token|share|lpToken" fees/olympus-dao.ts -B 2 -A 2Repository: DefiLlama/dimension-adapters
Length of output: 1195
🏁 Script executed:
sed -n '380,430p' fees/olympus-dao.tsRepository: DefiLlama/dimension-adapters
Length of output: 1530
🏁 Script executed:
sed -n '427,470p' fees/olympus-dao.tsRepository: DefiLlama/dimension-adapters
Length of output: 1329
🏁 Script executed:
rg "lpToken|liquidity.*balance|totalSupply.*arbitrum" fees/olympus-dao.tsRepository: DefiLlama/dimension-adapters
Length of output: 54
🏁 Script executed:
rg "ownership|LP.*balance|share.*protocol" dexs/ fees/ --type ts -l | head -5Repository: DefiLlama/dimension-adapters
Length of output: 173
🏁 Script executed:
fd "camelot" --type f | head -10Repository: DefiLlama/dimension-adapters
Length of output: 90
🏁 Script executed:
cat -n dexs/camelot/index.ts | head -80Repository: DefiLlama/dimension-adapters
Length of output: 3180
🏁 Script executed:
rg "100%|ownership|LP.*Olympus" fees/olympus-dao.tsRepository: DefiLlama/dimension-adapters
Length of output: 332
🏁 Script executed:
sed -n '1,100p' fees/camelot-v3.tsRepository: DefiLlama/dimension-adapters
Length of output: 137
🏁 Script executed:
rg "Olympus.*LP|LP.*ownership|100%.*LP" . --type ts --type mdRepository: DefiLlama/dimension-adapters
Length of output: 3012
🏁 Script executed:
grep -r "0x8acd42e4b5a5750b44a28c5fb50906ebff145359" . --include="*.md" --include="*.ts"Repository: DefiLlama/dimension-adapters
Length of output: 158
Document or validate the 100% LP ownership assumption.
The code assumes Olympus owns ~100% of the Camelot V2 LP (stated in the function docstring) and applies a fixed 0.25% fee rate to all swap inputs. However, there is no runtime validation of actual LP token ownership. If Olympus's share of the pool decreases, this calculation will overstate fees.
Add a comment clarifying this is a known constraint, or implement a check that queries the pool's LP token balance to scale fees accurately.
🤖 Prompt for AI Agents
In `@fees/olympus-dao.ts` around lines 436 - 454, The code assumes Olympus owns
~100% of the Camelot V2 LP but never validates that, so either document the
assumption or adjust fee calculation by scaling with actual LP ownership; update
the block using FEE_RATE, swapLogs, and fees.add to (a) add a clear comment near
FEE_RATE stating this is a hardcoded assumption and a TODO to validate
ownership, or (b) implement a runtime check that queries the pool for
totalSupply and Olympus LP token balance, compute ownershipShare =
olympusBalance / totalSupply and multiply fee0/fee1 by ownershipShare before
calling fees.add (use CHAIN_CONFIG.arbitrum addresses to identify tokens).
Ensure the chosen approach is clearly noted in comments and that any new async
lookup is performed before iterating swapLogs.
Summary
Comprehensive fees adapter for Olympus DAO tracking all protocol revenue sources across multiple chains using an additive approach (direct measurement of each revenue stream).
Revenue Sources Tracked
Ethereum:
ClaimedYieldeventsLoanRepaideventsCollecteventsBase:
CollecteventsArbitrum:
Swapevents × 0.25%Key Design Decisions
Pending Revenue Note
CD Lending currently has ~$437k in outstanding loans with ~$6k in fixed interest. This interest will be realized when loans mature (April-May 2026) and automatically captured via
LoanRepaidevents.Test Results
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.