Implement subgraph JSON host APIs fromBytes and try_fromBytes (#105)

* Implement JSON fromBytes host API

* Implement json.try_fromBytes host API

* Fill host API code for json number methods
This commit is contained in:
2022-01-03 18:15:20 +05:30
committed by GitHub
parent 6a2c99a0bb
commit 4867530da7
8 changed files with 433 additions and 141 deletions
+54
View File
@@ -0,0 +1,54 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import path from 'path';
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('json host api', () => {
let exports: any;
let db: Database;
let indexer: Indexer;
let provider: BaseProvider;
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 get JSONValue from bytes', async () => {
const { testJsonFromBytes } = exports;
await testJsonFromBytes();
});
it('should parse JSON safely', async () => {
const { testJsonTryFromBytes } = exports;
await testJsonTryFromBytes();
});
});
+69 -36
View File
@@ -24,7 +24,8 @@ import {
fromEthereumValue,
toEthereumValue,
resolveEntityFieldConflicts,
getEthereumTypes
getEthereumTypes,
jsonFromBytes
} from './utils';
import { Database } from './database';
@@ -255,7 +256,7 @@ export const instantiate = async (
return ptr;
},
'typeConversion.bigIntToHex': async (bigInt: number) => {
const bigIntInstance = await BigInt.wrap(bigInt);
const bigIntInstance = await ASBigInt.wrap(bigInt);
const bigIntString = await bigIntInstance.toString();
const bigNumber = BigNumber.from(__getString(bigIntString));
@@ -310,10 +311,10 @@ export const instantiate = async (
const bigDecimalInstance = BigDecimal.wrap(bigDecimal);
const digitsPtr = await bigDecimalInstance.digits;
const digitsBigInt = BigInt.wrap(digitsPtr);
const digitsBigInt = ASBigInt.wrap(digitsPtr);
const expPtr = await bigDecimalInstance.exp;
const expBigInt = BigInt.wrap(expPtr);
const expBigInt = ASBigInt.wrap(expPtr);
const digitsStringPtr = await digitsBigInt.toString();
const digits = __getString(digitsStringPtr);
@@ -339,11 +340,11 @@ export const instantiate = async (
const digitsBigNumber = BigNumber.from(digits);
const signBigNumber = BigNumber.from(decimal.value.s);
const digitsStringPtr = await __newString(digitsBigNumber.mul(signBigNumber).toString());
const digitsBigInt = await BigInt.fromString(digitsStringPtr);
const digitsBigInt = await ASBigInt.fromString(digitsStringPtr);
// Create an exp BigInt.
const expStringPtr = await __newString(exp.toString());
const expBigInt = await BigInt.fromString(expStringPtr);
const expBigInt = await ASBigInt.fromString(expStringPtr);
// Create a BigDecimal using digits and exp BigInts.
const bigDecimal = await BigDecimal.__new(digitsBigInt);
@@ -429,73 +430,73 @@ export const instantiate = async (
const uint8ArrayId = await getIdOfType(TypeId.Uint8Array);
const ptr = await __newArray(uint8ArrayId, bytes);
const bigInt = await BigInt.fromSignedBytes(ptr);
const bigInt = await ASBigInt.fromSignedBytes(ptr);
return bigInt;
},
'bigInt.plus': async (x: number, y: number) => {
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
const yBigInt = await BigInt.wrap(y);
const yBigInt = await ASBigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
const sum = xBigNumber.add(yBigNumber);
const ptr = await __newString(sum.toString());
const sumBigInt = await BigInt.fromString(ptr);
const sumBigInt = await ASBigInt.fromString(ptr);
return sumBigInt;
},
'bigInt.minus': async (x: number, y: number) => {
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
const yBigInt = await BigInt.wrap(y);
const yBigInt = await ASBigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
const diff = xBigNumber.sub(yBigNumber);
const ptr = await __newString(diff.toString());
const diffBigInt = BigInt.fromString(ptr);
const diffBigInt = ASBigInt.fromString(ptr);
return diffBigInt;
},
'bigInt.times': async (x: number, y: number) => {
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
const yBigInt = await BigInt.wrap(y);
const yBigInt = await ASBigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
const product = xBigNumber.mul(yBigNumber);
const ptr = await __newString(product.toString());
const productBigInt = BigInt.fromString(ptr);
const productBigInt = ASBigInt.fromString(ptr);
return productBigInt;
},
'bigInt.dividedBy': async (x: number, y: number) => {
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
const yBigInt = await BigInt.wrap(y);
const yBigInt = await ASBigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
const quotient = xBigNumber.div(yBigNumber);
const ptr = await __newString(quotient.toString());
const quotientBigInt = BigInt.fromString(ptr);
const quotientBigInt = ASBigInt.fromString(ptr);
return quotientBigInt;
},
'bigInt.dividedByDecimal': async (x: number, y: number) => {
// Create a decimal out of bigInt x.
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xDecimal = new GraphDecimal(__getString(xStringPtr));
@@ -513,94 +514,94 @@ export const instantiate = async (
},
'bigInt.mod': async (x: number, y: number) => {
// Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Create a bigNumber y.
const yBigInt = await BigInt.wrap(y);
const yBigInt = await ASBigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
// Perform the bigNumber mod operation.
const remainder = xBigNumber.mod(yBigNumber);
const ptr = await __newString(remainder.toString());
const remainderBigInt = BigInt.fromString(ptr);
const remainderBigInt = ASBigInt.fromString(ptr);
return remainderBigInt;
},
'bigInt.bitOr': async (x: number, y: number) => {
// Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Create a bigNumber y.
const yBigInt = await BigInt.wrap(y);
const yBigInt = await ASBigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
// Perform the bigNumber bit or operation.
const res = xBigNumber.or(yBigNumber);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
const resBigInt = ASBigInt.fromString(ptr);
return resBigInt;
},
'bigInt.bitAnd': async (x: number, y: number) => {
// Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Create a bigNumber y.
const yBigInt = await BigInt.wrap(y);
const yBigInt = await ASBigInt.wrap(y);
const yStringPtr = await yBigInt.toString();
const yBigNumber = BigNumber.from(__getString(yStringPtr));
// Perform the bigNumber bit and operation.
const res = xBigNumber.and(yBigNumber);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
const resBigInt = ASBigInt.fromString(ptr);
return resBigInt;
},
'bigInt.leftShift': async (x: number, y: number) => {
// Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Perform the bigNumber left shift operation.
const res = xBigNumber.shl(y);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
const resBigInt = ASBigInt.fromString(ptr);
return resBigInt;
},
'bigInt.rightShift': async (x: number, y: number) => {
// Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Perform the bigNumber right shift operation.
const res = xBigNumber.shr(y);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
const resBigInt = ASBigInt.fromString(ptr);
return resBigInt;
},
'bigInt.pow': async (x: number, y: number) => {
// Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xBigInt = await ASBigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Perform the bigNumber pow operation.
const res = xBigNumber.pow(y);
const ptr = await __newString(res.toString());
const resBigInt = BigInt.fromString(ptr);
const resBigInt = ASBigInt.fromString(ptr);
return resBigInt;
}
@@ -621,6 +622,37 @@ export const instantiate = async (
assert(dataSource);
return __newString(dataSource.network);
}
},
json: {
'json.fromBytes': async (bytes: number) => {
return jsonFromBytes(instanceExports, bytes);
},
'json.try_fromBytes': async (bytes: number) => {
try {
const jsonValue = await jsonFromBytes(instanceExports, bytes);
return JSONResult.__new(jsonValue);
} catch (error) {
log('json.try_fromBytes error', error);
return JSONResult.__new(null);
}
},
// TODO: Number methods do not work as 64bit values are not supported in js.
// Tried solution in https://github.com/AssemblyScript/assemblyscript/issues/117#issuecomment-531556954
'json.toI64': async (decimal: number) => {
return BigInt(__getString(decimal));
},
'json.toU64': async (decimal: number) => {
return BigInt(__getString(decimal));
},
'json.toF64': async (decimal: number) => {
return BigInt(__getString(decimal));
},
// TODO: Debug toBigInt not working.
'json.toBigInt': async (decimal: number) => {
return ASBigInt.fromString(decimal);
}
}
};
@@ -632,11 +664,12 @@ export const instantiate = async (
// TODO: Assign from types file generated by graph-cli
const getIdOfType: idOfType = instanceExports.id_of_type as idOfType;
const BigDecimal: any = instanceExports.BigDecimal as any;
const BigInt: any = instanceExports.BigInt as any;
const ASBigInt: any = instanceExports.BigInt as any;
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;
const JSONResult: any = instanceExports.JSONResult as any;
return instance;
};
+10
View File
@@ -3,6 +3,7 @@
//
// Enum types from @graphprotocol/graph-ts.
export enum TypeId {
String = 0,
ArrayBuffer = 1,
@@ -89,3 +90,12 @@ export enum Level {
INFO = 3,
DEBUG = 4,
}
export enum JSONValueKind {
NULL = 0,
BOOL = 1,
NUMBER = 2,
STRING = 3,
ARRAY = 4,
OBJECT = 5,
}
+62 -4
View File
@@ -548,7 +548,7 @@ const parseEntityValue = async (instanceExports: any, valuePtr: number) => {
const {
__getString,
__getArray,
BigInt: ExportBigInt,
BigInt: ASBigInt,
Bytes,
BigDecimal,
Value
@@ -583,7 +583,7 @@ const parseEntityValue = async (instanceExports: any, valuePtr: number) => {
case ValueKind.BIGINT: {
const bigIntPtr = await value.toBigInt();
const bigInt = ExportBigInt.wrap(bigIntPtr);
const bigInt = ASBigInt.wrap(bigIntPtr);
const bigIntStringPtr = await bigInt.toString();
const bigIntString = __getString(bigIntStringPtr);
@@ -616,7 +616,7 @@ const parseEntityValue = async (instanceExports: any, valuePtr: number) => {
};
const formatEntityValue = async (instanceExports: any, subgraphValue: any, type: string, value: any, isArray: boolean): Promise<any> => {
const { __newString, __newArray, BigInt: ExportBigInt, Value, ByteArray, Bytes, BigDecimal, id_of_type: getIdOfType } = instanceExports;
const { __newString, __newArray, BigInt: ASBigInt, Value, ByteArray, Bytes, BigDecimal, id_of_type: getIdOfType } = instanceExports;
if (isArray) {
const dataArrayPromises = value.map((el: any) => formatEntityValue(instanceExports, subgraphValue, type, el, false));
@@ -645,7 +645,7 @@ const formatEntityValue = async (instanceExports: any, subgraphValue: any, type:
case 'BigInt': {
const valueStringPtr = await __newString(value.toString());
const bigInt = await ExportBigInt.fromString(valueStringPtr);
const bigInt = await ASBigInt.fromString(valueStringPtr);
return Value.fromBigInt(bigInt);
}
@@ -695,3 +695,61 @@ export const resolveEntityFieldConflicts = (entity: any): any => {
return entity;
};
export const toJSONValue = async (instanceExports: any, value: any): Promise<any> => {
const { CustomJSONValue, JSONValueTypedMap, __newString, __newArray, id_of_type: getIdOfType } = instanceExports;
if (!value) {
return CustomJSONValue.fromNull();
}
if (Array.isArray(value)) {
const arrayPromise = value.map(async (el: any) => toJSONValue(instanceExports, el));
const array = await Promise.all(arrayPromise);
const arrayJsonValueId = await getIdOfType(TypeId.ArrayJsonValue);
const arrayPtr = __newArray(arrayJsonValueId, array);
return CustomJSONValue.fromArray(arrayPtr);
}
if (typeof value === 'object') {
const map = await JSONValueTypedMap.__new();
const valuePromises = Object.entries(value).map(async ([key, value]) => {
const valuePtr = await toJSONValue(instanceExports, value);
const keyPtr = await __newString(key);
await map.set(keyPtr, valuePtr);
});
await Promise.all(valuePromises);
return CustomJSONValue.fromObject(map);
}
if (typeof value === 'string') {
const stringPtr = await __newString(value);
return CustomJSONValue.fromString(stringPtr);
}
if (typeof value === 'number') {
const stringPtr = await __newString(value.toString());
return CustomJSONValue.fromNumber(stringPtr);
}
if (typeof value === 'boolean') {
return CustomJSONValue.fromBoolean(value);
}
};
export const jsonFromBytes = async (instanceExports: any, bytesPtr: number): Promise<any> => {
const { ByteArray, __getString } = instanceExports;
const byteArray = await ByteArray.wrap(bytesPtr);
const jsonStringPtr = await byteArray.toString();
const json = JSON.parse(__getString(jsonStringPtr));
const jsonValue = await toJSONValue(instanceExports, json);
return jsonValue;
};