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: add liquidity unbalanced permit2 #1117

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7fa63fe
feat: add permit2 signature step
agualis Sep 20, 2024
7175c7c
fix: types
agualis Sep 23, 2024
7aa9c95
chore: rename includesNativeAsset
agualis Sep 23, 2024
a720db9
feat: avoid buildcall when permit2 is nor signed
agualis Sep 23, 2024
f2c0788
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 24, 2024
863ea60
chore: bump sdk to v0.26.1
agualis Sep 25, 2024
c8aa8a6
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 25, 2024
8c58184
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 25, 2024
d4f70d1
fix: add nonces query
agualis Sep 25, 2024
8666be6
fix: remove old handler
agualis Sep 25, 2024
0125d2d
chore: rename permit2 transfer functions
agualis Sep 25, 2024
61b1563
chore: add token symbols to permit2 transfer label
agualis Sep 26, 2024
aaafbbd
chore: missing parameter
agualis Sep 26, 2024
0cfdcf9
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 26, 2024
49fd31a
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 27, 2024
cbec43a
refactor: extract base abstract class
agualis Sep 27, 2024
bb2285b
refactor: extract signature helpers
agualis Sep 30, 2024
272f36b
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 30, 2024
8db0520
chore: delete file
agualis Sep 30, 2024
90ec059
chore: refactor isPermit parameters
agualis Sep 30, 2024
46a2f82
chore: improve comment
agualis Oct 1, 2024
ae4198c
chore: improve naming
agualis Oct 1, 2024
5e1e154
chore: improve permit2 step
agualis Oct 1, 2024
b927f2f
Change promo banners to 'CoW is Live'
uiuxxx Oct 1, 2024
bb9ff72
fix: receipt hook integration tests after foundry update
agualis Oct 1, 2024
ca06eb0
chore: avoid foundry cache
agualis Oct 1, 2024
89b708e
chore: enable foundry cache again
agualis Oct 1, 2024
b20ee98
chore: temporarily disable foundry cache
agualis Oct 1, 2024
3cb8df6
fix: receipt hook integration tests after foundry update
agualis Oct 1, 2024
be9d4b0
Merge branch 'ui/promos' into feat/addLiquidityPermit2
agualis Oct 1, 2024
bebdb88
fix: sign permit2 loading condition
agualis Oct 1, 2024
04bba89
Merge branch 'main' into feat/addLiquidityPermit2
agualis Oct 1, 2024
3b0b1c6
chore: disable cache
agualis Oct 2, 2024
1fd092c
chore: extract token helpers
agualis Oct 2, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ jobs:
uses: ./.github/actions/setup
- name: Set up foundry (includes anvil)
uses: foundry-rs/foundry-toolchain@v1
with:
cache: false
- name: Run integration tests
run: pnpm test:integration
3 changes: 3 additions & 0 deletions app/(app)/debug/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ export default function Debug() {
<Link as={NextLink} href="/debug/remove-allowance">
Remove allowance
</Link>
<Link as={NextLink} href="/debug/permit2-allowance">
Permit2 allowance
</Link>
</VStack>
</FadeInOnView>
)
Expand Down
63 changes: 63 additions & 0 deletions app/(app)/debug/permit2-allowance/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client'

import { useUserAccount } from '@/lib/modules/web3/UserAccountProvider'
import { Center, Input, Text, VStack } from '@chakra-ui/react'
import { useState } from 'react'
import { Address } from 'viem'
import { sepolia } from 'viem/chains'
import { useReadContract } from 'wagmi'
import { fNum } from '@/lib/shared/utils/numbers'
import { getGqlChain, getNetworkConfig } from '@/lib/config/app.config'
import { permit2Abi } from '@balancer/sdk'

export default function Page() {
const [tokenAddress, setTokenAddress] = useState<Address>('' as Address)

const { chain, userAddress } = useUserAccount()

const chainId = chain?.id || sepolia.id

const { data } = usePermit2Allowance({ chainId, tokenAddress, owner: userAddress })

return (
<Center>
<VStack w="50%">
<Text>
Enter address of token to check permit2 allowance in the current chain:{' '}
{chain ? chain.name : 'None'}
</Text>
<Input type="text" onChange={e => setTokenAddress(e.target.value as Address)} />

{data && (
<div>
<div>Amount: {fNum('integer', data[0])}</div>
<div>Expires: {data[1]}</div>
<div>Nonce: {data[2]}</div>
</div>
)}
</VStack>
</Center>
)
}

