Skip to content
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

Revert "Revert "Fluid improvements"" #2487

Merged
merged 1 commit into from
Mar 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions fees/fluid/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ export const ABI: any = {
getAllVaultsAddresses: "function getAllVaultsAddresses() external view returns (address[] vaults_)",
getVaultEntireData: "function getVaultEntireData(address vault_) view returns ((address vault, (address liquidity, address factory, address adminImplementation, address secondaryImplementation, address supplyToken, address borrowToken, uint8 supplyDecimals, uint8 borrowDecimals, uint256 vaultId, bytes32 liquiditySupplyExchangePriceSlot, bytes32 liquidityBorrowExchangePriceSlot, bytes32 liquidityUserSupplySlot, bytes32 liquidityUserBorrowSlot) constantVariables, (uint16 supplyRateMagnifier, uint16 borrowRateMagnifier, uint16 collateralFactor, uint16 liquidationThreshold, uint16 liquidationMaxLimit, uint16 withdrawalGap, uint16 liquidationPenalty, uint16 borrowFee, address oracle, uint256 oraclePriceOperate, uint256 oraclePriceLiquidate, address rebalancer) configs, (uint256 lastStoredLiquiditySupplyExchangePrice, uint256 lastStoredLiquidityBorrowExchangePrice, uint256 lastStoredVaultSupplyExchangePrice, uint256 lastStoredVaultBorrowExchangePrice, uint256 liquiditySupplyExchangePrice, uint256 liquidityBorrowExchangePrice, uint256 vaultSupplyExchangePrice, uint256 vaultBorrowExchangePrice, uint256 supplyRateVault, uint256 borrowRateVault, uint256 supplyRateLiquidity, uint256 borrowRateLiquidity, uint256 rewardsRate) exchangePricesAndRates, (uint256 totalSupplyVault, uint256 totalBorrowVault, uint256 totalSupplyLiquidity, uint256 totalBorrowLiquidity, uint256 absorbedSupply, uint256 absorbedBorrow) totalSupplyAndBorrow, (uint256 withdrawLimit, uint256 withdrawableUntilLimit, uint256 withdrawable, uint256 borrowLimit, uint256 borrowableUntilLimit, uint256 borrowable, uint256 borrowLimitUtilization, uint256 minimumBorrowing) limitsAndAvailability, (uint256 totalPositions, int256 topTick, uint256 currentBranch, uint256 totalBranch, uint256 totalBorrow, uint256 totalSupply, (uint256 status, int256 minimaTick, uint256 debtFactor, uint256 partials, uint256 debtLiquidity, uint256 baseBranchId, int256 baseBranchMinima) currentBranchState) vaultState, (bool modeWithInterest, uint256 supply, uint256 withdrawalLimit, uint256 lastUpdateTimestamp, uint256 expandPercent, uint256 expandDuration, uint256 baseWithdrawalLimit, uint256 withdrawableUntilLimit, uint256 withdrawable) liquidityUserSupplyData, (bool modeWithInterest, uint256 borrow, uint256 borrowLimit, uint256 lastUpdateTimestamp, uint256 expandPercent, uint256 expandDuration, uint256 baseBorrowLimit, uint256 maxBorrowLimit, uint256 borrowableUntilLimit, uint256 borrowable, uint256 borrowLimitUtilization) liquidityUserBorrowData) vaultData_)",
},
vaultResolverSmart: {
getAllVaultsAddresses: "function getAllVaultsAddresses() external view returns (address[] vaults_)",
getVaultEntireData: "function getVaultEntireData(address vault_) view returns ((address vault, bool isSmartCol, bool isSmartDebt, (address liquidity, address factory, address operateImplementation, address adminImplementation, address secondaryImplementation, address deployer, address supply, address borrow, (address token0, address token1) supplyToken, (address token0, address token1) borrowToken, uint256 vaultId, uint256 vaultType, bytes32 supplyExchangePriceSlot, bytes32 borrowExchangePriceSlot, bytes32 userSupplySlot, bytes32 userBorrowSlot) constantVariables, (uint16 supplyRateMagnifier, uint16 borrowRateMagnifier, uint16 collateralFactor, uint16 liquidationThreshold, uint16 liquidationMaxLimit, uint16 withdrawalGap, uint16 liquidationPenalty, uint16 borrowFee, address oracle, uint256 oraclePriceOperate, uint256 oraclePriceLiquidate, address rebalancer, uint256 lastUpdateTimestamp) configs, (uint256 lastStoredLiquiditySupplyExchangePrice, uint256 lastStoredLiquidityBorrowExchangePrice, uint256 lastStoredVaultSupplyExchangePrice, uint256 lastStoredVaultBorrowExchangePrice, uint256 liquiditySupplyExchangePrice, uint256 liquidityBorrowExchangePrice, uint256 vaultSupplyExchangePrice, uint256 vaultBorrowExchangePrice, uint256 supplyRateLiquidity, uint256 borrowRateLiquidity, int256 supplyRateVault, int256 borrowRateVault, int256 rewardsOrFeeRateSupply, int256 rewardsOrFeeRateBorrow) exchangePricesAndRates, (uint256 totalSupplyVault, uint256 totalBorrowVault, uint256 totalSupplyLiquidityOrDex, uint256 totalBorrowLiquidityOrDex, uint256 absorbedSupply, uint256 absorbedBorrow) totalSupplyAndBorrow, (uint256 withdrawLimit, uint256 withdrawableUntilLimit, uint256 withdrawable, uint256 borrowLimit, uint256 borrowableUntilLimit, uint256 borrowable, uint256 borrowLimitUtilization, uint256 minimumBorrowing) limitsAndAvailability, (uint256 totalPositions, int256 topTick, uint256 currentBranch, uint256 totalBranch, uint256 totalBorrow, uint256 totalSupply, (uint256 status, int256 minimaTick, uint256 debtFactor, uint256 partials, uint256 debtLiquidity, uint256 baseBranchId, int256 baseBranchMinima) currentBranchState) vaultState, (bool modeWithInterest, uint256 supply, uint256 withdrawalLimit, uint256 lastUpdateTimestamp, uint256 expandPercent, uint256 expandDuration, uint256 baseWithdrawalLimit, uint256 withdrawableUntilLimit, uint256 withdrawable) liquidityUserSupplyData, (bool modeWithInterest, uint256 borrow, uint256 borrowLimit, uint256 lastUpdateTimestamp, uint256 expandPercent, uint256 expandDuration, uint256 baseBorrowLimit, uint256 maxBorrowLimit, uint256 borrowableUntilLimit, uint256 borrowable, uint256 borrowLimitUtilization) liquidityUserBorrowData) vaultData_)",
},
dexResolver: {
getAllDexAddresses: "function getAllDexAddresses() external view returns (address[] dexes_)",
getDexTokens: "function getDexTokens(address dex_) external view returns (address token0_, address token1_)",
getDexState: "function getDexState(address dex_) returns ((uint256 lastToLastStoredPrice, uint256 lastStoredPrice, uint256 centerPrice, uint256 lastUpdateTimestamp, uint256 lastPricesTimeDiff, uint256 oracleCheckPoint, uint256 oracleMapping, uint256 totalSupplyShares, uint256 totalBorrowShares, bool isSwapAndArbitragePaused, (bool isRangeChangeActive, bool isThresholdChangeActive, bool isCenterPriceShiftActive, (uint256 oldUpper, uint256 oldLower, uint256 duration, uint256 startTimestamp, uint256 oldTime) rangeShift, (uint256 oldUpper, uint256 oldLower, uint256 duration, uint256 startTimestamp, uint256 oldTime) thresholdShift, (uint256 shiftPercentage, uint256 duration, uint256 startTimestamp) centerPriceShift) shifts, uint256 token0PerSupplyShare, uint256 token1PerSupplyShare, uint256 token0PerBorrowShare, uint256 token1PerBorrowShare) state_)"
},
vault: {
constantsView: "function constantsView() public view returns((address liquidity,address factory,address adminImplementation,address secondaryImplementation,address supplyToken,address borrowToken,uint8 supplyDecimals,uint8 borrowDecimals,uint vaultId,bytes32 liquiditySupplyExchangePriceSlot,bytes32 liquidityBorrowExchangePriceSlot,bytes32 liquidityUserSupplySlot,bytes32 liquidityUserBorrowSlot))",
},
Expand Down
206 changes: 190 additions & 16 deletions fees/fluid/fees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,82 @@ import { ABI, EVENT_ABI, LIQUIDITY, parseInTopic, TOPIC0 } from "./config";

