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

fix: proportional slippage #424

Merged
merged 9 commits into from
Jan 15, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { getPoolActionableTokens, isV3NotSupportingWethIsEth } from '../../pool.
import { useUserSettings } from '@repo/lib/modules/user/settings/UserSettingsProvider'
import { isUnbalancedAddErrorMessage } from '@repo/lib/shared/utils/error-filters'
import { ApiToken } from '../../pool.types'
import { defaultProportionalSlippagePercentage } from '@repo/lib/shared/utils/slippage'

export type UseAddLiquidityResponse = ReturnType<typeof _useAddLiquidity>
export const AddLiquidityContext = createContext<UseAddLiquidityResponse | null>(null)
Expand All @@ -45,11 +46,17 @@ export function _useAddLiquidity(urlTxHash?: Hash) {
const [acceptPoolRisks, setAcceptPoolRisks] = useState(false)
const [wethIsEth, setWethIsEth] = useState(false)
const [totalUSDValue, setTotalUSDValue] = useState('0')
// Used when the user explicitly choses proportional input mode
const [wantsProportional, setWantsProportional] = useState(false)
const [proportionalSlippage, setProportionalSlippage] = useState<string>('0')

const { pool, refetch: refetchPool, isLoading } = usePool()

/* wantsProportional is true when:
- the pool requires proportional input
- the user selected the proportional tab
*/
const [wantsProportional, setWantsProportional] = useState(requiresProportionalInput(pool))
garethfuller marked this conversation as resolved.
Show resolved Hide resolved
const [proportionalSlippage, setProportionalSlippage] = useState<string>(
defaultProportionalSlippagePercentage
)

const { getNativeAssetToken, getWrappedNativeAssetToken, isLoadingTokenPrices } = useTokens()
const { isConnected } = useUserAccount()
const { hasValidationErrors } = useTokenInputsValidation()
Expand All @@ -68,8 +75,7 @@ export function _useAddLiquidity(urlTxHash?: Hash) {
const chain = pool.chain
const nativeAsset = getNativeAssetToken(chain)
const wNativeAsset = getWrappedNativeAssetToken(chain)
const isForcedProportionalAdd = requiresProportionalInput(pool)
const slippage = isForcedProportionalAdd ? proportionalSlippage : userSlippage
const slippage = wantsProportional ? proportionalSlippage : userSlippage
agualis marked this conversation as resolved.
Show resolved Hide resolved
const tokens = getPoolActionableTokens(pool)

function setInitialHumanAmountsIn() {
Expand Down Expand Up @@ -156,7 +162,7 @@ export function _useAddLiquidity(urlTxHash?: Hash) {
const hasQuoteContext = !!simulationQuery.data

async function refetchQuote() {
if (isForcedProportionalAdd) {
if (wantsProportional) {
/*
This is the only edge-case where the SDK needs pool onchain data from the frontend
(calculateProportionalAmounts uses pool.dynamicData.totalShares in its parameters)
Expand Down Expand Up @@ -221,8 +227,6 @@ export function _useAddLiquidity(urlTxHash?: Hash) {
hasQuoteContext,
addLiquidityTxSuccess,
slippage,
proportionalSlippage,
isForcedProportionalAdd,
wantsProportional,
referenceAmountAddress,
setWantsProportional,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ import { ApiToken } from '../../../pool.types'

// small wrapper to prevent out of context error
export function AddLiquidityForm() {
const { validTokens, proportionalSlippage } = useAddLiquidity()
const { validTokens, slippage } = useAddLiquidity()

return (
<TokenBalancesProvider bufferPercentage={proportionalSlippage} extTokens={validTokens}>
<TokenBalancesProvider bufferPercentage={slippage} extTokens={validTokens}>
<AddLiquidityMainForm />
</TokenBalancesProvider>
)
Expand All @@ -87,7 +87,6 @@ function AddLiquidityMainForm() {
nativeAsset,
wNativeAsset,
previewModalDisclosure,
proportionalSlippage,
slippage,
setProportionalSlippage,
setWantsProportional,
Expand Down Expand Up @@ -192,11 +191,11 @@ function AddLiquidityMainForm() {
<CardHeader>
<HStack justify="space-between" w="full">
<span>Add liquidity</span>
{requiresProportionalInput(pool) || wantsProportional ? (
{wantsProportional ? (
<ProportionalTransactionSettings
setSlippage={setProportionalSlippage}
size="sm"
slippage={proportionalSlippage}
slippage={slippage}
/>
) : (
<TransactionSettings size="sm" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class ProportionalBoostedAddLiquidityV3 implements AddLiquidityHandler {
...constructBaseBuildCallInput({
humanAmountsIn,
sdkQueryOutput: queryOutput.sdkQueryOutput,
slippagePercent: slippagePercent,
slippagePercent,
pool: this.helpers.pool,
}),
protocolVersion: 3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TwammAddLiquidityHandler } from './TwammAddLiquidity.handler'
import { UnbalancedAddLiquidityV2Handler } from './UnbalancedAddLiquidityV2.handler'
import { AddLiquidityHandler } from './AddLiquidity.handler'
import { NestedAddLiquidityV2Handler } from './NestedAddLiquidityV2.handler'
import { requiresProportionalInput, supportsNestedActions } from '../../LiquidityActionHelpers'
import { supportsNestedActions } from '../../LiquidityActionHelpers'
import { ProportionalAddLiquidityHandler } from './ProportionalAddLiquidity.handler'
import { isBoosted, isV3Pool } from '../../../pool.helpers'
import { ProportionalAddLiquidityHandlerV3 } from './ProportionalAddLiquidityV3.handler'
Expand Down Expand Up @@ -41,7 +41,7 @@ export function selectAddLiquidityHandler(
return new BoostedUnbalancedAddLiquidityV3Handler(pool)
}

if (requiresProportionalInput(pool) || wantsProportional) {
if (wantsProportional) {
if (isV3Pool(pool)) {
return new ProportionalAddLiquidityHandlerV3(pool)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useRelayerMode } from '@repo/lib/modules/relayer/useRelayerMode'
import { useTokenApprovalSteps } from '@repo/lib/modules/tokens/approvals/useTokenApprovalSteps'
import { getSpenderForAddLiquidity } from '@repo/lib/modules/tokens/token.helpers'
import { useSignRelayerStep } from '@repo/lib/modules/transactions/transaction-steps/useSignRelayerStep'
import { useUserSettings } from '@repo/lib/modules/user/settings/UserSettingsProvider'
import { useMemo } from 'react'
import { usePool } from '../../PoolProvider'
import { requiresPermit2Approval } from '../../pool.helpers'
Expand All @@ -24,10 +23,10 @@ export function useAddLiquiditySteps({
handler,
humanAmountsIn,
simulationQuery,
slippage,
}: AddLiquidityStepsParams) {
const { pool, chainId, chain } = usePool()
const shouldBatchTransactions = useShouldBatchTransactions(pool)
const { slippage } = useUserSettings()
const relayerMode = useRelayerMode(pool)
const shouldSignRelayerApproval = useShouldSignRelayerApproval(chainId, relayerMode)

Expand Down
11 changes: 6 additions & 5 deletions packages/lib/modules/user/settings/TransactionSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { fNum } from '@repo/lib/shared/utils/numbers'
import { AlertTriangle, Settings } from 'react-feather'
import { CurrencySelect } from './CurrencySelect'
import { SlippageInput } from './UserSettings'
import { defaultProportionalSlippagePercentage } from '@repo/lib/shared/utils/slippage'

export function TransactionSettings(props: ButtonProps) {
const { slippage, setSlippage } = useUserSettings()
Expand Down Expand Up @@ -104,11 +105,11 @@ export function ProportionalTransactionSettings({
<PopoverArrow />
<PopoverBody>
<Text fontSize="sm" fontWeight="500" lineHeight="18px" variant="secondary">
Slippage is set to 0 by default for forced proportional actions to reduce
dust left over. If you need to set slippage higher than 0 it will
effectively lower the amount of tokens you can add in the form below. Then,
if slippage occurs, the transaction can take the amount of tokens you
specified + slippage from your token balance.
Slippage is set to {defaultProportionalSlippagePercentage} by default for
forced proportional actions to reduce dust left over. If you need to set
slippage higher than 0 it will effectively lower the amount of tokens you
can add in the form below. Then, if slippage occurs, the transaction can
take the amount of tokens you specified + slippage from your token balance.
</Text>
</PopoverBody>
</PopoverContent>
Expand Down
7 changes: 7 additions & 0 deletions packages/lib/shared/utils/slippage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { HumanAmount } from '@balancer/sdk'
import { bn, fNum } from './numbers'

/*
We use this small slippage percentage by default for proportional adds because SDK calculation queries are not 100% precise.
For example, addLiquidityBoosted queries with tokens with 6 decimals would always fail if we used 0% slippage by default.
This 0.01% is big enough to prevent tx simulation errors in all types of proportional adds while keeping the dust amount small.
*/
export const defaultProportionalSlippagePercentage = '0.01'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I thought the SDK rounded down? The only reason the SDK calcs should fail with 0% slippage is if the pool balances move.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole reason we are using 0% slippage is to help the user avoid dust.

Copy link
Collaborator Author

@agualis agualis Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are cases where rounding down is not enough due to SDK query not being 100% precise as the comment explains. @brunoguerios has more context but it's an SC limitation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the problem lies in the AddLiquidityBoosted queries not matching the actual transaction 100%. It's an SC limitation.
The error is ~1000 wei, which is negligible on 18 decimal tokens, but not as much on 6 decimals tokens 😕
If you'd like to get even more context, we'd have to get SC team involved in the discussion.
For all other queries that are an exact match, rounding down should be enough.


export function slippageDiffLabel(actualAmount: HumanAmount, expectedAmount: HumanAmount) {
if (!expectedAmount) return ''
const bptDiff = bn(actualAmount).minus(expectedAmount)
Expand Down
Loading