Skip to content

Commit

Permalink
throw errors for runtime parameters
Browse files Browse the repository at this point in the history
remove public keyword from class functions

various small cleanups
  • Loading branch information
NoahZinsmeister committed Feb 19, 2020
1 parent cc692ef commit 05a05c5
Show file tree
Hide file tree
Showing 16 changed files with 113 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"plugins": [] // ["transform-jsbi-to-bigint"]
"plugins": [] // "transform-jsbi-to-bigint"
}
14 changes: 2 additions & 12 deletions src/abis/ERC20.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,9 @@
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "address"
}
],
"inputs": [{ "name": "", "type": "address" }],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
Expand Down
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,8 @@ export enum SolidityType {
uint8 = 'uint8',
uint256 = 'uint256'
}

export const SOLIDITY_TYPE_MAXIMA = {
[SolidityType.uint8]: JSBI.BigInt('0xff'),
[SolidityType.uint256]: JSBI.BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
}
76 changes: 40 additions & 36 deletions src/entities/exchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Contract } from '@ethersproject/contracts'

import { FACTORY_ADDRESS, INIT_CODE_HASH, ZERO, ONE, _997, _1000 } from '../constants'
import ERC20 from '../abis/ERC20.json'
import { InsufficientReservesError, InsufficientInputAmountError } from '../errors'
import { Token } from './token'
import { TokenAmount } from './fractions/tokenAmount'

Expand All @@ -18,8 +19,7 @@ export class Exchange {
private readonly tokenAmounts: [TokenAmount, TokenAmount]

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

if (CACHE?.[tokens[0].address]?.[tokens[1].address] === undefined) {
CACHE = {
Expand All @@ -34,6 +34,7 @@ export class Exchange {
}
}
}

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

Expand All @@ -42,78 +43,81 @@ export class Exchange {
tokenB: Token,
provider = getDefaultProvider(getNetwork(tokenA.chainId))
): Promise<Exchange> {
const exchangeAddress = Exchange.getAddress(tokenA, tokenB)
const address = Exchange.getAddress(tokenA, tokenB)
const balances = await Promise.all([
new Contract(tokenA.address, ERC20, provider).balanceOf(exchangeAddress),
new Contract(tokenB.address, ERC20, provider).balanceOf(exchangeAddress)
new Contract(tokenA.address, ERC20, provider).balanceOf(address),
new Contract(tokenB.address, ERC20, provider).balanceOf(address)
])
return new Exchange(new TokenAmount(tokenA, balances[0]), new TokenAmount(tokenB, balances[1]))
}

constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount) {
// performs the requisite safety checks
const tokenAmounts: [TokenAmount, TokenAmount] = tokenAmountA.token.sortsBefore(tokenAmountB.token)
const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks
? [tokenAmountA, tokenAmountB]
: [tokenAmountB, tokenAmountA]

this.address = Exchange.getAddress(tokenAmounts[0].token, tokenAmounts[1].token)
this.tokenAmounts = tokenAmounts
this.tokenAmounts = tokenAmounts as [TokenAmount, TokenAmount]
}

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

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

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

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

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

