Skip to content

Commit

Permalink
add subgraph unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mzywang committed May 20, 2024
1 parent b37b491 commit 3ca291f
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 66 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ build/
node_modules/
src/types/
.DS_STORE
yarn-error.log
yarn-error.log
tests/.bin/
tests/.docker/
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}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"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",
"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 +26,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
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
111 changes: 54 additions & 57 deletions src/utils/staticTokenDefinition.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,66 @@
import { Address, BigInt } from '@graphprotocol/graph-ts'
import { Address, BigInt, log } from '@graphprotocol/graph-ts'

// Initialize a Token Definition with the attributes
export class StaticTokenDefinition {
address: Address
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)
}
]
24 changes: 17 additions & 7 deletions src/utils/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ 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 { StaticTokenDefinition, getStaticDefinition, STATIC_TOKEN_DEFINITIONS } from './staticTokenDefinition'
import { log } from 'matchstick-as'

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 +25,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 +38,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 +56,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 +79,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 +93,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
4 changes: 4 additions & 0 deletions tests/.latest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"version": "0.6.0",
"timestamp": 1716213533842
}
70 changes: 70 additions & 0 deletions tests/handleMint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Address, BigInt, ethereum } from '@graphprotocol/graph-ts'
import { assert, beforeAll, createMockedFunction, describe, log, newMockEvent, test } from 'matchstick-as'
import { PoolCreated } from '../src/types/Factory/Factory'
import { handlePoolCreated } from '../src/mappings/factory'
import { FACTORY_ADDRESS } from '../src/utils/constants'

const USDC_MAINNET_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
const WETH_MAINNET_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
const USDC_WETH_03_MAINNET_POOL = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8'
const POOL_FEE_TIER_03 = 3000
const POOL_TICK_SPACING_03 = 60

describe('handleMint', () => {
beforeAll(() => {
// todo: dedupe this with handlePoolCreated
const mockEvent = newMockEvent()
const token0Address = Address.fromString(USDC_MAINNET_ADDRESS)
const token1Address = Address.fromString(WETH_MAINNET_ADDRESS)
const poolAddress = Address.fromString(USDC_WETH_03_MAINNET_POOL)
const parameters = [
new ethereum.EventParam('token0', ethereum.Value.fromAddress(token0Address)),
new ethereum.EventParam('token1', ethereum.Value.fromAddress(token1Address)),
new ethereum.EventParam('fee', ethereum.Value.fromI32(POOL_FEE_TIER_03)),
new ethereum.EventParam('tickSpacing', ethereum.Value.fromI32(POOL_TICK_SPACING_03)),
new ethereum.EventParam('pool', ethereum.Value.fromAddress(poolAddress))
]

const poolCreatedEvent = new PoolCreated(
mockEvent.address,
mockEvent.logIndex,
mockEvent.transactionLogIndex,
mockEvent.logType,
mockEvent.block,
mockEvent.transaction,
parameters,
mockEvent.receipt
)

// create mock contract calls for token0
createMockedFunction(token0Address, 'symbol', 'symbol():(string)').returns([ethereum.Value.fromString('USDC')])
createMockedFunction(token0Address, 'name', 'name():(string)').returns([ethereum.Value.fromString('USD Coin')])
createMockedFunction(token0Address, 'totalSupply', 'totalSupply():(uint256)').returns([
ethereum.Value.fromUnsignedBigInt(BigInt.fromString('300'))
])
createMockedFunction(token0Address, 'decimals', 'decimals():(uint32)').returns([
ethereum.Value.fromUnsignedBigInt(BigInt.fromString('6'))
])

// create mock contract calls for token1
createMockedFunction(token1Address, 'symbol', 'symbol():(string)').returns([ethereum.Value.fromString('WETH')])
createMockedFunction(token1Address, 'name', 'name():(string)').returns([ethereum.Value.fromString('Wrapped Ether')])
createMockedFunction(token1Address, 'totalSupply', 'totalSupply():(uint256)').returns([
ethereum.Value.fromUnsignedBigInt(BigInt.fromString('100'))
])
createMockedFunction(token1Address, 'decimals', 'decimals():(uint32)').returns([
ethereum.Value.fromUnsignedBigInt(BigInt.fromString('18'))
])

assert.notInStore('Factory', FACTORY_ADDRESS)
assert.notInStore('Pool', USDC_WETH_03_MAINNET_POOL)
assert.notInStore('Token', USDC_MAINNET_ADDRESS)
assert.notInStore('Token', WETH_MAINNET_ADDRESS)

handlePoolCreated(poolCreatedEvent)
})

test('success - mint event', () => {
log.success('mint event success', [])
})
})
Loading

0 comments on commit 3ca291f

Please sign in to comment.