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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,3 +24,7 @@ export function getTokenPrice(denom: string, prices: BNCoin[]): BigNumber {
export function getDebtAmountWithInterest(debt: BigNumber, apr: number) { export function getDebtAmountWithInterest(debt: BigNumber, apr: number) {
return debt.times(1 + apr / 365 / 24).integerValue() 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, vault: Vault,
primaryCoin: BNCoin, primaryCoin: BNCoin,
secondaryCoin: BNCoin, secondaryCoin: BNCoin,
minLpToReceive: BigNumber, slippage: number,
): Action[] { ): Action[] {
return [ return [
{ {
@ -156,7 +156,7 @@ export function getEnterVaultActions(
// Smart Contact demands that secondary coin is first // Smart Contact demands that secondary coin is first
coins_in: [secondaryCoin.toActionCoin(), primaryCoin.toActionCoin()], coins_in: [secondaryCoin.toActionCoin(), primaryCoin.toActionCoin()],
lp_token_out: vault.denoms.lp, lp_token_out: vault.denoms.lp,
minimum_receive: minLpToReceive.toString(), slippage: slippage.toString(),
}, },
}, },
{ {
@ -204,4 +204,3 @@ export function getVaultDepositCoinsFromActions(actions: Action[]) {
}) })
}) })
} }