Skip to content

Commit

Permalink
make the fetching code more tree shakeable (Uniswap#27)
Browse files Browse the repository at this point in the history
* make the fetching code more tree shakeable

* more documentation everywhere
  • Loading branch information
moodysalem committed Jul 6, 2020
1 parent f10af5d commit 280d3be
Show file tree
Hide file tree
Showing 15 changed files with 227 additions and 130 deletions.
9 changes: 9 additions & 0 deletions src/entities/currency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,17 @@ export class Currency {
public readonly symbol?: string
public readonly name?: string

/**
* The only instance of the base class `Currency`.
*/
public static readonly ETHER: Currency = new Currency(18, 'ETH', 'Ether')

/**
* Constructs an instance of the base class `Currency`. The only instance of the base class `Currency` is `Currency.ETHER`.
* @param decimals decimals of the currency
* @param symbol symbol of the currency
* @param name of the currency
*/
protected constructor(decimals: number, symbol?: string, name?: string) {
validateSolidityTypeInstance(JSBI.BigInt(decimals), SolidityType.uint8)

Expand Down
16 changes: 10 additions & 6 deletions src/entities/fractions/currencyAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,29 @@ export class CurrencyAmount extends Fraction {
this.currency = currency
}

get raw(): JSBI {
public get raw(): JSBI {
return this.numerator
}

add(other: CurrencyAmount): CurrencyAmount {
public add(other: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(this.currency, other.currency), 'TOKEN')
return new CurrencyAmount(this.currency, JSBI.add(this.raw, other.raw))
}

subtract(other: CurrencyAmount): CurrencyAmount {
public subtract(other: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(this.currency, other.currency), 'TOKEN')
return new CurrencyAmount(this.currency, JSBI.subtract(this.raw, other.raw))
}

toSignificant(significantDigits: number = 6, format?: object, rounding: Rounding = Rounding.ROUND_DOWN): string {
public toSignificant(
significantDigits: number = 6,
format?: object,
rounding: Rounding = Rounding.ROUND_DOWN
): string {
return super.toSignificant(significantDigits, format, rounding)
}

toFixed(
public toFixed(
decimalPlaces: number = this.currency.decimals,
format?: object,
rounding: Rounding = Rounding.ROUND_DOWN
Expand All @@ -58,7 +62,7 @@ export class CurrencyAmount extends Fraction {
return super.toFixed(decimalPlaces, format, rounding)
}

toExact(format: object = { groupSeparator: '' }): string {
public toExact(format: object = { groupSeparator: '' }): string {
Big.DP = this.currency.decimals
return new Big(this.numerator.toString()).div(this.denominator.toString()).toFormat(format)
}
Expand Down
26 changes: 13 additions & 13 deletions src/entities/fractions/fraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ export class Fraction {
public readonly numerator: JSBI
public readonly denominator: JSBI

constructor(numerator: BigintIsh, denominator: BigintIsh = ONE) {
public constructor(numerator: BigintIsh, denominator: BigintIsh = ONE) {
this.numerator = parseBigintIsh(numerator)
this.denominator = parseBigintIsh(denominator)
}

// performs floor division
get quotient(): JSBI {
public get quotient(): JSBI {
return JSBI.divide(this.numerator, this.denominator)
}

// remainder after floor division
get remainder(): Fraction {
public get remainder(): Fraction {
return new Fraction(JSBI.remainder(this.numerator, this.denominator), this.denominator)
}

invert(): Fraction {
public invert(): Fraction {
return new Fraction(this.denominator, this.numerator)
}

add(other: Fraction | BigintIsh): Fraction {
public add(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
if (JSBI.equal(this.denominator, otherParsed.denominator)) {
return new Fraction(JSBI.add(this.numerator, otherParsed.numerator), this.denominator)
Expand All @@ -60,7 +60,7 @@ export class Fraction {
)
}

subtract(other: Fraction | BigintIsh): Fraction {
public subtract(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
if (JSBI.equal(this.denominator, otherParsed.denominator)) {
return new Fraction(JSBI.subtract(this.numerator, otherParsed.numerator), this.denominator)
Expand All @@ -74,47 +74,47 @@ export class Fraction {
)
}

lessThan(other: Fraction | BigintIsh): boolean {
public lessThan(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.lessThan(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}

equalTo(other: Fraction | BigintIsh): boolean {
public equalTo(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.equal(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}

greaterThan(other: Fraction | BigintIsh): boolean {
public greaterThan(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.greaterThan(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}

multiply(other: Fraction | BigintIsh): Fraction {
public multiply(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return new Fraction(
JSBI.multiply(this.numerator, otherParsed.numerator),
JSBI.multiply(this.denominator, otherParsed.denominator)
)
}

divide(other: Fraction | BigintIsh): Fraction {
public divide(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return new Fraction(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(this.denominator, otherParsed.numerator)
)
}

toSignificant(
public toSignificant(
significantDigits: number,
format: object = { groupSeparator: '' },
rounding: Rounding = Rounding.ROUND_HALF_UP
Expand All @@ -129,7 +129,7 @@ export class Fraction {
return quotient.toFormat(quotient.decimalPlaces(), format)
}

toFixed(
public toFixed(
decimalPlaces: number,
format: object = { groupSeparator: '' },
rounding: Rounding = Rounding.ROUND_HALF_UP
Expand Down
4 changes: 2 additions & 2 deletions src/entities/fractions/percent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { Fraction } from './fraction'
const _100_PERCENT = new Fraction(_100)

export class Percent extends Fraction {
toSignificant(significantDigits: number = 5, format?: object, rounding?: Rounding): string {
public toSignificant(significantDigits: number = 5, format?: object, rounding?: Rounding): string {
return this.multiply(_100_PERCENT).toSignificant(significantDigits, format, rounding)
}

toFixed(decimalPlaces: number = 2, format?: object, rounding?: Rounding): string {
public toFixed(decimalPlaces: number = 2, format?: object, rounding?: Rounding): string {
return this.multiply(_100_PERCENT).toFixed(decimalPlaces, format, rounding)
}
}
18 changes: 9 additions & 9 deletions src/entities/fractions/price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class Price extends Fraction {
public readonly quoteCurrency: Currency // output i.e. numerator
public readonly scalar: Fraction // used to adjust the raw fraction w/r/t the decimals of the {base,quote}Token

static fromRoute(route: Route): Price {
public static fromRoute(route: Route): Price {
const prices: Price[] = []
for (const [i, pair] of route.pairs.entries()) {
prices.push(
Expand All @@ -28,7 +28,7 @@ export class Price extends Fraction {
}

// denominator and numerator _must_ be raw, i.e. in the native representation
constructor(baseCurrency: Currency, quoteCurrency: Currency, denominator: BigintIsh, numerator: BigintIsh) {
public constructor(baseCurrency: Currency, quoteCurrency: Currency, denominator: BigintIsh, numerator: BigintIsh) {
super(numerator, denominator)

this.baseCurrency = baseCurrency
Expand All @@ -39,38 +39,38 @@ export class Price extends Fraction {
)
}

get raw(): Fraction {
public get raw(): Fraction {
return new Fraction(this.numerator, this.denominator)
}

get adjusted(): Fraction {
public get adjusted(): Fraction {
return super.multiply(this.scalar)
}

invert(): Price {
public invert(): Price {
return new Price(this.quoteCurrency, this.baseCurrency, this.numerator, this.denominator)
}

multiply(other: Price): Price {
public multiply(other: Price): Price {
invariant(currencyEquals(this.quoteCurrency, other.baseCurrency), 'TOKEN')
const fraction = super.multiply(other)
return new Price(this.baseCurrency, other.quoteCurrency, fraction.denominator, fraction.numerator)
}

// performs floor division on overflow
quote(currencyAmount: CurrencyAmount): CurrencyAmount {
public quote(currencyAmount: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(currencyAmount.currency, this.baseCurrency), 'TOKEN')
if (this.quoteCurrency instanceof Token) {
return new TokenAmount(this.quoteCurrency, super.multiply(currencyAmount.raw).quotient)
}
return CurrencyAmount.ether(super.multiply(currencyAmount.raw).quotient)
}

toSignificant(significantDigits: number = 6, format?: object, rounding?: Rounding): string {
public toSignificant(significantDigits: number = 6, format?: object, rounding?: Rounding): string {
return this.adjusted.toSignificant(significantDigits, format, rounding)
}

toFixed(decimalPlaces: number = 4, format?: object, rounding?: Rounding): string {
public toFixed(decimalPlaces: number = 4, format?: object, rounding?: Rounding): string {
return this.adjusted.toFixed(decimalPlaces, format, rounding)
}
}
6 changes: 3 additions & 3 deletions src/entities/fractions/tokenAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ export class TokenAmount extends CurrencyAmount {
public readonly token: Token

// amount _must_ be raw, i.e. in the native representation
constructor(token: Token, amount: BigintIsh) {
public constructor(token: Token, amount: BigintIsh) {
super(token, amount)
this.token = token
}

add(other: TokenAmount): TokenAmount {
public add(other: TokenAmount): TokenAmount {
invariant(this.token.equals(other.token), 'TOKEN')
return new TokenAmount(this.token, JSBI.add(this.raw, other.raw))
}

subtract(other: TokenAmount): TokenAmount {
public subtract(other: TokenAmount): TokenAmount {
invariant(this.token.equals(other.token), 'TOKEN')
return new TokenAmount(this.token, JSBI.subtract(this.raw, other.raw))
}
Expand Down
54 changes: 21 additions & 33 deletions src/entities/pair.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { TokenAmount } from './fractions/tokenAmount'
import invariant from 'tiny-invariant'
import JSBI from 'jsbi'
import { getNetwork } from '@ethersproject/networks'
import { getDefaultProvider } from '@ethersproject/providers'
import { Contract } from '@ethersproject/contracts'
import { pack, keccak256 } from '@ethersproject/solidity'
import { getCreate2Address } from '@ethersproject/address'

Expand All @@ -19,25 +16,24 @@ import {
_1000,
ChainId
} from '../constants'
import IUniswapV2Pair from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { sqrt, parseBigintIsh } from '../utils'
import { InsufficientReservesError, InsufficientInputAmountError } from '../errors'
import { Token } from './token'

let CACHE: { [token0Address: string]: { [token1Address: string]: string } } = {}
let PAIR_ADDRESS_CACHE: { [token0Address: string]: { [token1Address: string]: string } } = {}

export class Pair {
public readonly liquidityToken: Token
private readonly tokenAmounts: [TokenAmount, TokenAmount]

static getAddress(tokenA: Token, tokenB: Token): string {
public static getAddress(tokenA: Token, tokenB: Token): string {
const tokens = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] // does safety checks

if (CACHE?.[tokens[0].address]?.[tokens[1].address] === undefined) {
CACHE = {
...CACHE,
if (PAIR_ADDRESS_CACHE?.[tokens[0].address]?.[tokens[1].address] === undefined) {
PAIR_ADDRESS_CACHE = {
...PAIR_ADDRESS_CACHE,
[tokens[0].address]: {
...CACHE?.[tokens[0].address],
...PAIR_ADDRESS_CACHE?.[tokens[0].address],
[tokens[1].address]: getCreate2Address(
FACTORY_ADDRESS,
keccak256(['bytes'], [pack(['address', 'address'], [tokens[0].address, tokens[1].address])]),
Expand All @@ -47,22 +43,10 @@ export class Pair {
}
}

return CACHE[tokens[0].address][tokens[1].address]
return PAIR_ADDRESS_CACHE[tokens[0].address][tokens[1].address]
}

static async fetchData(
tokenA: Token,
tokenB: Token,
provider = getDefaultProvider(getNetwork(tokenA.chainId))
): Promise<Pair> {
invariant(tokenA.chainId === tokenB.chainId, 'CHAIN_ID')
const address = Pair.getAddress(tokenA, tokenB)
const [reserves0, reserves1] = await new Contract(address, IUniswapV2Pair.abi, provider).getReserves()
const balances = tokenA.sortsBefore(tokenB) ? [reserves0, reserves1] : [reserves1, reserves0]
return new Pair(new TokenAmount(tokenA, balances[0]), new TokenAmount(tokenB, balances[1]))
}

constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount) {
public constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount) {
const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks
? [tokenAmountA, tokenAmountB]
: [tokenAmountB, tokenAmountA]
Expand All @@ -88,28 +72,28 @@ export class Pair {
return this.token0.chainId
}

get token0(): Token {
public get token0(): Token {
return this.tokenAmounts[0].token
}

get token1(): Token {
public get token1(): Token {
return this.tokenAmounts[1].token
}

get reserve0(): TokenAmount {
public get reserve0(): TokenAmount {
return this.tokenAmounts[0]
}

get reserve1(): TokenAmount {
public get reserve1(): TokenAmount {
return this.tokenAmounts[1]
}

reserveOf(token: Token): TokenAmount {
public reserveOf(token: Token): TokenAmount {
invariant(this.involvesToken(token), 'TOKEN')
return token.equals(this.token0) ? this.reserve0 : this.reserve1
}

getOutputAmount(inputAmount: TokenAmount): [TokenAmount, Pair] {
public getOutputAmount(inputAmount: TokenAmount): [TokenAmount, Pair] {
invariant(this.involvesToken(inputAmount.token), 'TOKEN')
if (JSBI.equal(this.reserve0.raw, ZERO) || JSBI.equal(this.reserve1.raw, ZERO)) {
throw new InsufficientReservesError()
Expand All @@ -129,7 +113,7 @@ export class Pair {
return [outputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}

getInputAmount(outputAmount: TokenAmount): [TokenAmount, Pair] {
public getInputAmount(outputAmount: TokenAmount): [TokenAmount, Pair] {
invariant(this.involvesToken(outputAmount.token), 'TOKEN')
if (
JSBI.equal(this.reserve0.raw, ZERO) ||
Expand All @@ -150,7 +134,11 @@ export class Pair {
return [inputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}

getLiquidityMinted(totalSupply: TokenAmount, tokenAmountA: TokenAmount, tokenAmountB: TokenAmount): TokenAmount {
public getLiquidityMinted(
totalSupply: TokenAmount,
tokenAmountA: TokenAmount,
tokenAmountB: TokenAmount
): TokenAmount {
invariant(totalSupply.token.equals(this.liquidityToken), 'LIQUIDITY')
const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks
? [tokenAmountA, tokenAmountB]
Expand All @@ -171,7 +159,7 @@ export class Pair {
return new TokenAmount(this.liquidityToken, liquidity)
}

getLiquidityValue(
public getLiquidityValue(
token: Token,
totalSupply: TokenAmount,
liquidity: TokenAmount,
Expand Down
Loading

0 comments on commit 280d3be

Please sign in to comment.