Implement bigDecimal host API (#62)

* Handle negative bigInt in store host API

* Increase size of bigInt in fromString API to support UInt256

* Implement bigDecimal divideBy host API

* Implement bigDecimal plus, minus and times host API

* Implement bigInt dividedByDecimal and mod host API

* Change BN js version to that used by ethers js
This commit is contained in:
prathamesh0 2021-11-25 16:19:45 +05:30 committed by nabarun
parent 129ba694fb
commit b04f6f2fba
5 changed files with 304 additions and 56 deletions

View File

@ -41,6 +41,7 @@
"@vulcanize/assemblyscript": "0.0.1", "@vulcanize/assemblyscript": "0.0.1",
"@vulcanize/ipld-eth-client": "^0.1.0", "@vulcanize/ipld-eth-client": "^0.1.0",
"@vulcanize/util": "^0.1.0", "@vulcanize/util": "^0.1.0",
"bn.js": "^4.11.9",
"debug": "^4.3.1", "debug": "^4.3.1",
"decimal.js": "^10.3.1", "decimal.js": "^10.3.1",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",

View File

@ -4,7 +4,6 @@
import assert from 'assert'; import assert from 'assert';
import fs from 'fs/promises'; import fs from 'fs/promises';
import loader from '@vulcanize/assemblyscript/lib/loader';
import { import {
utils, utils,
BigNumber, BigNumber,
@ -14,7 +13,9 @@ import {
} from 'ethers'; } from 'ethers';
import Decimal from 'decimal.js'; import Decimal from 'decimal.js';
import JSONbig from 'json-bigint'; import JSONbig from 'json-bigint';
import BN from 'bn.js';
import loader from '@vulcanize/assemblyscript/lib/loader';
import { IndexerInterface } from '@vulcanize/util'; import { IndexerInterface } from '@vulcanize/util';
import { TypeId } from './types'; import { TypeId } from './types';
@ -23,6 +24,18 @@ import { Database } from './database';
const NETWORK_URL = 'http://127.0.0.1:8081'; const NETWORK_URL = 'http://127.0.0.1:8081';
// Size (in bytes) of the BN used in bigInt store host API.
// The BN is being stored as a byte array in wasm memory in 2's compliment representation and interpreted as such in other APIs.
// 33 bytes is chosen so that it can support:
// - Int256 (32 bytes sufficient)
// - UInt256 (33 bytes required as we are storing the 2's compliment)
const BN_SIZE = 33;
// Endianness of BN used in bigInt store host API.
// Negative bigInt is being stored in wasm in 2's compliment, 'le' representation.
// (for eg. bigInt.fromString(negativeI32Value))
const BN_ENDIANNESS = 'le';
type idOfType = (TypeId: number) => number type idOfType = (TypeId: number) => number
interface DataSource { interface DataSource {
@ -185,8 +198,14 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
'typeConversion.bigIntToString': (bigInt: number) => { 'typeConversion.bigIntToString': (bigInt: number) => {
const bigIntByteArray = __getArray(bigInt); const bigIntByteArray = __getArray(bigInt);
const bigNumber = BigNumber.from(bigIntByteArray);
const ptr = __newString(bigNumber.toString()); // Create a BN with 'le' endianness.
const bigNumber = new BN(bigIntByteArray, BN_ENDIANNESS);
// Convert BN from two's compliment and to string.
const bigNumberString = bigNumber.fromTwos(bigIntByteArray.length * 8).toString();
const ptr = __newString(bigNumberString);
return ptr; return ptr;
}, },
@ -210,19 +229,22 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
}, },
numbers: { numbers: {
'bigDecimal.dividedBy': async (x: number, y: number) => { 'bigDecimal.dividedBy': async (x: number, y: number) => {
console.log('numbers bigDecimal.dividedBy'); // Creating decimal x.
const xBigDecimal = await BigDecimal.wrap(x);
const xStringPtr = await xBigDecimal.toString();
const xDecimal = new Decimal(__getString(xStringPtr));
const bigDecimaly = BigDecimal.wrap(y); // Create decimal y.
const yBigDecimal = await BigDecimal.wrap(y);
const yStringPtr = await yBigDecimal.toString();
const yDecimal = new Decimal(__getString(yStringPtr));
const digitsPtr = await bigDecimaly.digits; // Performing the decimal division operation.
const yDigitsBigIntArray = __getArray(digitsPtr); const divResult = xDecimal.dividedBy(yDecimal);
const yDigits = BigNumber.from(yDigitsBigIntArray); const ptr = await __newString(divResult.toString());
const divResultBigDecimal = await BigDecimal.fromString(ptr);
const expPtr = await bigDecimaly.exp; return divResultBigDecimal;
const yExpBigIntArray = __getArray(expPtr);
const yExp = BigNumber.from(yExpBigIntArray);
console.log('y digits and exp', yDigits, yExp);
}, },
'bigDecimal.toString': async (bigDecimal: number) => { 'bigDecimal.toString': async (bigDecimal: number) => {
const bigDecimalInstance = BigDecimal.wrap(bigDecimal); const bigDecimalInstance = BigDecimal.wrap(bigDecimal);
@ -265,21 +287,73 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
return bigDecimal; return bigDecimal;
}, },
'bigDecimal.plus': () => { 'bigDecimal.plus': async (x: number, y: number) => {
console.log('bigDecimal.plus'); // Create decimal x string.
const xBigDecimal = await BigDecimal.wrap(x);
const xStringPtr = await xBigDecimal.toString();
const xDecimalString = __getString(xStringPtr);
// Create decimal y string.
const yBigDecimal = await BigDecimal.wrap(y);
const yStringPtr = await yBigDecimal.toString();
const yDecimalString = __getString(yStringPtr);
// Perform the decimal sum operation.
const sumResult = Decimal.sum(xDecimalString, yDecimalString);
const ptr = await __newString(sumResult.toString());
const sumResultBigDecimal = await BigDecimal.fromString(ptr);
return sumResultBigDecimal;
}, },
'bigDecimal.minus': () => { 'bigDecimal.minus': async (x: number, y: number) => {
console.log('bigDecimal.minus'); // Create decimal x string.
const xBigDecimal = await BigDecimal.wrap(x);
const xStringPtr = await xBigDecimal.toString();
const xDecimalString = __getString(xStringPtr);
// Create decimal y string.
const yBigDecimal = await BigDecimal.wrap(y);
const yStringPtr = await yBigDecimal.toString();
const yDecimalString = __getString(yStringPtr);
// Perform the decimal sub operation.
const subResult = Decimal.sub(xDecimalString, yDecimalString);
const ptr = await __newString(subResult.toString());
const subResultBigDecimal = await BigDecimal.fromString(ptr);
return subResultBigDecimal;
}, },
'bigDecimal.times': () => { 'bigDecimal.times': async (x: number, y: number) => {
console.log('bigDecimal.times'); // Create decimal x string.
const xBigDecimal = await BigDecimal.wrap(x);
const xStringPtr = await xBigDecimal.toString();
const xDecimalString = __getString(xStringPtr);
// Create decimal y string.
const yBigDecimal = await BigDecimal.wrap(y);
const yStringPtr = await yBigDecimal.toString();
const yDecimalString = __getString(yStringPtr);
// Perform the decimal mul operation.
const mulResult = Decimal.mul(xDecimalString, yDecimalString);
const ptr = await __newString(mulResult.toString());
const mulResultBigDecimal = await BigDecimal.fromString(ptr);
return mulResultBigDecimal;
}, },
'bigInt.fromString': async (s: number) => { 'bigInt.fromString': async (s: number) => {
const string = __getString(s); const string = __getString(s);
const bigNumber = BigNumber.from(string);
const hex = bigNumber.toHexString(); // Create a BN in 2's compliment representation.
const bytes = utils.arrayify(hex); // Need to use BN as ethers.BigNumber:
// Doesn't store -ve numbers in 2's compilment form
// Stores in big endian form.
let bigNumber = new BN(string);
bigNumber = bigNumber.toTwos(BN_SIZE * 8);
// Create an array out of BN in 'le' endianness.
const bytes = bigNumber.toArray(BN_ENDIANNESS, BN_SIZE);
const uint8ArrayId = await getIdOfType(TypeId.Uint8Array); const uint8ArrayId = await getIdOfType(TypeId.Uint8Array);
const ptr = await __newArray(uint8ArrayId, bytes); const ptr = await __newArray(uint8ArrayId, bytes);
@ -347,11 +421,41 @@ export const instantiate = async (database: Database, indexer: IndexerInterface,
return quotientBigInt; return quotientBigInt;
}, },
'bigInt.dividedByDecimal': () => { 'bigInt.dividedByDecimal': async (x: number, y: number) => {
console.log('bigInt.dividedByDecimal'); // Create a decimal out of bigInt x.
const xBigInt = await BigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xDecimal = new Decimal(__getString(xStringPtr));
// Create decimal y.
const yBigDecimal = await BigDecimal.wrap(y);
const yStringPtr = await yBigDecimal.toString();
const yDecimal = new Decimal(__getString(yStringPtr));
// Perform the decimal division operation.
const divResult = xDecimal.dividedBy(yDecimal);
const ptr = await __newString(divResult.toString());
const divResultBigDecimal = await BigDecimal.fromString(ptr);
return divResultBigDecimal;
}, },
'bigInt.mod': () => { 'bigInt.mod': async (x: number, y: number) => {
console.log('bigInt.mod'); // Create a bigNumber x.
const xBigInt = await BigInt.wrap(x);
const xStringPtr = await xBigInt.toString();
const xBigNumber = BigNumber.from(__getString(xStringPtr));
// Create a bigNumber y.
const yBigInt = await BigInt.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);
return remainderBigInt;
}, },
'bigInt.bitOr': () => { 'bigInt.bitOr': () => {
console.log('bigInt.bitOr'); console.log('bigInt.bitOr');

View File

@ -29,20 +29,28 @@ describe('numbers wasm tests', () => {
}); });
describe('should execute bigInt fromString API', () => { describe('should execute bigInt fromString API', () => {
let testBigIntFromString: any, __getString: any, __newString: any; let testBigIntFromString: any, testBigIntWithI32: any, __getString: any, __newString: any, __getArray: any;
before(() => { before(() => {
({ testBigIntFromString, __getString, __newString } = exports); ({ testBigIntFromString, testBigIntWithI32, __getString, __newString, __getArray } = exports);
}); });
it('should get bigInt for positive numbers', async () => { it('should get bigInt for positive numbers', async () => {
const ptr = await testBigIntFromString(await __newString('123')); const ptr = await testBigIntFromString(await __newString('923567899898'));
expect(__getString(ptr)).to.equal('123'); expect(__getString(ptr)).to.equal('923567899898');
}); });
xit('should get bigInt for negative numbers', async () => { it('should get bigInt for negative numbers', async () => {
const ptr = await testBigIntFromString(await __newString('-123')); const ptr = await testBigIntFromString(await __newString('-1506556'));
expect(__getString(ptr)).to.equal('-123'); expect(__getString(ptr)).to.equal('-1506556');
});
it('should give equal values for bigInt fromString and fromI32', async () => {
const ptr = await testBigIntWithI32(await __newString('-1506556'));
const ptrs = __getArray(ptr);
expect(__getString(ptrs[0])).to.equal(__getString(ptrs[1]));
expect(__getString(ptrs[2])).to.equal('0');
}); });
}); });
@ -74,11 +82,46 @@ describe('numbers wasm tests', () => {
expect(__getString(ptr)).to.equal('100'); expect(__getString(ptr)).to.equal('100');
}); });
it('should execute bigDecimal toString API', async () => { describe('should execute bigInt dividedByDecimal API', async () => {
const { testBigDecimalToString, __getString } = exports; let testBigIntDividedByDecimal: any, __newString: any, __getString: any;
const ptr = await testBigDecimalToString(); before(() => {
expect(__getString(ptr)).to.equal('1000000000000000000'); ({ testBigIntDividedByDecimal, __newString, __getString } = exports);
});
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');
});
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');
});
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');
});
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');
});
});
it('should execute bigInt mod API', async () => {
const { testBigIntMod, __getString, __newString } = exports;
const ptr = await testBigIntMod(await __newString('2315432122132354'), await __newString('5465265645'));
expect(__getString(ptr)).to.equal('1283174719');
});
it('should execute bigDecimal toString API', async () => {
const { testBigDecimalToString, __newString, __getString } = exports;
const ptr = await testBigDecimalToString(await __newString('-5032485723458348569331745849735.3343434634691214453454356561'));
expect(__getString(ptr)).to.equal('-5032485723458348569331745849735.3343434634691214453454356561');
}); });
describe('should execute bigDecimal fromString API', () => { describe('should execute bigDecimal fromString API', () => {
@ -93,17 +136,37 @@ describe('numbers wasm tests', () => {
expect(__getString(ptr)).to.equal('43210'); expect(__getString(ptr)).to.equal('43210');
}); });
xit('should get bigDecimal for numbers with decimals', async () => { it('should get bigDecimal for numbers with decimals', async () => {
const ptr = await testBigDecimalFromString(await __newString('5032485723458348569331745.33434346346912144534543')); const ptr = await testBigDecimalFromString(await __newString('-5032485723458348569331745849735.3343434634691214453454356561'));
expect(__getString(ptr)).to.equal('5032485723458348569331745.33434346346912144534543'); expect(__getString(ptr)).to.equal('-5032485723458348569331745849735.3343434634691214453454356561');
}); });
}); });
xit('should execute bigDecimal dividedBy API', () => { it('should execute bigDecimal plus API', async () => {
const { testBigDecimalDividedBy, __getString } = exports; const { testBigDecimalPlus, __getString, __newString } = exports;
const ptr = testBigDecimalDividedBy(); const ptr = await testBigDecimalPlus(await __newString('231543212.2132354'), await __newString('54652.65645'));
expect(__getString(ptr)).to.equal('10000000000000000'); expect(__getString(ptr)).to.equal('231597864.8696854');
console.log(__getString(ptr)); });
it('should execute bigDecimal minus API', async () => {
const { testBigDecimalMinus, __getString, __newString } = exports;
const ptr = await testBigDecimalMinus(await __newString('231543212.2132354'), await __newString('54652.65645'));
expect(__getString(ptr)).to.equal('231488559.5567854');
});
it('should execute bigDecimal times API', async () => {
const { testBigDecimalTimes, __getString, __newString } = exports;
const ptr = await testBigDecimalTimes(await __newString('231543212.2132354'), await __newString('54652.65645'));
expect(__getString(ptr)).to.equal('12654451630419.398459');
});
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');
}); });
}); });