public getOutputAmount(inputAmount: TokenAmount): [TokenAmount, Exchange] {
getOutputAmount(inputAmount: TokenAmount): [TokenAmount, Exchange] {
invariant(inputAmount.token.equals(this.token0) || inputAmount.token.equals(this.token1), 'TOKEN')
invariant(JSBI.greaterThan(inputAmount.raw, ZERO), 'ZERO')
invariant(JSBI.greaterThan(this.reserve0.raw, ZERO), 'ZERO')
invariant(JSBI.greaterThan(this.reserve1.raw, ZERO), 'ZERO')

const inputReserve = inputAmount.token.equals(this.reserve0.token) ? this.reserve0 : this.reserve1
const outputReserve = inputAmount.token.equals(this.reserve0.token) ? this.reserve1 : this.reserve0
if (JSBI.equal(this.reserve0.raw, ZERO) || JSBI.equal(this.reserve1.raw, ZERO)) {
throw new InsufficientReservesError()
}
const inputReserve = this.reserveOf(inputAmount.token)
const outputReserve = this.reserveOf(inputAmount.token.equals(this.token0) ? this.token1 : this.token0)
const inputAmountWithFee = JSBI.multiply(inputAmount.raw, _997)
const numerator = JSBI.multiply(inputAmountWithFee, outputReserve.raw)
const denominator = JSBI.add(JSBI.multiply(inputReserve.raw, _1000), inputAmountWithFee)
const output = new TokenAmount(
const outputAmount = new TokenAmount(
inputAmount.token.equals(this.token0) ? this.token1 : this.token0,
JSBI.divide(numerator, denominator)
)
return [output, new Exchange(inputReserve.add(inputAmount), outputReserve.subtract(output))]
if (JSBI.equal(outputAmount.raw, ZERO)) {
throw new InsufficientInputAmountError()
}
return [outputAmount, new Exchange(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}

public getInputAmount(outputAmount: TokenAmount): [TokenAmount, Exchange] {
getInputAmount(outputAmount: TokenAmount): [TokenAmount, Exchange] {
invariant(outputAmount.token.equals(this.token0) || outputAmount.token.equals(this.token1), 'TOKEN')
invariant(JSBI.greaterThan(outputAmount.raw, ZERO), 'ZERO')
invariant(JSBI.greaterThan(this.reserve0.raw, ZERO), 'ZERO')
invariant(JSBI.greaterThan(this.reserve1.raw, ZERO), 'ZERO')
invariant(JSBI.lessThan(outputAmount.raw, this.reserveOf(outputAmount.token).raw), 'INSUFFICIENT_RESERVE')
if (
JSBI.equal(this.reserve0.raw, ZERO) ||
JSBI.equal(this.reserve1.raw, ZERO) ||
JSBI.greaterThanOrEqual(outputAmount.raw, this.reserveOf(outputAmount.token).raw)
) {
throw new InsufficientReservesError()
}

const inputReserve = outputAmount.token.equals(this.reserve0.token) ? this.reserve1 : this.reserve0
const outputReserve = outputAmount.token.equals(this.reserve0.token) ? this.reserve0 : this.reserve1
const outputReserve = this.reserveOf(outputAmount.token)
const inputReserve = this.reserveOf(outputAmount.token.equals(this.token0) ? this.token1 : this.token0)
const numerator = JSBI.multiply(JSBI.multiply(inputReserve.raw, outputAmount.raw), _1000)
const denominator = JSBI.multiply(JSBI.subtract(outputReserve.raw, outputAmount.raw), _997)
const input = new TokenAmount(
const inputAmount = new TokenAmount(
outputAmount.token.equals(this.token0) ? this.token1 : this.token0,
JSBI.add(JSBI.divide(numerator, denominator), ONE)
)
return [input, new Exchange(inputReserve.add(input), outputReserve.subtract(outputAmount))]
return [inputAmount, new Exchange(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}
}
14 changes: 7 additions & 7 deletions src/entities/fractions/fraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,34 +27,34 @@ export class Fraction {
public readonly numerator: JSBI
public readonly denominator: JSBI

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

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

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

public multiply(other: Fraction | BigintIsh): Fraction {
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)
)
}

public toSignificant(
toSignificant(
significantDigits: number,
format: object = { groupSeparator: '' },
rounding: Rounding = Rounding.ROUND_HALF_UP
): string {
invariant(Number.isInteger(significantDigits), `${significantDigits} is not a positive integer.`)
invariant(Number.isInteger(significantDigits), `${significantDigits} is not an integer.`)
invariant(significantDigits > 0, `${significantDigits} is not positive.`)

Decimal.set({ precision: significantDigits + 1, rounding: toSignificantRounding[rounding] })
Expand All @@ -64,7 +64,7 @@ export class Fraction {
return quotient.toFormat(quotient.decimalPlaces(), format)
}

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

const _100Percent = new Fraction(_100)
const _100_PERCENT = new Fraction(_100)

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

public toFixed(decimalPlaces: number = 2, format?: object, rounding?: Rounding): string {
return this.multiply(_100Percent).toSignificant(decimalPlaces, format, rounding)
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 @@ -11,7 +11,7 @@ import { TokenAmount } from './tokenAmount'
export class Price extends Fraction {
public readonly baseToken: Token // input i.e. denominator
public readonly quoteToken: Token // output i.e. numerator
public readonly scalar: Fraction // used to adjust the raw fraction w/r/t the decimals of the {base,quote}Tokens
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 {
const prices: Price[] = []
Expand All @@ -25,7 +25,7 @@ export class Price extends Fraction {
return prices.slice(1).reduce((accumulator, currentValue) => accumulator.multiply(currentValue), prices[0])
}

// denominator and numerator _must be_ scaled in units of the {base,quote}Tokens
// denominator and numerator _must_ be raw, i.e. in the native representation
constructor(baseToken: Token, quoteToken: Token, denominator: BigintIsh, numerator: BigintIsh) {
super(numerator, denominator)

Expand All @@ -37,35 +37,35 @@ export class Price extends Fraction {
)
}

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

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

public invert(): Price {
invert(): Price {
return new Price(this.quoteToken, this.baseToken, this.numerator, this.denominator)
}

public multiply(other: Price): Price {
multiply(other: Price): Price {
invariant(this.quoteToken.equals(other.baseToken), 'BASE')
const fraction = super.multiply(other)
return new Price(this.baseToken, other.quoteToken, fraction.denominator, fraction.numerator)
}

// performs floor division on overflow
public quote(tokenAmount: TokenAmount): TokenAmount {
quote(tokenAmount: TokenAmount): TokenAmount {
invariant(tokenAmount.token.equals(this.baseToken), 'TOKEN')
return new TokenAmount(this.quoteToken, super.multiply(tokenAmount.raw).quotient)
}

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

public toFixed(decimalPlaces: number = 6, format?: object, rounding?: Rounding): string {
toFixed(decimalPlaces: number = 4, format?: object, rounding?: Rounding): string {
return this.adjusted.toFixed(decimalPlaces, format, rounding)
}
}
18 changes: 7 additions & 11 deletions src/entities/fractions/tokenAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const Big = toFormat(_Big)
export class TokenAmount extends Fraction {
public readonly token: Token

// amount _must be_ scaled in units of the token
// amount _must_ be raw, i.e. in the native representation
constructor(token: Token, amount: BigintIsh) {
const parsedAmount = parseBigintIsh(amount)
validateSolidityTypeInstance(parsedAmount, SolidityType.uint256)
Expand All @@ -23,29 +23,25 @@ export class TokenAmount extends Fraction {
this.token = token
}

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

public get adjusted(): Fraction {
return this
}

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

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

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

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

public toExact(format: object = { groupSeparator: '' }): string {
toExact(format: object = { groupSeparator: '' }): string {
Big.DP = this.token.decimals
return new Big(this.numerator.toString()).div(this.denominator.toString()).toFormat(format)
}
Expand Down
1 change: 1 addition & 0 deletions src/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './token'
export * from './exchange'
export * from './route'
export * from './trade'

export * from './fractions'
12 changes: 8 additions & 4 deletions src/entities/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ERC20 from '../abis/ERC20.json'
import { validateAndParseAddress, validateSolidityTypeInstance } from '../utils'

let CACHE: { [chainId: number]: { [address: string]: number } } = {
1: {
[ChainId.MAINNET]: {
'0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A': 9 // DGD
}
}
Expand Down Expand Up @@ -54,13 +54,17 @@ export class Token {
if (typeof name === 'string') this.name = name
}

public equals(other: Token): boolean {
equals(other: Token): boolean {
const equal = this.chainId === other.chainId && this.address === other.address
if (equal) invariant(this.decimals === other.decimals, 'DECIMALS')
if (equal) {
invariant(this.decimals === other.decimals, 'DECIMALS')
if (this.symbol && other.symbol) invariant(this.symbol === other.symbol, 'SYMBOL')
if (this.name && other.name) invariant(this.name === other.name, 'NAME')
}
return equal
}

public sortsBefore(other: Token): boolean {
sortsBefore(other: Token): boolean {
invariant(this.chainId === other.chainId, 'CHAIN_IDS')
invariant(this.address !== other.address, 'ADDRESSES')
return this.address.toLowerCase() < other.address.toLowerCase()
Expand Down
Loading

0 comments on commit 05a05c5

Please sign in to comment.