Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

Commit

Permalink
Merge pull request #50 from Uniswap/tina/price-impact-fot
Browse files Browse the repository at this point in the history
feat: Handle fee-on-transfer token taxes in price impact calculations
  • Loading branch information
tinaszheng authored Sep 20, 2023
2 parents 70e23a3 + 2998d89 commit 756e560
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 9 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"license": "MIT",
"dependencies": {
"@ethersproject/abi": "^5.5.0",
"@uniswap/sdk-core": "^4",
"@uniswap/sdk-core": "^4.0.7",
"@uniswap/swap-router-contracts": "^1.1.0",
"@uniswap/v2-sdk": "^3.2.0",
"@uniswap/v3-sdk": "^3.10.0"
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Percent } from '@uniswap/sdk-core'
import JSBI from 'jsbi'

export const MSG_SENDER = '0x0000000000000000000000000000000000000001'
Expand All @@ -8,3 +9,5 @@ export const ONE = JSBI.BigInt(1)

// = 1 << 23 or 100000000000000000000000
export const V2_FEE_PATH_PLACEHOLDER = 8388608

export const ZERO_PERCENT = new Percent(ZERO)
75 changes: 75 additions & 0 deletions src/entities/trade.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { sqrt, Token, CurrencyAmount, TradeType, WETH9, Ether, Percent, Price } from '@uniswap/sdk-core'
import { BigNumber } from '@ethersproject/bignumber'
import JSBI from 'jsbi'
import { MixedRoute, RouteV2, RouteV3 } from './route'
import { Trade } from './trade'
Expand All @@ -21,6 +22,26 @@ describe('Trade', () => {
const token1 = new Token(1, '0x0000000000000000000000000000000000000002', 18, 't1', 'token1')
const token2 = new Token(1, '0x0000000000000000000000000000000000000003', 18, 't2', 'token2')
const token3 = new Token(1, '0x0000000000000000000000000000000000000004', 18, 't3', 'token3')
const token4WithTax = new Token(
1,
'0x0000000000000000000000000000000000000005',
18,
't4',
'token4',
false,
BigNumber.from(100),
BigNumber.from(100)
)
const token5WithTax = new Token(
1,
'0x0000000000000000000000000000000000000005',
18,
't5',
'token5',
false,
BigNumber.from(500),
BigNumber.from(500)
)

function v2StylePool(
reserve0: CurrencyAmount<Token>,
Expand Down Expand Up @@ -101,6 +122,16 @@ describe('Trade', () => {
CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(10000))
)

const pair_tax_output = new Pair(
CurrencyAmount.fromRawAmount(weth, JSBI.BigInt(100000)),
CurrencyAmount.fromRawAmount(token4WithTax, JSBI.BigInt(100000))
)

const pair_tax_input = new Pair(
CurrencyAmount.fromRawAmount(token5WithTax, JSBI.BigInt(100000)),
CurrencyAmount.fromRawAmount(weth, JSBI.BigInt(100000))
)

const pool_weth_0 = v2StylePool(
CurrencyAmount.fromRawAmount(weth, JSBI.BigInt(100000)),
CurrencyAmount.fromRawAmount(token0, JSBI.BigInt(100000))
Expand Down Expand Up @@ -1160,6 +1191,50 @@ describe('Trade', () => {
})
// v3 sdk price impact tests
describe('#priceImpact', () => {
describe('with FOT sell fees', () => {
const routev2 = new V2RouteSDK([pair_tax_output], weth, token4WithTax)
const trade = new Trade({
v2Routes: [
{
routev2,
inputAmount: CurrencyAmount.fromRawAmount(weth, 100),
outputAmount: CurrencyAmount.fromRawAmount(token4WithTax, 69),
},
],
v3Routes: [],
tradeType: TradeType.EXACT_INPUT,
})

it('is cached', () => {
expect(trade.priceImpact === trade.priceImpact).toStrictEqual(true)
})
it('is correct', () => {
expect(trade.priceImpact.toSignificant(3)).toEqual('30.3')
})
})

describe('with FOT buy fees', () => {
const routev2 = new V2RouteSDK([pair_tax_input], token5WithTax, weth)
const trade = new Trade({
v2Routes: [
{
routev2,
inputAmount: CurrencyAmount.fromRawAmount(token5WithTax, 100),
outputAmount: CurrencyAmount.fromRawAmount(weth, 69),
},
],
v3Routes: [],
tradeType: TradeType.EXACT_INPUT,
})

it('is cached', () => {
expect(trade.priceImpact === trade.priceImpact).toStrictEqual(true)
})
it('is correct', () => {
expect(trade.priceImpact.toSignificant(3)).toEqual('27.4')
})
})

describe('tradeType = EXACT_INPUT', () => {
const routev3 = new V3RouteSDK([pool_0_1, pool_1_2], token0, token2)
const mixedRoute = new MixedRouteSDK([pool_0_1, pool_1_2], token0, token2)
Expand Down
32 changes: 28 additions & 4 deletions src/entities/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Currency, CurrencyAmount, Fraction, Percent, Price, TradeType } from '@
import { Pair, Route as V2RouteSDK, Trade as V2TradeSDK } from '@uniswap/v2-sdk'
import { Pool, Route as V3RouteSDK, Trade as V3TradeSDK } from '@uniswap/v3-sdk'
import invariant from 'tiny-invariant'
import { ONE, ZERO } from '../constants'
import { ONE, ZERO, ZERO_PERCENT } from '../constants'
import { MixedRouteSDK } from './mixedRoute/route'
import { MixedRouteTrade as MixedRouteTradeSDK } from './mixedRoute/trade'
import { IRoute, MixedRoute, RouteV2, RouteV3 } from './route'
Expand Down Expand Up @@ -158,13 +158,35 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
)
}

/**
* Returns the sell tax of the input token
*/
public get inputTax(): Percent {
const inputCurrency = this.inputAmount.currency
if (inputCurrency.isNative || !inputCurrency.wrapped.sellFeeBps) return ZERO_PERCENT

return new Percent(inputCurrency.wrapped.sellFeeBps.toNumber(), 10000)
}

/**
* Returns the buy tax of the output token
*/
public get outputTax(): Percent {
const outputCurrency = this.outputAmount.currency
if (outputCurrency.isNative || !outputCurrency.wrapped.buyFeeBps) return ZERO_PERCENT

return new Percent(outputCurrency.wrapped.buyFeeBps.toNumber(), 10000)
}

/**
* The cached result of the price impact computation
* @private
*/
private _priceImpact: Percent | undefined
/**
* Returns the percent difference between the route's mid price and the price impact
* Returns the percent difference between the route's mid price and the expected execution price
* In order to exclude token taxes from the price impact calculation, the spot price is calculated
* using a ratio of values that go into the pools, which are the post-tax input amount and pre-tax output amount.
*/
public get priceImpact(): Percent {
if (this._priceImpact) {
Expand All @@ -174,10 +196,12 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
let spotOutputAmount = CurrencyAmount.fromRawAmount(this.outputAmount.currency, 0)
for (const { route, inputAmount } of this.swaps) {
const midPrice = route.midPrice
spotOutputAmount = spotOutputAmount.add(midPrice.quote(inputAmount))
const postTaxInputAmount = inputAmount.multiply(new Fraction(ONE).subtract(this.inputTax))
spotOutputAmount = spotOutputAmount.add(midPrice.quote(postTaxInputAmount))
}

const priceImpact = spotOutputAmount.subtract(this.outputAmount).divide(spotOutputAmount)
const preTaxOutputAmount = this.outputAmount.divide(new Fraction(ONE).subtract(this.outputTax))
const priceImpact = spotOutputAmount.subtract(preTaxOutputAmount).divide(spotOutputAmount)
this._priceImpact = new Percent(priceImpact.numerator, priceImpact.denominator)

return this._priceImpact
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1575,10 +1575,10 @@
resolved "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz"
integrity sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==

"@uniswap/sdk-core@^4", "@uniswap/sdk-core@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-4.0.2.tgz#2eca2b5bf00bad74519aef918465c19218285b4b"
integrity sha512-rR5xobsAAP4yMYC7C+0+duVx0pFoDn2lV9kTWpoKgH1WJuw7hD1uDEvuevU2dL89TuixVgGvnYd0QxmrMtsIlg==
"@uniswap/sdk-core@^4", "@uniswap/sdk-core@^4.0.2", "@uniswap/sdk-core@^4.0.7":
version "4.0.7"
resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-4.0.7.tgz#90dfd070d7e44494234618af398da158363ae827"
integrity sha512-jscx7KUIWzQatcL5PHY6xy0gEL9IGQcL5h/obxzX9foP2KoNk9cq66Ia8I2Kvpa7zBcPOeW1hU0hJNBq6CzcIQ==
dependencies:
"@ethersproject/address" "^5.0.2"
big.js "^5.2.2"
Expand Down

0 comments on commit 756e560

Please sign in to comment.