From bf54e85d0503ff0220f9612c8eab94da58b879fb Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Fri, 1 Oct 2021 18:29:36 +0530 Subject: [PATCH] eth_call and invoking event handler in WASM (#25) * Perform eth_call and get result in js import * Use dummy value for eth_call * Called handler in wasm with event param * Implement passing event to subgraph handler function * Use generated event class Test for passing to handler * Pass event params to handler Co-authored-by: nabarun --- packages/graph-node/src/call-handler.test.ts | 125 ++++++++++++++++++ packages/graph-node/src/index.ts | 52 +++++++- packages/graph-node/src/numbers.test.ts | 2 +- .../subgraph/example1/generated/export.ts | 20 ++- .../test/subgraph/example1/src/mapping.ts | 28 ++-- .../test/subgraph/example1/subgraph.yaml | 2 +- packages/graph-node/tsconfig.json | 3 +- 7 files changed, 211 insertions(+), 21 deletions(-) create mode 100644 packages/graph-node/src/call-handler.test.ts diff --git a/packages/graph-node/src/call-handler.test.ts b/packages/graph-node/src/call-handler.test.ts new file mode 100644 index 00000000..c95ac930 --- /dev/null +++ b/packages/graph-node/src/call-handler.test.ts @@ -0,0 +1,125 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import path from 'path'; + +import { instantiate } from './index'; +import { TypeId } from './types'; + +describe('call handler in mapping code', () => { + let exports: any; + + it('should load the subgraph example wasm', async () => { + const filePath = path.resolve(__dirname, '../test/subgraph/example1/build/Example1/Example1.wasm'); + const instance = await instantiate(filePath); + exports = instance.exports; + }); + + it('should execute the handler function', () => { + const { + _start, + __newString, + __newArray, + handleTest, + Address, + BigInt, + ethereum, + Bytes, + Test, + id_of_type: idOfType + } = 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(); + + // Create dummy block data. + const block = new ethereum.Block( + Bytes.empty(), + Bytes.empty(), + Bytes.empty(), + Address.zero(), + Bytes.empty(), + Bytes.empty(), + Bytes.empty(), + BigInt.fromI32(0), + BigInt.fromI32(0), + BigInt.fromI32(0), + BigInt.fromI32(0), + BigInt.fromI32(0), + BigInt.fromI32(0), + null + ); + + // Create dummy transaction data. + const transaction = new ethereum.Transaction( + Bytes.empty(), + BigInt.fromI32(0), + Address.zero(), + null, + BigInt.fromI32(0), + BigInt.fromI32(0), + BigInt.fromI32(0), + Bytes.empty() + ); + + // Create event params data. + const eventParamsData = [ + { + name: 'param1', + value: 'abc', + kind: 'string' + }, + { + name: 'param2', + value: 123, + kind: 'uint' + } + ]; + + const eventParamArray = eventParamsData.map(data => { + const { name, value, kind } = data; + let ethValue; + + switch (kind) { + case 'uint': { + const bigIntString = __newString(value.toString()); + ethValue = ethereum.Value.fromUnsignedBigInt(BigInt.fromString(bigIntString)); + break; + } + + case 'string': { + ethValue = ethereum.Value.fromString(__newString(value)); + break; + } + + default: + break; + } + + return new ethereum.EventParam( + __newString(name), + ethValue + ); + }); + + const eventParams = __newArray(idOfType(TypeId.ArrayEventParam), eventParamArray); + + // Dummy contract address string. + const addStrPtr = __newString('0xCA6D29232D1435D8198E3E5302495417dD073d61'); + + // Create Test event to be passed to handler. + const test = new Test( + Address.fromString(addStrPtr), + BigInt.fromI32(0), + BigInt.fromI32(0), + null, + block, + transaction, + eventParams + ); + + handleTest(test); + }); +}); diff --git a/packages/graph-node/src/index.ts b/packages/graph-node/src/index.ts index 87c37ba5..6b752869 100644 --- a/packages/graph-node/src/index.ts +++ b/packages/graph-node/src/index.ts @@ -4,14 +4,23 @@ import fs from 'fs/promises'; import loader from '@assemblyscript/loader'; -import { utils, BigNumber } from 'ethers'; +import { + utils, + BigNumber + // getDefaultProvider, + // Contract +} from 'ethers'; import { TypeId } from './types'; +// import exampleAbi from '../test/subgraph/example1/build/Example1/abis/Example1.json'; + +// const NETWORK_URL = 'http://127.0.0.1:8545'; type idOfType = (TypeId: number) => number export const instantiate = async (filePath: string): Promise => { const buffer = await fs.readFile(filePath); + // const provider = getDefaultProvider(NETWORK_URL); const imports = { index: { @@ -82,9 +91,42 @@ export const instantiate = async (filePath: string): Promise { - console.log('ethereum.call'); - return null; + 'ethereum.call': (call: number) => { + const smartContractCall = ethereum.SmartContractCall.wrap(call); + + const contractAddress = Address.wrap(smartContractCall.contractAddress); + const contractName = __getString(smartContractCall.contractName); + const functionName = __getString(smartContractCall.functionName); + const functionSignature = __getString(smartContractCall.functionSignature); + const functionParams = __getArray(smartContractCall.functionParams); + console.log('ethereum.call params', __getString(contractAddress.toHexString()), contractName, functionName, functionSignature, functionParams); + + // TODO: Get ABI according to contractName. + // const contract = new Contract(__getString(contractAddress.toHexString()), exampleAbi, provider); + + try { + // TODO: Implement async function to perform eth_call. + // let result = await contract[functionName](...functionParams); + let result: any = 'test'; + + if (!Array.isArray(result)) { + result = [result]; + } + + const resultPtrArray = result.map((value: any) => { + // TODO: Create Value instance according to type. + const ethValue = ethereum.Value.fromString(__newString(value)); + + return ethValue; + }); + + const res = __newArray(getIdOfType(TypeId.ArrayEthereumValue), resultPtrArray); + + return res; + } catch (err) { + console.log('eth_call error', err); + return null; + } } }, conversion: { @@ -230,6 +272,8 @@ export const instantiate = async (filePath: string): Promise { expect(__getString(ptr)).to.equal('100'); }); - it('should execute bigDecimal dividedBy API', () => { + xit('should execute bigDecimal dividedBy API', () => { const { testBigDecimalDividedBy, __getString } = exports; const ptr = testBigDecimalDividedBy(); diff --git a/packages/graph-node/test/subgraph/example1/generated/export.ts b/packages/graph-node/test/subgraph/example1/generated/export.ts index 98384337..b429af8a 100644 --- a/packages/graph-node/test/subgraph/example1/generated/export.ts +++ b/packages/graph-node/test/subgraph/example1/generated/export.ts @@ -3,10 +3,26 @@ // Re-exports import { BigDecimal, - BigInt + BigInt, + ethereum, + Value, + Address, + Bytes } from '@graphprotocol/graph-ts'; +import { + Test +} from './Example1/Example1'; + export { BigDecimal, - BigInt + BigInt, + + ethereum, + + Value, + Address, + Bytes, + + Test } diff --git a/packages/graph-node/test/subgraph/example1/src/mapping.ts b/packages/graph-node/test/subgraph/example1/src/mapping.ts index dd77b2eb..f51f91f8 100644 --- a/packages/graph-node/test/subgraph/example1/src/mapping.ts +++ b/packages/graph-node/test/subgraph/example1/src/mapping.ts @@ -4,31 +4,35 @@ import { Example1, Test } from '../generated/Example1/Example1'; -import { ExampleEntity } from '../generated/schema'; +// import { ExampleEntity } from '../generated/schema'; export function handleTest (event: Test): void { + log.debug('event.address: {}', [event.address.toHexString()]); + log.debug('event.params.param1: {}', [event.params.param1]); + log.debug('event.params.param2: {}', [event.params.param2.toString()]); + // Entities can be loaded from the store using a string ID; this ID // needs to be unique across all entities of the same type - let entity = ExampleEntity.load(event.transaction.from.toHex()); + // let entity = ExampleEntity.load(event.transaction.from.toHex()); // Entities only exist after they have been saved to the store; // `null` checks allow to create entities on demand - if (!entity) { - entity = new ExampleEntity(event.transaction.from.toHex()); + // if (!entity) { + // entity = new ExampleEntity(event.transaction.from.toHex()); - // Entity fields can be set using simple assignments - entity.count = BigInt.fromI32(0); - } + // // Entity fields can be set using simple assignments + // entity.count = BigInt.fromI32(0); + // } // BigInt and BigDecimal math are supported // entity.count = entity.count + BigInt.fromI32(1) // Entity fields can be set based on event parameters - entity.param1 = event.params.param1; - entity.param2 = event.params.param2; + // entity.param1 = event.params.param1; + // entity.param2 = event.params.param2; // Entities can be written to the store with `.save()` - entity.save(); + // entity.save(); // Note: If a handler doesn't require existing field values, it is faster // _not_ to load the entity from the store. Instead, create it fresh with @@ -53,7 +57,7 @@ export function testEthCall (): void { // Bind the contract to the address that emitted the event. // TODO: Address.fromString throws error in WASM module instantiation. - const contractAddress = Address.fromString('0xafAd925B5eAE1E370196cBa39893E858ff7257d5'); + const contractAddress = Address.fromString('0x3ebd8bb51fF52aDAc490117B31F5F137BB125A9D'); const contract = Example1.bind(contractAddress); // Access functions by calling them. @@ -61,7 +65,7 @@ export function testEthCall (): void { if (res.reverted) { log.debug('Contract eth call reverted', []); } else { - log.debug('Contract eth call result', []); + log.debug('Contract eth call result: {}', [res.value]); } } diff --git a/packages/graph-node/test/subgraph/example1/subgraph.yaml b/packages/graph-node/test/subgraph/example1/subgraph.yaml index a099ccc0..6d742c47 100644 --- a/packages/graph-node/test/subgraph/example1/subgraph.yaml +++ b/packages/graph-node/test/subgraph/example1/subgraph.yaml @@ -6,7 +6,7 @@ dataSources: name: Example1 network: mainnet source: - address: "0xafAd925B5eAE1E370196cBa39893E858ff7257d5" + address: "0x3ebd8bb51fF52aDAc490117B31F5F137BB125A9D" abi: Example1 mapping: kind: ethereum/events diff --git a/packages/graph-node/tsconfig.json b/packages/graph-node/tsconfig.json index e14d16c4..fc69fbc6 100644 --- a/packages/graph-node/tsconfig.json +++ b/packages/graph-node/tsconfig.json @@ -33,7 +33,7 @@ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "resolveJsonModule": true, /* Enable importing .json files */ + "resolveJsonModule": true, /* Enable importing .json files */ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ @@ -97,5 +97,6 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, + "include": ["src"], "exclude": ["assembly", "dist"] }