Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/currency-amount-comparators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@uniswap/sdk-core": minor
"@uniswap/v2-sdk": patch
"@uniswap/router-sdk": patch
---

Add `lessThan`, `equalTo`, and `greaterThan` overrides on `CurrencyAmount` that enforce the same currency invariant used by `add`/`subtract`. The operand type is narrowed to `CurrencyAmount<T> | 0`: comparing two `CurrencyAmount`s of different currencies now throws `CURRENCY`, and the only non-`CurrencyAmount` operand accepted is the literal `0` sentinel (so comparisons against bare `BigintIsh` values that ignore `decimalScale` are no longer allowed). Internal callers in `v2-sdk` and `router-sdk` that previously passed `JSBI.BigInt(0)` have been updated to pass `0`.
2 changes: 1 addition & 1 deletion sdks/router-sdk/src/entities/mixedRoute/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ export class MixedRouteTrade<TInput extends Currency, TOutput extends Currency,
// pool irrelevant
if (!pool.token0.equals(amountInAdjusted.currency) && !pool.token1.equals(amountInAdjusted.currency)) continue
if (pool instanceof Pair) {
if ((pool as Pair).reserve0.equalTo(ZERO) || (pool as Pair).reserve1.equalTo(ZERO)) continue
if ((pool as Pair).reserve0.equalTo(0) || (pool as Pair).reserve1.equalTo(0)) continue
}

let amountOut: CurrencyAmount<Currency>
Expand Down
2 changes: 1 addition & 1 deletion sdks/router-sdk/src/entities/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType

// if the total output of this trade is 0, then most likely the post-tax input was also 0, and therefore this swap
// does not move the pools' market price
if (spotOutputAmount.equalTo(ZERO)) return ZERO_PERCENT
if (spotOutputAmount.equalTo(0)) return ZERO_PERCENT

const preTaxOutputAmount = this.outputAmount.divide(new Fraction(ONE).subtract(this.outputTax))
const priceImpact = spotOutputAmount.subtract(preTaxOutputAmount).divide(spotOutputAmount)
Expand Down
48 changes: 48 additions & 0 deletions sdks/sdk-core/src/entities/fractions/currencyAmount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,54 @@ describe('CurrencyAmount', () => {
})
})

describe('comparators', () => {
const tokenA = new Token(1, ADDRESS_ONE, 18)
const tokenB = new Token(1, '0x0000000000000000000000000000000000000002', 18)

it('lessThan returns true when first amount is smaller', () => {
const a = CurrencyAmount.fromRawAmount(tokenA, 1)
const b = CurrencyAmount.fromRawAmount(tokenA, 2)
expect(a.lessThan(b)).toBe(true)
expect(b.lessThan(a)).toBe(false)
})

it('equalTo returns true for equal amounts of the same currency', () => {
const a = CurrencyAmount.fromRawAmount(tokenA, 100)
const b = CurrencyAmount.fromRawAmount(tokenA, 100)
expect(a.equalTo(b)).toBe(true)
})

it('greaterThan returns true when first amount is larger', () => {
const a = CurrencyAmount.fromRawAmount(tokenA, 5)
const b = CurrencyAmount.fromRawAmount(tokenA, 1)
expect(a.greaterThan(b)).toBe(true)
})

it('comparison against the literal 0 sentinel works without currency check', () => {
const zero = CurrencyAmount.fromRawAmount(tokenA, 0)
const nonZero = CurrencyAmount.fromRawAmount(tokenA, 1)
expect(zero.equalTo(0)).toBe(true)
expect(nonZero.greaterThan(0)).toBe(true)
expect(zero.lessThan(0)).toBe(false)
})

it('only the literal 0 sentinel is accepted as a non-CurrencyAmount operand', () => {
const amount = CurrencyAmount.fromRawAmount(tokenA, 1)
// @ts-expect-error — non-zero BigintIsh is rejected by the narrowed type
expect(() => amount.greaterThan(JSBI.BigInt(5))).toThrow()
// @ts-expect-error — strings are rejected by the narrowed type
expect(() => amount.equalTo('1')).toThrow()
})

it('throws CURRENCY when comparing different currencies', () => {
const a = CurrencyAmount.fromRawAmount(tokenA, 1)
const b = CurrencyAmount.fromRawAmount(tokenB, 1)
expect(() => a.lessThan(b)).toThrow('CURRENCY')
expect(() => a.equalTo(b)).toThrow('CURRENCY')
expect(() => a.greaterThan(b)).toThrow('CURRENCY')
})
})

describe('#toExact', () => {
it('does not throw for sig figs > currency.decimals', () => {
const token = new Token(1, ADDRESS_ONE, 0)
Expand Down
15 changes: 15 additions & 0 deletions sdks/sdk-core/src/entities/fractions/currencyAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ export class CurrencyAmount<T extends Currency> extends Fraction {
return CurrencyAmount.fromFractionalAmount(this.currency, divided.numerator, divided.denominator)
}

public lessThan(other: CurrencyAmount<T> | 0): boolean {
invariant(other === 0 || this.currency.equals(other.currency), 'CURRENCY')
return super.lessThan(other)
}

public equalTo(other: CurrencyAmount<T> | 0): boolean {
invariant(other === 0 || this.currency.equals(other.currency), 'CURRENCY')
return super.equalTo(other)
}

public greaterThan(other: CurrencyAmount<T> | 0): boolean {
invariant(other === 0 || this.currency.equals(other.currency), 'CURRENCY')
return super.greaterThan(other)
}

public toSignificant(
significantDigits: number = 6,
format?: object,
Expand Down
4 changes: 2 additions & 2 deletions sdks/v2-sdk/src/entities/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
const pair = pairs[i]
// pair irrelevant
if (!pair.token0.equals(amountIn.currency) && !pair.token1.equals(amountIn.currency)) continue
if (pair.reserve0.equalTo(ZERO) || pair.reserve1.equalTo(ZERO)) continue
if (pair.reserve0.equalTo(0) || pair.reserve1.equalTo(0)) continue

let amountOut: CurrencyAmount<Token>
try {
Expand Down Expand Up @@ -340,7 +340,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
const pair = pairs[i]
// pair irrelevant
if (!pair.token0.equals(amountOut.currency) && !pair.token1.equals(amountOut.currency)) continue
if (pair.reserve0.equalTo(ZERO) || pair.reserve1.equalTo(ZERO)) continue
if (pair.reserve0.equalTo(0) || pair.reserve1.equalTo(0)) continue

let amountIn: CurrencyAmount<Token>
try {
Expand Down
Loading