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(permit): refactor permit-utils package #3258

Merged
merged 45 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a6a8719
feat: add permit-utils lib
alfetopito Oct 19, 2023
dc787bd
chore: remove default files
alfetopito Oct 19, 2023
4a196cc
feat: add utils to permit-utils package
alfetopito Oct 19, 2023
d37901b
feat: remove utils from modules/permit in favor of permit-utils pkg
alfetopito Oct 19, 2023
c0d5f93
chore: move PermitProviderConnector to utils
alfetopito Oct 20, 2023
759a3df
chore: updated eslint rules similar to main app
alfetopito Oct 20, 2023
d232815
chore: remove external dependency on DAI
alfetopito Oct 20, 2023
4bb3c81
chore: remove types that are not used in the lib
alfetopito Oct 20, 2023
d938e72
chore: remove dependency on GP_VAULT_RELAYER
alfetopito Oct 20, 2023
7b34340
chore: remove dependency on @cowprotocol/common-utils
alfetopito Oct 20, 2023
7e7c300
refactor: sorting imports
alfetopito Oct 20, 2023
b274bd7
chore: update project name
alfetopito Oct 20, 2023
5e3e3b8
chore: add readme
alfetopito Oct 20, 2023
8767ef4
refactor: move buildPermitCallData to utils
alfetopito Oct 20, 2023
4f37219
refactor: rename checkIsTokenPermittable to getTokenPermitInfo
alfetopito Oct 20, 2023
1ca5fd3
chore: move IsTokenPermittableResult back to modules/permit
alfetopito Oct 20, 2023
9ed9b11
chore: set permit-utils pkg version to 0.0.1-RC.0
alfetopito Oct 20, 2023
f393444
refactor: export fns individually rather than everything from the file
alfetopito Oct 20, 2023
3bf50f5
fix: typo on project name 🤦
alfetopito Oct 20, 2023
a269156
chore: fix config with `nx repair`
alfetopito Oct 20, 2023
3eba17c
fix: project name has to match path I think. Now it builds
alfetopito Oct 20, 2023
4ae89c9
chore: pick the version from package.json when publishing a package
alfetopito Oct 20, 2023
d6d071e
chore: add otp arg to all projects
alfetopito Oct 20, 2023
9f9d4be
chore: copy README on publish to dist folder so it gets added to npm
alfetopito Oct 23, 2023
6f870ca
chore: remove dependency on cow-sdk package
alfetopito Oct 24, 2023
15c879a
fix: must have macrosPlugin as part of vite.config
alfetopito Oct 24, 2023
fab62f9
chore: change provider type to JsonRpcProvider
alfetopito Oct 24, 2023
66804a6
chore: expose GetTokenPermitIntoResult
alfetopito Oct 25, 2023
774d442
chore: remove stuff added automatically which are not needed
alfetopito Oct 26, 2023
8e130c7
fix: js code style
alfetopito Oct 26, 2023
e0ad9a1
chore: remove commands which are not relevant in the NPM context
alfetopito Oct 26, 2023
0ef4123
chore: add somewhat complete usage example
alfetopito Oct 26, 2023
fd6fbd9
chore: remove another dep added automatically jest-environment-node
alfetopito Oct 26, 2023
0df4608
chore: import AbiInput from @1inch lib instead of directly from web3
alfetopito Oct 26, 2023
100ee6d
chore: remove @ethersproject/bignumber dep. Already part of ethers
alfetopito Oct 27, 2023
02140f7
refactor: removed extra comma
alfetopito Oct 27, 2023
d0d295e
refactor: remove dependency on @uniswap/core
alfetopito Oct 27, 2023
0bdc35d
chore: exclude all external dependencies from the build
alfetopito Oct 27, 2023
a15a83a
chore: change package type from commonjs to module
alfetopito Oct 27, 2023
adbc37c
chore: add required dependencies to lib package.json
alfetopito Oct 27, 2023
53b85f8
feat: set different entry points for different module styles
alfetopito Oct 27, 2023
a02a6dd
chore: update some package dependencies
alfetopito Oct 27, 2023
d1b55a2
fix: exclusions MUST not be the whole /node_modules/ as it breaks the…
alfetopito Oct 27, 2023
9c80bee
fix: no longer using Token type on permit-utils
alfetopito Oct 27, 2023
b03b2f8
fix: uploaded local registry path by mistake
alfetopito Oct 27, 2023
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
30 changes: 0 additions & 30 deletions apps/cowswap-frontend/src/modules/permit/const.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,11 @@
import { DAI } from '@cowprotocol/common-const'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { MaxUint256 } from '@ethersproject/constants'
import { Wallet } from '@ethersproject/wallet'

