Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Implement Q96 number format - Closes #20 #35

Merged
merged 13 commits into from
Sep 12, 2022
48 changes: 45 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"@oclif/plugin-autocomplete": "0.3.0",
"@oclif/plugin-help": "3.2.2",
"axios": "0.21.2",
"bigint-buffer": "^1.1.5",
"fs-extra": "9.1.0",
"inquirer": "8.0.0",
"lisk-commander": "^6.0.0-alpha.0",
Expand Down
2 changes: 1 addition & 1 deletion src/app/modules/dex/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface FeeTiers {
[id: number]: number;
}

export type Q96 = BigInt;
export type Q96 = bigint;
export type TokenID = Buffer;
export type PoolID = Buffer;
export type PositionID = Buffer;
Expand Down
144 changes: 144 additions & 0 deletions src/app/modules/dex/utils/q96.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright © 2022 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*/

import {
toBufferBE
} from 'bigint-buffer';
import {
Q96
} from '../types';
import {
MAX_NUM_BYTES_Q96
} from '../constants';
import {
ONE,
TWO,
N_96
} from "./mathConstants";


//Fixed Point Arithmetic
export const numberToQ96 = (r: bigint): Q96 => {
const _exp: bigint = TWO ** N_96;
const _r = BigInt(r);
const num: bigint = _r * _exp;

return num;
}

export const roundDownQ96 = (a: Q96): Q96 => a >> N_96
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved

export const roundUpQ96 = (a: Q96) => {
if (a <= 0) {
throw new Error('Division by zero')
}
const _x = ONE >> N_96;
const _y = a % _x;

const _z = Number(_y);

if (_z === 0) {
return a >> N_96;
}

const _r = a >> N_96;
return _r + ONE;

}


// Arithmetic
export const addQ96 = (a: Q96, b: Q96): Q96 => a + b

export const subQ96 = (a: Q96, b: Q96): Q96 => {
if (a >= b) {
return a - b;
}

return BigInt(0); //To discuss
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved

}

export const mulQ96 = (a: Q96, b: Q96): Q96 => {
const _x: bigint = a * b;
return _x >> N_96;
}

export const divQ96 = (a: Q96, b: Q96): Q96 => {
const _x: bigint = a >> N_96;
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
return _x / b;
}

export const mulDivQ96 = (a: Q96, b: Q96, c: Q96): Q96 => {
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
const _x: bigint = a * b;
const _y: bigint = _x >> N_96;
const _z: bigint = _y / c;

return roundDownQ96(_z);
}

export const mulDivRoundUpQ96 = (a: Q96, b: Q96, c: Q96): Q96 => {
const _x: bigint = a * b;
const _y: bigint = _x >> N_96;
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
const _z: bigint = _y / c;

return roundUpQ96(_z)
}

export const q96ToInt = (a: Q96): bigint => roundDownQ96(a)

export const q96ToIntRoundUp = (a: Q96): bigint => roundUpQ96(a)

export const invQ96 = (a: Q96): Q96 => {
const _x: bigint = ONE >> N_96;
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
return divQ96(_x, a);
}


// Q96 Encoding and Decoding
export const bytesToQ96 = (numberBytes: Buffer): Q96 => {
if (numberBytes.length > MAX_NUM_BYTES_Q96) {
throw new Error();
}

const _hex: string[] = [];

for (let i = 0; i < numberBytes.length; i++) {
let current = numberBytes[i] < 0 ? numberBytes[i] + 256 : numberBytes[i];
_hex.push((current >>> 4).toString(16));
_hex.push((current & 0xF).toString(16));
}

const _hex_bi = _hex.join("");

//return big-endian decoding of bytes
return BigInt(`0x${_hex_bi}`);
}

export const q96ToBytes = (numberQ96: Q96): Buffer => {

const _hex: string = numberQ96.toString(16);
let _byteArr: number[] = [];

for (let c = 0; c < _hex.length; c += 2) {
_byteArr.push(parseInt(_hex.substring(c, 2), 16));
}

if (_byteArr.length > MAX_NUM_BYTES_Q96) {
throw new Error("Overflow when serializing a Q96 number")
}

// return result padded to length NUM_BYTES_Q96 with zero bytes
return toBufferBE(BigInt(`0x${_hex}`), MAX_NUM_BYTES_Q96) //big-endian encoding of numberQ96 as integer
}
54 changes: 54 additions & 0 deletions test/unit/modules/dex/q96.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
addQ96,
divQ96,
numberToQ96,
mulQ96,
subQ96,
bytesToQ96,
q96ToBytes,
q96ToInt,
mulDivQ96
} from '../../../../src/app/modules/dex/utils/q96';

import {
Q96
} from '../../../../src/app/modules/dex/types';

import {
MAX_SQRT_RATIO,
MAX_UINT_64
} from '../../../../src/app/modules/dex/constants';

describe('DexQ96Module', () => {
describe('constructor', () => {
const testValueMax: Q96 = numberToQ96(MAX_SQRT_RATIO);
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
const testValueMaxUint: Q96 = numberToQ96(BigInt(MAX_UINT_64));
const testValue2: Q96 = numberToQ96(BigInt(2));

it('add', async () => {
expect(q96ToInt(addQ96(testValueMaxUint, testValue2))).toBe(BigInt("18446744073709551617"));
});

it('sub', async () => {
expect(q96ToInt(subQ96(testValueMax, testValue2))).toBe(BigInt("1461446703529909599612049957420313862569572983182"));
});

it('mul', async () => {
expect(q96ToInt(mulQ96(testValueMaxUint, testValue2))).toBe(BigInt("36893488147419103230"));
});

it('div', async () => {
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
const test = mulQ96(testValue2, testValueMaxUint);
expect(divQ96(test, q96ToInt(testValueMaxUint))).toEqual(q96ToInt(testValue2));
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
});

it('mulDiv', async () => {
const test = divQ96(testValueMaxUint, testValue2);
expect(mulDivQ96(test, numberToQ96(BigInt('2')), numberToQ96(BigInt('2')))).toEqual(test);
emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
});

emiliolisk marked this conversation as resolved.
Show resolved Hide resolved
it('bytesToQ96 and q96ToBytes', async () => {
expect(bytesToQ96(q96ToBytes(testValueMaxUint))).toBe(testValueMaxUint)
});
});
});
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"target": "es2019",
"module": "commonjs",
Expand Down