Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add unit tests for handlePoolCreated #227

Merged
merged 5 commits into from
May 29, 2024
Merged
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
15 changes: 14 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,18 @@ module.exports = {
}
}
]
}
},
overrides: [
{
files: ['tests/**/*.ts'],
settings: {
jest: {
version: 26
},
// jest is added as a plugin in our org's eslint config, but we use
// matchstick, and this would crash when linting matchstick files.
'disable/plugins': ['jest']
}
}
]
}
11 changes: 9 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ jobs:
git commit -a -m "fix(lint): auto-fix [ci]"
git push

build:
build-and-test:
needs: lint
name: build
name: build-and-test
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -84,3 +84,10 @@ jobs:

- name: Build project
run: yarn build

- name: Build Docker
run: yarn build:docker

- name: Test
# We need to run test in this mode because github CI is non-interactive
run: yarn test:no-tty
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ build/
node_modules/
src/types/
.DS_STORE
yarn-error.log
yarn-error.log
tests/.bin/
tests/.docker/
tests/.latest.json
21 changes: 21 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# copied from https://github.com/LimeChain/demo-subgraph/blob/main/Dockerfile

FROM --platform=linux/x86_64 ubuntu:22.04

ARG DEBIAN_FRONTEND=noninteractive

ENV ARGS=""

RUN apt update \
&& apt install -y sudo curl postgresql postgresql-contrib

RUN curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - \
&& sudo apt-get install -y nodejs

RUN curl -OL https://github.com/LimeChain/matchstick/releases/download/0.6.0/binary-linux-22 \
&& chmod a+x binary-linux-22

RUN mkdir matchstick
WORKDIR /matchstick

CMD ../binary-linux-22 ${ARGS}
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# Uniswap V3 Subgraph

### Subgraph Endpoint
### Subgraph Endpoint

Synced at: https://thegraph.com/hosted-service/subgraph/ianlapham/uniswap-v3-subgraph?selected=playground

Pending Changes at same URL

### Running Unit Tests

