diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..281833a6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +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 + +# Commenting out for now as it seems there's no need to copy when using bind mount +# COPY ./ . + +CMD ../binary-linux-22 ${ARGS} \ No newline at end of file diff --git a/package.json b/package.json index 14867ded..17e6b4d8 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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" diff --git a/src/utils/index.ts b/src/utils/index.ts index 69017d27..c9ba3372 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -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 { diff --git a/src/utils/staticTokenDefinition.ts b/src/utils/staticTokenDefinition.ts index e2662764..f7be3000 100644 --- a/src/utils/staticTokenDefinition.ts +++ b/src/utils/staticTokenDefinition.ts @@ -1,4 +1,4 @@ -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 { @@ -14,38 +14,38 @@ export class StaticTokenDefinition { address: Address.fromString('0xe0b7927c4af23765cb51314a0e0521a9645f0e2a'), symbol: 'DGD', name: 'DGD', - decimals: BigInt.fromI32(9), + decimals: BigInt.fromI32(9) }, { address: Address.fromString('0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9'), symbol: 'AAVE', name: 'Aave Token', - decimals: BigInt.fromI32(18), + decimals: BigInt.fromI32(18) }, { address: Address.fromString('0xeb9951021698b42e4399f9cbb6267aa35f82d59d'), symbol: 'LIF', name: 'Lif', - decimals: BigInt.fromI32(18), + decimals: BigInt.fromI32(18) }, { address: Address.fromString('0xbdeb4b83251fb146687fa19d1c660f99411eefe3'), symbol: 'SVD', name: 'savedroid', - decimals: BigInt.fromI32(18), + decimals: BigInt.fromI32(18) }, { address: Address.fromString('0xbb9bc244d798123fde783fcc1c72d3bb8c189413'), symbol: 'TheDAO', name: 'TheDAO', - decimals: BigInt.fromI32(16), + decimals: BigInt.fromI32(16) }, { address: Address.fromString('0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2'), symbol: 'HPB', name: 'HPBCoin', - decimals: BigInt.fromI32(18), - }, + decimals: BigInt.fromI32(18) + } ] return staticDefinitions } @@ -67,3 +67,60 @@ export class StaticTokenDefinition { return null } } + +export const getStaticDefinition = ( + tokenAddress: Address, + staticDefinitions: Array +): 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 + } + } + + // If not found, return null + return null +} + +export const STATIC_TOKEN_DEFINITIONS: Array = [ + { + 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) + } +] diff --git a/src/utils/token.ts b/src/utils/token.ts index bacfdac8..9bb131e1 100644 --- a/src/utils/token.ts +++ b/src/utils/token.ts @@ -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) @@ -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 } @@ -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) @@ -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 } @@ -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() @@ -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 } diff --git a/tests/.bin/handlemint.wasm b/tests/.bin/handlemint.wasm new file mode 100644 index 00000000..c3860fd0 Binary files /dev/null and b/tests/.bin/handlemint.wasm differ diff --git a/tests/.bin/handlenewpool.wasm b/tests/.bin/handlenewpool.wasm new file mode 100644 index 00000000..3b5b75b2 Binary files /dev/null and b/tests/.bin/handlenewpool.wasm differ diff --git a/tests/.bin/handlepoolcreated.wasm b/tests/.bin/handlepoolcreated.wasm new file mode 100644 index 00000000..ff3f7e88 Binary files /dev/null and b/tests/.bin/handlepoolcreated.wasm differ diff --git a/tests/.docker/Dockerfile b/tests/.docker/Dockerfile new file mode 100644 index 00000000..e13a03c3 --- /dev/null +++ b/tests/.docker/Dockerfile @@ -0,0 +1,22 @@ +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 + +# Commenting out for now as it seems there's no need to copy when using bind mount +# COPY ./ . + +CMD ../binary-linux-22 ${ARGS} diff --git a/tests/.latest.json b/tests/.latest.json new file mode 100644 index 00000000..3aa15d03 --- /dev/null +++ b/tests/.latest.json @@ -0,0 +1,4 @@ +{ + "version": "0.6.0", + "timestamp": 1716213533842 +} \ No newline at end of file diff --git a/tests/handleMint.test.ts b/tests/handleMint.test.ts new file mode 100644 index 00000000..e85b34cd --- /dev/null +++ b/tests/handleMint.test.ts @@ -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', []) + }) +}) diff --git a/tests/handlePoolCreated.test.ts b/tests/handlePoolCreated.test.ts new file mode 100644 index 00000000..af4054ab --- /dev/null +++ b/tests/handlePoolCreated.test.ts @@ -0,0 +1,280 @@ +import { Address, BigInt, Bytes, ethereum, log } from '@graphprotocol/graph-ts' +import { assert, test, newMockEvent, dataSourceMock, createMockedFunction } from 'matchstick-as/assembly/index' +import { describe, test } from 'matchstick-as/assembly/index' +import { PoolCreated } from '../src/types/Factory/Factory' +import { handlePoolCreated } from '../src/mappings/factory' +import { FACTORY_ADDRESS } from '../src/utils/constants' +import { fetchTokenDecimals, fetchTokenName, fetchTokenSymbol, fetchTokenTotalSupply } from '../src/utils/token' +import { StaticTokenDefinition } from '../src/utils/staticTokenDefinition' +import { NULL_ETH_HEX_STRING } from '../src/utils' + +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('handlePoolCreated', () => { + test('success - create a pool', () => { + 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) + + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'poolCount', '1') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'totalVolumeETH', '0') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'totalVolumeUSD', '0') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'untrackedVolumeUSD', '0') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'totalFeesUSD', '0') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'totalFeesETH', '0') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'totalValueLockedETH', '0') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'totalValueLockedUSD', '0') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'totalValueLockedETHUntracked', '0') + assert.fieldEquals('Factory', FACTORY_ADDRESS, 'totalValueLockedUSDUntracked', '0') + + assert.fieldEquals('Bundle', '1', 'ethPriceUSD', '0') + + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'symbol', 'USDC') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'name', 'USD Coin') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'totalSupply', '300') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'decimals', '6') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'derivedETH', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'volume', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'volumeUSD', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'feesUSD', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'untrackedVolumeUSD', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'totalValueLocked', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'totalValueLockedUSD', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'totalValueLockedUSDUntracked', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'txCount', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'poolCount', '0') + assert.fieldEquals('Token', USDC_MAINNET_ADDRESS, 'whitelistPools', `[${USDC_WETH_03_MAINNET_POOL}]`) + + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'symbol', 'WETH') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'name', 'Wrapped Ether') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'totalSupply', '100') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'decimals', '18') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'derivedETH', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'volume', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'volumeUSD', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'feesUSD', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'untrackedVolumeUSD', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'totalValueLocked', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'totalValueLockedUSD', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'totalValueLockedUSDUntracked', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'txCount', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'poolCount', '0') + assert.fieldEquals('Token', WETH_MAINNET_ADDRESS, 'whitelistPools', `[${USDC_WETH_03_MAINNET_POOL}]`) + + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'token0', USDC_MAINNET_ADDRESS) + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'token1', WETH_MAINNET_ADDRESS) + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'feeTier', POOL_FEE_TIER_03.toString()) + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'createdAtTimestamp', mockEvent.block.timestamp.toString()) + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'createdAtBlockNumber', mockEvent.block.number.toString()) + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'liquidityProviderCount', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'txCount', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'sqrtPrice', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'token0Price', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'token1Price', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'observationIndex', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'totalValueLockedToken0', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'totalValueLockedToken1', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'totalValueLockedUSD', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'totalValueLockedETH', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'totalValueLockedUSDUntracked', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'volumeToken0', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'volumeToken1', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'volumeUSD', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'feesUSD', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'untrackedVolumeUSD', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'collectedFeesToken0', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'collectedFeesToken1', '0') + assert.fieldEquals('Pool', USDC_WETH_03_MAINNET_POOL, 'collectedFeesUSD', '0') + }) + + describe('fetchTokenSymbol', () => { + test('success - fetch token symbol', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'symbol', 'symbol():(string)').returns([ethereum.Value.fromString('USDC')]) + const symbol = fetchTokenSymbol(usdcAddress) + assert.stringEquals(symbol, 'USDC') + }) + + test('success - fetch token symbol falls back to bytes call', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'symbol', 'symbol():(string)').reverts() + createMockedFunction(usdcAddress, 'symbol', 'symbol():(bytes32)').returns([ + ethereum.Value.fromBytes(Bytes.fromUTF8('USDC')) + ]) + const symbol = fetchTokenSymbol(usdcAddress) + assert.stringEquals(symbol, 'USDC') + }) + + test('success - fetch token symbol falls back to static definition', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'symbol', 'symbol():(string)').reverts() + createMockedFunction(usdcAddress, 'symbol', 'symbol():(bytes32)').returns([ + ethereum.Value.fromBytes(Bytes.fromHexString(NULL_ETH_HEX_STRING)) + ]) + const staticDefinitions: Array = [ + { + address: Address.fromString(USDC_MAINNET_ADDRESS), + symbol: 'USDC', + name: 'USD Coin', + decimals: BigInt.fromI32(6) + } + ] + const symbol = fetchTokenSymbol(usdcAddress, staticDefinitions) + assert.stringEquals(symbol, 'USDC') + }) + + test('failure - fetch token symbol reverts', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'symbol', 'symbol():(string)').reverts() + createMockedFunction(usdcAddress, 'symbol', 'symbol():(bytes32)').reverts() + const symbol = fetchTokenSymbol(usdcAddress) + assert.stringEquals(symbol, 'unknown') + }) + }) + + describe('fetchTokenName', () => { + test('success - fetch token name', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'name', 'name():(string)').returns([ethereum.Value.fromString('USD Coin')]) + const name = fetchTokenName(usdcAddress) + assert.stringEquals(name, 'USD Coin') + }) + + test('success - fetch token name falls back to bytes call', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'name', 'name():(string)').reverts() + createMockedFunction(usdcAddress, 'name', 'name():(bytes32)').returns([ + ethereum.Value.fromBytes(Bytes.fromUTF8('USD Coin')) + ]) + const name = fetchTokenName(usdcAddress) + assert.stringEquals(name, 'USD Coin') + }) + + test('success - fetch token name falls back to static definition', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'name', 'name():(string)').reverts() + createMockedFunction(usdcAddress, 'name', 'name():(bytes32)').returns([ + ethereum.Value.fromBytes(Bytes.fromHexString(NULL_ETH_HEX_STRING)) + ]) + const staticDefinitions: Array = [ + { + address: Address.fromString(USDC_MAINNET_ADDRESS), + symbol: 'USDC', + name: 'USD Coin', + decimals: BigInt.fromI32(6) + } + ] + const name = fetchTokenName(usdcAddress, staticDefinitions) + assert.stringEquals(name, 'USD Coin') + }) + + test('failure - fetch token name reverts', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'name', 'name():(string)').reverts() + createMockedFunction(usdcAddress, 'name', 'name():(bytes32)').reverts() + const name = fetchTokenName(usdcAddress) + assert.stringEquals(name, 'unknown') + }) + }) + + describe('fetchTokenTotalSupply', () => { + test('success - fetch token total supply', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'totalSupply', 'totalSupply():(uint256)').returns([ + ethereum.Value.fromUnsignedBigInt(BigInt.fromString('300')) + ]) + const totalSupply = fetchTokenTotalSupply(usdcAddress) + assert.bigIntEquals(totalSupply, BigInt.fromString('300')) + }) + + test('failure - fetch token total supply reverts', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'totalSupply', 'totalSupply():(uint256)').reverts() + const totalSupply = fetchTokenTotalSupply(usdcAddress) + assert.bigIntEquals(totalSupply, BigInt.zero()) + }) + }) + + describe('fetchTokenDecimals', () => { + test('success - fetch token decimals', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'decimals', 'decimals():(uint32)').returns([ + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(6)) + ]) + const decimals = fetchTokenDecimals(usdcAddress) + assert.assertTrue(decimals == BigInt.fromI32(6)) + }) + + test('success - fetch token decimals falls back to static definition', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'decimals', 'decimals():(uint32)').reverts() + const staticDefinitions: Array = [ + { + address: Address.fromString(USDC_MAINNET_ADDRESS), + symbol: 'USDC', + name: 'USD Coin', + decimals: BigInt.fromI32(6) + } + ] + const decimals = fetchTokenDecimals(usdcAddress, staticDefinitions) + assert.assertTrue(decimals == BigInt.fromI32(6)) + }) + + test('failure - fetch token decimals reverts', () => { + const usdcAddress = Address.fromString(USDC_MAINNET_ADDRESS) + createMockedFunction(usdcAddress, 'decimals', 'decimals():(uint32)').reverts() + const decimals: BigInt | null = fetchTokenDecimals(usdcAddress) + assert.assertTrue(decimals === null) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index b4833d2d..2e146812 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3768,6 +3768,13 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +matchstick-as@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/matchstick-as/-/matchstick-as-0.6.0.tgz#c65296b1f51b1014d605c52067d9b5321ea630e8" + integrity sha512-E36fWsC1AbCkBFt05VsDDRoFvGSdcZg6oZJrtIe/YDBbuFh8SKbR5FcoqDhNWqSN+F7bN/iS2u8Md0SM+4pUpw== + dependencies: + wabt "1.0.24" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -5431,6 +5438,11 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +wabt@1.0.24: + version "1.0.24" + resolved "https://registry.yarnpkg.com/wabt/-/wabt-1.0.24.tgz#c02e0b5b4503b94feaf4a30a426ef01c1bea7c6c" + integrity sha512-8l7sIOd3i5GWfTWciPL0+ff/FK/deVK2Q6FN+MPz4vfUcD78i2M/49XJTwF6aml91uIiuXJEsLKWMB2cw/mtKg== + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"