Skip to content

Commit

Permalink
fix(permit): add support for usdc mainnet (#3231)
Browse files Browse the repository at this point in the history
* chore: remove unused prop `gasLimit` from SupportedPermitInfo

* feat: query and add version to permitInfo

* feat: bump permittableTokensAtom storage key to `v1`

* fix: usdc default tokens had the wrong name, causing the permit signature to fail

* chore: temporarily logging permit callData and params

* fix: hack to fix USDC token name when creating the permit callData

* feat: do not check for permit version if token cannot handle it

* chore: remove console logs
  • Loading branch information
alfetopito authored Oct 18, 2023
1 parent 1717cf9 commit 8de7cfc
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 10 deletions.
8 changes: 8 additions & 0 deletions apps/cowswap-frontend/src/modules/permit/const.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DAI } from '@cowprotocol/common-const'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { MaxUint256 } from '@ethersproject/constants'
import { Wallet } from '@ethersproject/wallet'
Expand Down Expand Up @@ -31,3 +32,10 @@ export const ORDER_TYPE_SUPPORTS_PERMIT: Record<TradeType, boolean> = {
}

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
Expand Up @@ -12,7 +12,7 @@ import { AddPermitTokenParams, PermittableTokens } from '../types'
* Contains either the permit info with `type` and `gasLimit` when supported or
* `false` when not supported
*/
export const permittableTokensAtom = atomWithStorage<PermittableTokens>('permittableTokens:v0', {
export const permittableTokensAtom = atomWithStorage<PermittableTokens>('permittableTokens:v1', {
[SupportedChainId.MAINNET]: {},
[SupportedChainId.GOERLI]: {},
[SupportedChainId.GNOSIS_CHAIN]: {},
Expand Down
2 changes: 1 addition & 1 deletion apps/cowswap-frontend/src/modules/permit/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type PermitType = 'dai-like' | 'eip-2612'

export type SupportedPermitInfo = {
type: PermitType
gasLimit: number
version: string | undefined // Some tokens have it different than `1`, and won't work without it
}
type UnsupportedPermitInfo = false
export type PermitInfo = SupportedPermitInfo | UnsupportedPermitInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ export async function buildEip2162PermitCallData({
eip2162Utils,
callDataParams,
}: BuildEip2162PermitCallDataParams): Promise<string> {
const callData = await eip2162Utils.buildPermitCallData(...callDataParams)
// TODO: this is ugly and I'm not happy with it either
// It'll probably go away when the tokens overhaul is implemented
// For now, this is a problem for favourite tokens cached locally with the hardcoded name for USDC token
// Using the wrong name breaks the signature.
const [permitParams, chainId, _tokenName, ...rest] = callDataParams
const tokenName = _tokenName === 'USD//C' ? 'USD Coin' : _tokenName

const callData = await eip2162Utils.buildPermitCallData(permitParams, chainId, tokenName, ...rest)
// For some reason, the method above removes the permit selector prefix
// https://github.com/1inch/permit-signed-approvals-utils/blob/master/src/eip-2612-permit.utils.ts#L92
// Adding it back
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { buildDaiLikePermitCallData, buildEip2162PermitCallData } from './buildP
import { getPermitDeadline } from './getPermitDeadline'
import { getPermitUtilsInstance } from './getPermitUtilsInstance'

import { DEFAULT_PERMIT_VALUE, PERMIT_GAS_LIMIT_MIN, PERMIT_SIGNER } from '../const'
import { DEFAULT_PERMIT_VALUE, PERMIT_GAS_LIMIT_MIN, PERMIT_SIGNER, TOKENS_TO_SKIP_VERSION } from '../const'
import { CheckIsTokenPermittableParams, EstimatePermitResult, PermitType } from '../types'

const EIP_2162_PERMIT_PARAMS = {
Expand Down Expand Up @@ -74,6 +74,22 @@ async function actuallyCheckTokenIsPermittable(params: CheckIsTokenPermittablePa
return { error: e.message || e.toString() }
}

let version: string | undefined = undefined

if (!TOKENS_TO_SKIP_VERSION.has(tokenAddress)) {
// If the token does not outright fails when calling with the `version` value
// returned by the contract, fetch it.

try {
// Required by USDC-mainnet as its version is `2`.
// There might be other tokens that need this as well.
version = await eip2612PermitUtils.getTokenVersion(tokenAddress)
} catch (e) {
// Not a problem, we can (try to) continue without it, and will default to `1` (part of the 1inch lib)
console.debug(`[checkTokenIsPermittable] Failed to get version for ${tokenAddress}`, e)
}
}

const baseParams: BaseParams = {
chainId,
eip2612PermitUtils,
Expand All @@ -82,6 +98,7 @@ async function actuallyCheckTokenIsPermittable(params: CheckIsTokenPermittablePa
tokenAddress,
tokenName,
walletAddress: owner,
version,
}

try {
Expand All @@ -108,6 +125,7 @@ type BaseParams = {
spender: string
eip2612PermitUtils: Eip2612PermitUtils
nonce: number
version: string | undefined
}

type EstimateParams = BaseParams & {
Expand All @@ -116,7 +134,7 @@ type EstimateParams = BaseParams & {
}

async function estimateTokenPermit(params: EstimateParams): Promise<EstimatePermitResult> {
const { provider, chainId, walletAddress, tokenAddress, type } = params
const { provider, chainId, walletAddress, tokenAddress, type, version } = params

const getCallDataFn = type === 'eip-2612' ? getEip2612CallData : getDaiLikeCallData

Expand All @@ -136,14 +154,14 @@ async function estimateTokenPermit(params: EstimateParams): Promise<EstimatePerm

return gasLimit > PERMIT_GAS_LIMIT_MIN[chainId]
? {
gasLimit,
type,
version,
}
: false
}

async function getEip2612CallData(params: BaseParams): Promise<string> {
const { eip2612PermitUtils, walletAddress, spender, nonce, chainId, tokenName, tokenAddress } = params
const { eip2612PermitUtils, walletAddress, spender, nonce, chainId, tokenName, tokenAddress, version } = params
return buildEip2162PermitCallData({
eip2162Utils: eip2612PermitUtils,
callDataParams: [
Expand All @@ -156,12 +174,13 @@ async function getEip2612CallData(params: BaseParams): Promise<string> {
+chainId,
tokenName,
tokenAddress,
version,
],
})
}

async function getDaiLikeCallData(params: BaseParams): Promise<string | false> {
const { eip2612PermitUtils, tokenAddress, walletAddress, spender, nonce, chainId, tokenName } = params
const { eip2612PermitUtils, tokenAddress, walletAddress, spender, nonce, chainId, tokenName, version } = params

const permitTypeHash = await eip2612PermitUtils.getPermitTypeHash(tokenAddress)

Expand All @@ -178,6 +197,7 @@ async function getDaiLikeCallData(params: BaseParams): Promise<string | false> {
chainId as number,
tokenName,
tokenAddress,
version,
],
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ async function generatePermitHookRaw(params: PermitHookParams): Promise<PermitHo
chainId as number,
tokenName,
tokenAddress,
permitInfo.version,
],
})
: await buildDaiLikePermitCallData({
Expand All @@ -81,6 +82,7 @@ async function generatePermitHookRaw(params: PermitHookParams): Promise<PermitHo
chainId as number,
tokenName,
tokenAddress,
permitInfo.version,
],
})

Expand Down
4 changes: 2 additions & 2 deletions libs/common-const/src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ export const USDC_MAINNET = new Token(
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
6,
'USDC',
'USD//C'
'USD Coin'
)
export const USDC_GOERLI = new Token(
SupportedChainId.GOERLI,
'0x07865c6e87b9f70255377e024ace6630c1eaa37f',
6,
'USDC',
'USD//C'
'USD Coin'
)
export const DAI = new Token(
SupportedChainId.MAINNET,
Expand Down

0 comments on commit 8de7cfc

Please sign in to comment.