mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-09 04:48:05 +00:00
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 <nabarun@deepstacksoft.com>
This commit is contained in:
parent
2bc40896b0
commit
bf54e85d05
125
packages/graph-node/src/call-handler.test.ts
Normal file
125
packages/graph-node/src/call-handler.test.ts
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
@ -4,14 +4,23 @@
|
|||||||
|
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import loader from '@assemblyscript/loader';
|
import loader from '@assemblyscript/loader';
|
||||||
import { utils, BigNumber } from 'ethers';
|
import {
|
||||||
|
utils,
|
||||||
|
BigNumber
|
||||||
|
// getDefaultProvider,
|
||||||
|
// Contract
|
||||||
|
} from 'ethers';
|
||||||
|
|
||||||
import { TypeId } from './types';
|
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
|
type idOfType = (TypeId: number) => number
|
||||||
|
|
||||||
export const instantiate = async (filePath: string): Promise<loader.ResultObject & { exports: any }> => {
|
export const instantiate = async (filePath: string): Promise<loader.ResultObject & { exports: any }> => {
|
||||||
const buffer = await fs.readFile(filePath);
|
const buffer = await fs.readFile(filePath);
|
||||||
|
// const provider = getDefaultProvider(NETWORK_URL);
|
||||||
|
|
||||||
const imports = {
|
const imports = {
|
||||||
index: {
|
index: {
|
||||||
@ -82,10 +91,43 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ethereum: {
|
ethereum: {
|
||||||
'ethereum.call': () => {
|
'ethereum.call': (call: number) => {
|
||||||
console.log('ethereum.call');
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
conversion: {
|
conversion: {
|
||||||
'typeConversion.stringToH160': (s: number) => {
|
'typeConversion.stringToH160': (s: number) => {
|
||||||
@ -230,6 +272,8 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
|
|||||||
const getIdOfType: idOfType = exports.id_of_type as idOfType;
|
const getIdOfType: idOfType = exports.id_of_type as idOfType;
|
||||||
const BigDecimal: any = exports.BigDecimal as any;
|
const BigDecimal: any = exports.BigDecimal as any;
|
||||||
const BigInt: any = exports.BigInt as any;
|
const BigInt: any = exports.BigInt as any;
|
||||||
|
const Address: any = exports.Address as any;
|
||||||
|
const ethereum: any = exports.ethereum as any;
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
@ -44,7 +44,7 @@ describe('numbers wasm tests', () => {
|
|||||||
expect(__getString(ptr)).to.equal('100');
|
expect(__getString(ptr)).to.equal('100');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should execute bigDecimal dividedBy API', () => {
|
xit('should execute bigDecimal dividedBy API', () => {
|
||||||
const { testBigDecimalDividedBy, __getString } = exports;
|
const { testBigDecimalDividedBy, __getString } = exports;
|
||||||
|
|
||||||
const ptr = testBigDecimalDividedBy();
|
const ptr = testBigDecimalDividedBy();
|
||||||
|
@ -3,10 +3,26 @@
|
|||||||
// Re-exports
|
// Re-exports
|
||||||
import {
|
import {
|
||||||
BigDecimal,
|
BigDecimal,
|
||||||
BigInt
|
BigInt,
|
||||||
|
ethereum,
|
||||||
|
Value,
|
||||||
|
Address,
|
||||||
|
Bytes
|
||||||
} from '@graphprotocol/graph-ts';
|
} from '@graphprotocol/graph-ts';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Test
|
||||||
|
} from './Example1/Example1';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
BigDecimal,
|
BigDecimal,
|
||||||
BigInt
|
BigInt,
|
||||||
|
|
||||||
|
ethereum,
|
||||||
|
|
||||||
|
Value,
|
||||||
|
Address,
|
||||||
|
Bytes,
|
||||||
|
|
||||||
|
Test
|
||||||
}
|
}
|
||||||
|
@ -4,31 +4,35 @@ import {
|
|||||||
Example1,
|
Example1,
|
||||||
Test
|
Test
|
||||||
} from '../generated/Example1/Example1';
|
} from '../generated/Example1/Example1';
|
||||||
import { ExampleEntity } from '../generated/schema';
|
// import { ExampleEntity } from '../generated/schema';
|
||||||
|
|
||||||
export function handleTest (event: Test): void {
|
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
|
// Entities can be loaded from the store using a string ID; this ID
|
||||||
// needs to be unique across all entities of the same type
|
// 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;
|
// Entities only exist after they have been saved to the store;
|
||||||
// `null` checks allow to create entities on demand
|
// `null` checks allow to create entities on demand
|
||||||
if (!entity) {
|
// if (!entity) {
|
||||||
entity = new ExampleEntity(event.transaction.from.toHex());
|
// entity = new ExampleEntity(event.transaction.from.toHex());
|
||||||
|
|
||||||
// Entity fields can be set using simple assignments
|
// // Entity fields can be set using simple assignments
|
||||||
entity.count = BigInt.fromI32(0);
|
// entity.count = BigInt.fromI32(0);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// BigInt and BigDecimal math are supported
|
// BigInt and BigDecimal math are supported
|
||||||
// entity.count = entity.count + BigInt.fromI32(1)
|
// entity.count = entity.count + BigInt.fromI32(1)
|
||||||
|
|
||||||
// Entity fields can be set based on event parameters
|
// Entity fields can be set based on event parameters
|
||||||
entity.param1 = event.params.param1;
|
// entity.param1 = event.params.param1;
|
||||||
entity.param2 = event.params.param2;
|
// entity.param2 = event.params.param2;
|
||||||
|
|
||||||
// Entities can be written to the store with `.save()`
|
// 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
|
// 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
|
// _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.
|
// Bind the contract to the address that emitted the event.
|
||||||
// TODO: Address.fromString throws error in WASM module instantiation.
|
// TODO: Address.fromString throws error in WASM module instantiation.
|
||||||
const contractAddress = Address.fromString('0xafAd925B5eAE1E370196cBa39893E858ff7257d5');
|
const contractAddress = Address.fromString('0x3ebd8bb51fF52aDAc490117B31F5F137BB125A9D');
|
||||||
const contract = Example1.bind(contractAddress);
|
const contract = Example1.bind(contractAddress);
|
||||||
|
|
||||||
// Access functions by calling them.
|
// Access functions by calling them.
|
||||||
@ -61,7 +65,7 @@ export function testEthCall (): void {
|
|||||||
if (res.reverted) {
|
if (res.reverted) {
|
||||||
log.debug('Contract eth call reverted', []);
|
log.debug('Contract eth call reverted', []);
|
||||||
} else {
|
} else {
|
||||||
log.debug('Contract eth call result', []);
|
log.debug('Contract eth call result: {}', [res.value]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ dataSources:
|
|||||||
name: Example1
|
name: Example1
|
||||||
network: mainnet
|
network: mainnet
|
||||||
source:
|
source:
|
||||||
address: "0xafAd925B5eAE1E370196cBa39893E858ff7257d5"
|
address: "0x3ebd8bb51fF52aDAc490117B31F5F137BB125A9D"
|
||||||
abi: Example1
|
abi: Example1
|
||||||
mapping:
|
mapping:
|
||||||
kind: ethereum/events
|
kind: ethereum/events
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
// "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. */
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "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 `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
/* JavaScript Support */
|
/* JavaScript Support */
|
||||||
@ -97,5 +97,6 @@
|
|||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
},
|
},
|
||||||
|
"include": ["src"],
|
||||||
"exclude": ["assembly", "dist"]
|
"exclude": ["assembly", "dist"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user