const reserveContract = "0x264786EF916af64a1DB19F513F24a3681734ce92"

export const getDexResolver = async (api: ChainApi) => {
const block = await api.getBlock()

let address: string
let abi: any = ABI.dexResolver;

switch (api.chain) {
case CHAIN.ETHEREUM:
if (block < 21041663) {
break;
}
address = "0x7af0C11F5c787632e567e6418D74e5832d8FFd4c";
break;

case CHAIN.ARBITRUM:
if (block < 286521718) {
break;
}
address = "0x1De42938De444d376eBc298E15D21F409b946E6D";
break;

case CHAIN.BASE:
if (block < 25847553) {
break;
}
address = "0x800104086BECa15A54e8c61dC1b419855fdA3377";
break;
}

return {
getAllDexAddresses: async () => !address ? [] : api.call({ target: address, abi: abi.getAllDexAddresses }),
getDexTokens: async (dexes: string []) => api.multiCall({ calls: dexes.map(dex => ({ target: address, params: [dex] })), abi: abi.getDexTokens }),
getDexStates: async (dexes: string []) => api.multiCall({ calls: dexes.map(dex => ({ target: address, params: [dex] })), abi: abi.getDexState }),
}
}

export const getVaultsResolver = async (api: ChainApi) => {
// includes smart vaults (smart col and/or smart debt)
const block = await api.getBlock()

let address: string
let abi: any = ABI.vaultResolverSmart;

switch (api.chain) {
case CHAIN.ETHEREUM:
if (block < 21041663) {
return getVaultsT1Resolver(api);
}

address = "0x49290f778faAD125f2FBCDE6F09600e73bf4bBd9";
break;

case CHAIN.ARBITRUM:
if (block < 286521718) {
return getVaultsT1Resolver(api);
}

address = "0xD6373b375665DE09533478E8859BeCF12427Bb5e";
break;

case CHAIN.BASE:
if (block < 25847553) {
return getVaultsT1Resolver(api);
}

address = "0xe7A6d56346d2ab4141Fa38e1B2Bc5ff3F69333CD";
break;
}

return {
getAllVaultsAddresses: async () => api.call({ target: address, abi: abi.getAllVaultsAddresses }),
getVaultEntireData: async (vaults: string []) => api.multiCall({ calls: vaults.map((vault) => ({ target: address, params: [vault] })), abi: abi.getVaultEntireData })
}
}