import ms from 'ms.macro'

import { TradeType } from '../trade'

// PK used only for signing permit requests for quoting and identifying token 'permittability'
// Do not use or try to send funds to it. Or do. It'll be your funds 🤷
const PERMIT_PK = '0xc58a2a421ca71ca57ae698f1c32feeb0b0ccb434da0b8089d88d80fb918f3f9d' // address: 0xFf65D1DfCF256cf4A8D5F2fb8e70F936606B7474

export const PERMIT_SIGNER = new Wallet(PERMIT_PK)

export const PERMIT_GAS_LIMIT_MIN: Record<SupportedChainId, number> = {
1: 55_000,
100: 55_000,
5: 36_000,
}

export const DEFAULT_PERMIT_GAS_LIMIT = '80000'

export const DEFAULT_PERMIT_VALUE = MaxUint256.toString()

export const DEFAULT_PERMIT_DURATION = ms`5 years`

export const ORDER_TYPE_SUPPORTS_PERMIT: Record<TradeType, boolean> = {
[TradeType.SWAP]: true,
[TradeType.LIMIT_ORDER]: true,
[TradeType.ADVANCED_ORDERS]: false,
}

export const PENDING_ORDER_PERMIT_CHECK_INTERVAL = ms`1min`

// DAI's mainnet contract (https://etherscan.io/address/0x6b175474e89094c44da98b954eedeac495271d0f#readContract) returns
// `1` for the version, while when calling the contract method returns `2`.
// Also, if we use the version returned by the contract, it simply doesn't work
// Thus, do not call it for DAI.
// TODO: figure out whether more tokens behave the same way
export const TOKENS_TO_SKIP_VERSION = new Set([DAI.address.toLowerCase()])
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'

import { Token } from '@uniswap/sdk-core'
import { PermitHookData } from '@cowprotocol/permit-utils'

import { useDerivedTradeState } from 'modules/trade'

Expand All @@ -9,7 +9,7 @@ import { useSafeMemo } from 'common/hooks/useSafeMemo'
import { useGeneratePermitHook } from './useGeneratePermitHook'
import { useIsTokenPermittable } from './useIsTokenPermittable'

import { GeneratePermitHookParams, PermitHookData } from '../types'
import { GeneratePermitHookParams } from '../types'

