Use async functions in wasm imports (#28)

* Use async functions in wasm imports

* Complete implementing asyncify with assemblyscript loader
This commit is contained in:
2021-12-28 16:08:05 +05:30
committed by nabarun
parent bf54e85d05
commit f34d83c04b
12 changed files with 189 additions and 108 deletions
+1
View File
@@ -1 +1,2 @@
require: 'ts-node/register'
timeout: 5000
+23
View File
@@ -34,10 +34,21 @@ import {
// Wrapped
} from '@graphprotocol/graph-ts';
/* eslint-disable @typescript-eslint/no-namespace */
export declare namespace test {
export function asyncMethod(): i32;
}
export function callGraphAPI (): void {
log.debug('hello {}', ['world']);
}
export function callAsyncMethod (): void {
log.debug('calling async method', []);
const res = test.asyncMethod();
log.debug('res after async: {}', [res.toString()]);
}
export class Foo {
static getFoo (): Foo {
return new Foo();
@@ -49,3 +60,15 @@ export class Foo {
}
export const FooID = idof<Foo>();
export class Bar {
prop: string
constructor (prop: string) {
this.prop = prop;
}
getProp (): string {
return this.prop;
}
}
+3 -4
View File
@@ -7,7 +7,6 @@
"@graphprotocol/graph-ts": "^0.22.0",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",
"assemblyscript": "^0.19.16",
"eslint": "^7.27.0",
"eslint-config-semistandard": "^15.0.1",
"eslint-config-standard": "^16.0.3",
@@ -23,12 +22,12 @@
"scripts": {
"lint": "eslint .",
"build": "tsc",
"asbuild:debug": "asc assembly/index.ts --lib ./node_modules --exportRuntime --target debug",
"asbuild:release": "asc assembly/index.ts --lib ./node_modules --exportRuntime --target release",
"asbuild:debug": "asc assembly/index.ts --lib ./node_modules --exportRuntime --target debug --runPasses asyncify",
"asbuild:release": "asc assembly/index.ts --lib ./node_modules --exportRuntime --target release --runPasses asyncify",
"asbuild": "yarn asbuild:debug && yarn asbuild:release",
"test": "yarn asbuild:debug && mocha src/**/*.test.ts"
},
"dependencies": {
"@assemblyscript/loader": "^0.19.16"
"assemblyscript": "https://github.com/vulcanize/assemblyscript.git#ng-integrate-asyncify"
}
}
+38 -36
View File
@@ -16,7 +16,7 @@ describe('call handler in mapping code', () => {
exports = instance.exports;
});
it('should execute the handler function', () => {
it('should execute the handler function', async () => {
const {
_start,
__newString,
@@ -35,33 +35,33 @@ describe('call handler in mapping code', () => {
_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),
const block = await ethereum.Block.__new(
await Bytes.empty(),
await Bytes.empty(),
await Bytes.empty(),
await Address.zero(),
await Bytes.empty(),
await Bytes.empty(),
await Bytes.empty(),
await BigInt.fromI32(0),
await BigInt.fromI32(0),
await BigInt.fromI32(0),
await BigInt.fromI32(0),
await BigInt.fromI32(0),
await BigInt.fromI32(0),
null
);
// Create dummy transaction data.
const transaction = new ethereum.Transaction(
Bytes.empty(),
BigInt.fromI32(0),
Address.zero(),
const transaction = await ethereum.Transaction.__new(
await Bytes.empty(),
await BigInt.fromI32(0),
await Address.zero(),
null,
BigInt.fromI32(0),
BigInt.fromI32(0),
BigInt.fromI32(0),
Bytes.empty()
await BigInt.fromI32(0),
await BigInt.fromI32(0),
await BigInt.fromI32(0),
await Bytes.empty()
);
// Create event params data.
@@ -78,19 +78,20 @@ describe('call handler in mapping code', () => {
}
];
const eventParamArray = eventParamsData.map(data => {
const eventParamArrayPromise = eventParamsData.map(async data => {
const { name, value, kind } = data;
let ethValue;
switch (kind) {
case 'uint': {
const bigIntString = __newString(value.toString());
ethValue = ethereum.Value.fromUnsignedBigInt(BigInt.fromString(bigIntString));
const bigIntString = await (await __newString(value.toString()));
const bigInt = await BigInt.fromString(bigIntString);
ethValue = await ethereum.Value.fromUnsignedBigInt(bigInt);
break;
}
case 'string': {
ethValue = ethereum.Value.fromString(__newString(value));
ethValue = await ethereum.Value.fromString(await __newString(value));
break;
}
@@ -98,28 +99,29 @@ describe('call handler in mapping code', () => {
break;
}
return new ethereum.EventParam(
__newString(name),
return ethereum.EventParam.__new(
await __newString(name),
ethValue
);
});
const eventParams = __newArray(idOfType(TypeId.ArrayEventParam), eventParamArray);
const eventParamArray = await Promise.all(eventParamArrayPromise);
const eventParams = await __newArray(await idOfType(TypeId.ArrayEventParam), eventParamArray);
// Dummy contract address string.
const addStrPtr = __newString('0xCA6D29232D1435D8198E3E5302495417dD073d61');
const addStrPtr = await __newString('0xCA6D29232D1435D8198E3E5302495417dD073d61');
// Create Test event to be passed to handler.
const test = new Test(
Address.fromString(addStrPtr),
BigInt.fromI32(0),
BigInt.fromI32(0),
const test = await Test.__new(
await Address.fromString(addStrPtr),
await BigInt.fromI32(0),
await BigInt.fromI32(0),
null,
block,
transaction,
eventParams
);
handleTest(test);
await handleTest(test);
});
});
+1 -1
View File
@@ -6,7 +6,7 @@ import path from 'path';
import { instantiate } from './index';
describe('eden wasm loader tests', () => {
xdescribe('eden wasm loader tests', () => {
it('should load the subgraph network wasm', async () => {
const filePath = path.resolve(__dirname, '../test/subgraph/eden/EdenNetwork/EdenNetwork.wasm');
await instantiate(filePath);
+2 -2
View File
@@ -15,13 +15,13 @@ describe('eth-call wasm tests', () => {
exports = instance.exports;
});
it('should execute exported function', () => {
it('should execute exported function', async () => {
const { _start, testEthCall } = 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();
testEthCall();
await testEthCall();
});
});
+14 -6
View File
@@ -23,23 +23,31 @@ describe('wasm loader tests', () => {
callGraphAPI();
});
it('should execute async function', async () => {
const { callAsyncMethod } = exports;
await callAsyncMethod();
});
it('should use a class/instance created in wasm from JS', async () => {
const { Foo, __getString, __pin, __unpin } = exports;
const fooPtr = __pin(Foo.getFoo());
const fooPtr = await __pin(await Foo.getFoo());
const foo = Foo.wrap(fooPtr);
const strPtr = foo.getString();
const strPtr = await foo.getString();
expect(__getString(strPtr)).to.equal('hello world!');
__unpin(fooPtr);
await __unpin(fooPtr);
});
it('should instantiate a class in wasm from JS', async () => {
const { Foo, FooID, __getString, __new, __pin, __unpin } = exports;
const { Foo, FooID, Bar, __getString, __new, __pin, __unpin, __newString } = exports;
const fooPtr = __pin(__new(FooID));
const fooPtr = await __pin(await __new(FooID));
const foo = Foo.wrap(fooPtr);
const strPtr = foo.getString();
const strPtr = await foo.getString();
expect(__getString(strPtr)).to.equal('hello world!');
__unpin(fooPtr);
const bar = await Bar.__new(await __newString('test'));
expect(__getString(await bar.prop)).to.equal('test');
});
});
+49 -28
View File
@@ -3,7 +3,7 @@
//
import fs from 'fs/promises';
import loader from '@assemblyscript/loader';
import loader from 'assemblyscript/lib/loader';
import {
utils,
BigNumber
@@ -22,7 +22,7 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
const buffer = await fs.readFile(filePath);
// const provider = getDefaultProvider(NETWORK_URL);
const imports = {
const imports: WebAssembly.Imports = {
index: {
'store.get': () => {
console.log('store.get');
@@ -80,7 +80,7 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
},
'log.log': (_: number, msg: number) => {
console.log('console.log', __getString(msg));
console.log('log.log', __getString(msg));
},
// 'dataSource.create': () => {
@@ -88,18 +88,38 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
// },
'dataSource.address': () => {
console.log('dataSource.address');
},
'test.asyncMethod': async () => {
console.log('before timer start');
await new Promise(resolve => {
setTimeout(() => {
resolve(1);
}, 3000);
});
console.log('after timer complete');
return 123;
}
},
ethereum: {
'ethereum.call': (call: number) => {
const smartContractCall = ethereum.SmartContractCall.wrap(call);
'ethereum.call': async (call: number) => {
const smartContractCall = await 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);
const contractAddress = await Address.wrap(await smartContractCall.contractAddress);
const contractName = __getString(await smartContractCall.contractName);
const functionName = __getString(await smartContractCall.functionName);
const functionSignature = __getString(await smartContractCall.functionSignature);
const functionParams = __getArray(await smartContractCall.functionParams);
console.log(
'ethereum.call params',
__getString(await contractAddress.toHexString()),
contractName,
functionName,
functionSignature,
functionParams
);
// TODO: Get ABI according to contractName.
// const contract = new Contract(__getString(contractAddress.toHexString()), exampleAbi, provider);
@@ -113,14 +133,15 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
result = [result];
}
const resultPtrArray = result.map((value: any) => {
const resultPtrArrayPromise = result.map(async (value: any) => {
// TODO: Create Value instance according to type.
const ethValue = ethereum.Value.fromString(__newString(value));
const ethValue = await ethereum.Value.fromString(await __newString(value));
return ethValue;
});
const res = __newArray(getIdOfType(TypeId.ArrayEthereumValue), resultPtrArray);
const resultPtrArray: any[] = await Promise.all(resultPtrArrayPromise);
const res = await __newArray(await getIdOfType(TypeId.ArrayEthereumValue), resultPtrArray);
return res;
} catch (err) {
@@ -130,12 +151,12 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
}
},
conversion: {
'typeConversion.stringToH160': (s: number) => {
'typeConversion.stringToH160': async (s: number) => {
const string = __getString(s);
const address = utils.getAddress(string);
const byteArray = utils.arrayify(address);
const uint8ArrayId = getIdOfType(TypeId.Uint8Array);
const uint8ArrayId = await getIdOfType(TypeId.Uint8Array);
const ptr = __newArray(uint8ArrayId, byteArray);
return ptr;
@@ -152,10 +173,10 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
console.log('index typeConversion.bigIntToHex');
},
'typeConversion.bytesToHex': (bytes: number) => {
'typeConversion.bytesToHex': async (bytes: number) => {
const byteArray = __getArray(bytes);
const hexString = utils.hexlify(byteArray);
const ptr = __newString(hexString);
const ptr = await __newString(hexString);
return ptr;
},
@@ -196,19 +217,19 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
console.log('bigDecimal.times');
},
'bigInt.fromString': (s: number) => {
'bigInt.fromString': async (s: number) => {
const string = __getString(s);
const bigNumber = BigNumber.from(string);
const hex = bigNumber.toHexString();
const bytes = utils.arrayify(hex);
const uint8ArrayId = getIdOfType(TypeId.Uint8Array);
const ptr = __newArray(uint8ArrayId, bytes);
const bigInt = BigInt.fromSignedBytes(ptr);
const uint8ArrayId = await getIdOfType(TypeId.Uint8Array);
const ptr = await __newArray(uint8ArrayId, bytes);
const bigInt = await BigInt.fromSignedBytes(ptr);
return bigInt;
},
'bigInt.plus': (x: number, y: number) => {
'bigInt.plus': async (x: number, y: number) => {
const xBigIntArray = __getArray(x);
const xBigNumber = BigNumber.from(xBigIntArray);
@@ -216,12 +237,12 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
const yBigNumber = BigNumber.from(yBigIntArray);
const sum = xBigNumber.add(yBigNumber);
const ptr = __newString(sum.toString());
const sumBigInt = BigInt.fromString(ptr);
const ptr = await __newString(sum.toString());
const sumBigInt = await BigInt.fromString(ptr);
return sumBigInt;
},
'bigInt.minus': (x: number, y: number) => {
'bigInt.minus': async (x: number, y: number) => {
const xBigIntArray = __getArray(x);
const xBigNumber = BigNumber.from(xBigIntArray);
@@ -229,7 +250,7 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
const yBigNumber = BigNumber.from(yBigIntArray);
const diff = xBigNumber.sub(yBigNumber);
const ptr = __newString(diff.toString());
const ptr = await __newString(diff.toString());
const sumBigInt = BigInt.fromString(ptr);
return sumBigInt;
@@ -265,8 +286,8 @@ export const instantiate = async (filePath: string): Promise<loader.ResultObject
};
const instance = await loader.instantiate(buffer, imports);
const { exports } = instance;
const exports = instance.exports;
const { __getString, __newString, __getArray, __newArray } = exports;
const getIdOfType: idOfType = exports.id_of_type as idOfType;
+6 -6
View File
@@ -23,24 +23,24 @@ describe('numbers wasm tests', () => {
_start();
});
it('should execute bigInt fromString API', () => {
it('should execute bigInt fromString API', async () => {
const { testBigIntFromString, __getString } = exports;
const ptr = testBigIntFromString();
const ptr = await testBigIntFromString();
expect(__getString(ptr)).to.equal('123');
});
it('should execute bigInt plus API', () => {
it('should execute bigInt plus API', async () => {
const { testBigIntPlus, __getString } = exports;
const ptr = testBigIntPlus();
const ptr = await testBigIntPlus();
expect(__getString(ptr)).to.equal('200');
});
it('should execute bigInt minus API', () => {
it('should execute bigInt minus API', async () => {
const { testBigIntMinus, __getString } = exports;
const ptr = testBigIntMinus();
const ptr = await testBigIntMinus();
expect(__getString(ptr)).to.equal('100');
});
@@ -23,24 +23,24 @@ describe('typeConversion wasm tests', () => {
_start();
});
it('should execute typeConversion bytesToHex API', () => {
it('should execute typeConversion bytesToHex API', async () => {
const { testBytesToHex, __getString } = exports;
const ptr = testBytesToHex();
const ptr = await testBytesToHex();
expect(__getString(ptr)).to.equal('0x231a');
});
it('should execute typeConversion bigIntToString API', () => {
it('should execute typeConversion bigIntToString API', async () => {
const { testBigIntToString, __getString } = exports;
const ptr = testBigIntToString();
const ptr = await testBigIntToString();
expect(__getString(ptr)).to.equal('1000000000000000000');
});
it('should execute typeConversion stringToH160 API', () => {
it('should execute typeConversion stringToH160 API', async () => {
const { testStringToH160, __getString } = exports;
const ptr = testStringToH160();
const ptr = await testStringToH160();
expect(__getString(ptr)).to.equal('0xafad925b5eae1e370196cba39893e858ff7257d5');
});
});
@@ -5,7 +5,6 @@ import {
BigDecimal,
BigInt,
ethereum,
Value,
Address,
Bytes
} from '@graphprotocol/graph-ts';
@@ -20,7 +19,6 @@ export {
ethereum,
Value,
Address,
Bytes,