Skip to content

Commit

Permalink
Fix old compound v3 on several chains + add base
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpeluche committed Aug 21, 2023
1 parent 75be5e1 commit 6560962
Show file tree
Hide file tree
Showing 14 changed files with 314 additions and 343 deletions.
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"badger-dao",
"balancer",
"bancor-v3",
"baseswap",
"beefy",
"bella-protocol",
"belt-finance",
Expand Down
27 changes: 11 additions & 16 deletions src/adapters/compound-v3/arbitrum/index.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
import { getAssetsContracts } from '@adapters/compound-v3/common/asset'
import { getCompLendBalances, getCompRewardBalances } from '@adapters/compound-v3/common/balance'
import type { BalancesContext, BaseContext, Contract, GetBalancesHandler } from '@lib/adapter'
import { resolveBalances } from '@lib/balance'
import type { BalanceWithExtraProps } from '@lib/compound/v2/lending'
import { getHealthFactor } from '@lib/compound/v2/lending'
import { getSingleStakeBalances } from '@lib/stake'
import type { Token } from '@lib/token'

import { getAssetsContracts, getLendBorrowBalances } from '../common/lend'
import { getRewardBalances } from '../common/rewards'
import { getStakeBalances } from '../common/stake'

const USDC: Token = {
chain: 'arbitrum',
address: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8',
decimals: 6,
symbol: 'USDC',
}