export const getVaultsT1Resolver = async (api: ChainApi) => {
const block = await api.getBlock()
let address: string
let abi: any = ABI.vaultResolver_after_19992222
Expand All @@ -23,61 +98,147 @@ export const getVaultsResolver = async (api: ChainApi) => {
} else if (block < 19992222) {
address = "0x93CAB6529aD849b2583EBAe32D13817A2F38cEb4";
abi = ABI.vaultResolver_before_19992222;
} else if (block < 20983000) {
} else if (block < 20970036) {
address = "0x56ddF84B2c94BF3361862FcEdB704C382dc4cd32";
} else {
address = "0x6922b85D6a7077BE56C0Ae8Cab97Ba3dc4d2E7fA"; // VaultT1Resolver compatibility
}
break;

case CHAIN.ARBITRUM:
address = "0x77648D39be25a1422467060e11E5b979463bEA3d";
if (block < 301152875) {
address = "0x77648D39be25a1422467060e11E5b979463bEA3d";
} else {
address = "0xFbFC36f44B5385AC68264dc9767662d02e0412d2"; // VaultT1Resolver compatibility
}
break;

case CHAIN.BASE:
address = "0x94695A9d0429aD5eFec0106a467aDEaDf71762F9";
if (block < 25765353) {
address = "0x94695A9d0429aD5eFec0106a467aDEaDf71762F9";
} else {
address = "0xb7AC1927a78ADCD33E5B0473c0A1DEA76ca2bff6"; // VaultT1Resolver compatibility
}
break;
}

