basic compound strategies

This commit is contained in:
Georges KABBOUCHI 2021-09-04 20:39:05 +03:00
parent e052cbe70d
commit 52ebdbfb3f
10 changed files with 438 additions and 10 deletions

View File

@ -14,6 +14,7 @@ import addresses from "~/constant/addresses";
import ctokens from "~/constant/ctokens";
import tokenIdMapping from "~/constant/tokenIdMapping";
import { useSorting } from "~/composables/useSorting";
import useEventBus from "../useEventBus";
const {
times,
@ -62,6 +63,7 @@ export function useCompoundPosition(
) {
overridePosition = overridePosition || (pos => pos);
const { onEvent } = useEventBus()
const { web3, networkName } = useWeb3();
const { activeAccount } = useDSA();
const { getTokenByKey } = useToken();
@ -100,6 +102,8 @@ export function useCompoundPosition(
position.value = await fetchPosition();
};
onEvent("protocol::compound::refresh", refreshPosition);
watch(
web3,
async val => {

View File

@ -9,6 +9,7 @@ BigNumber.config({ POW_PRECISION: 200 });
import abis from "~/constant/abis";
import addresses from "~/constant/addresses";
import { useDSA } from "../useDSA";
import useEventBus from "../useEventBus";
export const trove = ref<any>({
collateral: "0",
@ -49,6 +50,7 @@ export function useLiquityPosition(
collateralAmountRef: Ref = null,
debtAmountRef: Ref = null
) {
const { onEvent } = useEventBus()
const { web3 } = useWeb3();
const { activeAccount } = useDSA();
@ -178,6 +180,9 @@ export function useLiquityPosition(
}
}
onEvent("protocol::liquity::refresh", fetchPosition);
watch(
web3,
async val => {

View File

@ -9,6 +9,7 @@ import { useDSA } from "~/composables/useDSA";
import { useToken } from "~/composables/useToken";
import { useWeb3 } from "~/composables/useWeb3";
import { AbiItem } from "web3-utils";
import useEventBus from "../useEventBus";
const defaultVault = {
id: null,
@ -54,6 +55,7 @@ export function useMakerdaoPosition(
collateralAmountRef: Ref = null,
debtAmountRef: Ref = null
) {
const { onEvent } = useEventBus()
const { web3, chainId, networkName } = useWeb3();
const { activeAccount } = useDSA();
const { isZero, ensureValue, times, div, max, gt } = useBigNumber();
@ -132,6 +134,8 @@ export function useMakerdaoPosition(
}
};
onEvent("protocol::makerdao::refresh", fetchPosition);
watch(
web3,
async val => {

View File

@ -23,6 +23,7 @@ import { useSidebar } from "./useSidebar";
import { useToken } from "./useToken";
import { useWeb3 } from "./useWeb3";
import { useBigNumber } from "./useBigNumber";
import tokenIdMapping from "~/constant/tokenIdMapping";
export function useStrategy(defineStrategy: DefineStrategy) {
const { web3, networkName, account } = useWeb3();
@ -84,7 +85,8 @@ export function useStrategy(defineStrategy: DefineStrategy) {
convertTokenAmountToWei: valInt,
getTokenByKey,
toBN,
position
position,
tokenIdMapping
});
});

View File

@ -3,10 +3,16 @@ import Web3 from "web3";
import slugify from "slugify";
import { Strategy } from "./strategy";
import BigNumber from "bignumber.js";
import tokenIdMapping from "~/constant/tokenIdMapping";
export interface IStrategyContext {
dsa: DSA;
web3: Web3;
inputs: IStrategyInput<StrategyInputType>[];
// TODO: add types in useStrategy.ts
dsaBalances?: { [address: string]: IStrategyToken };
userBalances?: { [address: string]: IStrategyToken };
tokens?: { [address: string]: IStrategyToken };
@ -15,6 +21,8 @@ export interface IStrategyContext {
position?: any;
variables?: { [key: string]: any };
toBN?: (value: any) => BigNumber;
tokenIdMapping?: typeof tokenIdMapping;
}
export interface IStrategyToken {

View File

@ -1,7 +1,9 @@
import AaveV2 from "./protocols/aave-v2"
import Compound from "./protocols/compound"
export const protocolStrategies = {
aaveV2 : AaveV2,
compound : Compound,
}
export * from "./helpers"

View File

@ -0,0 +1,182 @@
import BigNumber from "bignumber.js";
import {
defineStrategy,
defineInput,
StrategyInputType,
StrategyProtocol
} from "../../helpers";
export default defineStrategy({
protocol: StrategyProtocol.COMPOUND,
name: "Deposit & Borrow",
description: "Deposit collateral & borrow asset in a single txn.",
details: `<p class="text-center">This strategy executes:</p>
<ul>
<li>Deposit collateral</li>
<li>Borrow Debt</li>
</ul>`,
submitText: "Deposit & Borrow",
author: "Instadapp Team",
variables: {
collateralTokenKey: "eth",
debtTokenKey: "dai"
},
inputs: [
defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Deposit` : "",
validate: ({ input, dsaBalances, toBN }) => {
if (!input.token) {
return "Collateral token is required";
}
if (!input.value) {
return "Collateral amount is required";
}
const collateralBalance = toBN(
dsaBalances[input.token.address]?.balance
);
if (toBN(collateralBalance).lt(input.value)) {
const collateralBalanceFormatted = collateralBalance.toFixed(2);
return `Your amount exceeds your maximum limit of ${collateralBalanceFormatted} ${input.token.symbol}`;
}
},
defaults: ({ getTokenByKey, variables }) => ({
token: getTokenByKey?.(variables.collateralTokenKey)
})
}),
defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Borrow` : "",
validate: ({ input }) => {
if (!input.token) {
return "Debt token is required";
}
if (!input.value) {
return "Debt amount is required";
}
},
defaults: ({ getTokenByKey, variables }) => ({
token: getTokenByKey?.(variables.debtTokenKey)
})
})
],
validate: async ({ position, inputs, toBN, tokenIdMapping }) => {
if (toBN(inputs[0].value).isZero() && toBN(inputs[1].value).isZero()) {
return;
}
const { tokenToId } = tokenIdMapping;
const newPositionData = position.data.map(position => {
const changedPosition = { ...position };
if (tokenToId.compound[inputs[1].token.key] === position.cTokenId) {
changedPosition.borrow = BigNumber.max(
toBN(position.borrow).plus(inputs[1].value),
"0"
).toFixed();
}
if (tokenToId.compound[inputs[0].token.key] === position.cTokenId) {
changedPosition.supply = BigNumber.max(
toBN(position.supply).plus(inputs[0].value),
"0"
).toFixed();
}
return changedPosition;
});
const stats = newPositionData.reduce(
(stats, { key, supply, borrow, priceInEth, factor }) => {
if (key === "eth") {
stats.ethSupplied = supply;
}
stats.totalSupplyInEth = toBN(supply)
.times(priceInEth)
.plus(stats.totalSupplyInEth)
.toFixed();
stats.totalBorrowInEth = toBN(borrow)
.times(priceInEth)
.plus(stats.totalBorrowInEth)
.toFixed();
stats.totalMaxBorrowLimitInEth = toBN(priceInEth)
.times(factor)
.times(supply)
.plus(stats.totalMaxBorrowLimitInEth)
.toFixed();
return stats;
},
{
totalSupplyInEth: "0",
totalBorrowInEth: "0",
totalMaxBorrowLimitInEth: "0",
ethSupplied: "0"
}
);
let liquidation = "0";
if (!toBN(stats.totalSupplyInEth).isZero()) {
liquidation = BigNumber.max(
toBN(stats.totalMaxBorrowLimitInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
}
const status = BigNumber.max(
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
"0"
);
if (status.gt(toBN(liquidation).minus("0.0001"))) {
return "Position will liquidate.";
}
},
spells: async ({ inputs, convertTokenAmountToWei, tokenIdMapping }) => {
const { tokenToId } = tokenIdMapping;
const collateralTokenId = tokenToId.compound[inputs[0].token.key];
const debtTokenId = tokenToId.compound[inputs[1].token.key];
return [
{
connector: "compound",
method: "deposit",
args: [
collateralTokenId,
convertTokenAmountToWei(inputs[0].value, inputs[0].token.decimals),
0,
0
]
},
{
connector: "compound",
method: "borrow",
args: [
debtTokenId,
convertTokenAmountToWei(inputs[1].value, inputs[1].token.decimals),
0,
0
]
}
];
}
});

View File

@ -0,0 +1,8 @@
import depositAndBorrow from "./deposit-and-borrow"
import paybackAndWithdraw from "./payback-and-withdraw"
export default [
depositAndBorrow,
paybackAndWithdraw,
]

View File

@ -0,0 +1,186 @@
import BigNumber from "bignumber.js";
import {
defineStrategy,
defineInput,
StrategyInputType,
StrategyProtocol
} from "../../helpers";
export default defineStrategy({
protocol: StrategyProtocol.COMPOUND,
name: "Payback & Withdraw",
description: "Payback debt & withdraw collateral in a single txn.",
author: "Instadapp Team",
submitText: "Payback & Withdraw",
details: `<p class="text-center">This strategy executes:</p>
<ul>
<li>Payback debt</li>
<li>Withdraw collateral</li>
</ul>`,
inputs: [
defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Debt",
placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Payback` : "",
validate: ({ input, toBN, dsaBalances }) => {
if (!input.token) {
return "Debt token is required";
}
const balance = toBN(dsaBalances[input.token.address]?.balance);
if (toBN(balance).lt(input.value)) {
return "You don't have enough balance to payback.";
}
},
defaults: ({ getTokenByKey }) => ({
token: getTokenByKey?.("dai")
})
}),
defineInput({
type: StrategyInputType.INPUT_WITH_TOKEN,
name: "Collateral",
placeholder: ({ input }) =>
input.token ? `${input.token.symbol} to Withdraw` : "",
validate: ({ input, position, toBN, tokenIdMapping }) => {
if (!input.token) {
return "Collateral token is required";
}
if (!input.value) {
return "Collateral amount is required";
}
const { tokenToId } = tokenIdMapping;
if (position) {
const collateralBalance = toBN(
position.data.find(
pos => pos.cTokenId === tokenToId.compound[input.token.key]
)?.supply || "0"
);
if (collateralBalance.lt(input.value)) {
const collateralBalanceFormatted = collateralBalance.toFixed(
2,
BigNumber.ROUND_FLOOR
);
return `Your amount exceeds your maximum limit of ${collateralBalanceFormatted} ${input.token.symbol}`;
}
}
},
defaults: ({ getTokenByKey }) => ({
token: getTokenByKey?.("eth")
})
})
],
validate: async ({ position, inputs, toBN, tokenIdMapping }) => {
if (toBN(inputs[0].value).isZero() && toBN(inputs[1].value).isZero()) {
return;
}
const { tokenToId } = tokenIdMapping;
const newPositionData = position.data.map(position => {
const changedPosition = { ...position };
if (tokenToId.compound[inputs[0].token.key] === position.cTokenId) {
changedPosition.borrow = BigNumber.max(
toBN(position.borrow).minus(inputs[0].value),
"0"
).toFixed();
}
if (tokenToId.compound[inputs[1].token.key] === position.cTokenId) {
changedPosition.supply = BigNumber.max(
toBN(position.supply).minus(inputs[1].value),
"0"
).toFixed();
}
return changedPosition;
});
const stats = newPositionData.reduce(
(stats, { key, supply, borrow, priceInEth, factor }) => {
if (key === "eth") {
stats.ethSupplied = supply;
}
stats.totalSupplyInEth = toBN(supply)
.times(priceInEth)
.plus(stats.totalSupplyInEth)
.toFixed();
stats.totalBorrowInEth = toBN(borrow)
.times(priceInEth)
.plus(stats.totalBorrowInEth)
.toFixed();
stats.totalMaxBorrowLimitInEth = toBN(priceInEth)
.times(factor)
.times(supply)
.plus(stats.totalMaxBorrowLimitInEth)
.toFixed();
return stats;
},
{
totalSupplyInEth: "0",
totalBorrowInEth: "0",
totalMaxBorrowLimitInEth: "0",
ethSupplied: '0',
}
);
let liquidation = "0";
if (!toBN(stats.totalSupplyInEth).isZero()) {
liquidation = BigNumber.max(
toBN(stats.totalMaxBorrowLimitInEth).div(stats.totalSupplyInEth),
"0"
).toFixed();
}
const status = BigNumber.max(
toBN(stats.totalBorrowInEth).div(stats.totalSupplyInEth),
"0"
);
if (status.gt(toBN(liquidation).minus("0.0001"))) {
return "Position will liquidate.";
}
},
spells: async ({ inputs, convertTokenAmountToWei, tokenIdMapping }) => {
const { tokenToId } = tokenIdMapping;
const debtTokenId = tokenToId.compound[inputs[0].token.key];
const collateralTokenId = tokenToId.compound[inputs[1].token.key];
return [
{
connector: "compound",
method: "payback",
args: [
debtTokenId,
convertTokenAmountToWei(inputs[0].value, inputs[0].token.decimals),
0,
0
]
},
{
connector: "compound",
method: "withdraw",
args: [
collateralTokenId,
convertTokenAmountToWei(inputs[1].value, inputs[1].token.decimals),
0,
0
]
}
];
}
});

View File

@ -10,18 +10,43 @@
</nuxt-link>
</div>
<div class="mt-10 flex items-center">
<div
style="background: radial-gradient(42.15% 42.15% at 48.94% 48.94%, #D6DAE0 75.67%, #F0F3F9 100%), #C4C4C4;"
class="w-16 h-16 rounded-full flex items-center justify-center border border-[#CCDCF3]"
>
<div class="mt-10 flex items-center justify-between">
<div class="flex items-center">
<div
class="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
style="background: radial-gradient(42.15% 42.15% at 48.94% 48.94%, #D6DAE0 75.67%, #F0F3F9 100%), #C4C4C4;"
class="w-16 h-16 rounded-full flex items-center justify-center border border-[#CCDCF3]"
>
<CompoundIcon class="w-8 h-8 text-white" />
<div
class="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
>
<CompoundIcon class="w-8 h-8 text-white" />
</div>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Compound</h1>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">Compound</h1>
<ButtonCTAOutlined
class="px-4 h-9 w-[173px]"
@click="$router.push({ hash: 'strategies?protocol=compound' })"
>
Strategies
<svg
class="ml-auto"
width="11"
height="10"
viewBox="0 0 11 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.3815 8.76139C4.38149 8.99703 4.64068 9.1407 4.8405 9.01581L10.859 5.25425C11.047 5.13675 11.047 4.86295 10.859 4.74545L4.84088 0.984069C4.64108 0.859187 4.38189 1.00283 4.38188 1.23845L4.38179 2.97869C4.38178 3.14437 4.24747 3.27867 4.08179 3.27867L1.23894 3.27867C1.07325 3.27867 0.93894 3.41299 0.93894 3.57867L0.93894 6.42096C0.93894 6.58664 1.07325 6.72096 1.23894 6.72096L4.08159 6.72096C4.24728 6.72096 4.3816 6.85528 4.38159 7.02097L4.3815 8.76139Z"
fill="#1874FF"
/>
</svg>
</ButtonCTAOutlined>
</div>
<div class="mt-10" v-if="position">
@ -163,13 +188,15 @@ import { useSearchFilter } from "~/composables/useSearchFilter";
import { useStatus } from "~/composables/useStatus";
import { useBigNumber } from "~/composables/useBigNumber";
import CardCompound from "~/components/protocols/CardCompound.vue";
import CompoundIcon from '~/assets/icons/compound.svg?inline'
import CompoundIcon from "~/assets/icons/compound.svg?inline";
import ButtonCTAOutlined from "~/components/common/input/ButtonCTAOutlined.vue";
export default defineComponent({
components: {
BackIcon,
CardCompound,
CompoundIcon,
ButtonCTAOutlined,
},
setup() {
const {