diff --git a/packages/graph-node/src/call-handler.test.ts b/packages/graph-node/src/call-handler.test.ts index 1040007e..f2f4b106 100644 --- a/packages/graph-node/src/call-handler.test.ts +++ b/packages/graph-node/src/call-handler.test.ts @@ -71,7 +71,7 @@ describe('call handler in mapping code', () => { db, indexer, provider, - { event: { block: dummyEventData.block } }, + { block: dummyEventData.block }, filePath, dummyGraphData ); diff --git a/packages/graph-node/src/eden.test.ts b/packages/graph-node/src/eden.test.ts index 13ce8144..a514dda5 100644 --- a/packages/graph-node/src/eden.test.ts +++ b/packages/graph-node/src/eden.test.ts @@ -88,7 +88,7 @@ describe('eden wasm loader tests', async () => { db, indexer, provider, - { event: { block: dummyEventData.block } }, + { block: dummyEventData.block }, filePath, data )); @@ -203,7 +203,7 @@ describe('eden wasm loader tests', async () => { ({ exports } = await instantiate(db, indexer, provider, - { event: { block: dummyEventData.block } }, + { block: dummyEventData.block }, filePath, data )); @@ -316,7 +316,7 @@ describe('eden wasm loader tests', async () => { db, indexer, provider, - { event: { block: dummyEventData.block } }, + { block: dummyEventData.block }, filePath, data )); diff --git a/packages/graph-node/src/eth-abi.test.ts b/packages/graph-node/src/eth-abi.test.ts new file mode 100644 index 00000000..aa7f4ad3 --- /dev/null +++ b/packages/graph-node/src/eth-abi.test.ts @@ -0,0 +1,70 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import path from 'path'; +import { expect } from 'chai'; + +import { BaseProvider } from '@ethersproject/providers'; + +import { instantiate } from './loader'; +import { getTestDatabase, getTestIndexer, getTestProvider } from '../test/utils'; +import { Database } from './database'; +import { Indexer } from '../test/utils/indexer'; + +describe('ethereum ABI encode decode', () => { + let exports: any; + let db: Database; + let indexer: Indexer; + let provider: BaseProvider; + let encoded: string; + + before(async () => { + db = getTestDatabase(); + indexer = getTestIndexer(); + provider = getTestProvider(); + }); + + it('should load the subgraph example wasm', async () => { + const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm'); + const instance = await instantiate( + db, + indexer, + provider, + {}, + filePath + ); + exports = instance.exports; + const { _start } = exports; + + // Important to call _start for built subgraphs on instantiation! + // TODO: Check api version https://github.com/graphprotocol/graph-node/blob/6098daa8955bdfac597cec87080af5449807e874/runtime/wasm/src/module/mod.rs#L533 + _start(); + }); + + it('should encode data', async () => { + const { testEthereumEncode, __getString } = exports; + + const encodedPtr = await testEthereumEncode(); + encoded = __getString(encodedPtr); + + expect(encoded) + .to + .equal('0x0000000000000000000000000000000000000000000000000000000000000420583bc7e1bc4799a225663353b82eb36d925399e6ef2799a6a95909f5ab8ac945000000000000000000000000000000000000000000000000000000000000003e000000000000000000000000000000000000000000000000000000000000003f0000000000000000000000000000000000000000000000000000000000000001'); + }); + + it('should decode data', async () => { + const { testEthereumDecode, __getString, __getArray, __newString } = exports; + + const encodedString = await __newString(encoded); + const decodedArrayPtr = await testEthereumDecode(encodedString); + const decodedArray = __getArray(decodedArrayPtr); + const [addressString, bytesString, bigInt1String, bigInt2String, boolString] = decodedArray.map((value: any) => __getString(value)); + + expect(addressString).to.equal('0x0000000000000000000000000000000000000420'); + expect(bytesString).to.equal('0x583bc7e1bc4799a225663353b82eb36d925399e6ef2799a6a95909f5ab8ac945'); + expect(bigInt1String).to.equal('62'); + expect(bigInt2String).to.equal('63'); + expect(boolString).to.equal('true'); + }); +}); diff --git a/packages/graph-node/src/eth-call.test.ts b/packages/graph-node/src/eth-call.test.ts index a8942aad..3a442e0b 100644 --- a/packages/graph-node/src/eth-call.test.ts +++ b/packages/graph-node/src/eth-call.test.ts @@ -49,7 +49,7 @@ describe('eth-call wasm tests', () => { db, indexer, provider, - { event: { block: dummyEventData.block } }, + { block: dummyEventData.block }, filePath, data ); diff --git a/packages/graph-node/src/loader.test.ts b/packages/graph-node/src/loader.test.ts index d97c2c64..1a7e2132 100644 --- a/packages/graph-node/src/loader.test.ts +++ b/packages/graph-node/src/loader.test.ts @@ -32,7 +32,7 @@ describe('wasm loader tests', () => { db, indexer, provider, - { event: {} }, + {}, filePath ); @@ -109,7 +109,7 @@ describe('wasm loader tests', () => { db, indexer, provider, - { event: {} }, + {}, module ); diff --git a/packages/graph-node/src/loader.ts b/packages/graph-node/src/loader.ts index 6e21a0d8..6fb01891 100644 --- a/packages/graph-node/src/loader.ts +++ b/packages/graph-node/src/loader.ts @@ -23,7 +23,8 @@ import { Block, fromEthereumValue, toEthereumValue, - resolveEntityFieldConflicts + resolveEntityFieldConflicts, + getEthereumTypes } from './utils'; import { Database } from './database'; @@ -194,6 +195,27 @@ export const instantiate = async ( console.log('eth_call error', err); return null; } + }, + 'ethereum.encode': async (token: number) => { + const ethValue = await ethereum.Value.wrap(token); + + const data = await fromEthereumValue(instanceExports, ethValue); + const type = await getEthereumTypes(instanceExports, ethValue); + + const encoded = utils.defaultAbiCoder.encode([type], [data]); + const encodedString = await __newString(encoded); + + return ByteArray.fromHexString(encodedString); + }, + 'ethereum.decode': async (types: number, data: number) => { + const typesString = __getString(types); + const byteArray = await ByteArray.wrap(data); + const bytesHex = await byteArray.toHex(); + const dataString = __getString(bytesHex); + + const [decoded] = utils.defaultAbiCoder.decode([typesString], dataString); + + return toEthereumValue(instanceExports, utils.ParamType.from(typesString), decoded); } }, conversion: { @@ -593,6 +615,7 @@ export const instantiate = async ( const Address: any = instanceExports.Address as any; const ethereum: any = instanceExports.ethereum as any; const Entity: any = instanceExports.Entity as any; + const ByteArray: any = instanceExports.ByteArray as any; return instance; }; diff --git a/packages/graph-node/src/numbers.test.ts b/packages/graph-node/src/numbers.test.ts index 777ca7d6..e5611668 100644 --- a/packages/graph-node/src/numbers.test.ts +++ b/packages/graph-node/src/numbers.test.ts @@ -42,7 +42,7 @@ describe('numbers wasm tests', () => { db, indexer, provider, - { event: {} }, + {}, filePath ); exports = instance.exports; diff --git a/packages/graph-node/src/type-conversion.test.ts b/packages/graph-node/src/type-conversion.test.ts index d05c870a..eac8e085 100644 --- a/packages/graph-node/src/type-conversion.test.ts +++ b/packages/graph-node/src/type-conversion.test.ts @@ -31,7 +31,7 @@ describe('typeConversion wasm tests', () => { db, indexer, provider, - { event: {} }, + {}, filePath ); exports = instance.exports; diff --git a/packages/graph-node/src/utils.ts b/packages/graph-node/src/utils.ts index f8aff14b..b1319a1b 100644 --- a/packages/graph-node/src/utils.ts +++ b/packages/graph-node/src/utils.ts @@ -63,6 +63,80 @@ export interface EventData { eventIndex: number; } +export const getEthereumTypes = async (instanceExports: any, value: any): Promise => { + const { + __getArray, + Bytes, + ethereum + } = instanceExports; + + const kind = await value.kind; + + switch (kind) { + case EthereumValueKind.ADDRESS: + return 'address'; + + case EthereumValueKind.BOOL: + return 'bool'; + + case EthereumValueKind.STRING: + return 'string'; + + case EthereumValueKind.BYTES: + return 'bytes'; + + case EthereumValueKind.FIXED_BYTES: { + const bytesPtr = await value.toBytes(); + const bytes = await Bytes.wrap(bytesPtr); + const length = await bytes.length; + + return `bytes${length}`; + } + + case EthereumValueKind.INT: + return 'int256'; + + case EthereumValueKind.UINT: { + return 'uint256'; + } + + case EthereumValueKind.ARRAY: { + const valuesPtr = await value.toArray(); + const [firstValuePtr] = await __getArray(valuesPtr); + const firstValue = await ethereum.Value.wrap(firstValuePtr); + const type = await getEthereumTypes(instanceExports, firstValue); + + return `${type}[]`; + } + + case EthereumValueKind.FIXED_ARRAY: { + const valuesPtr = await value.toArray(); + const values = await __getArray(valuesPtr); + const firstValue = await ethereum.Value.wrap(values[0]); + const type = await getEthereumTypes(instanceExports, firstValue); + + return `${type}[${values.length}]`; + } + + case EthereumValueKind.TUPLE: { + let values = await value.toTuple(); + values = await __getArray(values); + + const typePromises = values.map(async (value: any) => { + value = await ethereum.Value.wrap(value); + return getEthereumTypes(instanceExports, value); + }); + + const types = await Promise.all(typePromises); + + return `tuple(${types.join(',')})`; + } + + default: + break; + } +}; + /** * Method to get value from graph-ts ethereum.Value wasm instance. * @param instanceExports @@ -71,9 +145,12 @@ export interface EventData { */ export const fromEthereumValue = async (instanceExports: any, value: any): Promise => { const { + __getArray, __getString, BigInt, - Address + Address, + Bytes, + ethereum } = instanceExports; const kind = await value.kind; @@ -91,9 +168,15 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi return Boolean(bool); } + case EthereumValueKind.STRING: { + const stringPtr = await value.toString(); + return __getString(stringPtr); + } + case EthereumValueKind.BYTES: case EthereumValueKind.FIXED_BYTES: { - const bytes = await value.toBytes(); + const bytesPtr = await value.toBytes(); + const bytes = await Bytes.wrap(bytesPtr); const bytesStringPtr = await bytes.toHexString(); return __getString(bytesStringPtr); } @@ -107,6 +190,31 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi return BigNumber.from(bigIntString); } + case EthereumValueKind.ARRAY: + case EthereumValueKind.FIXED_ARRAY: { + const valuesPtr = await value.toArray(); + const values = __getArray(valuesPtr); + + const valuePromises = values.map(async (value: any) => { + value = await ethereum.Value.wrap(value); + return fromEthereumValue(instanceExports, value); + }); + + return Promise.all(valuePromises); + } + + case EthereumValueKind.TUPLE: { + let values = await value.toTuple(); + values = await __getArray(values); + + const valuePromises = values.map(async (value: any) => { + value = await ethereum.Value.wrap(value); + return fromEthereumValue(instanceExports, value); + }); + + return Promise.all(valuePromises); + } + default: break; } @@ -131,7 +239,26 @@ export const toEthereumValue = async (instanceExports: any, output: utils.ParamT id_of_type: getIdOfType } = instanceExports; - const { type } = output; + const { type, baseType, arrayChildren } = output; + + // For array type. + if (baseType === 'array') { + const arrayEthereumValueId = await getIdOfType(TypeId.ArrayEthereumValue); + + // Get values for array elements. + const ethereumValuePromises = value.map( + async (value: any) => toEthereumValue( + instanceExports, + arrayChildren, + value + ) + ); + + const ethereumValues: any[] = await Promise.all(ethereumValuePromises); + const ethereumValuesArray = await __newArray(arrayEthereumValueId, ethereumValues); + + return ethereum.Value.fromArray(ethereumValuesArray); + } // For tuple type. if (type === 'tuple') { @@ -140,10 +267,10 @@ export const toEthereumValue = async (instanceExports: any, output: utils.ParamT // Get values for struct elements. const ethereumValuePromises = output.components .map( - async (component: utils.ParamType) => toEthereumValue( + async (component: utils.ParamType, index) => toEthereumValue( instanceExports, component, - value[component.name] + value[index] ) ); diff --git a/packages/graph-node/test/subgraph/example1/src/mapping.ts b/packages/graph-node/test/subgraph/example1/src/mapping.ts index 84615e9a..790b71f0 100644 --- a/packages/graph-node/test/subgraph/example1/src/mapping.ts +++ b/packages/graph-node/test/subgraph/example1/src/mapping.ts @@ -1,4 +1,4 @@ -import { Address, log, BigInt, BigDecimal, ByteArray, dataSource, ethereum } from '@graphprotocol/graph-ts'; +import { Address, log, BigInt, BigDecimal, ByteArray, dataSource, ethereum, Bytes } from '@graphprotocol/graph-ts'; import { Example1, @@ -440,3 +440,67 @@ export function testBigIntToHex (value: string): string[] { return [res1, res2]; } + +export function testEthereumEncode (): string { + const address = ethereum.Value.fromAddress(Address.fromString('0x0000000000000000000000000000000000000420')); + const bigInt1 = ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(62)); + const bigInt2 = ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(63)); + const bool = ethereum.Value.fromBoolean(true); + + const bytes = ethereum.Value.fromFixedBytes( + Bytes.fromByteArray( + ByteArray.fromHexString('0x583bc7e1bc4799a225663353b82eb36d925399e6ef2799a6a95909f5ab8ac945') + ) + ); + + const fixedSizedArray = ethereum.Value.fromFixedSizedArray([ + bigInt1, + bigInt2 + ]); + + const tupleArray: Array = [ + fixedSizedArray, + bool + ]; + + const tuple = ethereum.Value.fromTuple(changetype(tupleArray)); + + const token: Array = [ + address, + bytes, + tuple + ]; + + const encoded = ethereum.encode(ethereum.Value.fromTuple(changetype(token)))!; + + log.debug('encoded: {}', [encoded.toHex()]); + + return encoded.toHex(); +} + +export function testEthereumDecode (encoded: string): string[] { + const decoded = ethereum.decode('(address,bytes32,(uint256[2],bool))', Bytes.fromByteArray(ByteArray.fromHexString(encoded))); + const tupleValues = decoded!.toTuple(); + + const decodedAddress = tupleValues[0].toAddress(); + const decodedBytes = tupleValues[1].toBytes(); + const decodedTuple = tupleValues[2].toTuple(); + const decodedFixedSizedArray = decodedTuple[0].toArray(); + const decodedBigInt1 = decodedFixedSizedArray[0].toBigInt(); + const decodedBigInt2 = decodedFixedSizedArray[1].toBigInt(); + const decodedBool = decodedTuple[1].toBoolean(); + + log.debug('decoded address: {}', [decodedAddress.toHex()]); + log.debug('decoded bytes: {}', [decodedBytes.toHex()]); + log.debug('decoded bigInt1: {}', [decodedBigInt1.toString()]); + log.debug('decoded bigInt2: {}', [decodedBigInt2.toString()]); + log.debug('decoded bool: {}', [decodedBool.toString()]); + + return [ + decodedAddress.toHex(), + decodedBytes.toHex(), + decodedBigInt1.toString(), + decodedBigInt2.toString(), + decodedBool.toString() + ]; +} diff --git a/packages/graph-node/test/utils/index.ts b/packages/graph-node/test/utils/index.ts index 4d814c88..8914dd6b 100644 --- a/packages/graph-node/test/utils/index.ts +++ b/packages/graph-node/test/utils/index.ts @@ -21,6 +21,7 @@ export const getDummyEventData = async (): Promise => { const ethersBlock = await provider.getBlock(blockNumber); const block = { + headerId: 0, blockHash: ethersBlock.hash, blockNumber: ethersBlock.number.toString(), timestamp: '0', @@ -41,7 +42,11 @@ export const getDummyEventData = async (): Promise => { hash: ZERO_HASH, index: 0, from: ZERO_ADDRESS, - to: ZERO_ADDRESS + to: ZERO_ADDRESS, + value: '0', + gasLimit: '0', + gasPrice: '0', + input: ZERO_HASH }; return {