return {
getAllVaultsAddresses: async () => api.call({ target: address, abi: abi.getAllVaultsAddresses }),
getVaultEntireData: async (vaults: string []) => api.multiCall({ calls: vaults.map((vault) => ({ target: address, params: [vault] })), abi: abi.getVaultEntireData, permitFailure: true })
getVaultEntireData: async (vaults: string []) => {
const permitFailure = api.chain == CHAIN.ARBITRUM && (await api.getBlock()) > 285530000 && address == "0x77648D39be25a1422467060e11E5b979463bEA3d";
return api.multiCall({ calls: vaults.map((vault) => ({ target: address, params: [vault] })), abi: abi.getVaultEntireData, permitFailure });
}
}
}

export const getFluidDexesDailyBorrowFees = async ({ fromApi, toApi, createBalances }: FetchOptions, liquidityOperateLogs: any[]) => {
// borrow fees for all dexes that have smart debt pool enabled (covers smart debt vaults).
const dailyFees = createBalances();
const dexes: string[] = await (await getDexResolver(fromApi)).getAllDexAddresses();

if(!dexes.length){
return dailyFees;
}

const [dexStatesFrom, dexStatesTo, dexTokens] = await Promise.all([
(await getDexResolver(fromApi)).getDexStates(dexes),
(await getDexResolver(toApi)).getDexStates(dexes),
(await getDexResolver(fromApi)).getDexTokens(dexes),
]);

for (const [i, dex] of dexes.entries()) {
const dexStateFrom = dexStatesFrom[i];
const dexStateTo = dexStatesTo[i];
const borrowToken0 = dexTokens[i].token0_;
const borrowToken1 = dexTokens[i].token1_;

// Skip the current dex if any required data is missing
if (!dexStateFrom || !dexStateTo || !borrowToken0 || !borrowToken1) continue;

const initialShares = Number(dexStateFrom.totalBorrowShares);
if (!initialShares || initialShares == 0) continue;
const initialBalance0 = initialShares * Number(dexStateFrom.token0PerBorrowShare) / 1e18;
const initialBalance1 = initialShares * Number(dexStateFrom.token1PerBorrowShare) / 1e18;

const borrowBalanceTo0 = Number(dexStateTo.totalBorrowShares) * Number(dexStateTo.token0PerBorrowShare) / 1e18;
const borrowBalanceTo1 = Number(dexStateTo.totalBorrowShares) * Number(dexStateTo.token1PerBorrowShare) / 1e18;

const liquidityLogs = liquidityOperateLogs.filter(log => log[0] == dex);

const liquidityLogs0 = liquidityLogs.filter(log => log[1] == borrowToken0);
const liquidityLogs1 = liquidityLogs.filter(log => log[1] == borrowToken1);

const borrowBalances0 = liquidityLogs0
.filter((log) => log[5] !== reserveContract)
.reduce((balance, [, , , amount]) => balance + Number(amount) , initialBalance0)
const borrowBalances1 = liquidityLogs1
.filter((log) => log[5] !== reserveContract)
.reduce((balance, [, , , amount]) => balance + Number(amount) , initialBalance1)

const fees0 = borrowBalanceTo0 > borrowBalances0 ? borrowBalanceTo0 - borrowBalances0 : 0n
const fees1 = borrowBalanceTo1 > borrowBalances1 ? borrowBalanceTo1 - borrowBalances1 : 0n

dailyFees.add(borrowToken0, fees0)
dailyFees.add(borrowToken1, fees1)

if (!dexStateFrom.totalSupplyShares || Number(dexStateFrom.totalSupplyShares) == 0) continue;

// if the dex has both col pool and debt pool enabled, there can be internal arbitrage fees
// filter events for arb logs: both supply and borrow amount must be + (deposit and borrow) or - (payback and withdraw)
const arbLogs = liquidityLogs.filter((log) => ((log[2] > 0 && log[3] > 0) || (log[2] < 0 && log[3] < 0)) && (log[2] != log[3]));

// abs diff is arb amount. = fee
const arbs0 = arbLogs
.filter((log) => log[1] == borrowToken0)
.reduce((balance, [, , supplyAmount, borrowAmount]) => balance + Math.abs(Number(supplyAmount) - Number(borrowAmount)) , 0);
const arbs1 = arbLogs
.filter((log) => log[1] == borrowToken1)
.reduce((balance, [, , supplyAmount, borrowAmount]) => balance + Math.abs(Number(supplyAmount) - Number(borrowAmount)) , 0);

dailyFees.add(borrowToken0, arbs0);
dailyFees.add(borrowToken1, arbs1);
}

return dailyFees;
}

