Add / update slippage for Vault Messages (#469)

* replaced minLpToReceive with slippage

* Re-lend assets after entering vault

* 🐛Fix vault unlock bug
This commit is contained in:
Bob van der Helm 2023-09-13 20:27:17 +02:00 committed by GitHub
parent 141b522c56
commit 2f9f0f4b02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 101 additions and 81 deletions

View File

@ -1,25 +0,0 @@
import { getCreditManagerQueryClient } from 'api/cosmwasm-client'
import { ENV } from 'constants/env'
import { BN } from 'utils/helpers'
export default async function getVaultConfigs(
coins: Coin[],
lpDenom: string,
slippage: number,
): Promise<BigNumber> {
if (!ENV.ADDRESS_CREDIT_MANAGER) return BN(Infinity)
const creditManagerQueryClient = await getCreditManagerQueryClient()
try {
return BN(
await creditManagerQueryClient.estimateProvideLiquidity({
coinsIn: coins,
lpTokenOut: lpDenom,
}),
)
.multipliedBy(1 - slippage)
.integerValue()
} catch (ex) {
throw ex
}
}

View File

@ -6,6 +6,9 @@ import { useParams } from 'react-router-dom'
import Button from 'components/Button'
import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/Icons'
import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { SLIPPAGE_KEY } from 'constants/localStore'
import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store'
import { VaultStatus } from 'types/enums/vault'
@ -19,6 +22,7 @@ export default function VaultExpanded(props: Props) {
const { accountId } = useParams()
const [isConfirming, setIsConfirming] = useState(false)
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
function depositMoreHandler() {
useStore.setState({
@ -42,6 +46,7 @@ export default function VaultExpanded(props: Props) {
await withdrawFromVaults({
accountId: accountId,
vaults,
slippage,
})
}

View File

@ -4,6 +4,9 @@ import { useParams } from 'react-router-dom'
import Button from 'components/Button'
import { ChevronRight } from 'components/Icons'
import NotificationBanner from 'components/NotificationBanner'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { SLIPPAGE_KEY } from 'constants/localStore'
import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store'
interface Props {
@ -14,6 +17,7 @@ export default function VaultUnlockBanner(props: Props) {
const { accountId } = useParams()
const [isConfirming, setIsConfirming] = useState(false)
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
async function handleWithdraw() {
if (!accountId) return
@ -26,6 +30,7 @@ export default function VaultUnlockBanner(props: Props) {
await withdrawFromVaults({
accountId: accountId,
vaults: props.vaults,
slippage,
})
setIsConfirming(false)
}

View File

@ -7,6 +7,9 @@ import VaultLogo from 'components/Earn/Farm/VaultLogo'
import { FormattedNumber } from 'components/FormattedNumber'
import Modal from 'components/Modal'
import Text from 'components/Text'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { SLIPPAGE_KEY } from 'constants/localStore'
import useLocalStorage from 'hooks/useLocalStorage'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { getAssetByDenom } from 'utils/assets'
@ -18,6 +21,7 @@ export default function WithdrawFromVaultsModal() {
const showTxLoader = useStore((s) => s.showTxLoader)
const withdrawFromVaults = useStore((s) => s.withdrawFromVaults)
const baseCurrency = useStore((s) => s.baseCurrency)
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
function onClose() {
useStore.setState({ withdrawFromVaultsModal: null })
@ -28,6 +32,7 @@ export default function WithdrawFromVaultsModal() {
await withdrawFromVaults({
accountId: accountId,
vaults: modal,
slippage,
})
onClose()
}

View File

@ -1,14 +1,14 @@
import debounce from 'debounce-promise'
import { useMemo, useState } from 'react'
import { useMemo } from 'react'
import getMinLpToReceive from 'api/vaults/getMinLpToReceive'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { SLIPPAGE_KEY } from 'constants/localStore'
import { BN_ZERO } from 'constants/math'
import useAutoLend from 'hooks/useAutoLend'
import useLocalStorage from 'hooks/useLocalStorage'
import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin'
import { Action } from 'types/generated/mars-credit-manager/MarsCreditManager.types'
import { getLendEnabledAssets } from 'utils/assets'
import { getDenomsFromBNCoins } from 'utils/tokens'
import {
getEnterVaultActions,
getVaultDepositCoinsAndValue,
@ -24,13 +24,11 @@ interface Props {
export default function useDepositVault(props: Props): {
actions: Action[]
minLpToReceive: string
totalValue: BigNumber
} {
const [minLpToReceive, setMinLpToReceive] = useState<BigNumber>(BN_ZERO)
const { data: prices } = usePrices()
const [slippage] = useLocalStorage<number>(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
const { isAutoLendEnabledForCurrentAccount: isAutoLend } = useAutoLend()
const borrowings: BNCoin[] = useMemo(
() => props.borrowings.filter((borrowing) => borrowing.amount.gt(0)),
[props.borrowings],
@ -44,8 +42,6 @@ export default function useDepositVault(props: Props): {
[props.reclaims],
)
const debouncedGetMinLpToReceive = useMemo(() => debounce(getMinLpToReceive, 500), [])
const { primaryCoin, secondaryCoin, totalValue } = useMemo(
() => getVaultDepositCoinsAndValue(props.vault, deposits, borrowings, reclaims, prices),
[reclaims, deposits, borrowings, props.vault, prices],
@ -68,41 +64,42 @@ export default function useDepositVault(props: Props): {
[totalValue, prices, props.vault, deposits, borrowings, slippage],
)
useMemo(async () => {
if (primaryCoin.amount.isZero() || secondaryCoin.amount.isZero()) return
const lpAmount = await debouncedGetMinLpToReceive(
[secondaryCoin.toCoin(), primaryCoin.toCoin()],
props.vault.denoms.lp,
slippage,
)
if (!lpAmount || lpAmount.isEqualTo(minLpToReceive)) return
setMinLpToReceive(lpAmount)
}, [
primaryCoin,
secondaryCoin,
props.vault.denoms.lp,
debouncedGetMinLpToReceive,
minLpToReceive,
slippage,
])
const enterVaultActions: Action[] = useMemo(() => {
if (primaryCoin.amount.isZero() || secondaryCoin.amount.isZero() || minLpToReceive.isZero())
return []
if (primaryCoin.amount.isZero() || secondaryCoin.amount.isZero()) return []
return getEnterVaultActions(props.vault, primaryCoin, secondaryCoin, minLpToReceive)
}, [props.vault, primaryCoin, secondaryCoin, minLpToReceive])
return getEnterVaultActions(props.vault, primaryCoin, secondaryCoin, slippage)
}, [props.vault, primaryCoin, secondaryCoin, slippage])
const actions = useMemo(
() => [...reclaimActions, ...borrowActions, ...swapActions, ...enterVaultActions],
[reclaimActions, borrowActions, swapActions, enterVaultActions],
)
const lendActions: Action[] = useMemo(() => {
if (!isAutoLend) return []
const denoms = Array.from(
new Set(getDenomsFromBNCoins([...props.reclaims, ...props.deposits, ...props.borrowings])),
)
const denomsForLend = getLendEnabledAssets()
.filter((asset) => denoms.includes(asset.denom))
.map((asset) => asset.denom)
return denomsForLend.map((denom) => ({
lend: {
denom,
amount: 'account_balance',
},
}))
}, [isAutoLend, props.borrowings, props.deposits, props.reclaims])
const actions = useMemo(() => {
return [
...reclaimActions,
...borrowActions,
...swapActions,
...enterVaultActions,
...lendActions,
]
}, [reclaimActions, borrowActions, swapActions, enterVaultActions, lendActions])
return {
actions,
minLpToReceive: minLpToReceive.toString(),
totalValue,
}
}
}

View File

@ -349,16 +349,27 @@ export default function createBroadcastSlice(
return !!response.result
},
withdrawFromVaults: async (options: { accountId: string; vaults: DepositedVault[] }) => {
withdrawFromVaults: async (options: {
accountId: string
vaults: DepositedVault[]
slippage: number
}) => {
const actions: CreditManagerAction[] = []
options.vaults.forEach((vault) => {
if (vault.unlockId)
if (vault.unlockId) {
actions.push({
exit_vault_unlocked: {
id: vault.unlockId,
vault: { address: vault.address },
},
})
actions.push({
withdraw_liquidity: {
lp_token: { denom: vault.denoms.lp, amount: 'account_balance' },
slippage: options.slippage.toString(),
},
})
}
})
const msg: CreditManagerExecuteMsg = {
update_credit_account: {

View File

@ -10,6 +10,7 @@ import { StdFee } from '@cosmjs/amino'
import {
HealthContractBaseForString,
IncentivesUnchecked,
Decimal,
Uint128,
OracleBaseForString,
ParamsBaseForString,
@ -23,7 +24,6 @@ import {
ActionAmount,
LiquidateRequestForVaultBaseForString,
VaultPositionType,
Decimal,
AccountNftBaseForString,
OwnerUpdate,
CallbackMsg,
@ -57,6 +57,7 @@ import {
VaultPositionResponseItem,
ConfigResponse,
OwnerResponse,
RewardsCollector,
ArrayOfCoin,
Positions,
DebtAmount,

View File

@ -11,6 +11,7 @@ import { toUtf8 } from '@cosmjs/encoding'
import {
HealthContractBaseForString,
IncentivesUnchecked,
Decimal,
Uint128,
OracleBaseForString,
ParamsBaseForString,
@ -24,7 +25,6 @@ import {
ActionAmount,
LiquidateRequestForVaultBaseForString,
VaultPositionType,
Decimal,
AccountNftBaseForString,
OwnerUpdate,
CallbackMsg,
@ -58,6 +58,7 @@ import {
VaultPositionResponseItem,
ConfigResponse,
OwnerResponse,
RewardsCollector,
ArrayOfCoin,
Positions,
DebtAmount,

View File

@ -11,6 +11,7 @@ import { StdFee } from '@cosmjs/amino'
import {
HealthContractBaseForString,
IncentivesUnchecked,
Decimal,
Uint128,
OracleBaseForString,
ParamsBaseForString,
@ -24,7 +25,6 @@ import {
ActionAmount,
LiquidateRequestForVaultBaseForString,
VaultPositionType,
Decimal,
AccountNftBaseForString,
OwnerUpdate,
CallbackMsg,
@ -58,6 +58,7 @@ import {
VaultPositionResponseItem,
ConfigResponse,
OwnerResponse,
RewardsCollector,
ArrayOfCoin,
Positions,
DebtAmount,

View File

@ -7,6 +7,7 @@
export type HealthContractBaseForString = string
export type IncentivesUnchecked = string
export type Decimal = string
export type Uint128 = string
export type OracleBaseForString = string
export type ParamsBaseForString = string
@ -16,6 +17,7 @@ export type ZapperBaseForString = string
export interface InstantiateMsg {
health_contract: HealthContractBaseForString
incentives: IncentivesUnchecked
max_slippage: Decimal
max_unlocking_positions: Uint128
oracle: OracleBaseForString
owner: string
@ -124,13 +126,13 @@ export type Action =
provide_liquidity: {
coins_in: ActionCoin[]
lp_token_out: string
minimum_receive: Uint128
slippage: Decimal
}
}
| {
withdraw_liquidity: {
lp_token: ActionCoin
minimum_receive: Coin[]
slippage: Decimal
}
}
| {
@ -155,7 +157,6 @@ export type LiquidateRequestForVaultBaseForString =
}
}
export type VaultPositionType = 'u_n_l_o_c_k_e_d' | 'l_o_c_k_e_d' | 'u_n_l_o_c_k_i_n_g'
export type Decimal = string
export type AccountNftBaseForString = string
export type OwnerUpdate =
| {
@ -297,14 +298,14 @@ export type CallbackMsg =
account_id: string
coins_in: ActionCoin[]
lp_token_out: string
minimum_receive: Uint128
slippage: Decimal
}
}
| {
withdraw_liquidity: {
account_id: string
lp_token: ActionCoin
minimum_receive: Coin[]
slippage: Decimal
}
}
| {
@ -313,7 +314,7 @@ export type CallbackMsg =
}
}
| {
assert_account_reqs: {
assert_hls_rules: {
account_id: string
}
}
@ -358,6 +359,7 @@ export interface ConfigUpdates {
account_nft?: AccountNftBaseForString | null
health_contract?: HealthContractBaseForString | null
incentives?: IncentivesUnchecked | null
max_slippage?: Decimal | null
max_unlocking_positions?: Uint128 | null
oracle?: OracleBaseForString | null
red_bank?: RedBankUnchecked | null
@ -366,6 +368,7 @@ export interface ConfigUpdates {
zapper?: ZapperBaseForString | null
}
export interface NftConfigUpdates {
credit_manager_contract_addr?: string | null
health_contract_addr?: string | null
max_value_for_burn?: Uint128 | null
}
@ -494,12 +497,13 @@ export interface ConfigResponse {
account_nft?: string | null
health_contract: string
incentives: string
max_slippage: Decimal
max_unlocking_positions: Uint128
oracle: string
ownership: OwnerResponse
params: string
red_bank: string
rewards_collector?: string | null
rewards_collector?: RewardsCollector | null
swapper: string
zapper: string
}
@ -510,6 +514,10 @@ export interface OwnerResponse {
owner?: string | null
proposed?: string | null
}
export interface RewardsCollector {
account_id: string
address: string
}
export type ArrayOfCoin = Coin[]
export interface Positions {
account_id: string

View File

@ -98,7 +98,11 @@ interface BroadcastSlice {
vault: DepositedVault
amount: string
}) => Promise<boolean>
withdrawFromVaults: (options: { accountId: string; vaults: DepositedVault[] }) => Promise<boolean>
withdrawFromVaults: (options: {
accountId: string
vaults: DepositedVault[]
slippage: number
}) => Promise<boolean>
withdraw: (options: {
accountId: string
coins: Array<{ coin: BNCoin; isMax?: boolean }>

View File

@ -31,3 +31,7 @@ export function getAllAssets(): Asset[] {
export function findCoinByDenom(denom: string, coins: BigNumberCoin[]) {
return coins.find((coin) => coin.denom === denom)
}
export function getLendEnabledAssets() {
return ASSETS.filter((asset) => asset.isAutoLendEnabled)
}

View File

@ -13,4 +13,4 @@ export const SECONDS_IN_A_YEAR = 31540000
export const LTV_BUFFER = 0.01
export const DEPOSIT_CAP_BUFFER = 0.999
export const VAULT_DEPOSIT_BUFFER = 0.9999
export const VAULT_DEPOSIT_BUFFER = 0.9999

View File

@ -24,3 +24,7 @@ export function getTokenPrice(denom: string, prices: BNCoin[]): BigNumber {
export function getDebtAmountWithInterest(debt: BigNumber, apr: number) {
return debt.times(1 + apr / 365 / 24).integerValue()
}
export function getDenomsFromBNCoins(coins: BNCoin[]) {
return coins.map((coin) => coin.denom)
}

View File

@ -148,7 +148,7 @@ export function getEnterVaultActions(
vault: Vault,
primaryCoin: BNCoin,
secondaryCoin: BNCoin,
minLpToReceive: BigNumber,
slippage: number,
): Action[] {
return [
{
@ -156,7 +156,7 @@ export function getEnterVaultActions(
// Smart Contact demands that secondary coin is first
coins_in: [secondaryCoin.toActionCoin(), primaryCoin.toActionCoin()],
lp_token_out: vault.denoms.lp,
minimum_receive: minLpToReceive.toString(),
slippage: slippage.toString(),
},
},
{
@ -204,4 +204,3 @@ export function getVaultDepositCoinsFromActions(actions: Action[]) {
})
})
}