Commit 53e3b8e9 authored by Miguel Cervera's avatar Miguel Cervera Committed by GitHub

Receive an option to decide if we calculate FOT tax or not (#146)

* Receive an option to decide if we calculate FOT tax or not

* Fix code style issues with Prettier

---------
Co-authored-by: default avatarLint Action <lint-action@samuelmeuli.com>
parent 4411fae6
dist dist
node_modules node_modules
.idea
...@@ -199,78 +199,120 @@ describe('Pair', () => { ...@@ -199,78 +199,120 @@ describe('Pair', () => {
BLASTERSSellFeeBps BLASTERSSellFeeBps
) )
describe('getOutputAmount', () => { let calculateFotFees: boolean = false
it('getOutputAmount for input token BLASTERS and output token BLAST', () => {
const reserveBlasterAmount = CurrencyAmount.fromRawAmount(BLASTERS, '10000') describe('when calculating FOT fees', () => {
const reserveBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '10000') beforeEach(() => {
calculateFotFees = true
const pair = new Pair(reserveBlasterAmount, reserveBlastAmount) })
const inputBlastersAmount = CurrencyAmount.fromRawAmount(BLASTERS, '100') describe('getOutputAmount', () => {
const [outputBlastAmount] = pair.getOutputAmount(inputBlastersAmount) it('getOutputAmount for input token BLASTERS and output token BLAST', () => {
const reserveBlasterAmount = CurrencyAmount.fromRawAmount(BLASTERS, '10000')
// Theoretical amount out: const reserveBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '10000')
// (10000 * 997 * 100 * (1 - 3.5%) / (10000 * 1000 + 997 * 100 * (1 - 3.5%))) * (1 - 4%)
// = 91.48 const pair = new Pair(reserveBlasterAmount, reserveBlastAmount)
//
// However in practice, we have round down of precisions in multiple steps const inputBlastersAmount = CurrencyAmount.fromRawAmount(BLASTERS, '100')
// hence the amount out will be slightly less than 91.48: const [outputBlastAmount] = pair.getOutputAmount(inputBlastersAmount, calculateFotFees)
//
// inputAmount = 100 // Theoretical amount out:
// percentAfterSellFeesInDecimal = fraction(9650, 10000) // (10000 * 997 * 100 * (1 - 3.5%) / (10000 * 1000 + 997 * 100 * (1 - 3.5%))) * (1 - 4%)
// inputAmountAfterTax = 100 * fraction(9650, 10000) = 96.5 = 96 (rounded down) // = 91.48
// inputAmountWithFeeAndAfterTax = 96 * 997 = 95712 //
// numerator = 95712 * 10000 = 957120000 // However in practice, we have round down of precisions in multiple steps
// denominator = 10000 * 1000 + 95712 = 10095712 // hence the amount out will be slightly less than 91.48:
// outputAmount = 957120000 / 10095712 = 94.8046061536 = 94 (rounded down) //
// buyFeePercentInDecimal = fraction(400, 10000) // inputAmount = 100
// percentAfterBuyFeesInDecimal = fraction(9600, 10000) // percentAfterSellFeesInDecimal = fraction(9650, 10000)
// outputAmountAfterTax = 94 * fraction(9600, 10000) // inputAmountAfterTax = 100 * fraction(9650, 10000) = 96.5 = 96 (rounded down)
// = 94 * 0.96 // inputAmountWithFeeAndAfterTax = 96 * 997 = 95712
// = 90.24 // numerator = 95712 * 10000 = 957120000
// = 90 (rounded down) // denominator = 10000 * 1000 + 95712 = 10095712
const expectedOutputBlastAmount = '0.00000000000000009' // outputAmount = 957120000 / 10095712 = 94.8046061536 = 94 (rounded down)
expect(outputBlastAmount.toExact()).toEqual(expectedOutputBlastAmount) // buyFeePercentInDecimal = fraction(400, 10000)
// percentAfterBuyFeesInDecimal = fraction(9600, 10000)
// outputAmountAfterTax = 94 * fraction(9600, 10000)
// = 94 * 0.96
// = 90.24
// = 90 (rounded down)
const expectedOutputBlastAmount = '0.00000000000000009'
expect(outputBlastAmount.toExact()).toEqual(expectedOutputBlastAmount)
})
it('getInputAmount for input token BLASTERS and output token BLAST', () => {
const reserveBlasterAmount = CurrencyAmount.fromRawAmount(BLASTERS, '10000')
const reserveBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '10000')
const pair = new Pair(reserveBlasterAmount, reserveBlastAmount)
const outputBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '91')
const [inputBlasterAmount] = pair.getInputAmount(outputBlastAmount, calculateFotFees)
// Theoretical amount in:
// 10000 * 100 * (1 - 4%) * 1000 / ((10000 - 100 * (1 - 4%)) * 997) / (1 - 3.5%)
// = 100.7483934892
//
// However in practice, we have round up of precisions in multiple steps
// hence the amount out will be slightly more than 100.7483934892:
//
// buyFeePercentInDecimal = fraction(400, 10000)
// percentAfterBuyFeesInDecimal = 1 - fraction(400, 10000) = fraction(9600, 10000)
// outputAmountBeforeTax = 91 / fraction(960000, 10000) + 1
// = 91 / 0.96 + 1
// = 94.7916666667 + 1
// = 94 (rounded down) + 1
// = 95 (rounded up)
// numerator = 10000 * 95 * 1000 = 950000000
// denominator = (10000 - 95) * 997 = 9875285
// inputAmount = 950000000 / 9875285 + 1
// = 96.1997552476 + 1
// = 96 (rounded down) + 1
// = 97 (rounded up)
// sellFeePercentInDecimal = fraction(350, 10000)
// percentAfterSellFeesInDecimal = 1 - fraction(350, 10000) = fraction(9650, 10000)
// inputAmountBeforeTax = (97 / fraction(9650, 10000)) + 1
// = (97 / 0.965) + 1
// = 100.518134715 + 1
// = 100 (rounded down) + 1
// = 101
const expectedInputBlasterAmount = '0.000000101'
expect(inputBlasterAmount.toExact()).toEqual(expectedInputBlasterAmount)
})
}) })
})
describe('when NOT calculating FOT fees', () => {
beforeEach(() => {
calculateFotFees = false
})
describe('getOutputAmount', () => {
it('getOutputAmount for input token BLASTERS and output token BLAST', () => {
const reserveBlasterAmount = CurrencyAmount.fromRawAmount(BLASTERS, '10000')
const reserveBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '10000')
const pair = new Pair(reserveBlasterAmount, reserveBlastAmount)
const inputBlastersAmount = CurrencyAmount.fromRawAmount(BLASTERS, '100')
const [outputBlastAmount] = pair.getOutputAmount(inputBlastersAmount, calculateFotFees)
const expectedOutputBlastAmount = '0.000000000000000098'
expect(outputBlastAmount.toExact()).toEqual(expectedOutputBlastAmount)
})
it('getInputAmount for input token BLASTERS and output token BLAST', () => {
const reserveBlasterAmount = CurrencyAmount.fromRawAmount(BLASTERS, '10000')
const reserveBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '10000')
const pair = new Pair(reserveBlasterAmount, reserveBlastAmount)
const outputBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '91')
const [inputBlasterAmount] = pair.getInputAmount(outputBlastAmount, calculateFotFees)
it('getInputAmount for input token BLASTERS and output token BLAST', () => { const expectedInputBlasterAmount = '0.000000093'
const reserveBlasterAmount = CurrencyAmount.fromRawAmount(BLASTERS, '10000') expect(inputBlasterAmount.toExact()).toEqual(expectedInputBlasterAmount)
const reserveBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '10000') })
const pair = new Pair(reserveBlasterAmount, reserveBlastAmount)
const outputBlastAmount = CurrencyAmount.fromRawAmount(BLAST, '91')
const [inputBlasterAmount] = pair.getInputAmount(outputBlastAmount)
// Theoretical amount in:
// 10000 * 100 * (1 - 4%) * 1000 / ((10000 - 100 * (1 - 4%)) * 997) / (1 - 3.5%)
// = 100.7483934892
//
// However in practice, we have round up of precisions in multiple steps
// hence the amount out will be slightly more than 100.7483934892:
//
// buyFeePercentInDecimal = fraction(400, 10000)
// percentAfterBuyFeesInDecimal = 1 - fraction(400, 10000) = fraction(9600, 10000)
// outputAmountBeforeTax = 91 / fraction(960000, 10000) + 1
// = 91 / 0.96 + 1
// = 94.7916666667 + 1
// = 94 (rounded down) + 1
// = 95 (rounded up)
// numerator = 10000 * 95 * 1000 = 950000000
// denominator = (10000 - 95) * 997 = 9875285
// inputAmount = 950000000 / 9875285 + 1
// = 96.1997552476 + 1
// = 96 (rounded down) + 1
// = 97 (rounded up)
// sellFeePercentInDecimal = fraction(350, 10000)
// percentAfterSellFeesInDecimal = 1 - fraction(350, 10000) = fraction(9650, 10000)
// inputAmountBeforeTax = (97 / fraction(9650, 10000)) + 1
// = (97 / 0.965) + 1
// = 100.518134715 + 1
// = 100 (rounded down) + 1
// = 101
const expectedInputBlasterAmount = '0.000000101'
expect(inputBlasterAmount.toExact()).toEqual(expectedInputBlasterAmount)
}) })
}) })
}) })
......
...@@ -178,7 +178,10 @@ export class Pair { ...@@ -178,7 +178,10 @@ export class Pair {
* *
* @param inputAmount * @param inputAmount
*/ */
public getOutputAmount(inputAmount: CurrencyAmount<Token>): [CurrencyAmount<Token>, Pair] { public getOutputAmount(
inputAmount: CurrencyAmount<Token>,
calculateFotFees: boolean = false
): [CurrencyAmount<Token>, Pair] {
invariant(this.involvesToken(inputAmount.currency), 'TOKEN') invariant(this.involvesToken(inputAmount.currency), 'TOKEN')
if (JSBI.equal(this.reserve0.quotient, ZERO) || JSBI.equal(this.reserve1.quotient, ZERO)) { if (JSBI.equal(this.reserve0.quotient, ZERO) || JSBI.equal(this.reserve1.quotient, ZERO)) {
throw new InsufficientReservesError() throw new InsufficientReservesError()
...@@ -186,7 +189,7 @@ export class Pair { ...@@ -186,7 +189,7 @@ export class Pair {
const inputReserve = this.reserveOf(inputAmount.currency) const inputReserve = this.reserveOf(inputAmount.currency)
const outputReserve = this.reserveOf(inputAmount.currency.equals(this.token0) ? this.token1 : this.token0) const outputReserve = this.reserveOf(inputAmount.currency.equals(this.token0) ? this.token1 : this.token0)
const percentAfterSellFees = this.derivePercentAfterSellFees(inputAmount) const percentAfterSellFees = calculateFotFees ? this.derivePercentAfterSellFees(inputAmount) : ZERO_PERCENT
const inputAmountAfterTax = percentAfterSellFees.greaterThan(ZERO_PERCENT) const inputAmountAfterTax = percentAfterSellFees.greaterThan(ZERO_PERCENT)
? CurrencyAmount.fromRawAmount( ? CurrencyAmount.fromRawAmount(
inputAmount.currency, inputAmount.currency,
...@@ -206,7 +209,7 @@ export class Pair { ...@@ -206,7 +209,7 @@ export class Pair {
throw new InsufficientInputAmountError() throw new InsufficientInputAmountError()
} }
const percentAfterBuyFees = this.derivePercentAfterBuyFees(outputAmount) const percentAfterBuyFees = calculateFotFees ? this.derivePercentAfterBuyFees(outputAmount) : ZERO_PERCENT
const outputAmountAfterTax = percentAfterBuyFees.greaterThan(ZERO_PERCENT) const outputAmountAfterTax = percentAfterBuyFees.greaterThan(ZERO_PERCENT)
? CurrencyAmount.fromRawAmount( ? CurrencyAmount.fromRawAmount(
outputAmount.currency, outputAmount.currency,
...@@ -265,9 +268,12 @@ export class Pair { ...@@ -265,9 +268,12 @@ export class Pair {
* *
* @param outputAmount * @param outputAmount
*/ */
public getInputAmount(outputAmount: CurrencyAmount<Token>): [CurrencyAmount<Token>, Pair] { public getInputAmount(
outputAmount: CurrencyAmount<Token>,
calculateFotFees: boolean = false
): [CurrencyAmount<Token>, Pair] {
invariant(this.involvesToken(outputAmount.currency), 'TOKEN') invariant(this.involvesToken(outputAmount.currency), 'TOKEN')
const percentAfterBuyFees = this.derivePercentAfterBuyFees(outputAmount) const percentAfterBuyFees = calculateFotFees ? this.derivePercentAfterBuyFees(outputAmount) : ZERO_PERCENT
const outputAmountBeforeTax = percentAfterBuyFees.greaterThan(ZERO_PERCENT) const outputAmountBeforeTax = percentAfterBuyFees.greaterThan(ZERO_PERCENT)
? CurrencyAmount.fromRawAmount( ? CurrencyAmount.fromRawAmount(
outputAmount.currency, outputAmount.currency,
...@@ -294,7 +300,7 @@ export class Pair { ...@@ -294,7 +300,7 @@ export class Pair {
JSBI.add(JSBI.divide(numerator, denominator), ONE) // add 1 here is part of the formula, no rounding needed here, since there will not be decimal at this point JSBI.add(JSBI.divide(numerator, denominator), ONE) // add 1 here is part of the formula, no rounding needed here, since there will not be decimal at this point
) )
const percentAfterSellFees = this.derivePercentAfterSellFees(inputAmount) const percentAfterSellFees = calculateFotFees ? this.derivePercentAfterSellFees(inputAmount) : ZERO_PERCENT
const inputAmountBeforeTax = percentAfterSellFees.greaterThan(ZERO_PERCENT) const inputAmountBeforeTax = percentAfterSellFees.greaterThan(ZERO_PERCENT)
? CurrencyAmount.fromRawAmount( ? CurrencyAmount.fromRawAmount(
inputAmount.currency, inputAmount.currency,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment