Handle BigNumber event params and customize Decimal (#63)

* Handle BigNumber event params in watchers

* Customize decimal according to limits of IEEE-754 decimal128

* Add definition for custom scalar BigDecimal
This commit is contained in:
prathamesh0
2021-12-28 16:08:05 +05:30
committed by nabarun
parent 238ad21189
commit 94e9182dd3
19 changed files with 232 additions and 56 deletions
+12 -6
View File
@@ -35,11 +35,16 @@ describe('call handler in mapping code', () => {
sandbox.on(db, 'fromGraphEntity', async (instanceExports: any, block: Block, entity: string, entityInstance: any) => {
const entityFields = [
{ type: 'varchar', propertyName: 'blockHash' },
{ type: 'integer', propertyName: 'blockNumber' },
{ type: 'varchar', propertyName: 'id' },
{ type: 'bigint', propertyName: 'count' },
{ type: 'varchar', propertyName: 'param1' },
{ type: 'integer', propertyName: 'param2' }
{ type: 'varchar', propertyName: 'paramString' },
{ type: 'integer', propertyName: 'paramInt' },
{ type: 'bigint', propertyName: 'paramBigInt' },
{ type: 'boolean', propertyName: 'paramBoolean' },
{ type: 'varchar', propertyName: 'paramBytes' },
{ type: 'numeric', propertyName: 'paramBigDecimal' },
{ type: 'varchar', propertyName: 'related' },
{ type: 'varchar', propertyName: 'manyRelated' }
];
return db.getEntityValues(instanceExports, block, entityInstance, entityFields);
@@ -69,12 +74,13 @@ describe('call handler in mapping code', () => {
// Create event params data.
const contractInterface = new utils.Interface(abi);
const eventFragment = contractInterface.getEvent('Test(string,uint8)');
const eventFragment = contractInterface.getEvent('Test(string,uint8,uint256)');
dummyEventData.inputs = eventFragment.inputs;
dummyEventData.event = {
param1: 'abc',
param2: BigInt(123)
param2: BigInt(150),
param3: BigInt(564894232132154)
};
// Dummy contract address string.
+13 -12
View File
@@ -11,7 +11,6 @@ import {
Contract,
ContractInterface
} from 'ethers';
import Decimal from 'decimal.js';
import JSONbig from 'json-bigint';
import BN from 'bn.js';
@@ -19,7 +18,7 @@ import loader from '@vulcanize/assemblyscript/lib/loader';
import { IndexerInterface } from '@vulcanize/util';
import { TypeId } from './types';
import { Block, fromEthereumValue, toEthereumValue, resolveEntityFieldConflicts } from './utils';
import { Block, fromEthereumValue, toEthereumValue, resolveEntityFieldConflicts, GraphDecimal, digitsToString } from './utils';
import { Database } from './database';
const NETWORK_URL = 'http://127.0.0.1:8081';
@@ -238,12 +237,12 @@ export const instantiate = async (
// Creating decimal x.
const xBigDecimal = await BigDecimal.wrap(x);
const xStringPtr = await xBigDecimal.toString();
const xDecimal = new Decimal(__getString(xStringPtr));
const xDecimal = new GraphDecimal(__getString(xStringPtr));
// Create decimal y.
const yBigDecimal = await BigDecimal.wrap(y);
const yStringPtr = await yBigDecimal.toString();
const yDecimal = new Decimal(__getString(yStringPtr));
const yDecimal = new GraphDecimal(__getString(yStringPtr));
// Performing the decimal division operation.
const divResult = xDecimal.dividedBy(yDecimal);
@@ -267,17 +266,19 @@ export const instantiate = async (
const expStringPtr = await expBigInt.toString();
const exp = __getString(expStringPtr);
const decimal = new Decimal(`${digits}e${exp}`);
const decimal = new GraphDecimal(`${digits}e${exp}`);
const ptr = __newString(decimal.toFixed());
return ptr;
},
'bigDecimal.fromString': async (s: number) => {
const string = __getString(s);
const decimal = new Decimal(string);
// Creating a decimal with the configured precision applied.
const decimal = new GraphDecimal(string).toSignificantDigits();
// Convert from digits array to BigInt.
const digits = decimal.d.join('');
const digits = digitsToString(decimal.d);
const digitsBigNumber = BigNumber.from(digits);
const signBigNumber = BigNumber.from(decimal.s);
const digitsStringPtr = await __newString(digitsBigNumber.mul(signBigNumber).toString());
@@ -305,7 +306,7 @@ export const instantiate = async (
const yDecimalString = __getString(yStringPtr);
// Perform the decimal sum operation.
const sumResult = Decimal.sum(xDecimalString, yDecimalString);
const sumResult = GraphDecimal.sum(xDecimalString, yDecimalString);
const ptr = await __newString(sumResult.toString());
const sumResultBigDecimal = await BigDecimal.fromString(ptr);
@@ -323,7 +324,7 @@ export const instantiate = async (
const yDecimalString = __getString(yStringPtr);
// Perform the decimal sub operation.
const subResult = Decimal.sub(xDecimalString, yDecimalString);
const subResult = GraphDecimal.sub(xDecimalString, yDecimalString);
const ptr = await __newString(subResult.toString());
const subResultBigDecimal = await BigDecimal.fromString(ptr);
@@ -341,7 +342,7 @@ export const instantiate = async (
const yDecimalString = __getString(yStringPtr);
// Perform the decimal mul operation.
const mulResult = Decimal.mul(xDecimalString, yDecimalString);
const mulResult = GraphDecimal.mul(xDecimalString, yDecimalString);
const ptr = await __newString(mulResult.toString());
const mulResultBigDecimal = await BigDecimal.fromString(ptr);
@@ -431,12 +432,12 @@ export const instantiate = async (
// Create a decimal out of bigInt x.
const xBigInt = await BigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xDecimal = new Decimal(__getString(xStringPtr));
const xDecimal = new GraphDecimal(__getString(xStringPtr));
// Create decimal y.
const yBigDecimal = await BigDecimal.wrap(y);
const yStringPtr = await yBigDecimal.toString();
const yDecimal = new Decimal(__getString(yStringPtr));
const yDecimal = new GraphDecimal(__getString(yStringPtr));
// Perform the decimal division operation.
const divResult = xDecimal.dividedBy(yDecimal);
+12 -12
View File
@@ -90,23 +90,23 @@ describe('numbers wasm tests', () => {
});
it('should execute bigInt dividedByDecimal for positive dividend and positive divisor', async () => {
const ptr = await testBigIntDividedByDecimal(await __newString('2315432122132354'), await __newString('54652.65645'));
expect(__getString(ptr)).to.equal('42366323478.725506672');
const ptr = await testBigIntDividedByDecimal(await __newString('231543212213235645154'), await __newString('552.65645'));
expect(__getString(ptr)).to.equal('418964100053904455.7500414588484401');
});
it('should execute bigInt dividedByDecimal for negative dividend and positive divisor', async () => {
const ptr = await testBigIntDividedByDecimal(await __newString('-2315432122132354'), await __newString('54652.65645'));
expect(__getString(ptr)).to.equal('-42366323478.725506672');
const ptr = await testBigIntDividedByDecimal(await __newString('-231543212213235645154'), await __newString('552.65645'));
expect(__getString(ptr)).to.equal('-418964100053904455.7500414588484401');
});
it('should execute bigInt dividedByDecimal for positive dividend and negative divisor', async () => {
const ptr = await testBigIntDividedByDecimal(await __newString('2315432122132354'), await __newString('-54652.65645'));
expect(__getString(ptr)).to.equal('-42366323478.725506672');
const ptr = await testBigIntDividedByDecimal(await __newString('231543212213235645154'), await __newString('-552.65645'));
expect(__getString(ptr)).to.equal('-418964100053904455.7500414588484401');
});
it('should execute bigInt dividedByDecimal for negative dividend and negative divisor', async () => {
const ptr = await testBigIntDividedByDecimal(await __newString('-2315432122132354'), await __newString('-54652.65645'));
expect(__getString(ptr)).to.equal('42366323478.725506672');
const ptr = await testBigIntDividedByDecimal(await __newString('-231543212213235645154'), await __newString('-552.65645'));
expect(__getString(ptr)).to.equal('418964100053904455.7500414588484401');
});
});
@@ -121,7 +121,7 @@ describe('numbers wasm tests', () => {
const { testBigDecimalToString, __newString, __getString } = exports;
const ptr = await testBigDecimalToString(await __newString('-5032485723458348569331745849735.3343434634691214453454356561'));
expect(__getString(ptr)).to.equal('-5032485723458348569331745849735.3343434634691214453454356561');
expect(__getString(ptr)).to.equal('-5032485723458348569331745849735.334');
});
describe('should execute bigDecimal fromString API', () => {
@@ -138,7 +138,7 @@ describe('numbers wasm tests', () => {
it('should get bigDecimal for numbers with decimals', async () => {
const ptr = await testBigDecimalFromString(await __newString('-5032485723458348569331745849735.3343434634691214453454356561'));
expect(__getString(ptr)).to.equal('-5032485723458348569331745849735.3343434634691214453454356561');
expect(__getString(ptr)).to.equal('-5032485723458348569331745849735.334');
});
});
@@ -160,13 +160,13 @@ describe('numbers wasm tests', () => {
const { testBigDecimalTimes, __getString, __newString } = exports;
const ptr = await testBigDecimalTimes(await __newString('231543212.2132354'), await __newString('54652.65645'));
expect(__getString(ptr)).to.equal('12654451630419.398459');
expect(__getString(ptr)).to.equal('12654451630419.39845917833');
});
it('should execute bigDecimal dividedBy API', async () => {
const { testBigDecimalDividedBy, __getString, __newString } = exports;
const ptr = await testBigDecimalDividedBy(await __newString('231543212.2132354'), await __newString('54652.65645'));
expect(__getString(ptr)).to.equal('4236.6323478725506672');
expect(__getString(ptr)).to.equal('4236.632347872550667205491344419362');
});
});
+45 -1
View File
@@ -11,6 +11,13 @@ import { TypeId, EthereumValueKind, ValueKind } from './types';
const log = debug('vulcanize:utils');
// Customize Decimal according the limits of IEEE-754 decimal128.
// Reference: https://github.com/graphprotocol/graph-node/blob/v0.24.2/graph/src/data/store/scalar.rs#L42
export const GraphDecimal = Decimal.clone({ minE: -6143, maxE: 6144, precision: 34 });
// Constant used in function digitsToString.
const LOG_BASE = 7;
interface Transaction {
hash: string;
index: number;
@@ -436,7 +443,7 @@ const parseEntityValue = async (instanceExports: any, valuePtr: number) => {
const bigDecimal = BigDecimal.wrap(bigDecimalPtr);
const bigDecimalStringPtr = await bigDecimal.toString();
return new Decimal(__getString(bigDecimalStringPtr));
return new GraphDecimal(__getString(bigDecimalStringPtr)).toFixed();
}
case ValueKind.ARRAY: {
@@ -542,3 +549,40 @@ export const resolveEntityFieldConflicts = (entity: any): any => {
return entity;
};
// Get digits in a string from an array of digit numbers (Decimal().d)
// https://github.com/MikeMcl/decimal.js/blob/master/decimal.mjs#L2516
export function digitsToString (d: any) {
let i, k, ws;
const indexOfLastWord = d.length - 1;
let str = '';
let w = d[0];
if (indexOfLastWord > 0) {
str += w;
for (i = 1; i < indexOfLastWord; i++) {
ws = d[i] + '';
k = LOG_BASE - ws.length;
if (k) str += getZeroString(k);
str += ws;
}
w = d[i];
ws = w + '';
k = LOG_BASE - ws.length;
if (k) str += getZeroString(k);
} else if (w === 0) {
return '0';
}
// Remove trailing zeros of last w.
for (; w % 10 === 0;) w /= 10;
return str + w;
}
function getZeroString (k: any) {
let zs = '';
for (; k--;) zs += '0';
return zs;
}
@@ -10,7 +10,7 @@ contract Example {
uint128 bidAmount2;
}
event Test(string param1, uint8 param2);
event Test(string param1, uint8 param2, uint256 param3);
function getMethod() public view virtual returns (string memory)
{
@@ -30,7 +30,7 @@ contract Example {
}
function emitEvent() public virtual returns (bool) {
emit Test('abc', 123);
emit Test('abc', 150, 564894232132154);
return true;
}
@@ -13,6 +13,12 @@
"internalType": "uint8",
"name": "param2",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint256",
"name": "param3",
"type": "uint256"
}
],
"name": "Test",
@@ -30,6 +30,10 @@ export class Test__Params {
get param2(): i32 {
return this._event.parameters[1].value.toI32();
}
get param3(): BigInt {
return this._event.parameters[2].value.toBigInt();
}
}
export class Example1__structMethodResultValue0Struct extends ethereum.Tuple {
@@ -105,6 +105,7 @@ export class Author extends Entity {
this.set("name", Value.fromString(""));
this.set("rating", Value.fromBigDecimal(BigDecimal.zero()));
this.set("paramInt", Value.fromI32(0));
this.set("paramBigInt", Value.fromBigInt(BigInt.zero()));
this.set("paramBytes", Value.fromBytes(Bytes.empty()));
}
@@ -170,6 +171,15 @@ export class Author extends Entity {
this.set("paramInt", Value.fromI32(value));
}
get paramBigInt(): BigInt {
let value = this.get("paramBigInt");
return value!.toBigInt();
}
set paramBigInt(value: BigInt) {
this.set("paramBigInt", Value.fromBigInt(value));
}
get paramBytes(): Bytes {
let value = this.get("paramBytes");
return value!.toBytes();
@@ -18,6 +18,7 @@ type Author @entity {
name: String! # string
rating: BigDecimal!
paramInt: Int! # uint8
paramBigInt: BigInt! # uint256
paramBytes: Bytes!
blogs: [Blog!]! @derivedFrom(field: "author")
}
@@ -10,6 +10,7 @@ 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()]);
log.debug('event.params.param3: {}', [event.params.param3.toString()]);
log.debug('event.block.hash: {}', [event.block.hash.toHexString()]);
log.debug('event.block.stateRoot: {}', [event.block.stateRoot.toHexString()]);
@@ -32,8 +33,9 @@ export function handleTest (event: Test): void {
// Entity fields can be set based on event parameters
author.name = event.params.param1;
author.paramInt = event.params.param2;
author.paramBigInt = event.params.param3;
author.paramBytes = event.address;
author.rating = BigDecimal.fromString('4');
author.rating = BigDecimal.fromString('3.2132354');
// Entities can be written to the store with `.save()`
author.save();
@@ -19,7 +19,7 @@ dataSources:
- name: Example1
file: ./abis/Example1.json
eventHandlers:
- event: Test(string,uint8)
- event: Test(string,uint8,uint256)
handler: handleTest
blockHandlers:
- handler: handleBlock