Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: thorchain LP unsafe high-slippage deposits flow #6600

Merged
merged 6 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2223,7 +2223,7 @@
"emptyTitle": "The Land of Zero Loans!",
"emptyBody": "It appears you don't have any loans at the moment. Is this financial zen or just a break before your next big lending adventure? Either way, enjoy the calm!"
},
"unsafeBorrow": "This borrow has high slippage. Proceed with caution."
"unsafeBorrow": "This borrow has high slippage (%{slippagePercentage}%). Proceed with caution."
},
"foxDiscounts": {
"currentFoxPower": "Your FOX Power",
Expand Down Expand Up @@ -2258,6 +2258,7 @@
"availablePools": "Available Pools",
"addLiquidity": "Add Liquidity",
"customSlippageDisabled": "We are unable to set a custom slippage",
"unsafeQuote": "This deposit has high slippage (%{slippagePercentage}%). Proceed with caution.",
"removeLiquidity": "Remove Liquidity",
"initialPricesAndPoolShare": "Initial Prices and Pool Share",
"pricePerAsset": "%{from} per %{to}",
Expand Down
12 changes: 7 additions & 5 deletions src/lib/utils/thorchain/lp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,25 @@ export const getSlippage = ({
const numerator = bnOrZero(R).times(a).minus(bnOrZero(A).times(r))
const denominator = bnOrZero(A).times(r).plus(bnOrZero(R).times(A))

const slippageBps = numerator.div(denominator).abs()
const slippageDecimalPercent = numerator.div(denominator).abs()
const assetPriceInRune = bnOrZero(pool.balance_rune).div(pool.balance_asset)

if (a.gt(0) && r.eq(0)) {
const aInRune = a.times(assetPriceInRune)
return {
decimalPercent: slippageBps.times(100).toFixed(),
runeAmountCryptoPrecision: fromThorBaseUnit(aInRune.times(slippageBps)).toFixed(
decimalPercent: slippageDecimalPercent.toFixed(),
runeAmountCryptoPrecision: fromThorBaseUnit(aInRune.times(slippageDecimalPercent)).toFixed(
THOR_PRECISION,
),
}
}

if (r.gt(0) && a.eq(0)) {
return {
decimalPercent: slippageBps.times(100).toFixed(),
runeAmountCryptoPrecision: fromThorBaseUnit(r.times(slippageBps)).toFixed(THOR_PRECISION),
decimalPercent: slippageDecimalPercent.toFixed(),
runeAmountCryptoPrecision: fromThorBaseUnit(r.times(slippageDecimalPercent)).toFixed(
THOR_PRECISION,
),
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/pages/Lending/Pool/components/Borrow/BorrowInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,11 @@ export const BorrowInput = ({
<AlertIcon color='red' />
<Stack spacing={0}>
<AlertDescription lineHeight='short'>
{translate('lending.unsafeBorrow')}
{translate('lending.unsafeBorrow', {
slippagePercentage: bnOrZero(quoteSlippageDecimalPercentage)
.times(100)
.toFixed(2),
})}
</AlertDescription>
</Stack>
</Alert>
Expand Down
128 changes: 88 additions & 40 deletions src/pages/ThorChainLP/components/AddLiquidity/AddLiquidityInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ import { ReadOnlyAsset } from '../ReadOnlyAsset'
import { PoolSummary } from './components/PoolSummary'
import { AddLiquidityRoutePaths } from './types'

const UNSAFE_SLIPPAGE_DECIMAL_PERCENT = 0.05 // 5%

const buttonProps = { flex: 1, justifyContent: 'space-between' }

const formControlProps = {
Expand Down Expand Up @@ -194,6 +196,30 @@ export const AddLiquidityInput: React.FC<AddLiquidityInputProps> = ({
const [virtualRuneDepositAmountFiatUserCurrency, setVirtualRuneDepositAmountFiatUserCurrency] =
useState<string | undefined>()

const [slippageDecimalPercentage, setSlippageDecimalPercentage] = useState<string | undefined>()
const [isUnsafeQuoteNoticeDismissed, setIsUnsafeQuoteNoticeDismissed] = useState<boolean | null>(
null,
)

const handleAcknowledgeUnsafeQuote = useCallback(() => {
kaladinlight marked this conversation as resolved.
Show resolved Hide resolved
// We don't want to *immediately* set this or there will be a "click-through"
// i.e the regular continue button will render immediately, and click will bubble to it
setTimeout(() => {
setIsUnsafeQuoteNoticeDismissed(true)
}, 100)
}, [])

const isUnsafeQuote = useMemo(
() =>
slippageDecimalPercentage &&
bn(slippageDecimalPercentage).gt(UNSAFE_SLIPPAGE_DECIMAL_PERCENT),
[slippageDecimalPercentage],
)

useEffect(() => {
if (isUnsafeQuote) setIsUnsafeQuoteNoticeDismissed(false)
}, [isUnsafeQuote])

const { data: pools } = usePools()
const assets = useAppSelector(selectAssets)

Expand Down Expand Up @@ -989,6 +1015,8 @@ export const AddLiquidityInput: React.FC<AddLiquidityInputProps> = ({
assetId: poolAsset.assetId,
})

setSlippageDecimalPercentage(estimate.slippageDecimalPercent)

const _slippageFiatUserCurrency = bnOrZero(estimate.slippageRuneCryptoPrecision)
.times(runeMarketData.price)
.toFixed()
Expand Down Expand Up @@ -1549,46 +1577,66 @@ export const AddLiquidityInput: React.FC<AddLiquidityInputProps> = ({
{incompleteAlert}
{maybeOpportunityNotSupportedExplainer}
{symAlert}
<Button
mx={-2}
size='lg'
colorScheme={errorCopy ? 'red' : 'blue'}
isDisabled={
isTradingActive === false ||
!isThorchainLpDepositEnabled ||
!confirmedQuote ||
!votingPower ||
isVotingPowerLoading ||
!hasEnoughAssetBalance ||
!hasEnoughRuneBalance ||
isApprovalTxPending ||
(isSweepNeededEnabled && isSweepNeeded === undefined) ||
isSweepNeededError ||
isEstimatedPoolAssetFeesDataError ||
isEstimatedRuneFeesDataError ||
bnOrZero(actualAssetDepositAmountCryptoPrecision)
.plus(bnOrZero(actualRuneDepositAmountCryptoPrecision))
.isZero() ||
notEnoughFeeAssetError ||
notEnoughRuneFeeError ||
!walletSupportsOpportunity
}
isLoading={
(poolAssetTxFeeCryptoBaseUnit === undefined && isEstimatedPoolAssetFeesDataLoading) ||
isVotingPowerLoading ||
isInboundAddressesDataLoading ||
isTradingActiveLoading ||
isSmartContractAccountAddressLoading ||
isAllowanceDataLoading ||
isApprovalTxPending ||
(isSweepNeeded === undefined && isSweepNeededLoading) ||
isInboundAddressesDataLoading ||
(runeTxFeeCryptoBaseUnit === undefined && isEstimatedPoolAssetFeesDataLoading)
}
onClick={handleSubmit}
>
{confirmCopy}
</Button>
{isUnsafeQuote && !isUnsafeQuoteNoticeDismissed ? (
<>
<Flex direction='column' gap={2}>
<Alert status='error' width='auto' fontSize='sm' variant='solid'>
<AlertIcon color='red' />
<Stack spacing={0}>
<AlertDescription lineHeight='short'>
{translate('pools.unsafeQuote', {
slippagePercentage: bnOrZero(slippageDecimalPercentage).times(100).toFixed(2),
})}
</AlertDescription>
</Stack>
</Alert>
</Flex>
<Button size='lg' colorScheme='red' onClick={handleAcknowledgeUnsafeQuote}>
<Text translation={'defi.modals.saversVaults.understand'} />
</Button>
</>
) : (
<Button
mx={-2}
size='lg'
colorScheme={errorCopy ? 'red' : 'blue'}
isDisabled={
isTradingActive === false ||
!isThorchainLpDepositEnabled ||
!confirmedQuote ||
!votingPower ||
isVotingPowerLoading ||
!hasEnoughAssetBalance ||
!hasEnoughRuneBalance ||
isApprovalTxPending ||
(isSweepNeededEnabled && isSweepNeeded === undefined) ||
isSweepNeededError ||
isEstimatedPoolAssetFeesDataError ||
isEstimatedRuneFeesDataError ||
bnOrZero(actualAssetDepositAmountCryptoPrecision)
.plus(bnOrZero(actualRuneDepositAmountCryptoPrecision))
.isZero() ||
notEnoughFeeAssetError ||
notEnoughRuneFeeError ||
!walletSupportsOpportunity
}
isLoading={
(poolAssetTxFeeCryptoBaseUnit === undefined && isEstimatedPoolAssetFeesDataLoading) ||
isVotingPowerLoading ||
isInboundAddressesDataLoading ||
isTradingActiveLoading ||
isSmartContractAccountAddressLoading ||
isAllowanceDataLoading ||
isApprovalTxPending ||
(isSweepNeeded === undefined && isSweepNeededLoading) ||
isInboundAddressesDataLoading ||
(runeTxFeeCryptoBaseUnit === undefined && isEstimatedPoolAssetFeesDataLoading)
}
onClick={handleSubmit}
>
{confirmCopy}
</Button>
)}
</CardFooter>
<FeeModal
isOpen={showFeeModal}
Expand Down
Loading