View File

@ -171,13 +171,12 @@ export function testStringToH160 (): string {
return res; return res;
} }
export function testBigDecimalToString (): string { export function testBigDecimalToString (value: string): string {
log.debug('In test bigDecimalToString', []); log.debug('In test bigDecimalToString', []);
const bigInt = BigInt.fromString('1000000000000000000'); const bigDecimal = BigDecimal.fromString(value);
const bigDecimal = bigInt.toBigDecimal();
const res = bigDecimal.toString(); const res = bigDecimal.toString();
log.debug('typeConversion.bigIntToString from hex result: {}', [res]); log.debug('typeConversion.bigIntToString result: {}', [res]);
return res; return res;
} }
@ -192,20 +191,54 @@ export function testBigDecimalFromString (value: string): string {
return res; return res;
} }
export function testBigDecimalDividedBy (): string { export function testBigDecimalDividedBy (value1: string, value2: string): string {
log.debug('In test bigDecimal.dividedBy', []); log.debug('In test bigDecimal.dividedBy', []);
const bigInt1 = BigInt.fromString('1000000000000000000'); const bigDecimal1 = BigDecimal.fromString(value1);
const bigInt2 = BigInt.fromString('100'); const bigDecimal2 = BigDecimal.fromString(value2);
const bigDecimal1 = new BigDecimal(bigInt1);
const bigDecimal2 = new BigDecimal(bigInt2);
const res = bigDecimal1 / bigDecimal2; const res = bigDecimal1 / bigDecimal2;
log.debug('bigDecimal.dividedBy result: {}', [res.toString()]); log.debug('bigDecimal.dividedBy result: {}', [res.toString()]);
return res.toString(); return res.toString();
} }
export function testBigDecimalPlus (value1: string, value2: string): string {
log.debug('In test bigDecimal.plus', []);
const bigDecimal1 = BigDecimal.fromString(value1);
const bigDecimal2 = BigDecimal.fromString(value2);
const res = bigDecimal1 + bigDecimal2;
log.debug('bigDecimal.plus result: {}', [res.toString()]);
return res.toString();
}
export function testBigDecimalMinus (value1: string, value2: string): string {
log.debug('In test bigDecimal.minus', []);
const bigDecimal1 = BigDecimal.fromString(value1);
const bigDecimal2 = BigDecimal.fromString(value2);
const res = bigDecimal1 - bigDecimal2;
log.debug('bigDecimal.minus result: {}', [res.toString()]);
return res.toString();
}
export function testBigDecimalTimes (value1: string, value2: string): string {
log.debug('In test bigDecimal.times', []);
const bigDecimal1 = BigDecimal.fromString(value1);
const bigDecimal2 = BigDecimal.fromString(value2);
const res = bigDecimal1 * bigDecimal2;
log.debug('bigDecimal.times result: {}', [res.toString()]);
return res.toString();
}
export function testBigIntPlus (): string { export function testBigIntPlus (): string {
log.debug('In test bigInt.plus', []); log.debug('In test bigInt.plus', []);
@ -250,6 +283,28 @@ export function testBigIntDividedBy (): string {
return res.toString(); return res.toString();
} }
export function testBigIntDividedByDecimal (value1: string, value2: string): string {
log.debug('In test bigInt.dividedByDecimal', []);
const bigInt = BigInt.fromString(value1);
const bigDecimal = BigDecimal.fromString(value2);
const res = bigInt.divDecimal(bigDecimal);
log.debug('bigInt.dividedByDecimal result: {}', [res.toString()]);
return res.toString();
}
export function testBigIntMod (value1: string, value2: string): string {
log.debug('In test bigInt.mod', []);
const bigInt1 = BigInt.fromString(value1);
const bigInt2 = BigInt.fromString(value2);
const res = bigInt1.mod(bigInt2);
log.debug('bigInt.mod result: {}', [res.toString()]);
return res.toString();
}
export function testBigIntFromString (value: string): string { export function testBigIntFromString (value: string): string {
log.debug('In test bigInt.fromString', []); log.debug('In test bigInt.fromString', []);
@ -259,3 +314,23 @@ export function testBigIntFromString (value: string): string {
return res; return res;
} }
export function testBigIntWithI32 (value: string): string[] {
log.debug('In testBigIntWithI32', []);
const variableI32: i32 = parseInt(value) as i32;
const bigInt1 = BigInt.fromI32(variableI32);
const bigInt2 = BigInt.fromString(value);
const res1 = bigInt1.toString();
log.debug('bigInt.FromString result 1: {}', [res1]);
const res2 = bigInt2.toString();
log.debug('bigInt.FromString result 2: {}', [res2]);
const res3 = BigInt.compare(bigInt1, bigInt2).toString();
log.debug('bigInt.FromString result 3: {}', [res3]);
return [res1, res2, res3];
}

View File

@ -17,7 +17,11 @@ export const getDummyEventData = (): EventData => {
stateRoot: ZERO_HASH, stateRoot: ZERO_HASH,
td: ZERO_HASH, td: ZERO_HASH,
txRoot: ZERO_HASH, txRoot: ZERO_HASH,
receiptRoot: ZERO_HASH receiptRoot: ZERO_HASH,
uncleHash: ZERO_HASH,
difficulty: '0',
gasLimit: '0',
gasUsed: '0'
}; };
const tx = { const tx = {
@ -30,7 +34,8 @@ export const getDummyEventData = (): EventData => {
return { return {
block, block,
tx, tx,
eventParams: [], inputs: [],
event: {},
eventIndex: 0 eventIndex: 0
}; };
}; };