assembly/composables/useCompoundPosition.ts
Georges KABBOUCHI 074671deb8 wip compound
2021-08-07 01:44:51 +03:00

501 lines
13 KiB
TypeScript

import { AbiItem } from "web3-utils";
import compoundABI from "~/abis/read/compound.json";
import { computed, ref, watch } from "@nuxtjs/composition-api";
import { useDSA } from "./useDSA";
import { useWeb3 } from "./useWeb3";
import BigNumber from "bignumber.js";
import tokens from "~/constant/tokens";
import { Network } from "./useNetwork";
import { useBigNumber } from "./useBigNumber";
import { usePosition } from "./usePosition";
import { useToken } from "./useToken";
import addresses from "~/constant/addresses";
import ctokens from "~/constant/ctokens";
import tokenIdMapping from "~/constant/tokenIdMapping";
const {
times,
isZero,
div,
max,
gt,
minus,
ensureValue,
plus
} = useBigNumber();
const { getType } = usePosition();
const position = ref<any>({
totalSupplyInEth: new BigNumber(0),
totalBorrowInEth: new BigNumber(0),
totalBorrowStableInEth: new BigNumber(0),
totalBorrowVariableInEth: new BigNumber(0),
maxBorrowLimitInEth: new BigNumber(0),
maxBorrowLiquidityLimitInEth: new BigNumber(0),
ethPriceInUsd: "0",
data: []
});
const totalSupply = computed(() =>
position.value
? times(
position.value.totalSupplyInEth,
position.value.ethPriceInUsd
).toFixed()
: 0
);
const totalBorrow = computed(() =>
position.value
? times(
position.value.totalBorrowInEth,
position.value.ethPriceInUsd
).toFixed()
: 0
);
const ethPriceInUsd = computed(() => position.value?.ethPriceInUsd);
const annualPercentageRateTypes = computed(() => [
{ label: "Variable", value: "variable", rateMode: 2 },
{ label: "Stable", value: "stable", rateMode: 1 }
]);
export function useCompoundPosition(
{ overridePosition } = { overridePosition: null }
) {
overridePosition = overridePosition || (pos => pos);
const { web3, chainId, networkName } = useWeb3();
const { activeAccount } = useDSA();
const { getTokenByKey, allATokensV2 } = useToken();
const resolver = computed(() => addresses.mainnet.resolver.compound);
const fetchPosition = async () => {
if (!web3.value) {
return;
}
if (!activeAccount.value) {
return;
}
const resolverInstance = new web3.value.eth.Contract(
compoundABI as AbiItem[],
resolver.value
);
const tokensArr = ctokens[networkName.value].allTokens.map(a => a.address);
const compoundRawData = await resolverInstance.methods
.getPosition(activeAccount.value.address, tokensArr)
.call();
const newPos = calculateCompoundPosition(
compoundRawData,
networkName.value
);
return newPos;
};
const refreshPosition = async () => {
position.value = await fetchPosition();
};
watch(
web3,
async val => {
if (val) {
refreshPosition();
}
},
{ immediate: true }
);
watch(
activeAccount,
async val => {
if (val) {
refreshPosition();
}
},
{ immediate: true }
);
const stats = computed(() =>
displayPositions.value.reduce(
(stats, { key, supply, borrow, priceInEth, factor, liquidation }) => {
if (key === "eth") {
stats.ethSupplied = supply;
}
stats.totalSupplyInEth = plus(
stats.totalSupplyInEth,
times(supply, priceInEth)
).toFixed();
stats.totalBorrowInEth = plus(
stats.totalBorrowInEth,
times(borrow, priceInEth)
).toFixed();
stats.totalMaxBorrowLimitInEth = plus(
stats.totalMaxBorrowLimitInEth,
times(supply, times(priceInEth, factor))
).toFixed();
stats.totalMaxLiquidationLimitInEth = plus(
stats.totalMaxLiquidationLimitInEth,
times(supply, times(priceInEth, liquidation))
).toFixed();
return stats;
},
{
totalSupplyInEth: "0",
totalBorrowInEth: "0",
totalMaxBorrowLimitInEth: "0",
totalMaxLiquidationLimitInEth: "0",
ethSupplied: "0"
}
)
);
const rewardTokenPriceInUsd = computed(() => {
return ensureValue(
position.value.data.find(position => position.key === "comp")?.priceInUsd
);
});
const displayPositions = computed(() => {
if (!position.value) {
return [];
}
return ctokens[networkName.value].allTokens
.flatMap(ctoken => {
const token = getTokenByKey(ctoken.root);
if (!token) {
return [];
}
const ctokenPosition = position.value.data.find(
x => x.cTokenId === ctoken.id
);
const p = getPositionOrDefaultPosition(token, ctokenPosition);
if (gt(p.supply, "0") && gt(p.borrow, "0")) {
return [
{ ...p, type: "supply" },
{ ...p, type: "borrow" }
];
} else {
return [p];
}
})
.filter(p => {
if (
tokenIdMapping.archived.compound.includes(p.tokenId) &&
isZero(p.supply) &&
isZero(p.borrow)
) {
return false;
}
return true;
})
.map(overridePosition);
});
function getPositionOrDefaultPosition(token, p) {
if (!p) {
const defaultPosition = {
key: token?.key,
tokenId: `${token?.symbol}-A`,
ctknBalance: "0",
cTokenDecimals: "0",
cf: "0",
supply: "0",
supplyUsd: "0",
supplyRate: "0",
supplyYield: "0",
borrow: "0",
borrowUsd: "0",
borrowRate: "0",
borrowYield: "0",
type: "no",
borrowEnabled: true,
supplyRewardRate: "0",
borrowRewardRate: "0",
priceInUsd: "0"
};
return defaultPosition;
}
return {
key: token?.key,
tokenId: p.cTokenId,
ctknBalance: p.ctknBalance,
cTokenDecimals: p.cTokenDecimals,
cf: p.factor,
supply: p.supply,
supplyUsd: times(p.supply, p.priceInUsd).toFixed(),
supplyRate: p.supplyRate,
supplyYield: p.supplyYield,
borrow: p.borrow,
borrowUsd: times(p.borrow, p.priceInUsd).toFixed(),
borrowRate: p.borrowRate,
borrowYield: p.borrowYield,
borrowEnabled: p.borrowEnabled,
type: getType(p),
supplyRewardRate: times(
p.compSupplyApy,
rewardTokenPriceInUsd.value
).toFixed(),
borrowRewardRate: times(
p.compBorrowApy,
rewardTokenPriceInUsd.value
).toFixed(),
priceInUsd: p.priceInUsd
};
}
const maxLiquidation = computed(() => {
if (isZero(stats.value.totalSupplyInEth)) return "0";
return max(
div(
stats.value.totalMaxLiquidationLimitInEth,
stats.value.totalSupplyInEth
),
"0"
).toFixed();
});
const liquidationPrice = computed(() => {
if (isZero(stats.value.ethSupplied)) return "0";
return max(
times(
div(
stats.value.totalBorrowInEth,
stats.value.totalMaxLiquidationLimitInEth
),
ethPriceInUsd.value
),
"0"
).toFixed();
});
const status = computed(() => {
if (
isZero(stats.value.totalSupplyInEth) &&
!isZero(stats.value.totalBorrowInEth)
)
return "1.1";
if (isZero(stats.value.totalSupplyInEth)) return "0";
return max(
div(stats.value.totalBorrowInEth, stats.value.totalSupplyInEth),
"0"
).toFixed();
});
const liquidation = computed(() => {
if (isZero(stats.value.totalSupplyInEth)) return "0";
return max(
div(stats.value.totalMaxBorrowLimitInEth, stats.value.totalSupplyInEth),
"0"
).toFixed();
});
return {
stats,
displayPositions,
position: position,
fetchPosition,
refreshPosition,
totalSupply,
totalBorrow,
status,
liquidation,
maxLiquidation,
liquidationPrice,
liquidationMaxPrice: ethPriceInUsd,
annualPercentageRateTypes
};
}
function calculateCompoundPosition(
res: any[],
network: Network = Network.Mainnet
) {
try {
const newPos = {
totalSupplyInEth: new BigNumber(0),
totalBorrowInEth: new BigNumber(0),
totalBorrowStableInEth: new BigNumber(0),
totalBorrowVariableInEth: new BigNumber(0),
maxBorrowLimitInEth: new BigNumber(0),
maxBorrowLiquidityLimitInEth: new BigNumber(0),
tokens: {},
data: []
};
const dataPos = [];
let ethPrice = "0";
ctokens[network].allTokens.forEach((ctoken, i) => {
const key = ctoken.address;
const [
priceInEthInWei,
priceInUsdInWei,
exchangeRateInWei,
cBalanceInWei,
borrowInWei,
totalBorrowsInWei,
totalSuppliedInWei,
borrowCapInWei,
supplyRatePerBlock,
borrowRatePerBlock,
collateralFactorInWei,
compSpeed,
isComped,
isBorrowPaused
] = res[0][i];
const decimals = tokens[network].getTokenByKey(ctoken.root).decimals;
const root = ctoken.root;
const blocksInYear = 2398050;
const priceInEth = new BigNumber(priceInEthInWei).dividedBy(1e18);
const priceInUsd = new BigNumber(priceInUsdInWei).dividedBy(1e18);
const exchangeRate = new BigNumber(exchangeRateInWei).dividedBy(1e18);
const supply = new BigNumber(cBalanceInWei)
.multipliedBy(exchangeRate)
.dividedBy(10 ** decimals);
const borrow = new BigNumber(borrowInWei).dividedBy(10 ** decimals);
const supplyRate = new BigNumber(supplyRatePerBlock)
.multipliedBy(blocksInYear)
.dividedBy(1e18);
const supplyYield = supplyRate
.dividedBy(365)
.plus(1)
.exponentiatedBy(365)
.minus(1)
.toFixed(18);
const borrowRate = new BigNumber(borrowRatePerBlock)
.multipliedBy(blocksInYear)
.dividedBy(1e18);
const borrowYield = borrowRate
.dividedBy(365)
.plus(1)
.exponentiatedBy(365)
.minus(1)
.toFixed(18);
const totalBorrow = new BigNumber(totalBorrowsInWei).dividedBy(
10 ** decimals
);
const borrowCap = new BigNumber(borrowCapInWei)
.dividedBy(10 ** decimals)
.multipliedBy(0.9999)
.toFixed();
const collateralFactor = new BigNumber(collateralFactorInWei)
.dividedBy(1e18)
.toFixed();
const totalSupplied = new BigNumber(totalSuppliedInWei)
.dividedBy(10 ** decimals)
.multipliedBy(priceInUsd);
const compSupplyApy = new BigNumber(compSpeed)
.multipliedBy(blocksInYear)
.dividedBy(1e18)
.dividedBy(totalSupplied)
.toFixed();
const compBorrowApy = new BigNumber(compSpeed)
.multipliedBy(blocksInYear)
.dividedBy(1e18)
.dividedBy(totalBorrow.multipliedBy(priceInUsd))
.toFixed();
newPos.totalSupplyInEth = newPos.totalSupplyInEth.plus(
supply.multipliedBy(priceInEth)
);
newPos.maxBorrowLimitInEth = newPos.maxBorrowLimitInEth.plus(
supply.multipliedBy(priceInEth).multipliedBy(ctoken.factor)
);
newPos.totalBorrowInEth = newPos.totalBorrowInEth.plus(
borrow.multipliedBy(priceInEth)
);
if (ctoken.root === "eth") ethPrice = priceInUsd.toFixed();
dataPos.push({
key: root,
cTokenAddr: key,
cTokenKey: ctoken.key,
cTokenDecimals: ctoken.decimals.toString(),
cTokenId: ctoken.id,
priceInEth: priceInEth.toFixed(),
priceInUsd: priceInUsd.toFixed(),
exchangeRate: exchangeRate.toFixed(),
ctknBalance: cBalanceInWei,
supply: supply.toFixed(),
borrow: borrow.toFixed(),
supplyRate: supplyRate.toFixed(),
supplyYield,
borrowRate: borrowRate.toFixed(),
borrowYield,
factor: collateralFactor,
totalBorrow: totalBorrow.toFixed(),
borrowCap,
isComped,
borrowEnabled: !isBorrowPaused,
compSupplyApy,
compBorrowApy
});
});
const totalSupplyInEthIsZero = newPos.totalSupplyInEth.isZero();
const status = totalSupplyInEthIsZero
? 0
: newPos.totalBorrowInEth.dividedBy(newPos.totalSupplyInEth).toFixed();
const liquidation = totalSupplyInEthIsZero
? 0
: newPos.maxBorrowLimitInEth.dividedBy(newPos.totalSupplyInEth).toFixed();
//@ts-ignore
newPos.totalSupplyInEth = newPos.totalSupplyInEth.toFixed();
//@ts-ignore
newPos.totalBorrowInEth = newPos.totalBorrowInEth.toFixed();
//@ts-ignore
newPos.maxBorrowLimitInEth = newPos.maxBorrowLimitInEth.toFixed();
//@ts-ignore
newPos.status = status;
//@ts-ignore
newPos.liquidation = liquidation;
//@ts-ignore
newPos.ethPriceInUsd = ethPrice;
const comp = res[1];
//@ts-ignore
newPos.compBalance = new BigNumber(comp.balance).dividedBy(1e18).toFixed();
//@ts-ignore
newPos.compAccrued = new BigNumber(comp.allocated)
.dividedBy(1e18)
.toFixed();
//@ts-ignore
newPos.compDelegate = comp.delegate;
//@ts-ignore
newPos.compVotes = comp.votes;
newPos.data = dataPos;
return newPos;
} catch (error) {
console.error(error);
return Promise.reject(error);
}
}