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

Commit

Permalink
Receive an option to decide if we calculate FOT tax or not (#146)
Browse files Browse the repository at this point in the history
* Receive an option to decide if we calculate FOT tax or not

* Fix code style issues with Prettier

---------

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
  • Loading branch information
mikeki and lint-action authored Sep 29, 2023
1 parent 4411fae commit 53e3b8e
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 76 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist
node_modules
.idea
182 changes: 112 additions & 70 deletions src/entities/pair.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,78 +199,120 @@ describe('Pair', () => {
BLASTERSSellFeeBps
)

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)

// Theoretical amount out:
// (10000 * 997 * 100 * (1 - 3.5%) / (10000 * 1000 + 997 * 100 * (1 - 3.5%))) * (1 - 4%)
// = 91.48
//
// However in practice, we have round down of precisions in multiple steps
// hence the amount out will be slightly less than 91.48:
//
// inputAmount = 100
// percentAfterSellFeesInDecimal = fraction(9650, 10000)
// inputAmountAfterTax = 100 * fraction(9650, 10000) = 96.5 = 96 (rounded down)
// inputAmountWithFeeAndAfterTax = 96 * 997 = 95712
// numerator = 95712 * 10000 = 957120000
// denominator = 10000 * 1000 + 95712 = 10095712
// outputAmount = 957120000 / 10095712 = 94.8046061536 = 94 (rounded down)
// 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)
let calculateFotFees: boolean = false

describe('when calculating FOT fees', () => {
beforeEach(() => {
calculateFotFees = true
})

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)

// Theoretical amount out:
// (10000 * 997 * 100 * (1 - 3.5%) / (10000 * 1000 + 997 * 100 * (1 - 3.5%))) * (1 - 4%)
// = 91.48
//
// However in practice, we have round down of precisions in multiple steps
// hence the amount out will be slightly less than 91.48:
//
// inputAmount = 100
// percentAfterSellFeesInDecimal = fraction(9650, 10000)
// inputAmountAfterTax = 100 * fraction(9650, 10000) = 96.5 = 96 (rounded down)
// inputAmountWithFeeAndAfterTax = 96 * 997 = 95712
// numerator = 95712 * 10000 = 957120000
// denominator = 10000 * 1000 + 95712 = 10095712
// outputAmount = 957120000 / 10095712 = 94.8046061536 = 94 (rounded down)
// 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 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)

// 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)
const expectedInputBlasterAmount = '0.000000093'
expect(inputBlasterAmount.toExact()).toEqual(expectedInputBlasterAmount)
})
})
})
})
Expand Down
18 changes: 12 additions & 6 deletions src/entities/pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,18 @@ export class Pair {
*
* @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')
if (JSBI.equal(this.reserve0.quotient, ZERO) || JSBI.equal(this.reserve1.quotient, ZERO)) {
throw new InsufficientReservesError()
}
const inputReserve = this.reserveOf(inputAmount.currency)
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)
? CurrencyAmount.fromRawAmount(
inputAmount.currency,
Expand All @@ -206,7 +209,7 @@ export class Pair {
throw new InsufficientInputAmountError()
}

const percentAfterBuyFees = this.derivePercentAfterBuyFees(outputAmount)
const percentAfterBuyFees = calculateFotFees ? this.derivePercentAfterBuyFees(outputAmount) : ZERO_PERCENT
const outputAmountAfterTax = percentAfterBuyFees.greaterThan(ZERO_PERCENT)
? CurrencyAmount.fromRawAmount(
outputAmount.currency,
Expand Down Expand Up @@ -265,9 +268,12 @@ export class Pair {
*
* @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')
const percentAfterBuyFees = this.derivePercentAfterBuyFees(outputAmount)
const percentAfterBuyFees = calculateFotFees ? this.derivePercentAfterBuyFees(outputAmount) : ZERO_PERCENT
const outputAmountBeforeTax = percentAfterBuyFees.greaterThan(ZERO_PERCENT)
? CurrencyAmount.fromRawAmount(
outputAmount.currency,
Expand All @@ -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
)

const percentAfterSellFees = this.derivePercentAfterSellFees(inputAmount)
const percentAfterSellFees = calculateFotFees ? this.derivePercentAfterSellFees(inputAmount) : ZERO_PERCENT
const inputAmountBeforeTax = percentAfterSellFees.greaterThan(ZERO_PERCENT)
? CurrencyAmount.fromRawAmount(
inputAmount.currency,
Expand Down

0 comments on commit 53e3b8e

Please sign in to comment.