type Params = {
chainId: number
tokenAddress: Address
owner: Address
}
function usePermit2Allowance({ chainId, tokenAddress, owner }: Params) {
const permit2Address = '0x000000000022D473030F116dDEE9F6B43aC78BA3'
const balancerRouter = getNetworkConfig(getGqlChain(chainId)).contracts.balancer.router!
const spender = balancerRouter

return useReadContract({
chainId,
address: permit2Address,
abi: permit2Abi,
functionName: 'allowance',
args: [owner, tokenAddress, spender],
query: {
enabled: !!tokenAddress && !!owner,
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { isHash } from 'viem'
import { usePoolRedirect } from '@/lib/modules/pool/pool.hooks'
import { DefaultPageContainer } from '@/lib/shared/components/containers/DefaultPageContainer'
import { AddLiquidityProvider } from '@/lib/modules/pool/actions/add-liquidity/AddLiquidityProvider'
import { Permit2SignatureProvider } from '@/lib/modules/tokens/approvals/permit2/Permit2SignatureProvider'

type Props = PropsWithChildren<{
params: { txHash?: string[] }
Expand Down Expand Up @@ -40,11 +41,13 @@ export default function AddLiquidityLayout({ params: { txHash }, children }: Pro
<DefaultPageContainer>
<TransactionStateProvider>
<RelayerSignatureProvider>
<TokenInputsValidationProvider>
<AddLiquidityProvider urlTxHash={urlTxHash}>
<PriceImpactProvider>{children}</PriceImpactProvider>
</AddLiquidityProvider>
</TokenInputsValidationProvider>
<Permit2SignatureProvider>
<TokenInputsValidationProvider>
<AddLiquidityProvider urlTxHash={urlTxHash}>
<PriceImpactProvider>{children}</PriceImpactProvider>
</AddLiquidityProvider>
</TokenInputsValidationProvider>
</Permit2SignatureProvider>
</RelayerSignatureProvider>
</TransactionStateProvider>
</DefaultPageContainer>
Expand Down
2 changes: 2 additions & 0 deletions lib/config/config.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface ContractsConfig {
vaultV2: Address
// TODO: make it required when v3 is deployed in all networks
vaultV3?: Address
// TODO: make it required when v3 is deployed in all networks
router?: Address
relayerV6: Address
minter: Address
}
Expand Down
1 change: 1 addition & 0 deletions lib/config/networks/sepolia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const networkConfig: NetworkConfig = {
balancer: {
vaultV2: '0xBA12222222228d8Ba445958a75a0704d566BF2C8',
vaultV3: '0x0EF1c156a7986F394d90eD1bEeA6483Cc435F542',
router: '0xB12FcB422aAe6720f882E22C340964a7723f2387',
relayerV6: '0x7852fB9d0895e6e8b3EedA553c03F6e2F9124dF9',
minter: '0x1783Cd84b3d01854A96B4eD5843753C2CcbD574A',
},
Expand Down
49 changes: 33 additions & 16 deletions lib/modules/pool/actions/LiquidityActionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,31 @@ import {
MinimalToken,
NestedPoolState,
PoolState,
PoolStateWithBalances,
Token,
TokenAmount,
mapPoolToNestedPoolState,
mapPoolType,
PoolStateWithBalances,
Token,
} from '@balancer/sdk'
import { Hex, formatUnits, parseUnits, Address } from 'viem'
import {
isAffectedByCspIssue,
isComposableStableV1,
isCowAmmPool,
isGyro,
isV3Pool,
} from '../pool.helpers'
import { Pool } from '../PoolProvider'
import BigNumber from 'bignumber.js'
import { Address, Hex, formatUnits, parseUnits } from 'viem'
import { GetTokenFn } from '../../tokens/TokensProvider'
import {
isNativeAsset,
isNativeOrWrappedNative,
isWrappedNativeAsset,
swapNativeWithWrapped,
} from '../../tokens/token.helpers'
import { HumanTokenAmountWithAddress } from '../../tokens/token.types'
import BigNumber from 'bignumber.js'
import { Pool } from '../PoolProvider'
import {
isAffectedByCspIssue,
isComposableStableV1,
isCowAmmPool,
isGyro,
isV3Pool,
} from '../pool.helpers'
import { SdkQueryAddLiquidityOutput } from './add-liquidity/add-liquidity.types'

// Null object used to avoid conditional checks during hook loading state
const NullPool: Pool = {
Expand Down Expand Up @@ -313,10 +315,25 @@ export function hasNoLiquidity(pool: Pool): boolean {
}

// When the pool has version < v3, it adds extra buildCall params (sender and recipient) that must be present only in V1/V2
export function formatBuildCallParams<T>(buildCallParams: T, isV3Pool: boolean, account: Address) {
// sender must be undefined for v3 pools
if (isV3Pool) return buildCallParams

export function formatBuildCallParams<T>(buildCallParams: T, account: Address) {
// sender and recipient must be defined only for v1 and v2 pools
return { ...buildCallParams, sender: account, recipient: account }
}

export function getTokenSymbols(
getToken: GetTokenFn,
chain: GqlChain,
queryOutput?: SdkQueryAddLiquidityOutput
) {
if (!queryOutput?.sdkQueryOutput) return []
const amountsIn = queryOutput.sdkQueryOutput.amountsIn
const tokenSymbols = amountsIn
?.filter(a => a.amount > 0n)
.map(a => getToken(a.token.address, chain)?.symbol ?? 'Unknown')
return tokenSymbols
}

export function getTokenAddresses(queryOutput?: SdkQueryAddLiquidityOutput): Address[] | undefined {
if (!queryOutput?.sdkQueryOutput) return undefined
return queryOutput.sdkQueryOutput.amountsIn.map(t => t.token.address)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { AddLiquidityNestedQueryOutput, AddLiquidityQueryOutput, TokenAmount } from '@balancer/sdk'
import {
AddLiquidityNestedQueryOutput,
AddLiquidityQueryOutput,
Permit2,
TokenAmount,
} from '@balancer/sdk'
import { Address } from 'viem'
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'

Expand All @@ -17,6 +22,7 @@ export interface BuildAddLiquidityInput {
slippagePercent: string
queryOutput: QueryAddLiquidityOutput
relayerApprovalSignature?: Address //only used by Nested Add Liquidity in signRelayer mode
permit2?: Permit2 //only used by v3 add liquidity
}

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { BuildAddLiquidityInput, QueryAddLiquidityOutput } from '../add-liquidity.types'
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'

/**
* AddLiquidityHandler is an interface that defines the methods that must be implemented by a handler.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { getRpcUrl } from '@/lib/modules/web3/transports'
import {
AddLiquidity,
AddLiquidityKind,
AddLiquidityUnbalancedInput,
PriceImpact,
PriceImpactAmount,
} from '@balancer/sdk'
import { Pool } from '../../../PoolProvider'
import { LiquidityActionHelpers, areEmptyAmounts } from '../../LiquidityActionHelpers'
import { SdkBuildAddLiquidityInput, SdkQueryAddLiquidityOutput } from '../add-liquidity.types'
import { AddLiquidityHandler } from './AddLiquidity.handler'

/**
* Base abstract class that shares common logic shared by v3 and v2/v1 pool unbalanced handlers
*/
export abstract class BaseUnbalancedAddLiquidityHandler implements AddLiquidityHandler {
protected helpers: LiquidityActionHelpers

constructor(pool: Pool) {
this.helpers = new LiquidityActionHelpers(pool)
}

public async simulate(
humanAmountsIn: HumanTokenAmountWithAddress[]
): Promise<SdkQueryAddLiquidityOutput> {
const addLiquidity = new AddLiquidity()
const addLiquidityInput = this.constructSdkInput(humanAmountsIn)

const sdkQueryOutput = await addLiquidity.query(addLiquidityInput, this.helpers.poolState)

return { bptOut: sdkQueryOutput.bptOut, sdkQueryOutput }
}

public async getPriceImpact(humanAmountsIn: HumanTokenAmountWithAddress[]): Promise<number> {
if (areEmptyAmounts(humanAmountsIn)) {
// Avoid price impact calculation when there are no amounts in
return 0
}

const addLiquidityInput = this.constructSdkInput(humanAmountsIn)

const priceImpactABA: PriceImpactAmount = await PriceImpact.addLiquidityUnbalanced(
addLiquidityInput,
this.helpers.poolState
)

return priceImpactABA.decimal
}

public abstract buildCallData(input: SdkBuildAddLiquidityInput): Promise<TransactionConfig>

/**
* PRIVATE METHODS
*/
protected constructSdkInput(
humanAmountsIn: HumanTokenAmountWithAddress[]
): AddLiquidityUnbalancedInput {
const amountsIn = this.helpers.toSdkInputAmounts(humanAmountsIn)

return {
chainId: this.helpers.chainId,
rpcUrl: getRpcUrl(this.helpers.chainId),
amountsIn,
kind: AddLiquidityKind.Unbalanced,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { UnbalancedAddLiquidityHandler } from './UnbalancedAddLiquidity.handler'
import { selectAddLiquidityHandler } from './selectAddLiquidityHandler'
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { Pool } from '../../../PoolProvider'
import { getNetworkConfig } from '@/lib/config/app.config'
import { GqlChain } from '@/lib/shared/services/api/generated/graphql'

function selectUnbalancedHandler() {
return selectAddLiquidityHandler(aWjAuraWethPoolElementMock()) as UnbalancedAddLiquidityHandler
Expand Down Expand Up @@ -110,7 +112,8 @@ describe.skip('When adding unbalanced liquidity for a V3 pool', async () => {
queryOutput,
})

const sepoliaRouter = '0xB12FcB422aAe6720f882E22C340964a7723f2387'
const sepoliaRouter = getNetworkConfig(GqlChain.Sepolia).contracts.balancer.router

expect(result.to).toBe(sepoliaRouter)
expect(result.data).toBeDefined()
})
Expand Down
Loading