Skip to content

track euler fees & revenue #2708

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

Merged
merged 8 commits into from
Apr 5, 2025
136 changes: 136 additions & 0 deletions fees/euler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Interface } from "ethers";
import { Adapter, FetchOptions, SimpleAdapter } from "../../adapters/types"
import { CHAIN } from "../../helpers/chains"
import * as sdk from "@defillama/sdk";

const eVaultFactories = {
[CHAIN.ETHEREUM]: "0x29a56a1b8214D9Cf7c5561811750D5cBDb45CC8e",
[CHAIN.SONIC]: "0xF075cC8660B51D0b8a4474e3f47eDAC5fA034cFB",
[CHAIN.BASE]: "0x7F321498A801A191a93C840750ed637149dDf8D0"
}

const eulerFactoryABI = {
vaultLength: "function getProxyListLength() view returns (uint256)",
getProxyListSlice: "function getProxyListSlice(uint256 start, uint256 end) view returns (address[] list)",
}

const eulerVaultABI = {
asset: "function asset() view returns (address)",
interestAccumulator: "function interestAccumulator() view returns (uint256)",
accumulatedFees: "function accumulatedFees() view returns (uint256)",
totalBorrows: "function totalBorrows() view returns (uint256)",
convertToAssets: "function convertToAssets(uint256 shares) view returns (uint256)",
convertFees: "event ConvertFees(address indexed account, address indexed protocolReceiver, address indexed governorReceiver, uint256 protocolShares, uint256 governorShares)",
vaultStatus: "event VaultStatus(uint256 totalShares, uint256 totalBorrows, uint256 accumulatedFees, uint256 cash, uint256 interestAccumulator, uint256 interestRate, uint256 timestamp)"
}

const getVaults = async ({createBalances, api, fromApi, toApi, getLogs, chain}: FetchOptions, {
dailyFees,
dailyRevenue,
}: {
dailyFees?: sdk.Balances,
dailyRevenue?: sdk.Balances,
}) => {

if (!dailyFees) dailyFees = createBalances()
if (!dailyRevenue) dailyRevenue = createBalances()
const vaultLength = await fromApi.call({target: eVaultFactories[chain], abi: eulerFactoryABI.vaultLength})
const vaults = await fromApi.call({target: eVaultFactories[chain], abi: eulerFactoryABI.getProxyListSlice, params: [0, vaultLength]})
const underlyings = await fromApi.multiCall({calls: vaults, abi: eulerVaultABI.asset})
underlyings.forEach((underlying, index) => {
if (!underlying) underlyings[index] = '0x0000000000000000000000000000000000000000'
})

const accumulatedFeesStart = await fromApi.multiCall({calls: vaults, abi: eulerVaultABI.accumulatedFees})
const accumulatedFeesEnd = await toApi.multiCall({calls: vaults, abi: eulerVaultABI.accumulatedFees})
const interestAccumulatorStart = await fromApi.multiCall({calls: vaults, abi: eulerVaultABI.interestAccumulator})
const interestAccumulatorEnd = await toApi.multiCall({calls: vaults, abi: eulerVaultABI.interestAccumulator})
const totalBorrows = await toApi.multiCall({calls: vaults, abi: eulerVaultABI.totalBorrows})
const dailyInterest = totalBorrows.map((borrow, i) => borrow * (interestAccumulatorEnd[i] - interestAccumulatorStart[i]) / interestAccumulatorStart[i])

const logs = (await getLogs({targets: vaults, eventAbi: eulerVaultABI.convertFees, flatten:false, entireLog: true}))
Copy link
Member

Choose a reason for hiding this comment

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

this is a bit of an edge case, if target/targets param is not passed, it will look for all events matching a given topic, so, good to add a check if vaults.length > 0 here or at the start and return empty object if there are no vaults

const iface = new Interface([eulerVaultABI.convertFees]);

const processedLogs = logs.map((vaultLogs, index) => {
if (!vaultLogs.length) return 0n;
const vaultAddress = vaults[index].toLowerCase();
let totalShares = 0n;
for (const log of vaultLogs) {
try {
const parsedLog = iface.parseLog({
Copy link
Member

Choose a reason for hiding this comment

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

if you pass eventAbi, the data should already be parsed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i need the addresses so i used entireLog: true, i'm not sure i can get the address without entireLog

Copy link
Member

Choose a reason for hiding this comment

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

yeah, but why do you need the addresses in the first place?

topics: log.topics,
data: log.data
});
if (!parsedLog || log.address.toLowerCase() !== vaultAddress) continue;
Copy link
Member

Choose a reason for hiding this comment

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

log.address.toLowerCase() !== vaultAddress this should never happen

Copy link
Member

Choose a reason for hiding this comment

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

when I think about it, log parsing should also never fail

// add protocol and governor shares - accumulates for all events from this vault
totalShares += (parsedLog.args.protocolShares + parsedLog.args.governorShares);
} catch (error) {
continue;
}
}

return totalShares;
});

//calculate (accumulatedFeesEnd - accumulatedFeesStart) + totalShares from convertFees
const accumulatedFees = accumulatedFeesEnd.map((fees, i) => {
const feesEnd = BigInt(fees.toString());
const feesStart = BigInt(accumulatedFeesStart[i].toString());
return feesEnd - feesStart + processedLogs[i];
});

//we then convert the accumulatedFees to asset by calling convertToAssets at the end therefore we won't have any problem with conversion rate changing
const totalAssets = await toApi.multiCall({
calls: accumulatedFees.map((fees, i) => ({
target: vaults[i],
params: [fees.toString()]
})),
abi: eulerVaultABI.convertToAssets,
});

totalAssets.forEach((assets, i) => {
dailyFees.add(underlyings[i], dailyInterest[i])
dailyRevenue.add(underlyings[i], assets)
})

return {dailyFees,dailyRevenue}
Copy link
Member

Choose a reason for hiding this comment

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

can also track dailySupplySideRevenue as

const dailySupplySideRevenue = dailyFees.clone()
dailySupplySideRevenue.subtract(dailyRevenue)

}

const fetch = async (options: FetchOptions) => {
return await getVaults(options, {})
}

const methodology = {
dailyFees: "Interest that are paid by the borrowers to the vaults",
dailyRevenue: "Protocol & Governor fees share"
}

const adapters: Adapter = {
adapter: {
[CHAIN.ETHEREUM]: {
fetch: fetch,
start: '2024-08-18',
meta: {
methodology
}
},
[CHAIN.SONIC]: {
fetch: fetch,
start: '2025-01-31',
meta: {
methodology
}
},
[CHAIN.BASE]: {
fetch: fetch,
start: '2024-11-27',
meta: {
methodology
}
}
},

version: 2
}

export default adapters;
Loading