const CompoundUSDCv3: Contract = {
const cUSDCv3: Contract = {
chain: 'arbitrum',
address: '0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA',
underlyings: [USDC],
}

const CompoundRewards: Contract = {
const rewarder: Contract = {
chain: 'arbitrum',
address: '0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae',
}

export const getContracts = async (ctx: BaseContext) => {
const assets = await getAssetsContracts(ctx, [CompoundUSDCv3])
const assets = await getAssetsContracts(ctx, [cUSDCv3])

return {
contracts: { compounders: [CompoundUSDCv3], assets, CompoundRewards },
contracts: { compounders: [cUSDCv3], assets, rewarder },
}
}

const compoundBalances = async (ctx: BalancesContext, compounders: Contract[], rewarder: Contract) => {
return Promise.all([getStakeBalances(ctx, compounders), getRewardBalances(ctx, rewarder, compounders)])
return Promise.all([getSingleStakeBalances(ctx, compounders), getCompRewardBalances(ctx, rewarder, compounders)])
}

export const getBalances: GetBalancesHandler<typeof getContracts> = async (ctx, contracts) => {
const balances = await resolveBalances<typeof getContracts>(ctx, contracts, {
assets: (...args) => getLendBorrowBalances(...args, [CompoundUSDCv3]),
compounders: (...args) => compoundBalances(...args, CompoundRewards),
assets: (...args) => getCompLendBalances(...args, [cUSDCv3]),
compounders: (...args) => compoundBalances(...args, rewarder),
})

const healthFactor = await getHealthFactor(balances as BalanceWithExtraProps[])

return {
groups: [{ balances, healthFactor }],
groups: [{ balances }],
}
}
60 changes: 60 additions & 0 deletions src/adapters/compound-v3/base/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { getAssetsContracts } from '@adapters/compound-v3/common/asset'
import { getCompLendBalances, getCompRewardBalances } from '@adapters/compound-v3/common/balance'
import type { BalancesContext, BaseContext, Contract, GetBalancesHandler } from '@lib/adapter'
import { resolveBalances } from '@lib/balance'
import { getSingleStakeBalances } from '@lib/stake'
import type { Token } from '@lib/token'

const USDbC: Token = {
chain: 'base',
address: '0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca',
decimals: 6,
symbol: 'USDbC',
}

const WETH: Token = {
chain: 'base',
address: '0x4200000000000000000000000000000000000006',
decimals: 18,
symbol: 'WETH',
}

const cUSDbCv3: Contract = {
chain: 'base',
address: '0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf',
underlyings: [USDbC],
}

const cWETHv3: Contract = {
chain: 'base',
address: '0x46e6b214b524310239732D51387075E0e70970bf',
underlyings: [WETH],
}

const rewarder: Contract = {
chain: 'base',
address: '0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1',
}

export const getContracts = async (ctx: BaseContext) => {
const assets = await getAssetsContracts(ctx, [cUSDbCv3, cWETHv3])

return {
contracts: { compounders: [cUSDbCv3, cWETHv3], assets, rewarder },
}
}

const compoundBalances = async (ctx: BalancesContext, compounders: Contract[], rewarder: Contract) => {
return Promise.all([getSingleStakeBalances(ctx, compounders), getCompRewardBalances(ctx, rewarder, compounders)])
}

export const getBalances: GetBalancesHandler<typeof getContracts> = async (ctx, contracts) => {
const balances = await resolveBalances<typeof getContracts>(ctx, contracts, {
assets: (...args) => getCompLendBalances(...args, [cUSDbCv3, cWETHv3]),
compounders: (...args) => compoundBalances(...args, rewarder),
})

return {
groups: [{ balances }],
}
}
72 changes: 72 additions & 0 deletions src/adapters/compound-v3/common/asset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { BaseContext, Contract } from '@lib/adapter'
import { mapSuccessFilter, range } from '@lib/array'
import { multicall } from '@lib/multicall'

const abi = {
getAssetInfo: {
inputs: [{ internalType: 'uint8', name: 'i', type: 'uint8' }],
name: 'getAssetInfo',
outputs: [
{
components: [
{ internalType: 'uint8', name: 'offset', type: 'uint8' },
{ internalType: 'address', name: 'asset', type: 'address' },
{ internalType: 'address', name: 'priceFeed', type: 'address' },
{ internalType: 'uint64', name: 'scale', type: 'uint64' },
{
internalType: 'uint64',
name: 'borrowCollateralFactor',
type: 'uint64',
},
{
internalType: 'uint64',
name: 'liquidateCollateralFactor',
type: 'uint64',
},
{
internalType: 'uint64',
name: 'liquidationFactor',
type: 'uint64',
},
{ internalType: 'uint128', name: 'supplyCap', type: 'uint128' },
],
internalType: 'struct CometCore.AssetInfo',
name: '',
type: 'tuple',
},
],
stateMutability: 'view',
type: 'function',
},
numAssets: {
inputs: [],
name: 'numAssets',
outputs: [{ internalType: 'uint8', name: '', type: 'uint8' }],
stateMutability: 'view',
type: 'function',
},
} as const

export async function getAssetsContracts(ctx: BaseContext, compounders: Contract[]): Promise<Contract[]> {
const numberOfAssets = await multicall({
ctx,
calls: compounders.map((contract) => ({ target: contract.address })),
abi: abi.numAssets,
})

const assetsInfoRes = await multicall({
ctx,
//@ts-expect-error
calls: mapSuccessFilter(numberOfAssets, (responses) =>
range(0, responses.output).map((res) => ({ target: responses.input.target, params: [res] })),
).flat(),
abi: abi.getAssetInfo,
})

return mapSuccessFilter(assetsInfoRes, (res) => ({
chain: ctx.chain,
address: res.output.asset,
compounder: res.input.target,
collateralFactor: res.output.borrowCollateralFactor,
}))
}
129 changes: 129 additions & 0 deletions src/adapters/compound-v3/common/balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import type { Balance, BalancesContext, Contract } from '@lib/adapter'
import { mapSuccessFilter } from '@lib/array'
import { multicall } from '@lib/multicall'
import { isNotNullish } from '@lib/type'

const abi = {
borrowBalanceOf: {
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'borrowBalanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
userCollateral: {
inputs: [
{ internalType: 'address', name: '', type: 'address' },
{ internalType: 'address', name: '', type: 'address' },
],
name: 'userCollateral',
outputs: [
{ internalType: 'uint128', name: 'balance', type: 'uint128' },
{ internalType: 'uint128', name: '_reserved', type: 'uint128' },
],
stateMutability: 'view',
type: 'function',
},
getRewardOwed: {
inputs: [
{ internalType: 'address', name: 'comet', type: 'address' },
{ internalType: 'address', name: 'account', type: 'address' },
],
name: 'getRewardOwed',
outputs: [
{
components: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'uint256', name: 'owed', type: 'uint256' },
],
internalType: 'struct CometRewards.RewardOwed',
name: '',
type: 'tuple',
},
],
stateMutability: 'nonpayable',
type: 'function',
},
} as const

const COMP: { [key: string]: `0x${string}` } = {
arbitrum: '0x354A6dA3fcde098F8389cad84b0182725c6C91dE',
base: '0x9e1028F5F1D5eDE59748FFceE5532509976840E0',
ethereum: '0xc00e94cb662c3520282e6f5717214004a7f26888',
polygon: '0x8505b9d2254A7Ae468c0E9dd10Ccea3A837aef5c',
}

export async function getCompLendBalances(
ctx: BalancesContext,
assets: Contract[],
compounders: Contract[],
): Promise<Balance[]> {
const [userLendBalances, userBorrowBalances] = await Promise.all([
multicall({
ctx,
calls: assets.map((asset) => ({ target: asset.compounder, params: [ctx.address, asset.address] }) as const),
abi: abi.userCollateral,
}),
multicall({
ctx,
calls: compounders.map((compounder) => ({ target: compounder.address, params: [ctx.address] }) as const),
abi: abi.borrowBalanceOf,
}),
])

const supplyBalance: Balance[] = mapSuccessFilter(userLendBalances, (res, index) => {
const [balance, _reserved] = res.output

return {
chain: ctx.chain,
decimals: assets[index].decimals,
symbol: assets[index].symbol,
address: assets[index].address,
amount: balance,
collateralFactor: assets[index].collateralFactor,
category: 'lend',
}
})

const borrowBalance: Balance[] = mapSuccessFilter(userBorrowBalances, (res, index) => {
const underlying = compounders[index].underlyings?.[0] as Contract

if (!underlying) {
return null
}

return {
chain: ctx.chain,
decimals: underlying.decimals,
symbol: underlying.symbol,
address: underlying.address,
amount: res.output,
category: 'borrow',
}
}).filter(isNotNullish) as Balance[]

return [...supplyBalance, ...borrowBalance]
}

export async function getCompRewardBalances(
ctx: BalancesContext,
rewarder: Contract,
compounders: Contract[],
): Promise<Balance[]> {
const pendingCompRewards = await multicall({
ctx,
calls: compounders.map(
(contract) => ({ target: rewarder.address, params: [contract.address, ctx.address] }) as const,
),
abi: abi.getRewardOwed,
})

return mapSuccessFilter(pendingCompRewards, (res) => ({
chain: ctx.chain,
address: COMP[ctx.chain],
decimals: 18,
symbol: 'COMP',
amount: res.output.owed,
category: 'reward',
}))
}
Loading

0 comments on commit 6560962

Please sign in to comment.