From 8de7cfc51e2324f75b968aee015a2488d4dd2ba9 Mon Sep 17 00:00:00 2001 From: Leandro Date: Wed, 18 Oct 2023 01:07:08 -0700 Subject: [PATCH] fix(permit): add support for usdc mainnet (#3231) * 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 --- .../src/modules/permit/const.ts | 8 +++++ .../permit/state/permittableTokensAtom.ts | 2 +- .../src/modules/permit/types.ts | 2 +- .../permit/utils/buildPermitCallData.ts | 8 ++++- .../permit/utils/checkIsTokenPermittable.ts | 30 +++++++++++++++---- .../permit/utils/generatePermitHook.ts | 2 ++ libs/common-const/src/tokens.ts | 4 +-- 7 files changed, 46 insertions(+), 10 deletions(-) diff --git a/apps/cowswap-frontend/src/modules/permit/const.ts b/apps/cowswap-frontend/src/modules/permit/const.ts index 516059f1f3..b6225c5134 100644 --- a/apps/cowswap-frontend/src/modules/permit/const.ts +++ b/apps/cowswap-frontend/src/modules/permit/const.ts @@ -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' @@ -31,3 +32,10 @@ export const ORDER_TYPE_SUPPORTS_PERMIT: Record = { } 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()]) diff --git a/apps/cowswap-frontend/src/modules/permit/state/permittableTokensAtom.ts b/apps/cowswap-frontend/src/modules/permit/state/permittableTokensAtom.ts index daf8a9de07..aeebdf1716 100644 --- a/apps/cowswap-frontend/src/modules/permit/state/permittableTokensAtom.ts +++ b/apps/cowswap-frontend/src/modules/permit/state/permittableTokensAtom.ts @@ -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:v0', { +export const permittableTokensAtom = atomWithStorage('permittableTokens:v1', { [SupportedChainId.MAINNET]: {}, [SupportedChainId.GOERLI]: {}, [SupportedChainId.GNOSIS_CHAIN]: {}, diff --git a/apps/cowswap-frontend/src/modules/permit/types.ts b/apps/cowswap-frontend/src/modules/permit/types.ts index d1cc15571f..312b5442a9 100644 --- a/apps/cowswap-frontend/src/modules/permit/types.ts +++ b/apps/cowswap-frontend/src/modules/permit/types.ts @@ -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 diff --git a/apps/cowswap-frontend/src/modules/permit/utils/buildPermitCallData.ts b/apps/cowswap-frontend/src/modules/permit/utils/buildPermitCallData.ts index 67d5ddcca0..aba6099b8f 100644 --- a/apps/cowswap-frontend/src/modules/permit/utils/buildPermitCallData.ts +++ b/apps/cowswap-frontend/src/modules/permit/utils/buildPermitCallData.ts @@ -6,8 +6,14 @@ export async function buildEip2162PermitCallData({ eip2162Utils, callDataParams, }: BuildEip2162PermitCallDataParams): Promise { - 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 diff --git a/apps/cowswap-frontend/src/modules/permit/utils/checkIsTokenPermittable.ts b/apps/cowswap-frontend/src/modules/permit/utils/checkIsTokenPermittable.ts index 8093044c24..3b4c5dd599 100644 --- a/apps/cowswap-frontend/src/modules/permit/utils/checkIsTokenPermittable.ts +++ b/apps/cowswap-frontend/src/modules/permit/utils/checkIsTokenPermittable.ts @@ -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 = { @@ -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, @@ -82,6 +98,7 @@ async function actuallyCheckTokenIsPermittable(params: CheckIsTokenPermittablePa tokenAddress, tokenName, walletAddress: owner, + version, } try { @@ -108,6 +125,7 @@ type BaseParams = { spender: string eip2612PermitUtils: Eip2612PermitUtils nonce: number + version: string | undefined } type EstimateParams = BaseParams & { @@ -116,7 +134,7 @@ type EstimateParams = BaseParams & { } async function estimateTokenPermit(params: EstimateParams): Promise { - const { provider, chainId, walletAddress, tokenAddress, type } = params + const { provider, chainId, walletAddress, tokenAddress, type, version } = params const getCallDataFn = type === 'eip-2612' ? getEip2612CallData : getDaiLikeCallData @@ -136,14 +154,14 @@ async function estimateTokenPermit(params: EstimateParams): Promise PERMIT_GAS_LIMIT_MIN[chainId] ? { - gasLimit, type, + version, } : false } async function getEip2612CallData(params: BaseParams): Promise { - const { eip2612PermitUtils, walletAddress, spender, nonce, chainId, tokenName, tokenAddress } = params + const { eip2612PermitUtils, walletAddress, spender, nonce, chainId, tokenName, tokenAddress, version } = params return buildEip2162PermitCallData({ eip2162Utils: eip2612PermitUtils, callDataParams: [ @@ -156,12 +174,13 @@ async function getEip2612CallData(params: BaseParams): Promise { +chainId, tokenName, tokenAddress, + version, ], }) } async function getDaiLikeCallData(params: BaseParams): Promise { - 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) @@ -178,6 +197,7 @@ async function getDaiLikeCallData(params: BaseParams): Promise { chainId as number, tokenName, tokenAddress, + version, ], }) } diff --git a/apps/cowswap-frontend/src/modules/permit/utils/generatePermitHook.ts b/apps/cowswap-frontend/src/modules/permit/utils/generatePermitHook.ts index 7d781342aa..ea9db1f713 100644 --- a/apps/cowswap-frontend/src/modules/permit/utils/generatePermitHook.ts +++ b/apps/cowswap-frontend/src/modules/permit/utils/generatePermitHook.ts @@ -65,6 +65,7 @@ async function generatePermitHookRaw(params: PermitHookParams): Promise