From e207cb67769e9a6733de6bd6f9ee01d0d76d651c Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:02:31 +0200 Subject: [PATCH 1/8] fix: partially exclude fot from fee --- lib/graphql/graphql-queries.ts | 3 +++ lib/graphql/graphql-schemas.ts | 3 +++ lib/graphql/graphql-token-fee-fetcher.ts | 7 +++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/graphql/graphql-queries.ts b/lib/graphql/graphql-queries.ts index 8166aa0417..8d9db2ced2 100644 --- a/lib/graphql/graphql-queries.ts +++ b/lib/graphql/graphql-queries.ts @@ -11,6 +11,9 @@ query Token($chain: Chain!, $address: String!) { feeData { buyFeeBps sellFeeBps + feeTakenOnTransfer + externalTransferFailed + sellReverted } } } diff --git a/lib/graphql/graphql-schemas.ts b/lib/graphql/graphql-schemas.ts index 6dbbc8e650..c432dd4b1e 100644 --- a/lib/graphql/graphql-schemas.ts +++ b/lib/graphql/graphql-schemas.ts @@ -21,5 +21,8 @@ export interface TokenInfo { feeData?: { buyFeeBps?: string sellFeeBps?: string + feeTakenOnTransfer?: boolean + externalTransferFailed?: boolean + sellReverted?: boolean } } diff --git a/lib/graphql/graphql-token-fee-fetcher.ts b/lib/graphql/graphql-token-fee-fetcher.ts index 7dd4d6d507..818609c5c3 100644 --- a/lib/graphql/graphql-token-fee-fetcher.ts +++ b/lib/graphql/graphql-token-fee-fetcher.ts @@ -46,9 +46,12 @@ export class GraphQLTokenFeeFetcher implements ITokenFeeFetcher { if (token.feeData?.buyFeeBps || token.feeData?.sellFeeBps) { const buyFeeBps = token.feeData.buyFeeBps ? BigNumber.from(token.feeData.buyFeeBps) : undefined const sellFeeBps = token.feeData.sellFeeBps ? BigNumber.from(token.feeData.sellFeeBps) : undefined - tokenFeeMap[token.address] = { buyFeeBps, sellFeeBps } + const feeTakenOnTransfer = token.feeData.feeTakenOnTransfer + const externalTransferFailed = token.feeData.externalTransferFailed + const sellReverted = token.feeData.sellReverted + tokenFeeMap[token.address] = { buyFeeBps, sellFeeBps, feeTakenOnTransfer, externalTransferFailed, sellReverted } } else { - tokenFeeMap[token.address] = { buyFeeBps: undefined, sellFeeBps: undefined } + tokenFeeMap[token.address] = { buyFeeBps: undefined, sellFeeBps: undefined, feeTakenOnTransfer: false, externalTransferFailed: false, sellReverted: false } } }) metric.putMetric('GraphQLTokenFeeFetcherFetchFeesSuccess', 1, MetricLoggerUnit.Count) From 21e19376cbb52490170131d3b96ae3e34c1ad766 Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:05:00 +0200 Subject: [PATCH 2/8] fix prettier --- lib/graphql/graphql-token-fee-fetcher.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/graphql/graphql-token-fee-fetcher.ts b/lib/graphql/graphql-token-fee-fetcher.ts index 818609c5c3..8bbd03598f 100644 --- a/lib/graphql/graphql-token-fee-fetcher.ts +++ b/lib/graphql/graphql-token-fee-fetcher.ts @@ -49,9 +49,21 @@ export class GraphQLTokenFeeFetcher implements ITokenFeeFetcher { const feeTakenOnTransfer = token.feeData.feeTakenOnTransfer const externalTransferFailed = token.feeData.externalTransferFailed const sellReverted = token.feeData.sellReverted - tokenFeeMap[token.address] = { buyFeeBps, sellFeeBps, feeTakenOnTransfer, externalTransferFailed, sellReverted } + tokenFeeMap[token.address] = { + buyFeeBps, + sellFeeBps, + feeTakenOnTransfer, + externalTransferFailed, + sellReverted, + } } else { - tokenFeeMap[token.address] = { buyFeeBps: undefined, sellFeeBps: undefined, feeTakenOnTransfer: false, externalTransferFailed: false, sellReverted: false } + tokenFeeMap[token.address] = { + buyFeeBps: undefined, + sellFeeBps: undefined, + feeTakenOnTransfer: false, + externalTransferFailed: false, + sellReverted: false, + } } }) metric.putMetric('GraphQLTokenFeeFetcherFetchFeesSuccess', 1, MetricLoggerUnit.Count) From c996b403d89baac98d8b23150ca3aed90bdc94c6 Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Thu, 18 Jul 2024 21:08:26 +0200 Subject: [PATCH 3/8] Add DFNDR FOT test case for fee exclusion --- test/mocha/e2e/quote.test.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/mocha/e2e/quote.test.ts b/test/mocha/e2e/quote.test.ts index 527ad4a657..e8afa17595 100644 --- a/test/mocha/e2e/quote.test.ts +++ b/test/mocha/e2e/quote.test.ts @@ -80,6 +80,24 @@ const BULLET_WHT_TAX = new Token( BigNumber.from(500), BigNumber.from(500) ) +export const DFNDR = new Token( + ChainId.MAINNET, + '0x3f57c35633cb29834bb7577ba8052eab90f52a02', + 18, + 'DFNDR', + 'Defender Bot', + false +) +export const DFNDR_WITH_TAX = new Token( + ChainId.MAINNET, + '0x3f57c35633cb29834bb7577ba8052eab90f52a02', + 18, + 'DFNDR', + 'Defender Bot', + false, + BigNumber.from(500), + BigNumber.from(500) +) const V2_SUPPORTED_PAIRS = [ [WETH9[ChainId.ARBITRUM_ONE], USDC_NATIVE_ARBITRUM], @@ -1114,6 +1132,7 @@ describe('quote', function () { const tokenInAndTokenOut = [ [BULLET, WETH9[ChainId.MAINNET]!], [WETH9[ChainId.MAINNET]!, BULLET], + [WETH9[ChainId.MAINNET]!, DFNDR], ] tokenInAndTokenOut.forEach(([tokenIn, tokenOut]) => { @@ -1156,9 +1175,12 @@ describe('quote', function () { enableUniversalRouter: true, // if fee-on-transfer flag is not enabled, most likely the simulation will fail due to quote not subtracting the tax simulateFromAddress: enableFeeOnTransferFeeFetching ? simulateFromAddress : undefined, + portionBips: FLAT_PORTION.bips, + portionRecipient: FLAT_PORTION.recipient } const queryParams = qs.stringify(quoteReq) + console.log(`${API}?${queryParams}`) const response: AxiosResponse = await axios.get( `${API}?${queryParams}` @@ -1193,6 +1215,13 @@ describe('quote', function () { new Fraction(BigNumber.from(BULLET_WHT_TAX.buyFeeBps ?? 0).toString(), 10_000).toFixed(3) ) } + + // in case of FOT token that should not take a portion/fee, we assert that all portion fields are undefined + if (tokenOut?.equals(DFNDR)) { + expect(r.data.portionAmount).to.be.undefined + expect(r.data.portionBips).to.be.undefined + expect(r.data.portionRecipient).to.be.undefined + } } }) From ab92582ef6f1bc3c2766ed5b2ad76c024b4951b4 Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:57:45 +0200 Subject: [PATCH 4/8] fix gql batch token fee query --- lib/graphql/graphql-queries.ts | 3 +++ test/mocha/e2e/quote.test.ts | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/graphql/graphql-queries.ts b/lib/graphql/graphql-queries.ts index 8d9db2ced2..e24d943288 100644 --- a/lib/graphql/graphql-queries.ts +++ b/lib/graphql/graphql-queries.ts @@ -32,6 +32,9 @@ query Tokens($contracts: [ContractInput!]!) { feeData { buyFeeBps sellFeeBps + feeTakenOnTransfer + externalTransferFailed + sellReverted } } } diff --git a/test/mocha/e2e/quote.test.ts b/test/mocha/e2e/quote.test.ts index e8afa17595..917bbbd10f 100644 --- a/test/mocha/e2e/quote.test.ts +++ b/test/mocha/e2e/quote.test.ts @@ -1192,6 +1192,18 @@ describe('quote', function () { const quoteWithFlagOn = responses.find((r) => r.enableFeeOnTransferFeeFetching === true) expect(quoteWithFlagOn).not.to.be.undefined + + // in case of FOT token that should not take a portion/fee, we assert that all portion fields are undefined + if (tokenOut?.equals(DFNDR)) { + expect(quoteWithFlagOn!.data.portionAmount).to.be.undefined + expect(quoteWithFlagOn!.data.portionBips).to.be.undefined + expect(quoteWithFlagOn!.data.portionRecipient).to.be.undefined + } else { + expect(quoteWithFlagOn!.data.portionAmount).to.be.not.undefined + expect(quoteWithFlagOn!.data.portionBips).to.be.not.undefined + expect(quoteWithFlagOn!.data.portionRecipient).to.be.not.undefined + } + responses .filter((r) => r.enableFeeOnTransferFeeFetching !== true) .forEach((r) => { @@ -1215,13 +1227,6 @@ describe('quote', function () { new Fraction(BigNumber.from(BULLET_WHT_TAX.buyFeeBps ?? 0).toString(), 10_000).toFixed(3) ) } - - // in case of FOT token that should not take a portion/fee, we assert that all portion fields are undefined - if (tokenOut?.equals(DFNDR)) { - expect(r.data.portionAmount).to.be.undefined - expect(r.data.portionBips).to.be.undefined - expect(r.data.portionRecipient).to.be.undefined - } } }) From e25396de1c09cd7917af461431bac84d289d85ff Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:18:21 +0200 Subject: [PATCH 5/8] add more unit test --- test/mocha/e2e/quote.test.ts | 2 +- .../graphql/graphql-token-fee-fetcher.test.ts | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/test/mocha/e2e/quote.test.ts b/test/mocha/e2e/quote.test.ts index 917bbbd10f..5ee7cce8a7 100644 --- a/test/mocha/e2e/quote.test.ts +++ b/test/mocha/e2e/quote.test.ts @@ -1176,7 +1176,7 @@ describe('quote', function () { // if fee-on-transfer flag is not enabled, most likely the simulation will fail due to quote not subtracting the tax simulateFromAddress: enableFeeOnTransferFeeFetching ? simulateFromAddress : undefined, portionBips: FLAT_PORTION.bips, - portionRecipient: FLAT_PORTION.recipient + portionRecipient: FLAT_PORTION.recipient, } const queryParams = qs.stringify(quoteReq) diff --git a/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts b/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts index 04e41c2d9d..3847bccce7 100644 --- a/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts +++ b/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts @@ -64,9 +64,15 @@ describe('integration test for GraphQLTokenFeeFetcher', () => { expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]).to.not.be.undefined expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.buyFeeBps).to.be.undefined expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.sellFeeBps).to.be.undefined + expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.feeTakenOnTransfer).to.not.be.undefined + expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.externalTransferFailed).to.not.be.undefined + expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.sellReverted).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]?.buyFeeBps?._hex).equals(BITBOY.buyFeeBps?._hex) expect(tokenFeeMap[BITBOY.address]?.sellFeeBps?._hex).equals(BITBOY.sellFeeBps?._hex) + expect(tokenFeeMap[BITBOY.address]?.feeTakenOnTransfer).equals(false) + expect(tokenFeeMap[BITBOY.address]?.externalTransferFailed).equals(true) + expect(tokenFeeMap[BITBOY.address]?.sellReverted).equals(false) }) it('Fetch BULLET and BITBOY, should return BOTH', async () => { @@ -77,9 +83,16 @@ describe('integration test for GraphQLTokenFeeFetcher', () => { expect(tokenFeeMap[BULLET.address]).to.not.be.undefined expect(tokenFeeMap[BULLET.address]?.buyFeeBps?._hex).equals(BULLET.buyFeeBps?._hex) expect(tokenFeeMap[BULLET.address]?.sellFeeBps?._hex).equals(BULLET.sellFeeBps?._hex) + expect(tokenFeeMap[BULLET.address]?.feeTakenOnTransfer).equals(false) + expect(tokenFeeMap[BULLET.address]?.externalTransferFailed).equals(false) + expect(tokenFeeMap[BULLET.address]?.sellReverted).equals(false) + expect(tokenFeeMap[BITBOY.address]).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]?.buyFeeBps?._hex).equals(BITBOY.buyFeeBps?._hex) expect(tokenFeeMap[BITBOY.address]?.sellFeeBps?._hex).equals(BITBOY.sellFeeBps?._hex) + expect(tokenFeeMap[BITBOY.address]?.feeTakenOnTransfer).equals(false) + expect(tokenFeeMap[BITBOY.address]?.externalTransferFailed).equals(true) + expect(tokenFeeMap[BITBOY.address]?.sellReverted).equals(false) expect(spyGraphQLFetcher.calledOnce).to.be.true expect(spyOnChainFetcher.calledOnce).to.be.false @@ -97,6 +110,9 @@ describe('integration test for GraphQLTokenFeeFetcher', () => { expect(tokenFeeMap[BITBOY.address]).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]?.buyFeeBps?._hex).equals(BITBOY.buyFeeBps?._hex) expect(tokenFeeMap[BITBOY.address]?.sellFeeBps?._hex).equals(BITBOY.sellFeeBps?._hex) + expect(tokenFeeMap[BITBOY.address]?.feeTakenOnTransfer).equals(false) + expect(tokenFeeMap[BITBOY.address]?.externalTransferFailed).equals(true) + expect(tokenFeeMap[BITBOY.address]?.sellReverted).equals(false) expect(spyGraphQLFetcher.calledOnce).to.be.true expect(spyOnChainFetcher.calledOnce).to.be.true From bef5221386a35e40a87a66cd61aad83aa19c18be Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:35:24 +0200 Subject: [PATCH 6/8] chore: bump sor to 3.37.0 to support some FOT fee --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5db310327..ca9249f89d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@uniswap/permit2-sdk": "^1.3.0", "@uniswap/router-sdk": "^1.9.2", "@uniswap/sdk-core": "^5.3.0", - "@uniswap/smart-order-router": "3.36.2", + "@uniswap/smart-order-router": "3.37.0", "@uniswap/token-lists": "^1.0.0-beta.33", "@uniswap/universal-router-sdk": "^2.2.0", "@uniswap/v2-sdk": "^4.3.2", @@ -4748,9 +4748,9 @@ } }, "node_modules/@uniswap/smart-order-router": { - "version": "3.36.2", - "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-3.36.2.tgz", - "integrity": "sha512-IJG36HLyI5h2vHKmIFxsMIHh4vS3TmgBF07xzM3uaWHzKN6yEUJqD5B/7x2WmC6C/t84o7NkyR6Rgr1tP5m6FA==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-3.37.0.tgz", + "integrity": "sha512-yxS21l4UqX5B1fM2y3pEsw8HxYQVJzPdRsplriLiwrqnyt21CsX0VdkxTHKVF8HeOa1/XFMao9DgCzmcM0soxw==", "dependencies": { "@eth-optimism/sdk": "^3.2.2", "@types/brotli": "^1.3.4", @@ -28385,9 +28385,9 @@ } }, "@uniswap/smart-order-router": { - "version": "3.36.2", - "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-3.36.2.tgz", - "integrity": "sha512-IJG36HLyI5h2vHKmIFxsMIHh4vS3TmgBF07xzM3uaWHzKN6yEUJqD5B/7x2WmC6C/t84o7NkyR6Rgr1tP5m6FA==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-3.37.0.tgz", + "integrity": "sha512-yxS21l4UqX5B1fM2y3pEsw8HxYQVJzPdRsplriLiwrqnyt21CsX0VdkxTHKVF8HeOa1/XFMao9DgCzmcM0soxw==", "requires": { "@eth-optimism/sdk": "^3.2.2", "@types/brotli": "^1.3.4", diff --git a/package.json b/package.json index c50c204139..c45768c350 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@uniswap/router-sdk": "^1.9.2", "@uniswap/sdk-core": "^5.3.0", "@types/semver": "^7.5.8", - "@uniswap/smart-order-router": "3.36.2", + "@uniswap/smart-order-router": "3.37.0", "@uniswap/token-lists": "^1.0.0-beta.33", "@uniswap/universal-router-sdk": "^2.2.0", "@uniswap/v2-sdk": "^4.3.2", From e875ab00ef6c73d402f06e989e3ff2ce1d19f3e5 Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:40:09 +0200 Subject: [PATCH 7/8] fix test assertion --- test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts b/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts index 3847bccce7..245313e781 100644 --- a/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts +++ b/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts @@ -84,8 +84,8 @@ describe('integration test for GraphQLTokenFeeFetcher', () => { expect(tokenFeeMap[BULLET.address]?.buyFeeBps?._hex).equals(BULLET.buyFeeBps?._hex) expect(tokenFeeMap[BULLET.address]?.sellFeeBps?._hex).equals(BULLET.sellFeeBps?._hex) expect(tokenFeeMap[BULLET.address]?.feeTakenOnTransfer).equals(false) - expect(tokenFeeMap[BULLET.address]?.externalTransferFailed).equals(false) - expect(tokenFeeMap[BULLET.address]?.sellReverted).equals(false) + expect(tokenFeeMap[BULLET.address]?.externalTransferFailed).equals(true) + expect(tokenFeeMap[BULLET.address]?.sellReverted).equals(true) expect(tokenFeeMap[BITBOY.address]).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]?.buyFeeBps?._hex).equals(BITBOY.buyFeeBps?._hex) From bfcafbb5660dcb11da463ad2ebd76752c136e363 Mon Sep 17 00:00:00 2001 From: jsy1218 <91580504+jsy1218@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:52:53 +0200 Subject: [PATCH 8/8] fix test assertion --- test/mocha/e2e/quote.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocha/e2e/quote.test.ts b/test/mocha/e2e/quote.test.ts index 5ee7cce8a7..0fed155df3 100644 --- a/test/mocha/e2e/quote.test.ts +++ b/test/mocha/e2e/quote.test.ts @@ -1194,7 +1194,7 @@ describe('quote', function () { expect(quoteWithFlagOn).not.to.be.undefined // in case of FOT token that should not take a portion/fee, we assert that all portion fields are undefined - if (tokenOut?.equals(DFNDR)) { + if (!tokenOut?.equals(WETH9[ChainId.MAINNET])) { expect(quoteWithFlagOn!.data.portionAmount).to.be.undefined expect(quoteWithFlagOn!.data.portionBips).to.be.undefined expect(quoteWithFlagOn!.data.portionRecipient).to.be.undefined