/**
* Returns PermitHookData using an account agnostic signer if inputCurrency is permittable
Expand Down Expand Up @@ -44,10 +44,10 @@ function useGeneratePermitHookParams(): GeneratePermitHookParams | undefined {
const permitInfo = useIsTokenPermittable(inputCurrency, tradeType)

return useSafeMemo(() => {
if (!inputCurrency || !permitInfo) return undefined
if (!inputCurrency || !('address' in inputCurrency) || !permitInfo) return undefined

return {
inputToken: inputCurrency as Token,
inputToken: { address: inputCurrency.address, name: inputCurrency.name },
permitInfo,
}
}, [inputCurrency, permitInfo])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { useCallback } from 'react'

import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { checkIsCallDataAValidPermit, getPermitUtilsInstance, PermitInfo } from '@cowprotocol/permit-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
import { Web3Provider } from '@ethersproject/providers'
import { useWeb3React } from '@web3-react/core'

import { DAI_PERMIT_SELECTOR, Eip2612PermitUtils, EIP_2612_PERMIT_SELECTOR } from '@1inch/permit-signed-approvals-utils'

import { getAppDataHooks } from 'modules/appData'

import { ParsedOrder } from 'utils/orderUtils/parseOrder'

import { useGetPermitInfo } from './useGetPermitInfo'

import { CheckHasValidPendingPermit, PermitInfo, SupportedPermitInfo } from '../types'
import { fixTokenName } from '../utils/fixTokenName'
import { getPermitUtilsInstance } from '../utils/getPermitUtilsInstance'
import { CheckHasValidPendingPermit } from '../types'

export function useCheckHasValidPendingPermit(): CheckHasValidPendingPermit {
const { chainId } = useWalletInfo()
Expand All @@ -38,7 +35,7 @@ export function useCheckHasValidPendingPermit(): CheckHasValidPendingPermit {

return checkHasValidPendingPermit(order, provider, chainId, permitInfo)
},
[chainId, provider]
[chainId, getPermitInfo, provider]
)
}

Expand Down Expand Up @@ -71,7 +68,7 @@ async function checkHasValidPendingPermit(

const checkedHooks = await Promise.all(
preHooks.map(({ callData }) =>
checkIsSingleCallDataAValidPermit(order, chainId, eip2162Utils, tokenAddress, tokenName, callData, permitInfo)
checkIsCallDataAValidPermit(order.owner, chainId, eip2162Utils, tokenAddress, tokenName, callData, permitInfo)
)
)

Expand All @@ -85,52 +82,3 @@ async function checkHasValidPendingPermit(
// Only when all permits are valid, then the order permits are still valid
return validPermits.every(Boolean)
}

async function checkIsSingleCallDataAValidPermit(
order: ParsedOrder,
chainId: SupportedChainId,
eip2162Utils: Eip2612PermitUtils,
tokenAddress: string,
tokenName: string,
callData: string,
{ version }: SupportedPermitInfo
): Promise<boolean | undefined> {
const params = { chainId, tokenName: fixTokenName(tokenName), tokenAddress, callData, version }

let recoverPermitOwnerPromise: Promise<string> | undefined = undefined

// If pre-hook doesn't start with either selector, it's not a permit
if (callData.startsWith(EIP_2612_PERMIT_SELECTOR)) {
recoverPermitOwnerPromise = eip2162Utils.recoverPermitOwnerFromCallData({
...params,
// I don't know why this was removed, ok?
// We added it back on buildPermitCallData.ts
// But it looks like this is needed 🤷
// Check the test for this method https://github.com/1inch/permit-signed-approvals-utils/blob/master/src/eip-2612-permit.test.ts#L85-L106
callData: callData.replace(EIP_2612_PERMIT_SELECTOR, '0x'),
})
} else if (callData.startsWith(DAI_PERMIT_SELECTOR)) {
recoverPermitOwnerPromise = eip2162Utils.recoverDaiLikePermitOwnerFromCallData({
...params,
callData: callData.replace(DAI_PERMIT_SELECTOR, '0x'),
})
}

if (!recoverPermitOwnerPromise) {
// The callData doesn't match any known permit type
return undefined
}

try {
const recoveredOwner = await recoverPermitOwnerPromise

// Permit is valid when recovered owner matches order owner
return recoveredOwner.toLowerCase() === order.owner.toLowerCase()
} catch (e) {
console.debug(
`[checkHasValidPendingPermit] Failed to check permit validity for order ${order.id} with callData ${callData}`,
e
)
return false
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useAtomValue, useSetAtom } from 'jotai'
import { useCallback } from 'react'

import { GP_VAULT_RELAYER } from '@cowprotocol/common-const'
import { generatePermitHook, getPermitUtilsInstance, PermitHookData } from '@cowprotocol/permit-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
import { useWeb3React } from '@web3-react/core'

Expand All @@ -10,9 +12,7 @@ import {
storePermitCacheAtom,
userPermitCacheAtom,
} from '../state/permitCacheAtom'
import { GeneratePermitHook, GeneratePermitHookParams, PermitHookData } from '../types'
import { generatePermitHook } from '../utils/generatePermitHook'
import { getPermitUtilsInstance } from '../utils/getPermitUtilsInstance'
import { GeneratePermitHook, GeneratePermitHookParams } from '../types'

/**
* Hook that returns callback to generate permit hook data
Expand All @@ -32,6 +32,8 @@ export function useGeneratePermitHook(): GeneratePermitHook {
const { chainId } = useWalletInfo()
const { provider } = useWeb3React()

const spender = GP_VAULT_RELAYER[chainId]

return useCallback(
async (params: GeneratePermitHookParams): Promise<PermitHookData | undefined> => {
const { inputToken, account, permitInfo } = params
Expand All @@ -57,6 +59,7 @@ export function useGeneratePermitHook(): GeneratePermitHook {
const hookData = await generatePermitHook({
chainId,
inputToken,
spender,
provider,
permitInfo,
eip2162Utils,
Expand All @@ -68,6 +71,6 @@ export function useGeneratePermitHook(): GeneratePermitHook {

return hookData
},
[storePermit, chainId, getCachedPermit, provider]
[provider, chainId, getCachedPermit, spender, storePermit]
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useAtomValue, useSetAtom } from 'jotai'
import { useEffect, useMemo } from 'react'

import { GP_VAULT_RELAYER } from '@cowprotocol/common-const'
import { getIsNativeToken, getWrappedToken } from '@cowprotocol/common-utils'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { getTokenPermitInfo } from '@cowprotocol/permit-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
import { Currency } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
Expand All @@ -16,7 +18,6 @@ import { useIsPermitEnabled } from 'common/hooks/featureFlags/useIsPermitEnabled
import { ORDER_TYPE_SUPPORTS_PERMIT } from '../const'
import { addPermitInfoForTokenAtom, permittableTokensAtom } from '../state/permittableTokensAtom'
import { IsTokenPermittableResult } from '../types'
import { checkIsTokenPermittable } from '../utils/checkIsTokenPermittable'

/**
* Checks whether the token is permittable, and caches the result on localStorage
Expand Down Expand Up @@ -45,12 +46,14 @@ export function useIsTokenPermittable(
const addPermitInfo = useAddPermitInfo()
const permitInfo = usePermitInfo(chainId, isPermitEnabled ? lowerCaseAddress : undefined)

const spender = GP_VAULT_RELAYER[chainId]

useEffect(() => {
if (!chainId || !isPermitEnabled || !lowerCaseAddress || !provider || permitInfo !== undefined || isNative) {
return
}

checkIsTokenPermittable({ tokenAddress: lowerCaseAddress, tokenName, chainId, provider }).then((result) => {
getTokenPermitInfo({ spender, tokenAddress: lowerCaseAddress, tokenName, chainId, provider }).then((result) => {
if (!result) {
// When falsy, we know it doesn't support permit. Cache it.
addPermitInfo({ chainId, tokenAddress: lowerCaseAddress, permitInfo: false })
Expand All @@ -64,7 +67,7 @@ export function useIsTokenPermittable(
addPermitInfo({ chainId, tokenAddress: lowerCaseAddress, permitInfo: result })
}
})
}, [addPermitInfo, chainId, isNative, isPermitEnabled, lowerCaseAddress, permitInfo, provider, tokenName])
}, [addPermitInfo, chainId, isNative, isPermitEnabled, lowerCaseAddress, permitInfo, provider, spender, tokenName])

if (isNative) {
return false
Expand Down
60 changes: 5 additions & 55 deletions apps/cowswap-frontend/src/modules/permit/types.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,30 @@
import { latest } from '@cowprotocol/app-data'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { Web3Provider } from '@ethersproject/providers'
import { Token } from '@uniswap/sdk-core'

import { Eip2612PermitUtils } from '@1inch/permit-signed-approvals-utils'
import { PermitHookData, PermitHookParams, PermitInfo } from '@cowprotocol/permit-utils'
import { Currency } from '@uniswap/sdk-core'

import { AppDataInfo } from 'modules/appData'

import { ParsedOrder } from 'utils/orderUtils/parseOrder'

export type PermitType = 'dai-like' | 'eip-2612'

export type SupportedPermitInfo = {
type: PermitType
version: string | undefined // Some tokens have it different than `1`, and won't work without it
}
type UnsupportedPermitInfo = false
export type PermitInfo = SupportedPermitInfo | UnsupportedPermitInfo
export type IsTokenPermittableResult = PermitInfo | undefined

export type PermittableTokens = Record<SupportedChainId, Record<string, PermitInfo>>

export type IsTokenPermittableResult = PermitInfo | undefined

export type AddPermitTokenParams = {
chainId: SupportedChainId
tokenAddress: string
permitInfo: PermitInfo
}

export type PermitHookParams = {
inputToken: Token
chainId: SupportedChainId
permitInfo: SupportedPermitInfo
provider: Web3Provider
eip2162Utils: Eip2612PermitUtils
account?: string | undefined
nonce?: number | undefined
}

export type GeneratePermitHookParams = Pick<PermitHookParams, 'inputToken' | 'permitInfo' | 'account'>

export type GeneratePermitHook = (params: GeneratePermitHookParams) => Promise<PermitHookData | undefined>

export type HandlePermitParams = Omit<GeneratePermitHookParams, 'permitInfo'> & {
export type HandlePermitParams = Omit<GeneratePermitHookParams, 'permitInfo' | 'inputToken'> & {
permitInfo: IsTokenPermittableResult
appData: AppDataInfo
generatePermitHook: GeneratePermitHook
}

export type PermitHookData = latest.CoWHook

type FailedToIdentify = { error: string }

export type EstimatePermitResult =
// When it's a permittable token:
| SupportedPermitInfo
// When something failed:
| FailedToIdentify
// When it's not permittable:
| UnsupportedPermitInfo

type BasePermitCallDataParams = {
eip2162Utils: Eip2612PermitUtils
}
export type BuildEip2162PermitCallDataParams = BasePermitCallDataParams & {
callDataParams: Parameters<Eip2612PermitUtils['buildPermitCallData']>
}
export type BuildDaiLikePermitCallDataParams = BasePermitCallDataParams & {
callDataParams: Parameters<Eip2612PermitUtils['buildDaiLikePermitCallData']>
}

export type CheckIsTokenPermittableParams = {
tokenAddress: string
tokenName: string
chainId: SupportedChainId
provider: Web3Provider
inputToken: Currency
}

export type PermitCache = Record<string, string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import { HandlePermitParams } from '../types'
export async function handlePermit(params: HandlePermitParams): Promise<AppDataInfo> {
const { permitInfo, inputToken, account, appData, generatePermitHook } = params

if (permitInfo) {
if (permitInfo && 'address' in inputToken) {
// permitInfo will only be set if there's NOT enough allowance

const permitData = await generatePermitHook({
inputToken,
inputToken: { address: inputToken.address, name: inputToken.name },
account,
permitInfo,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Percent, Token } from '@uniswap/sdk-core'
import { Percent } from '@uniswap/sdk-core'

import { PriceImpact } from 'legacy/hooks/usePriceImpact'
import { partialOrderUpdate } from 'legacy/state/orders/utils'
Expand Down Expand Up @@ -30,7 +30,7 @@ export async function swapFlow(

input.orderParams.appData = await handlePermit({
appData: input.orderParams.appData,
inputToken: input.context.trade.inputAmount.currency as Token,
inputToken: input.context.trade.inputAmount.currency,
account: input.orderParams.account,
permitInfo: input.permitInfo,
generatePermitHook: input.generatePermitHook,
Expand Down
3 changes: 2 additions & 1 deletion apps/cowswap-frontend/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"@cowprotocol/common-hooks": ["../../../libs/common-hooks/src/index.ts"],
"@cowprotocol/ens": ["../../../libs/ens/src/index.ts"],
"@cowprotocol/core": ["../../../libs/core/src/index.ts"],
"@cowprotocol/analytics": ["../../../libs/analytics/src/index.ts"]
"@cowprotocol/analytics": ["../../../libs/analytics/src/index.ts"],
"@cowprotocol/permit-utils": ["../../../libs/permit-utils/src/index.ts"]
}
},
"files": [],
Expand Down
2 changes: 1 addition & 1 deletion apps/widget-configurator/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
"test": {
"executor": "@nx/vite:test",
"outputs": ["coverage/apps/widget-configurator"],
"outputs": ["{workspaceRoot}/coverage/apps/widget-configurator"],
"options": {
"passWithNoTests": true,
"reportsDirectory": "../../coverage/apps/widget-configurator"
Expand Down
5 changes: 5 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { getJestProjects } from '@nx/jest'

export default {
projects: getJestProjects(),
}
Loading