Skip to content

Commit

Permalink
Extend the tests
Browse files Browse the repository at this point in the history
  • Loading branch information
barak manos committed Aug 15, 2024
1 parent 81301f3 commit a17e548
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 53 deletions.
9 changes: 5 additions & 4 deletions src/trade-matcher/trade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const mulDivF = (a: BigNumber, b: BigNumber, c: BigNumber) =>
check(a.mul(b).div(c), MAX_UINT256);
const mulDivC = (a: BigNumber, b: BigNumber, c: BigNumber) =>
check(a.mul(b).add(c).sub(1).div(c), MAX_UINT256);
const minFactor = (a: BigNumber, b: BigNumber) => mulDivC(a, b, MAX_UINT256);

//
// x * (A * y + B * z) ^ 2
Expand All @@ -50,8 +51,8 @@ const getEncodedTradeBySourceAmount = (
const temp2 = add(mul(y, A), mul(z, B));
const temp3 = mul(temp2, x);

const factor1 = mulDivC(temp1, temp1, MAX_UINT256);
const factor2 = mulDivC(temp3, A, MAX_UINT256);
const factor1 = minFactor(temp1, temp1);
const factor2 = minFactor(temp3, A);
const factor = BigNumberMax(factor1, factor2);

const temp4 = mulDivC(temp1, temp1, factor);
Expand Down Expand Up @@ -88,8 +89,8 @@ const getEncodedTradeByTargetAmount = (
const temp2 = add(mul(y, A), mul(z, B));
const temp3 = sub(temp2, mul(x, A));

const factor1 = mulDivC(temp1, temp1, MAX_UINT256);
const factor2 = mulDivC(temp2, temp3, MAX_UINT256);
const factor1 = minFactor(temp1, temp1);
const factor2 = minFactor(temp2, temp3);
const factor = BigNumberMax(factor1, factor2);

const temp4 = mulDivC(temp1, temp1, factor);
Expand Down
116 changes: 73 additions & 43 deletions src/trade-matcher/trade_gradient.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
import { BigNumber } from '../utils/numerics';

const R_ONE = BigNumber.from(1).shl(48); // = 2 ^ 48
const M_ONE = BigNumber.from(1).shl(24); // = 2 ^ 24
const EXP_ONE = BigNumber.from(1).shl(127); // = 2 ^ 127
const MAX_VAL = BigNumber.from(1).shl(131); // = 2 ^ 131
const RR = R_ONE.mul(R_ONE); // = 2 ^ 96
const MM = M_ONE.mul(M_ONE); // = 2 ^ 48
const RR_MUL_MM = RR.mul(MM); // = 2 ^ 144
const RR_DIV_MM = RR.div(MM); // = 2 ^ 48
const EXP_ONE_MUL_RR = EXP_ONE.mul(RR); // = 2 ^ 223
const EXP_ONE_DIV_RR = EXP_ONE.div(RR); // = 2 ^ 31
const EXP_ONE_DIV_MM = EXP_ONE.div(MM); // = 2 ^ 79

const MAX_UINT128 = BigNumber.from(2).pow(128).sub(1);
const MAX_UINT256 = BigNumber.from(2).pow(256).sub(1);

Expand All @@ -29,6 +17,25 @@ const mulDivF = (a: BigNumber, b: BigNumber, c: BigNumber) =>
check(a.mul(b).div(c), MAX_UINT256);
const mulDivC = (a: BigNumber, b: BigNumber, c: BigNumber) =>
check(a.mul(b).add(c).sub(1).div(c), MAX_UINT256);
const minFactor = (a: BigNumber, b: BigNumber) => mulDivC(a, b, MAX_UINT256);

const EXP_ONE = BigNumber.from("0x0080000000000000000000000000000000"); // 1
const EXP_MID = BigNumber.from("0x0400000000000000000000000000000000"); // 8
const EXP_MAX = BigNumber.from("0x2cb53f09f05cc627c85ddebfccfeb72758"); // ceil(ln2) * 129
const EXP_LN2 = BigNumber.from("0x0058b90bfbe8e7bcd5e4f1d9cc01f97b58"); // ceil(ln2)

const R_ONE = BigNumber.from(1).shl(48); // = 2 ^ 48
const M_ONE = BigNumber.from(1).shl(24); // = 2 ^ 24

const RR = R_ONE.mul(R_ONE); // = 2 ^ 96
const MM = M_ONE.mul(M_ONE); // = 2 ^ 48

const RR_MUL_MM = RR.mul(MM); // = 2 ^ 144
const RR_DIV_MM = RR.div(MM); // = 2 ^ 48

const EXP_ONE_MUL_RR = EXP_ONE.mul(RR); // = 2 ^ 223
const EXP_ONE_DIV_RR = EXP_ONE.div(RR); // = 2 ^ 31
const EXP_ONE_DIV_MM = EXP_ONE.div(MM); // = 2 ^ 79

enum GradientType {
LINEAR_INCREASE,
Expand Down Expand Up @@ -62,23 +69,23 @@ function calcSourceAmount(
}

/**
* @dev Given the following parameters:
* r - the gradient's initial exchange rate
* m - the gradient's multiplication factor
* t - the time elapsed since strategy creation
*
* Calculate the current exchange rate for each one of the following gradients:
* +----------------+-----------+-----------------+----------------------------------------------+
* | type | direction | formula | restriction |
* +----------------+-----------+-----------------+----------------------------------------------+
* | linear | increase | r * (1 + m * t) | |
* | linear | decrease | r * (1 - m * t) | m * t < 1 (ensure a finite-positive rate) |
* | linear-inverse | increase | r / (1 - m * t) | m * t < 1 (ensure a finite-positive rate) |
* | linear-inverse | decrease | r / (1 + m * t) | |
* | exponential | increase | r * e ^ (m * t) | m * t < 16 (due to computational limitation) |
* | exponential | decrease | r / e ^ (m * t) | m * t < 16 (due to computational limitation) |
* +----------------+-----------+-----------------+----------------------------------------------+
*/
* @dev Given the following parameters:
* r - the gradient's initial exchange rate
* m - the gradient's multiplication factor
* t - the time elapsed since strategy creation
*
* Calculate the current exchange rate for each one of the following gradients:
* +----------------+-----------+-----------------+----------------------------------------------+
* | type | direction | formula | restriction |
* +----------------+-----------+-----------------+----------------------------------------------+
* | linear | increase | r * (1 + m * t) | |
* | linear | decrease | r * (1 - m * t) | m * t < 1 (ensure a finite-positive rate) |
* | linear-inverse | increase | r / (1 - m * t) | m * t < 1 (ensure a finite-positive rate) |
* | linear-inverse | decrease | r / (1 + m * t) | |
* | exponential | increase | r * e ^ (m * t) | m * t < 129 * ln2 (computational limitation) |
* | exponential | decrease | r / e ^ (m * t) | m * t < 129 * ln2 (computational limitation) |
* +----------------+-----------+-----------------+----------------------------------------------+
*/
function calcCurrentRate(
gradientType: GradientType,
initialRate: BigNumber, // the 48-bit-mantissa-6-bit-exponent encoding of the initial exchange rate square root
Expand All @@ -104,7 +111,7 @@ function calcCurrentRate(
// initial_rate * (1 + multi_factor * time_elapsed)
const temp1 = rr; /////////// < 2 ^ 192
const temp2 = add(MM, mt); // < 2 ^ 81
const temp3 = mulDivC(temp1, temp2, MAX_UINT256);
const temp3 = minFactor(temp1, temp2);
const temp4 = RR_MUL_MM;
return [mulDivF(temp1, temp2, temp3), temp4.div(temp3)]; // not ideal
}
Expand Down Expand Up @@ -134,7 +141,7 @@ function calcCurrentRate(
// initial_rate * e ^ (multi_factor * time_elapsed)
const temp1 = rr; //////////////////////// < 2 ^ 192
const temp2 = exp(mul(mt, EXP_ONE_DIV_MM)); // < 2 ^ 159 (inner expression)
const temp3 = mulDivC(temp1, temp2, MAX_UINT256);
const temp3 = minFactor(temp1, temp2);
const temp4 = EXP_ONE_MUL_RR;
return [mulDivF(temp1, temp2, temp3), temp4.div(temp3)]; // not ideal
}
Expand All @@ -149,6 +156,9 @@ function calcCurrentRate(
throw new Error(`Invalid gradientType ${gradientType}`);
}

/**
* @dev Ensure a finite positive rate
*/
function sub(one: BigNumber, mt: BigNumber): BigNumber {
if (one.lte(mt)) {
throw new Error('InvalidRate');
Expand All @@ -157,21 +167,41 @@ function sub(one: BigNumber, mt: BigNumber): BigNumber {
}

/**
* @dev Compute e ^ (x / EXP_ONE) * EXP_ONE
* Input range: 0 <= x <= MAX_VAL - 1
* Detailed description:
* - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible
* - The exponentiation of each binary exponent is given (pre-calculated)
* - The exponentiation of r is calculated via Taylor series for e^x, where x = r
* - The exponentiation of the input is calculated by multiplying the intermediate results above
* - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859
*/
* @dev Compute e ^ (x / EXP_ONE) * EXP_ONE
* Input range: 0 <= x <= EXP_MAX - 1
* Detailed description:
* - For x < EXP_MID, this function computes e ^ x
* - For x < EXP_MAX, this function computes e ^ mod(x, ln2) * 2 ^ div(x, ln2)
* - The latter relies on the following identity:
* e ^ x =
* e ^ x * 2 ^ k / 2 ^ k =
* e ^ x * 2 ^ k / e ^ (k * ln2) =
* e ^ x / e ^ (k * ln2) * 2 ^ k =
* e ^ (x - k * ln2) * 2 ^ k
* - Replacing k with div(x, ln2) gives the solution above
* - The value of ln2 is represented as ceil(ln2 * EXP_ONE)
*/
function exp(x: BigNumber): BigNumber {
// prettier-ignore
if (x.gte(MAX_VAL)) {
throw new Error('ExpOverflow');
if (x.lt(EXP_MID)) {
return _exp(x); // slightly more accurate
}
if (x.lt(EXP_MAX)) {
return _exp(x.mod(EXP_LN2)).shl(x.div(EXP_LN2).toNumber());
}
throw new Error('ExpOverflow');
}

/**
* @dev Compute e ^ (x / EXP_ONE) * EXP_ONE
* Input range: 0 <= x <= EXP_ONE * 16 - 1
* Detailed description:
* - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible
* - The exponentiation of each binary exponent is given (pre-calculated)
* - The exponentiation of r is calculated via Taylor series for e^x, where x = r
* - The exponentiation of the input is calculated by multiplying the intermediate results above
* - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859
*/
function _exp(x: BigNumber): BigNumber {
let res = BigNumber.from(0);

let y: BigNumber;
Expand Down
97 changes: 91 additions & 6 deletions tests/trade_gradient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,40 @@ import { Decimal, BnToDec, DecToBn } from '../src/utils/numerics';
const ONE = new Decimal(1);
const TWO = new Decimal(2);

const EXP_ONE = new Decimal(2).pow(127);
const MAX_VAL = new Decimal(2).pow(131);
const EXP_ONE = TWO.pow(127);
const EXP_MAX = EXP_ONE.mul(TWO.ln()).ceil().mul(129);

const getInitialRate = (x: Decimal) => decodeScaleInitialRate(BnToDec(decodeFloatInitialRate(encodeFloatInitialRate(encodeScaleInitialRate(x)))));
const getMultiFactor = (x: Decimal) => decodeScaleMultiFactor(BnToDec(decodeFloatMultiFactor(encodeFloatMultiFactor(encodeScaleMultiFactor(x)))));

function testConfiguration(
paramName: string,
preConfig: Decimal,
postConfig: (x: Decimal) => (Decimal),
maxAbsoluteError: string,
maxRelativeError: string
) {
it(`testConfiguration: ${paramName} = ${preConfig}`, async () => {
const expected = preConfig;
const actual = postConfig(preConfig);
if (!actual.eq(expected)) {
expect(actual.lt(expected)).to.be.equal(
true,
`\n- expected = ${expected.toFixed()}` +
`\n- actual = ${actual.toFixed()}`
);
const absoluteError = actual.sub(expected).abs();
const relativeError = actual.div(expected).sub(1).abs();
expect(absoluteError.lte(maxAbsoluteError) || relativeError.lte(maxRelativeError)).to.be.equal(
true,
`\n- expected = ${expected.toFixed()}` +
`\n- actual = ${actual.toFixed()}` +
`\n- absoluteError = ${absoluteError.toFixed()}` +
`\n- relativeError = ${relativeError.toFixed()}`
);
}
});
}

function expectedCurrentRate(
gradientType: number,
Expand Down Expand Up @@ -68,7 +100,27 @@ function testCurrentRate(
});
}

describe.only('trade_gradient', () => {
describe('trade_gradient', () => {
for (let a = 1; a <= 100; a++) {
const initialRate = new Decimal(a).mul(1234.5678);
testConfiguration('initialRate', initialRate, getInitialRate, '0', '0.00000000000002');
}

for (let b = 1; b <= 100; b++) {
const multiFactor = new Decimal(b).mul(0.00001234);
testConfiguration('multiFactor', multiFactor, getMultiFactor, '0', '0.0000002');
}

for (let a = -28; a <= 28; a++) {
const initialRate = new Decimal(10).pow(a);
testConfiguration('initialRate', initialRate, getInitialRate, '0.0000000000000005', '0.00000000000002');
}

for (let b = -14; b <= -1; b++) {
const multiFactor = new Decimal(10).pow(b);
testConfiguration('multiFactor', multiFactor, getMultiFactor, '0.000000000000004', '0.00000007');
}

for (let a = 1; a <= 10; a++) {
for (let b = 1; b <= 10; b++) {
for (let c = 1; c <= 10; c++) {
Expand All @@ -91,15 +143,48 @@ describe.only('trade_gradient', () => {
const initialRate = new Decimal(10).pow(a);
const multiFactor = new Decimal(10).pow(b);
const timeElapsed = Decimal.min(
MAX_VAL.div(EXP_ONE).div(multiFactor).sub(1).ceil(),
TWO.pow(4).div(multiFactor).sub(1).ceil(),
TWO.pow(25).sub(1)
).mul(c).div(10).ceil();
testCurrentRate(0, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(1, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(2, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(3, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(4, initialRate, multiFactor, timeElapsed, '0.00000000000000000000000000000000000006');
testCurrentRate(5, initialRate, multiFactor, timeElapsed, '0.00000000000000000000000000000000000006');
}
}
}

for (const a of [-27, -10, 0, 10, 27]) {
for (const b of [-14, -9, -6, -1]) {
for (const c of [1, 4, 7, 10]) {
const initialRate = new Decimal(10).pow(a);
const multiFactor = new Decimal(10).pow(b);
const timeElapsed = Decimal.min(
EXP_MAX.div(EXP_ONE).div(multiFactor).sub(1).ceil(),
TWO.pow(25).sub(1)
).mul(c).div(10).ceil();
testCurrentRate(0, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(1, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(2, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(3, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(4, initialRate, multiFactor, timeElapsed, '0.000000000000000000000000000000000002');
testCurrentRate(5, initialRate, multiFactor, timeElapsed, '0.000000000000000000000000000000000002');
testCurrentRate(4, initialRate, multiFactor, timeElapsed, '0.000000000004');
testCurrentRate(5, initialRate, multiFactor, timeElapsed, '0.0000000000000000000000000000000000003');
}
}
}

for (const a of [-27, -10, 0, 10, 27]) {
for (const b of [-14, -9, -6, -1]) {
for (const c of [19, 24, 29]) {
const initialRate = new Decimal(10).pow(a);
const multiFactor = new Decimal(10).pow(b);
const timeElapsed = new Decimal(2).pow(c).sub(1);
testCurrentRate(0, initialRate, multiFactor, timeElapsed, '0.0000000000000000000000000000000000000000003');
testCurrentRate(1, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(2, initialRate, multiFactor, timeElapsed, '0');
testCurrentRate(3, initialRate, multiFactor, timeElapsed, '0');
}
}
}
Expand Down

0 comments on commit a17e548

Please sign in to comment.