Add support for subgraph schema data types (#46)

* Fix packages build

* Add support for boolean type in subgraph schema

* Add BigDecimal fromString and toString implementation

* Add implementation for subgraph Bytes type

* Implement enum type for subgraph schema

* Add implementation for subgraph schema BigDecimal type

* JSON stringify and parse entity data for auto-diff
This commit is contained in:
nikugogoi
2021-12-28 16:08:05 +05:30
committed by nabarun
parent 73dcb882a6
commit 47cef8d5d7
13 changed files with 346 additions and 162 deletions
+36 -60
View File
@@ -12,6 +12,8 @@ import {
Contract,
ContractInterface
} from 'ethers';
import Decimal from 'decimal.js';
import JSONbig from 'json-bigint';
import { IndexerInterface } from '@vulcanize/util';
@@ -73,7 +75,9 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
// Prepare the diff data.
const diffData: any = { state: {} };
diffData.state[entityName] = dbData;
// JSON stringify and parse data for handling unknown types when encoding.
// For example, decimal.js values are converted to string in the diff data.
diffData.state[entityName] = JSONbig.parse(JSONbig.stringify(dbData));
// Create an auto-diff.
assert(indexer.createDiffStaged);
@@ -81,65 +85,10 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
await indexer.createDiffStaged(dataSource.address, context.event.block.blockHash, diffData);
},
'typeConversion.stringToH160': () => {
console.log('index typeConversion.stringToH160');
},
'typeConversion.bytesToHex': () => {
console.log('index typeConversion.bytesToHex');
},
// 'typeConversion.bytesToString': () => {
// console.log('typeConversion.bytesToString');
// },
'typeConversion.bigIntToString': () => {
console.log('index typeConversion.bigIntToString');
},
// 'bigDecimal.fromString': () => {
// console.log('bigDecimal.fromString');
// },
// 'bigDecimal.times': () => {
// console.log('bigDecimal.times');
// },
'bigDecimal.dividedBy': () => {
console.log('bigDecimal.dividedBy');
},
// 'bigDecimal.plus': () => {
// console.log('bigDecimal.plus');
// },
// 'bigDecimal.minus': () => {
// console.log('bigDecimal.minus');
// },
'bigInt.plus': () => {
console.log('bigInt.plus');
},
'bigInt.minus': () => {
console.log('bigInt.minus');
},
'bigInt.times': () => {
console.log('bigInt.times');
},
'bigInt.dividedBy': () => {
console.log('bigInt.dividedBy');
},
// 'bigInt.mod': () => {
// console.log('bigInt.mod');
// },
'bigInt.fromString': () => {
console.log('bigInt.fromString');
},
'log.log': (_: number, msg: number) => {
console.log('log.log', __getString(msg));
},
// 'dataSource.create': () => {
// console.log('dataSource.create');
// },
'dataSource.address': () => {
console.log('dataSource.address');
},
'test.asyncMethod': async () => {
console.log('before timer start');
await new Promise(resolve => {
@@ -253,11 +202,38 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
console.log('y digits and exp', yDigits, yExp);
},
'bigDecimal.toString': () => {
console.log('numbers bigDecimal.toString');
'bigDecimal.toString': async (bigDecimal: number) => {
const bigDecimalInstance = BigDecimal.wrap(bigDecimal);
const digitsBigInt = BigInt.wrap(await bigDecimalInstance.digits);
const expBigInt = BigInt.wrap(await bigDecimalInstance.exp);
const digits = __getString(await digitsBigInt.toString());
const exp = __getString(await expBigInt.toString());
const decimal = new Decimal(`${digits}e${exp}`);
const ptr = __newString(decimal.toFixed());
return ptr;
},
'bigDecimal.fromString': () => {
console.log('numbers bigDecimal.toString');
'bigDecimal.fromString': async (s: number) => {
const string = __getString(s);
const decimal = new Decimal(string);
// Convert from digits array to BigInt.
const digits = decimal.d.join('');
const digitsBigNumber = BigNumber.from(digits);
const signBigNumber = BigNumber.from(decimal.s);
const digitsBigInt = await BigInt.fromString(await __newString(digitsBigNumber.mul(signBigNumber).toString()));
// Calculate exp after converting digits to BigInt above.
const exp = decimal.e - digits.length + 1;
const expBigInt = await BigInt.fromString(await __newString(exp.toString()));
const bigDecimal = await BigDecimal.__new(digitsBigInt);
bigDecimal.exp = expBigInt;
return bigDecimal;
},
'bigDecimal.plus': () => {
console.log('bigDecimal.plus');
+40 -4
View File
@@ -28,11 +28,22 @@ describe('numbers wasm tests', () => {
_start();
});
it('should execute bigInt fromString API', async () => {
const { testBigIntFromString, __getString } = exports;
describe('should execute bigInt fromString API', () => {
let testBigIntFromString: any, __getString: any, __newString: any;
const ptr = await testBigIntFromString();
expect(__getString(ptr)).to.equal('123');
before(() => {
({ testBigIntFromString, __getString, __newString } = exports);
});
it('should get bigInt for positive numbers', async () => {
const ptr = await testBigIntFromString(await __newString('123'));
expect(__getString(ptr)).to.equal('123');
});
xit('should get bigInt for negative numbers', async () => {
const ptr = await testBigIntFromString(await __newString('-123'));
expect(__getString(ptr)).to.equal('-123');
});
});
it('should execute bigInt plus API', async () => {
@@ -63,6 +74,31 @@ describe('numbers wasm tests', () => {
expect(__getString(ptr)).to.equal('100');
});
it('should execute bigDecimal toString API', async () => {
const { testBigDecimalToString, __getString } = exports;
const ptr = await testBigDecimalToString();
expect(__getString(ptr)).to.equal('1000000000000000000');
});
describe('should execute bigDecimal fromString API', () => {
let testBigDecimalFromString: any, __getString: any, __newString: any;
before(() => {
({ testBigDecimalFromString, __getString, __newString } = exports);
});
it('should get bigDecimal for numbers without decimals', async () => {
const ptr = await testBigDecimalFromString(await __newString('4.321e+4'));
expect(__getString(ptr)).to.equal('43210');
});
xit('should get bigDecimal for numbers with decimals', async () => {
const ptr = await testBigDecimalFromString(await __newString('5032485723458348569331745.33434346346912144534543'));
expect(__getString(ptr)).to.equal('5032485723458348569331745.33434346346912144534543');
});
});
xit('should execute bigDecimal dividedBy API', () => {
const { testBigDecimalDividedBy, __getString } = exports;
+14 -2
View File
@@ -58,8 +58,8 @@ export enum TypeId {
ArrayBigDecimal = 51,
}
// ValueKind from https://github.com/graphprotocol/graph-ts/blob/master/chain/ethereum.ts#L13
export enum ValueKind {
// ethereum ValueKind from https://github.com/graphprotocol/graph-ts/blob/master/chain/ethereum.ts#L13
export enum EthereumValueKind {
ADDRESS = 0,
FIXED_BYTES = 1,
BYTES = 2,
@@ -71,3 +71,15 @@ export enum ValueKind {
ARRAY = 8,
TUPLE = 9,
}
// ValueKind from https://github.com/graphprotocol/graph-ts/blob/master/common/value.ts#L8
export enum ValueKind {
STRING = 0,
INT = 1,
BIGDECIMAL = 2,
BOOL = 3,
ARRAY = 4,
NULL = 5,
BYTES = 6,
BIGINT = 7,
}
+65 -11
View File
@@ -4,7 +4,8 @@ import fs from 'fs-extra';
import debug from 'debug';
import yaml from 'js-yaml';
import { TypeId, ValueKind } from './types';
import { TypeId, EthereumValueKind, ValueKind } from './types';
import Decimal from 'decimal.js';
const log = debug('vulcanize:utils');
@@ -55,26 +56,26 @@ export const fromEthereumValue = async (instanceExports: any, value: any): Promi
const kind = await value.kind;
switch (kind) {
case ValueKind.ADDRESS: {
case EthereumValueKind.ADDRESS: {
const address = Address.wrap(await value.toAddress());
const addressStringPtr = await address.toHexString();
return __getString(addressStringPtr);
}
case ValueKind.BOOL: {
case EthereumValueKind.BOOL: {
const bool = await value.toBoolean();
return Boolean(bool);
}
case ValueKind.BYTES:
case ValueKind.FIXED_BYTES: {
case EthereumValueKind.BYTES:
case EthereumValueKind.FIXED_BYTES: {
const bytes = await value.toBytes();
const bytesStringPtr = await bytes.toHexString();
return __getString(bytesStringPtr);
}
case ValueKind.INT:
case ValueKind.UINT: {
case EthereumValueKind.INT:
case EthereumValueKind.UINT: {
const bigInt = BigInt.wrap(await value.toBigInt());
const bigIntStringPtr = await bigInt.toString();
const bigIntString = __getString(bigIntStringPtr);
@@ -292,7 +293,7 @@ export const getSubgraphConfig = async (subgraphPath: string): Promise<any> => {
};
export const toEntityValue = async (instanceExports: any, entityInstance: any, data: any, type: string, key: string) => {
const { __newString, BigInt: ExportBigInt } = instanceExports;
const { __newString, BigInt: ExportBigInt, Value, ByteArray, Bytes, BigDecimal } = instanceExports;
const entityKey = await __newString(key);
const value = data[key];
@@ -300,7 +301,20 @@ export const toEntityValue = async (instanceExports: any, entityInstance: any, d
case 'varchar': {
const entityValue = await __newString(value);
return entityInstance.setString(entityKey, entityValue);
const graphValue = Value.wrap(await entityInstance.get(entityKey));
const kind = await graphValue.kind;
switch (kind) {
case ValueKind.BYTES: {
const byteArray = await ByteArray.fromHexString(entityValue);
const bytes = await Bytes.fromByteArray(byteArray);
return entityInstance.setBytes(entityKey, bytes);
}
default:
return entityInstance.setString(entityKey, entityValue);
}
}
case 'integer': {
@@ -313,6 +327,20 @@ export const toEntityValue = async (instanceExports: any, entityInstance: any, d
return entityInstance.setBigInt(entityKey, bigInt);
}
case 'boolean': {
return entityInstance.setBoolean(entityKey, value ? 1 : 0);
}
case 'enum': {
const entityValue = await __newString(value);
return entityInstance.setString(entityKey, entityValue);
}
case 'numeric': {
const bigDecimal = await BigDecimal.fromString(await __newString(value.toString()));
return entityInstance.setBigDecimal(entityKey, bigDecimal);
}
// TODO: Support more types.
default:
throw new Error(`Unsupported type: ${type}`);
@@ -320,12 +348,25 @@ export const toEntityValue = async (instanceExports: any, entityInstance: any, d
};
export const fromEntityValue = async (instanceExports: any, entityInstance: any, type: string, key: string): Promise<any> => {
const { __newString, __getString, BigInt: ExportBigInt } = instanceExports;
const { __newString, __getString, BigInt: ExportBigInt, Value, BigDecimal, Bytes } = instanceExports;
const entityKey = await __newString(key);
switch (type) {
case 'varchar': {
return __getString(await entityInstance.getString(entityKey));
const value = Value.wrap(await entityInstance.get(entityKey));
const kind = await value.kind;
switch (kind) {
case ValueKind.BYTES: {
const bytes = await Bytes.wrap(await value.toBytes());
const bytesStringPtr = await bytes.toHexString();
return __getString(bytesStringPtr);
}
default:
return __getString(await entityInstance.getString(entityKey));
}
}
case 'integer': {
@@ -337,6 +378,19 @@ export const fromEntityValue = async (instanceExports: any, entityInstance: any,
return BigInt(__getString(await bigInt.toString()));
}
case 'boolean': {
return Boolean(await entityInstance.getBoolean(entityKey));
}
case 'enum': {
return __getString(await entityInstance.getString(entityKey));
}
case 'numeric': {
const bigDecimal = BigDecimal.wrap(await entityInstance.getBigDecimal(entityKey));
return new Decimal(__getString(await bigDecimal.toString()));
}
// TODO: Support more types.
default:
throw new Error(`Unsupported type: ${type}`);