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

Commit

Permalink
v2 quote with fot tax considerations
Browse files Browse the repository at this point in the history
  • Loading branch information
Siyu Jiang authored and Siyu Jiang committed Sep 8, 2023
1 parent 629a46b commit 178f8dc
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 11 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"dependencies": {
"@ethersproject/address": "^5.0.0",
"@ethersproject/solidity": "^5.0.0",
"@uniswap/sdk-core": "^4.0.2",
"@uniswap/sdk-core": "^4.0.7",
"tiny-invariant": "^1.1.0",
"tiny-warning": "^1.0.3"
},
Expand Down
147 changes: 141 additions & 6 deletions src/entities/pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,61 @@ export class Pair {
return token.equals(this.token0) ? this.reserve0 : this.reserve1
}

/**
* getAmountOut is the linear algebra of reserve ratio against amountIn:amountOut.
* https://ethereum.stackexchange.com/questions/101629/what-is-math-for-uniswap-calculates-the-amountout-and-amountin-why-997-and-1000
* has the math deduction for the reserve calculation without fee-on-transfer fees.
*
* With fee-on-transfer fees, intuitively it's just:
* inputAmountWithFeeAndTax = 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn
* = (1 - amountIn.sellFeesBips / 10000) * amountInWithFee
* outputAmountWithTax = amountOut * (1 - amountOut.buyFeesBips / 10000)
*
* But we are illustrating the math deduction below to ensure that's the case.
*
* before swap A * B = K where A = reserveIn B = reserveOut
*
* after swap A' * B' = K where only k is a constant value
*
* getAmountOut
*
* A' = A + 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn # here 0.3% is deducted
* B' = B - amountOut * (1 - amountOut.buyFeesBips / 10000)
* amountOut = (B - B') / (1 - amountOut.buyFeesBips / 10000) # where A' * B' still is k
* = (B - K/(A + 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn))
* /
* (1 - amountOut.buyFeesBips / 10000)
* = (B - AB/(A + 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn))
* /
* (1 - amountOut.buyFeesBips / 10000)
* = ((BA + B * 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn - AB)/(A + 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn))
* /
* (1 - amountOut.buyFeesBips / 10000)
* = (B * 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn / (A + 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn)
* /
* (1 - amountOut.buyFeesBips / 10000)
* amountOut * (1 - amountOut.buyFeesBips / 10000) = (B * 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn
* /
* (A + 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn)
*
* outputAmountWithTax = (B * 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn
* /
* (A + 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn)
* = (B * 997 * (1 - amountIn.sellFeesBips / 10000) * amountIn
* /
* (1000 * A + 997 * (1 - amountIn.sellFeesBips / 10000) * amountIn)
* = (B * (1 - amountIn.sellFeesBips / 10000) * inputAmountWithFee)
* /
* (1000 * A + (1 - amountIn.sellFeesBips / 10000) * inputAmountWithFee)
* = (B * inputAmountWithFeeAndTax)
* /
* (1000 * A + inputAmountWithFeeAndTax)
*
* inputAmountWithFeeAndTax = (1 - amountIn.sellFeesBips / 10000) * inputAmountWithFee
* outputAmountWithTax = amountOut * (1 - amountOut.buyFeesBips / 10000)
*
* @param inputAmount
*/
public getOutputAmount(inputAmount: CurrencyAmount<Token>): [CurrencyAmount<Token>, Pair] {
invariant(this.involvesToken(inputAmount.currency), 'TOKEN')
if (JSBI.equal(this.reserve0.quotient, ZERO) || JSBI.equal(this.reserve1.quotient, ZERO)) {
Expand All @@ -114,18 +169,67 @@ export class Pair {
const inputReserve = this.reserveOf(inputAmount.currency)
const outputReserve = this.reserveOf(inputAmount.currency.equals(this.token0) ? this.token1 : this.token0)
const inputAmountWithFee = JSBI.multiply(inputAmount.quotient, _997)
const numerator = JSBI.multiply(inputAmountWithFee, outputReserve.quotient)
const denominator = JSBI.add(JSBI.multiply(inputReserve.quotient, _1000), inputAmountWithFee)

const inputAmountWithFeeAndTax = this.deriveInputAmountWithTax(
CurrencyAmount.fromRawAmount(inputAmount.currency, inputAmountWithFee));

const numerator = JSBI.multiply(inputAmountWithFeeAndTax.quotient, outputReserve.quotient)
const denominator = JSBI.add(JSBI.multiply(inputReserve.quotient, _1000), inputAmountWithFeeAndTax.quotient)
const outputAmount = CurrencyAmount.fromRawAmount(
inputAmount.currency.equals(this.token0) ? this.token1 : this.token0,
JSBI.divide(numerator, denominator)
)
if (JSBI.equal(outputAmount.quotient, ZERO)) {
throw new InsufficientInputAmountError()
}
return [outputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]

const outputAmountWithTax = this.deriveOutputAmountWithTax(outputAmount)
return [outputAmountWithTax, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}

/**
* getAmountIn is the linear algebra of reserve ratio against amountIn:amountOut.
* https://ethereum.stackexchange.com/questions/101629/what-is-math-for-uniswap-calculates-the-amountout-and-amountin-why-997-and-1000
* has the math deduction for the reserve calculation without fee-on-transfer fees.
*
* With fee-on-transfer fees, intuitively it's just:
* outputAmountWithTax = amountOut * (1 - amountOut.buyFeesBips / 10000)
* inputAmountWithTax = 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn
* = (1 - amountIn.sellFeesBips / 10000) * amountInWithFee
*
* But we are illustrating the math deduction below to ensure that's the case.
*
* before swap A * B = K where A = reserveIn B = reserveOut
*
* after swap A' * B' = K where only k is a constant value
*
* getAmountIn
*
* B' = B - amountOut * (1 - amountOut.buyFeesBips / 10000)
* A' = A + 0.997 * (1 - amountIn.sellFeesBips / 10000) * amountIn # here 0.3% is deducted
* amountIn = (A' - A) / (0.997 * (1 - amountIn.sellFeesBips / 10000))
* = (K / (B - amountOut * (1 - amountOut.buyFeesBips / 10000)) - A)
* /
* (0.997 * (1 - amountIn.sellFeesBips / 10000))
* = (AB / (B - amountOut * (1 - amountOut.buyFeesBips / 10000)) - A)
* /
* (0.997 * (1 - amountIn.sellFeesBips / 10000))
* = ((AB - AB + A * amountOut * (1 - amountOut.buyFeesBips / 10000)) / (B - amountOut * (1 - amountOut.buyFeesBips / 10000)))
* /
* (0.997 * (1 - amountIn.sellFeesBips / 10000))
* = ((A * amountOut * (1 - amountOut.buyFeesBips / 10000)) / (B - amountOut * (1 - amountOut.buyFeesBips / 10000)))
* /
* (0.997 * (1 - amountIn.sellFeesBips / 10000))
* = ((A * amountOut * 1000 * (1 - amountOut.buyFeesBips / 10000)) / (B - amountOut * (1 - amountOut.buyFeesBips / 10000)))
* /
* (997 * (1 - amountIn.sellFeesBips / 10000))
*
* outputAmountWithTax = amountOut * (1 - amountOut.buyFeesBips / 10000)
* inputAmountWithTax = (1 - amountIn.sellFeesBips / 10000) * amountIn
* = (A * outputAmountWithTax * 1000) / ((B - outputAmountWithTax) * 997)
*
* @param outputAmount
*/
public getInputAmount(outputAmount: CurrencyAmount<Token>): [CurrencyAmount<Token>, Pair] {
invariant(this.involvesToken(outputAmount.currency), 'TOKEN')
if (
Expand All @@ -138,13 +242,16 @@ export class Pair {

const outputReserve = this.reserveOf(outputAmount.currency)
const inputReserve = this.reserveOf(outputAmount.currency.equals(this.token0) ? this.token1 : this.token0)
const numerator = JSBI.multiply(JSBI.multiply(inputReserve.quotient, outputAmount.quotient), _1000)
const denominator = JSBI.multiply(JSBI.subtract(outputReserve.quotient, outputAmount.quotient), _997)

const outputAmountWithTax = this.deriveOutputAmountWithTax(outputAmount)
const numerator = JSBI.multiply(JSBI.multiply(inputReserve.quotient, outputAmountWithTax.quotient), _1000)
const denominator = JSBI.multiply(JSBI.subtract(outputReserve.quotient, outputAmountWithTax.quotient), _997)
const inputAmount = CurrencyAmount.fromRawAmount(
outputAmount.currency.equals(this.token0) ? this.token1 : this.token0,
JSBI.add(JSBI.divide(numerator, denominator), ONE)
)
return [inputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
const inputAmountWithTax = this.deriveInputAmountWithTax(inputAmount)
return [inputAmountWithTax, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}

public getLiquidityMinted(
Expand Down Expand Up @@ -214,4 +321,32 @@ export class Pair {
JSBI.divide(JSBI.multiply(liquidity.quotient, this.reserveOf(token).quotient), totalSupplyAdjusted.quotient)
)
}

private deriveInputAmountWithTax(inputAmount: CurrencyAmount<Token>): CurrencyAmount<Token> {
const sellFeeBips = inputAmount.currency.sellFeeBps
if (sellFeeBips) {
const sellFeePercentInDecimal = JSBI.divide(JSBI.BigInt(inputAmount.currency.sellFeeBps), JSBI.BigInt(10000))
const taxAmount = JSBI.multiply(inputAmount.quotient, sellFeePercentInDecimal)
return CurrencyAmount.fromRawAmount(
inputAmount.currency,
JSBI.subtract(inputAmount.quotient, taxAmount)
);
} else {
return inputAmount
}
}

private deriveOutputAmountWithTax(outputAmount: CurrencyAmount<Token>): CurrencyAmount<Token> {
const buyFeeBps = outputAmount.currency.buyFeeBps
if (buyFeeBps) {
const buyFeePercentInDecimal = JSBI.divide(JSBI.BigInt(outputAmount.currency.buyFeeBps), JSBI.BigInt(10000))
const taxAmount = JSBI.multiply(outputAmount.quotient, buyFeePercentInDecimal)
return CurrencyAmount.fromRawAmount(
outputAmount.currency,
JSBI.subtract(outputAmount.quotient, taxAmount)
)
} else {
return outputAmount
}
}
}
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1706,10 +1706,10 @@
semver "^7.3.2"
tsutils "^3.17.1"

"@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.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 178f8dc

Please sign in to comment.