Commit 8a7e75b9 authored by Noah Zinsmeister's avatar Noah Zinsmeister

add create2 address computation w/ cache

add sort order to token
parent 7a01bdd6
[
{
"constant": true,
"inputs": [
{
"name": "tokenA",
"type": "address"
},
{
"name": "tokenB",
"type": "address"
}
],
"name": "getExchange",
"outputs": [
{
"name": "exchange",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
......@@ -17,6 +17,8 @@ export const FACTORY_ADDRESS = {
[ChainId.KOVAN]: ''
}
export const INIT_CODE_HASH = '0xa447d0eea6a2235380af6f64c8be6e6c410241c4ade4dfaa99cebfde475ed036'
export enum TradeType {
EXACT_INPUT,
EXACT_OUTPUT
......
import invariant from 'tiny-invariant'
import JSBI from 'jsbi'
import { getCreate2Address } from '@ethersproject/address'
import { keccak256 } from '@ethersproject/keccak256'
import { getNetwork } from '@ethersproject/networks'
import { getDefaultProvider } from '@ethersproject/providers'
import { Contract } from '@ethersproject/contracts'
import { FACTORY_ADDRESS, ZERO, ONE, _997, _1000 } from '../constants'
import UniswapV2Factory from '../abis/UniswapV2Factory.json'
import { FACTORY_ADDRESS, INIT_CODE_HASH, ZERO, ONE, _997, _1000 } from '../constants'
import ERC20 from '../abis/ERC20.json'
import { validateAndParseAddress } from '../utils'
import { Token } from './token'
import { TokenAmount } from './fractions/tokenAmount'
let CACHE: { [token0Address: string]: { [token1Address: string]: string } } = {}
export class Exchange {
public readonly address: string
private readonly tokenAmounts: [TokenAmount, TokenAmount]
public readonly address?: string
static getAddress(tokenA: Token, tokenB: Token): string {
// performs the requisite safety checks
const tokens: [Token, Token] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]
if (CACHE?.[tokens[0].address]?.[tokens[1].address] === undefined) {
CACHE = {
...CACHE,
[tokens[0].address]: {
...CACHE?.[tokens[0].address],
[tokens[1].address]: getCreate2Address(
FACTORY_ADDRESS[tokens[0].chainId],
keccak256(`${tokens[0].address.toLowerCase()}${tokens[1].address.slice(2).toLowerCase()}`),
INIT_CODE_HASH
)
}
}
}
return CACHE[tokens[0].address][tokens[1].address]
}
static async fetchData(
tokenA: Token,
tokenB: Token,
provider = getDefaultProvider(getNetwork(tokenA.chainId)),
address?: string
provider = getDefaultProvider(getNetwork(tokenA.chainId))
): Promise<Exchange> {
const parsedAddress =
typeof address === 'string'
? address
: await new Contract(FACTORY_ADDRESS[tokenA.chainId], UniswapV2Factory, provider).getExchange(
tokenA.address,
tokenB.address
)
const exchangeAddress = Exchange.getAddress(tokenA, tokenB)
const balances = await Promise.all([
new Contract(tokenA.address, ERC20, provider).balanceOf(parsedAddress),
new Contract(tokenB.address, ERC20, provider).balanceOf(parsedAddress)
new Contract(tokenA.address, ERC20, provider).balanceOf(exchangeAddress),
new Contract(tokenB.address, ERC20, provider).balanceOf(exchangeAddress)
])
return new Exchange(new TokenAmount(tokenA, balances[0]), new TokenAmount(tokenB, balances[1]), parsedAddress)
return new Exchange(new TokenAmount(tokenA, balances[0]), new TokenAmount(tokenB, balances[1]))
}
constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount, address?: string) {
invariant(tokenAmountA.token.chainId === tokenAmountB.token.chainId, 'CHAIN_IDS')
const tokenAmounts: [TokenAmount, TokenAmount] =
tokenAmountA.token.address < tokenAmountB.token.address
? [tokenAmountA, tokenAmountB]
: [tokenAmountB, tokenAmountA]
invariant(tokenAmounts[0].token.address < tokenAmounts[1].token.address, 'ADDRESSES')
constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount) {
// performs the requisite safety checks
const tokenAmounts: [TokenAmount, TokenAmount] = tokenAmountA.token.sortsBefore(tokenAmountB.token)
? [tokenAmountA, tokenAmountB]
: [tokenAmountB, tokenAmountA]
this.address = Exchange.getAddress(tokenAmounts[0].token, tokenAmounts[1].token)
this.tokenAmounts = tokenAmounts
if (typeof address === 'string') this.address = validateAndParseAddress(address)
}
public get reserve0(): TokenAmount {
......@@ -78,7 +91,7 @@ export class Exchange {
inputAmount.token.equals(this.token0) ? this.token1 : this.token0,
JSBI.divide(numerator, denominator)
)
return [output, new Exchange(inputReserve.add(inputAmount), outputReserve.subtract(output), this.address)]
return [output, new Exchange(inputReserve.add(inputAmount), outputReserve.subtract(output))]
}
public getInputAmount(outputAmount: TokenAmount): [TokenAmount, Exchange] {
......@@ -95,6 +108,6 @@ export class Exchange {
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), this.address)]
return [input, new Exchange(inputReserve.add(input), outputReserve.subtract(outputAmount))]
}
}
......@@ -8,7 +8,7 @@ import { ChainId, SolidityType } from '../constants'
import ERC20 from '../abis/ERC20.json'
import { validateAndParseAddress, validateSolidityTypeInstance } from '../utils'
const CACHE: { [chainId: number]: { [address: string]: number } } = {
let CACHE: { [chainId: number]: { [address: string]: number } } = {
1: {
'0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A': 9 // DGD
}
......@@ -32,7 +32,13 @@ export class Token {
typeof CACHE?.[chainId]?.[address] === 'number'
? CACHE[chainId][address]
: await new Contract(address, ERC20, provider).decimals().then((decimals: number): number => {
CACHE[chainId][address] = decimals
CACHE = {
...CACHE,
[chainId]: {
...CACHE?.[chainId],
[address]: decimals
}
}
return decimals
})
return new Token(chainId, address, parsedDecimals, symbol, name)
......@@ -53,6 +59,12 @@ export class Token {
if (equal) invariant(this.decimals === other.decimals, 'DECIMALS')
return equal
}
public sortsBefore(other: Token): boolean {
invariant(this.chainId === other.chainId, 'CHAIN_IDS')
invariant(this.address !== other.address, 'ADDRESSES')
return this.address.toLowerCase() < other.address.toLowerCase()
}
}
export const WETH = {
......
......@@ -14,6 +14,6 @@ describe('data', () => {
it('Exchange', async () => {
const token = new Token(ChainId.RINKEBY, '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735', 18) // DAI
const exchange = await Exchange.fetchData(WETH[ChainId.RINKEBY], token)
expect(exchange.address).toEqual('0xC0568fA2FC63123B7352c506076DFa5623D62Db5')
expect(exchange.address).toEqual('0xCbDc281884F23dE2c829960b112F946329434678')
})
})
......@@ -877,7 +877,7 @@
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/keccak256@>=5.0.0-beta.127":
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@^5.0.0-beta.131":
version "5.0.0-beta.131"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.0-beta.131.tgz#b5778723ee75208065b9b9ad30c71d480f41bb31"
integrity sha512-KQnqMwGV0IMOjAr/UTFO8DuLrmN1uaMvcV3zh9hiXhh3rCuY+WXdeUh49w1VQ94kBKmaP0qfGb7z4SdhUWUHjw==
......@@ -890,13 +890,20 @@
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.0-beta.133.tgz#2d62d495ed413c7045054d4f99a0fb4920079b2e"
integrity sha512-1ISf7rFKFbMHlEB37JS7Oy3FgFlvzF2Ze2uFZMJHGKp9xgDvFy1VHNMBM1KrJPK4AqCZXww0//e2keLsN3g/Cw==
"@ethersproject/networks@>=5.0.0-beta.129", "@ethersproject/networks@^5.0.0-beta.134":
"@ethersproject/networks@>=5.0.0-beta.129":
version "5.0.0-beta.134"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.0-beta.134.tgz#934cb8d62c0dce123ef436bebc2578692e20c25d"
integrity sha512-96Gw2z27mFxcSNGD3ZqzFC4ssnrKkYSUHH8rqqyFBfz+WEWrP5Qc2gkBpMJ0QluVNOHr5MhptufvZXAgsGTuAw==
dependencies:
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/networks@^5.0.0-beta.135":
version "5.0.0-beta.135"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.0-beta.135.tgz#c4a19ea19e01ec84d8b6a4ae75994b7417b694fa"
integrity sha512-9wesbAlsewNe0dU8B/lhK449GZml+08Opjf6nFpcV8BwWlYnPLs7EnOsK3GpMM0KDPZlksVYltyq+X1B7YxOcA==
dependencies:
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties@>=5.0.0-beta.131":
version "5.0.0-beta.136"
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.0-beta.136.tgz#4834f6eeb4d66aa9d2bb4d8b7a8517077df3eb63"
......@@ -904,10 +911,10 @@
dependencies:
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/providers@^5.0.0-beta.151":
version "5.0.0-beta.151"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.0-beta.151.tgz#fb280b3f1475b9abd9e9e17e3d6db6a6e64c19bd"
integrity sha512-qlbxsyAQ4W/QK/fMynwzt/woO8JbZW/37iJpTS+JNK3FrMQoumsyCjhr4N8FREUHOei2vDuGBIsjN6ltbT8qvA==
"@ethersproject/providers@^5.0.0-beta.153":
version "5.0.0-beta.153"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.0-beta.153.tgz#a24923146870af50cdcfefbd4eeef77bfdda5507"
integrity sha512-FJ/dNM5fj25m1XAEJzdC1B6GM9On3eRn/Vls6cUcSBWTOfKsTRf0uRSkBq1kNbPdx3lF0zQT4xIVmCCklA+DaQ==
dependencies:
"@ethersproject/abstract-provider" ">=5.0.0-beta.131"
"@ethersproject/abstract-signer" ">=5.0.0-beta.132"
......
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