1. Install [Docker](https://docs.docker.com/get-docker/) if you don't have it already
2. Install postgres: `brew install postgresql`
3. `yarn run build:docker`
4. `yarn run test`
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
"scripts": {
"lint": "eslint . --ext .ts --fix",
"build": "run-s codegen && graph build",
"build:docker": "docker build -t matchstick .",
"buildonly": "graph build",
"deploy:alchemy": "graph deploy --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz",
"codegen": "graph codegen --output-dir src/types/",
"test": "graph test -d",
"test:no-tty": "docker run -i --rm --mount type=bind,source=$INIT_CWD,target=/matchstick matchstick",
"create-local": "graph create ianlapham/uniswap-v3 --node http://127.0.0.1:8020",
"deploy-local": "graph deploy ianlapham/uniswap-v3 --debug --ipfs http://localhost:5001 --node http://127.0.0.1:8020",
"deploy": "graph deploy ianlapham/uniswap-v3-subgraph --ipfs https://api.thegraph.com/ipfs/ --node https://api.thegraph.com/deploy/ --debug",
Expand All @@ -24,6 +27,7 @@
"@uniswap/eslint-config": "^1.2.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^6.1.0",
"matchstick-as": "^0.6.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.18.2",
"typescript": "^3.5.2"
Expand Down
27 changes: 20 additions & 7 deletions src/mappings/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,33 @@ import { PoolCreated } from '../types/Factory/Factory'
import { Factory } from '../types/schema'
import { Bundle, Pool, Token } from '../types/schema'
import { Pool as PoolTemplate } from '../types/templates'
import { STATIC_TOKEN_DEFINITIONS, StaticTokenDefinition } from '../utils/staticTokenDefinition'
import { fetchTokenDecimals, fetchTokenName, fetchTokenSymbol, fetchTokenTotalSupply } from '../utils/token'
import { ADDRESS_ZERO, FACTORY_ADDRESS, ONE_BI, ZERO_BD, ZERO_BI } from './../utils/constants'
import { WHITELIST_TOKENS } from './../utils/pricing'

// The subgraph handler must have this signature to be able to handle events,
// however, we invoke a helper in order to inject dependencies for unit tests.
export function handlePoolCreated(event: PoolCreated): void {
handlePoolCreatedHelper(event)
}

// Exported for unit tests
export function handlePoolCreatedHelper(
event: PoolCreated,
factoryAddress: string = FACTORY_ADDRESS,
whitelistTokens: string[] = WHITELIST_TOKENS,
staticTokenDefinitions: StaticTokenDefinition[] = STATIC_TOKEN_DEFINITIONS
): void {
// temp fix
if (event.params.pool == Address.fromHexString('0x8fe8d9bb8eeba3ed688069c3d6b556c9ca258248')) {
return
}

// load factory
let factory = Factory.load(FACTORY_ADDRESS)
let factory = Factory.load(factoryAddress)
if (factory === null) {
factory = new Factory(FACTORY_ADDRESS)
factory = new Factory(factoryAddress)
factory.poolCount = ZERO_BI
factory.totalVolumeETH = ZERO_BD
factory.totalVolumeUSD = ZERO_BD
Expand Down Expand Up @@ -46,10 +59,10 @@ export function handlePoolCreated(event: PoolCreated): void {
// fetch info if null
if (token0 === null) {
token0 = new Token(event.params.token0.toHexString())
token0.symbol = fetchTokenSymbol(event.params.token0)
token0.name = fetchTokenName(event.params.token0)
token0.symbol = fetchTokenSymbol(event.params.token0, staticTokenDefinitions)
token0.name = fetchTokenName(event.params.token0, staticTokenDefinitions)
token0.totalSupply = fetchTokenTotalSupply(event.params.token0)
const decimals = fetchTokenDecimals(event.params.token0)
const decimals = fetchTokenDecimals(event.params.token0, staticTokenDefinitions)

// bail if we couldn't figure out the decimals
if (decimals === null) {
Expand Down Expand Up @@ -97,12 +110,12 @@ export function handlePoolCreated(event: PoolCreated): void {
}

// update white listed pools
if (WHITELIST_TOKENS.includes(token0.id)) {
if (whitelistTokens.includes(token0.id)) {
const newPools = token1.whitelistPools
newPools.push(pool.id)
token1.whitelistPools = newPools
}
if (WHITELIST_TOKENS.includes(token1.id)) {
if (whitelistTokens.includes(token1.id)) {
const newPools = token0.whitelistPools
newPools.push(pool.id)
token0.whitelistPools = newPools
Expand Down
4 changes: 3 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ export function equalToZero(value: BigDecimal): boolean {
return false
}

export const NULL_ETH_HEX_STRING = '0x0000000000000000000000000000000000000000000000000000000000000001'

export function isNullEthValue(value: string): boolean {
return value == '0x0000000000000000000000000000000000000000000000000000000000000001'
return value == NULL_ETH_HEX_STRING
}

export function bigDecimalExp18(): BigDecimal {
Expand Down
109 changes: 53 additions & 56 deletions src/utils/staticTokenDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,61 @@ export class StaticTokenDefinition {
symbol: string
name: string
decimals: BigInt
}

// Get all tokens with a static defintion
static getStaticDefinitions(): Array<StaticTokenDefinition> {
const staticDefinitions: Array<StaticTokenDefinition> = [
{
address: Address.fromString('0xe0b7927c4af23765cb51314a0e0521a9645f0e2a'),
symbol: 'DGD',
name: 'DGD',
decimals: BigInt.fromI32(9),
},
{
address: Address.fromString('0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9'),
symbol: 'AAVE',
name: 'Aave Token',
decimals: BigInt.fromI32(18),
},
{
address: Address.fromString('0xeb9951021698b42e4399f9cbb6267aa35f82d59d'),
symbol: 'LIF',
name: 'Lif',
decimals: BigInt.fromI32(18),
},
{
address: Address.fromString('0xbdeb4b83251fb146687fa19d1c660f99411eefe3'),
symbol: 'SVD',
name: 'savedroid',
decimals: BigInt.fromI32(18),
},
{
address: Address.fromString('0xbb9bc244d798123fde783fcc1c72d3bb8c189413'),
symbol: 'TheDAO',
name: 'TheDAO',
decimals: BigInt.fromI32(16),
},
{
address: Address.fromString('0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2'),
symbol: 'HPB',
name: 'HPBCoin',
decimals: BigInt.fromI32(18),
},
]
return staticDefinitions
}

// Helper for hardcoded tokens
static fromAddress(tokenAddress: Address): StaticTokenDefinition | null {
const staticDefinitions = this.getStaticDefinitions()
const tokenAddressHex = tokenAddress.toHexString()
export const getStaticDefinition = (
tokenAddress: Address,
staticDefinitions: Array<StaticTokenDefinition>
): StaticTokenDefinition | null => {
const tokenAddressHex = tokenAddress.toHexString()

// Search the definition using the address
for (let i = 0; i < staticDefinitions.length; i++) {
const staticDefinition = staticDefinitions[i]
if (staticDefinition.address.toHexString() == tokenAddressHex) {
return staticDefinition
}
// Search the definition using the address
for (let i = 0; i < staticDefinitions.length; i++) {
const staticDefinition = staticDefinitions[i]
if (staticDefinition.address.toHexString() == tokenAddressHex) {
return staticDefinition
}

// If not found, return null
return null
}

// If not found, return null
return null
}

export const STATIC_TOKEN_DEFINITIONS: Array<StaticTokenDefinition> = [
{
address: Address.fromString('0xe0b7927c4af23765cb51314a0e0521a9645f0e2a'),
symbol: 'DGD',
name: 'DGD',
decimals: BigInt.fromI32(9),
},
{
address: Address.fromString('0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9'),
symbol: 'AAVE',
name: 'Aave Token',
decimals: BigInt.fromI32(18),
},
{
address: Address.fromString('0xeb9951021698b42e4399f9cbb6267aa35f82d59d'),
symbol: 'LIF',
name: 'Lif',
decimals: BigInt.fromI32(18),
},
{
address: Address.fromString('0xbdeb4b83251fb146687fa19d1c660f99411eefe3'),
symbol: 'SVD',
name: 'savedroid',
decimals: BigInt.fromI32(18),
},
{
address: Address.fromString('0xbb9bc244d798123fde783fcc1c72d3bb8c189413'),
symbol: 'TheDAO',
name: 'TheDAO',
decimals: BigInt.fromI32(16),
},
{
address: Address.fromString('0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2'),
symbol: 'HPB',
name: 'HPBCoin',
decimals: BigInt.fromI32(18),
},
]
23 changes: 16 additions & 7 deletions src/utils/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { ERC20 } from '../types/Factory/ERC20'
import { ERC20NameBytes } from '../types/Factory/ERC20NameBytes'
import { ERC20SymbolBytes } from '../types/Factory/ERC20SymbolBytes'
import { isNullEthValue } from '.'
import { StaticTokenDefinition } from './staticTokenDefinition'
import { getStaticDefinition, STATIC_TOKEN_DEFINITIONS, StaticTokenDefinition } from './staticTokenDefinition'

export function fetchTokenSymbol(tokenAddress: Address): string {
export function fetchTokenSymbol(
tokenAddress: Address,
staticTokenDefinitions: StaticTokenDefinition[] = STATIC_TOKEN_DEFINITIONS
): string {
const contract = ERC20.bind(tokenAddress)
const contractSymbolBytes = ERC20SymbolBytes.bind(tokenAddress)

Expand All @@ -21,7 +24,7 @@ export function fetchTokenSymbol(tokenAddress: Address): string {
symbolValue = symbolResultBytes.value.toString()
} else {
// try with the static definition
const staticTokenDefinition = StaticTokenDefinition.fromAddress(tokenAddress)
const staticTokenDefinition = getStaticDefinition(tokenAddress, staticTokenDefinitions)
if (staticTokenDefinition != null) {
symbolValue = staticTokenDefinition.symbol
}
Expand All @@ -34,7 +37,10 @@ export function fetchTokenSymbol(tokenAddress: Address): string {
return symbolValue
}

export function fetchTokenName(tokenAddress: Address): string {
export function fetchTokenName(
tokenAddress: Address,
staticTokenDefinitions: StaticTokenDefinition[] = STATIC_TOKEN_DEFINITIONS
): string {
const contract = ERC20.bind(tokenAddress)
const contractNameBytes = ERC20NameBytes.bind(tokenAddress)

Expand All @@ -49,7 +55,7 @@ export function fetchTokenName(tokenAddress: Address): string {
nameValue = nameResultBytes.value.toString()
} else {
// try with the static definition
const staticTokenDefinition = StaticTokenDefinition.fromAddress(tokenAddress)
const staticTokenDefinition = getStaticDefinition(tokenAddress, staticTokenDefinitions)
if (staticTokenDefinition != null) {
nameValue = staticTokenDefinition.name
}
Expand All @@ -72,7 +78,10 @@ export function fetchTokenTotalSupply(tokenAddress: Address): BigInt {
return totalSupplyValue
}

export function fetchTokenDecimals(tokenAddress: Address): BigInt | null {
export function fetchTokenDecimals(
tokenAddress: Address,
staticTokenDefinitions: StaticTokenDefinition[] = STATIC_TOKEN_DEFINITIONS
): BigInt | null {
const contract = ERC20.bind(tokenAddress)
// try types uint8 for decimals
const decimalResult = contract.try_decimals()
Expand All @@ -83,7 +92,7 @@ export function fetchTokenDecimals(tokenAddress: Address): BigInt | null {
}
} else {
// try with the static definition
const staticTokenDefinition = StaticTokenDefinition.fromAddress(tokenAddress)
const staticTokenDefinition = getStaticDefinition(tokenAddress, staticTokenDefinitions)
if (staticTokenDefinition) {
return staticTokenDefinition.decimals
}
Expand Down
Loading