export const getFluidDailyFees = async ({ api, fromApi, toApi, getLogs, createBalances }: FetchOptions) => {
export const getFluidVaultsDailyBorrowFees = async ({ fromApi, toApi, createBalances }: FetchOptions, liquidityOperateLogs: any[]) => {
// borrow fees for all normal debt vaults.
const dailyFees = createBalances();
const vaults: string[] = await (await getVaultsResolver(api)).getAllVaultsAddresses();
const vaults: string[] = await (await getVaultsResolver(fromApi)).getAllVaultsAddresses();

const [vaultDatasFrom, vaultDatasTo, vaultBorrowDatas] = await Promise.all([
const [vaultDatasFrom, vaultDatasTo] = await Promise.all([
(await getVaultsResolver(fromApi)).getVaultEntireData(vaults),
(await getVaultsResolver(toApi)).getVaultEntireData(vaults),
toApi.multiCall({ calls: vaults, abi: ABI.vault.constantsView })
])
]);

for (const [i, vault] of vaults.entries()) {
const vaultDataFrom = vaultDatasFrom[i];
const vaultDataTo = vaultDatasTo[i];
const borrowData = vaultBorrowDatas[i];
// Skip the current vault if any required data is missing
if (!vaultDataFrom || !vaultDataTo || !borrowData) continue;
if (!vaultDataFrom || !vaultDataTo ) continue;

const vaultFrom = vaultDataFrom.vault
const vaultTo = vaultDataTo.vault

if (!vaultFrom || !vaultTo || vaultFrom !== vault || vaultTo !== vault) continue

const { borrowToken } = borrowData;
const { totalSupplyAndBorrow: totalSupplyAndBorrowFrom } = vaultDataFrom;
const { totalSupplyAndBorrow: totalSupplyAndBorrowTo } = vaultDataTo;
if(vaultDataFrom.constantVariables.vaultType > 0 && vaultDataFrom.constantVariables.borrowToken.token1 != "0x0000000000000000000000000000000000000000"){
// skip any smart debt vault. tracked at dex level instead.
continue;
}

const borrowToken = vaultDataFrom.constantVariables.vaultType > 0 ? vaultDataFrom.constantVariables.borrowToken.token0 : vaultDataFrom.constantVariables.borrowToken;
if (!borrowToken) continue;

const { totalSupplyAndBorrow: totalSupplyAndBorrowFrom } = vaultDataFrom;
const { totalSupplyAndBorrow: totalSupplyAndBorrowTo } = vaultDataTo;

const initialBalance = Number(totalSupplyAndBorrowFrom.totalBorrowVault);
const borrowBalanceTo = Number(totalSupplyAndBorrowTo.totalBorrowVault);

const liquidityLogs = await getLogs({ target: LIQUIDITY, onlyArgs: true, topics: [TOPIC0.logOperate, parseInTopic(vault), parseInTopic(borrowToken)], eventAbi: EVENT_ABI.logOperate, flatten: true, skipCacheRead: true });
const liquidityLogs = liquidityOperateLogs.filter(log => (log[0] == vault && log[1] == borrowToken));

const borrowBalances = liquidityLogs
.filter((log) => log[5] !== reserveContract)
.reduce((balance, [, , , amount]) => balance + Number(amount) , initialBalance)
Expand All @@ -87,4 +248,17 @@ export const getFluidDailyFees = async ({ api, fromApi, toApi, getLogs, createBa
}

return dailyFees;
};

export const getFluidDailyFees = async (options: FetchOptions) => {
// fetch all operate logs at liquidity layer at once
const liquidityOperateLogs = await options.getLogs({ target: LIQUIDITY, onlyArgs: true, topics: [TOPIC0.logOperate], eventAbi: EVENT_ABI.logOperate, flatten: true, skipCacheRead: true });

const [vaultFees, dexFees] = await Promise.all([
await getFluidVaultsDailyBorrowFees(options, liquidityOperateLogs),
await getFluidDexesDailyBorrowFees(options, liquidityOperateLogs),
]);

vaultFees.addBalances(dexFees);
return vaultFees;
};
Loading
Loading