@node-dlc/core
Version:
332 lines • 13.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.roundPayout = exports.mergePayouts = exports.splitIntoRanges = exports.groupByIgnoringDigits = exports.middleGroupings = exports.backGroupings = exports.frontGroupings = exports.separatePrefix = exports.decompose = exports.dropUntil = exports.zipWithIndex = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const BigIntUtils_1 = require("../utils/BigIntUtils");
function zipWithIndex(arr) {
return arr.map((a, i) => [a, i]);
}
exports.zipWithIndex = zipWithIndex;
function dropUntil(data, check) {
for (let i = 0; i < data.length; i++) {
if (check(data[i])) {
return data.slice(i);
}
}
return [];
}
exports.dropUntil = dropUntil;
function decompose(num, base, numDigits) {
let currentNumber = num;
const digits = [];
while (numDigits > 0) {
digits.push(Number(currentNumber % BigInt(base)));
currentNumber = currentNumber / BigInt(base);
numDigits--;
}
return digits.reverse();
}
exports.decompose = decompose;
function separatePrefix(start, end, base, numDigits) {
const startDigits = decompose(start, base, numDigits);
const endDigits = decompose(end, base, numDigits);
const prefixDigits = [];
for (let i = 0; i < startDigits.length; i++) {
if (startDigits[i] === endDigits[i]) {
prefixDigits.push(startDigits[i]);
}
else
break;
}
return {
prefixDigits,
startDigits: startDigits.splice(prefixDigits.length),
endDigits: endDigits.splice(prefixDigits.length),
};
}
exports.separatePrefix = separatePrefix;
function frontGroupings(digits, base) {
const digitsReversed = Array.from(digits).reverse();
const nonZeroDigits = dropUntil(zipWithIndex(digitsReversed), (a) => a[0] !== 0);
if (nonZeroDigits.length === 0) {
return [[0]];
}
const fromFront = nonZeroDigits
.filter((_, i) => i !== nonZeroDigits.length - 1)
.flatMap(([d, i]) => {
const fixedDigits = Array.from(digits);
fixedDigits.length = fixedDigits.length - (i + 1);
const result = [];
for (let n = d + 1; n < base; n++) {
result.push([...Array.from(fixedDigits), n]);
}
return result;
});
return [nonZeroDigits.map((a) => a[0]).reverse(), ...fromFront];
}
exports.frontGroupings = frontGroupings;
function backGroupings(digits, base) {
const digitsReversed = Array.from(digits).reverse();
const nonMaxDigits = dropUntil(zipWithIndex(digitsReversed), (a) => a[0] !== base - 1);
if (nonMaxDigits.length === 0) {
return [[base - 1]];
}
const fromBack = nonMaxDigits
.filter((_, i) => i !== nonMaxDigits.length - 1)
.flatMap(([d, i]) => {
const fixedDigits = Array.from(digits);
fixedDigits.length = fixedDigits.length - (i + 1);
const result = [];
for (let n = d - 1; n >= 0; n--) {
result.push([...Array.from(fixedDigits), n]);
}
return result;
});
return [...fromBack.reverse(), nonMaxDigits.map((a) => a[0]).reverse()];
}
exports.backGroupings = backGroupings;
function middleGroupings(firstDigitStart, firstDigitEnd) {
const result = [];
while (++firstDigitStart < firstDigitEnd) {
result.push([firstDigitStart]);
}
return result;
}
exports.middleGroupings = middleGroupings;
function groupByIgnoringDigits(start, end, base, numDigits) {
const { prefixDigits, startDigits, endDigits } = separatePrefix(start, end, base, numDigits);
if (start === end ||
(startDigits.every((n) => n === 0) &&
endDigits.every((n) => n === base - 1) &&
prefixDigits.length !== 0)) {
return [prefixDigits];
}
else if (prefixDigits.length === numDigits - 1) {
const result = [];
for (let i = startDigits[startDigits.length - 1]; i <= endDigits[endDigits.length - 1]; i++) {
result.push([...prefixDigits, i]);
}
return result;
}
else {
const front = frontGroupings(startDigits, base);
const middle = middleGroupings(startDigits[0], endDigits[0]);
const back = backGroupings(endDigits, base);
const groupings = [...front, ...middle, ...back];
return groupings.map((g) => [...prefixDigits, ...g]);
}
}
exports.groupByIgnoringDigits = groupByIgnoringDigits;
/**
* Performs optimized evaluation and rounding for strictly monotonic hyperbolas on intervals (from, to)
* e.g. hyperbolas with the form b = c = 0
*
* The next start of a payout range is determined by finding the outcome at the next mid-rounding payout.
* Uses an inverse function of the hyperbola to find the outcome.
*
* Optimizes rounding from O(to - from) to O(totalCollateral / rounding)
*
*
* Evaluates and rounds a payout_function equivalent to:
*
* payout_function_v0
* num_pieces: 1
* endpoint_0: from
* endpoint_payout_0: fromPayout
* extra_precision_0: 0
* payout_curve_piece: HyperbolaPayoutCurve
* endpoint_1: to
* endpoint_payout_1: toPayout
*/
function splitIntoRanges(from, to, fromPayout, // endpoint_payout
toPayout, // endpoint_payout
totalCollateral, curve, roundingIntervals) {
if (to - from <= 0) {
throw new Error('`to` must be strictly greater than `from`');
}
const reversedIntervals = [...roundingIntervals].reverse();
const getRoundingForOutcome = (outcome) => {
const roundingIndex = reversedIntervals.findIndex((interval) => interval.beginInterval <= outcome);
return [
roundingIndex !== -1
? reversedIntervals[roundingIndex].roundingMod
: BigInt(1),
roundingIndex,
];
};
const clamp = (val) => BigIntUtils_1.BigIntMath.clamp(BigInt(0), val, totalCollateral);
const totalCollateralBN = new bignumber_js_1.default(totalCollateral.toString());
const clampBN = (val) => bignumber_js_1.default.max(0, bignumber_js_1.default.min(val, totalCollateralBN));
const result = [];
// outcome = endpoint_0
result.push({
payout: fromPayout,
indexFrom: from,
indexTo: from,
});
// In the case of a constant payout curve (fromPayout === toPayout), we can skip the range evaluation
if (fromPayout === toPayout) {
result.push({
payout: toPayout,
indexFrom: from,
indexTo: to,
});
// outcome = endpoint_1
result.push({
payout: toPayout,
indexFrom: to,
indexTo: to,
});
// merge neighbouring ranges with same payout
return (0, exports.mergePayouts)(result);
}
let currentOutcome = from + BigInt(1);
// iterate over entire range of outcomes from [from, to]
while (currentOutcome < to) {
const [rounding, roundingIndex] = getRoundingForOutcome(currentOutcome);
// either the next rounding interval, or the end of the range
const nextFirstRoundingOutcome = reversedIntervals[roundingIndex - 1]?.beginInterval || to;
// temporary variable to hold the current payout
let currentPayout = new bignumber_js_1.default(roundPayout(clampBN(curve.getPayout(currentOutcome)), rounding).toString());
let currentMidRoundedOutcome = currentOutcome;
const isAscending = curve
.getPayout(nextFirstRoundingOutcome)
.gt(currentPayout);
// Add loop counter to prevent infinite loops
let loopCounter = 0;
const maxIterations = Number(to - from) + 1000; // Allow some extra iterations
while (currentMidRoundedOutcome < nextFirstRoundingOutcome) {
// Prevent infinite loops
if (++loopCounter > maxIterations) {
// Breaking out of potential infinite loop - add remaining range and exit
result.push({
payout: clamp((0, BigIntUtils_1.toBigInt)(currentPayout)),
indexFrom: currentMidRoundedOutcome,
indexTo: to - BigInt(1),
});
currentOutcome = to;
break;
}
const nextRoundedPayout = currentPayout
.integerValue()
.plus(isAscending ? Number(rounding) : -Number(rounding));
const nextRoundedPayoutBigInt = (0, BigIntUtils_1.toBigInt)(nextRoundedPayout);
const nextMidRoundedPayout = currentPayout.plus(isAscending ? Number(rounding) / 2 : -Number(rounding) / 2);
let nextMidRoundedOutcome = curve.getOutcomeForPayout(nextMidRoundedPayout);
// Handle invalid outcomes from getOutcomeForPayout
if (nextMidRoundedOutcome < 0) {
// If getOutcomeForPayout returns invalid value, advance manually
nextMidRoundedOutcome = currentMidRoundedOutcome + BigInt(1);
}
if ((!isAscending &&
nextMidRoundedOutcome >= 0 &&
curve.getPayout(nextMidRoundedOutcome).lt(nextMidRoundedPayout)) ||
(isAscending &&
nextMidRoundedOutcome >= 0 &&
curve.getPayout(nextMidRoundedOutcome).gte(nextMidRoundedPayout))) {
nextMidRoundedOutcome = nextMidRoundedOutcome - BigInt(1);
// Ensure we don't go negative
if (nextMidRoundedOutcome < 0) {
nextMidRoundedOutcome = currentMidRoundedOutcome;
}
}
const nextOutcome = curve.getOutcomeForPayout(nextRoundedPayout);
if (nextMidRoundedOutcome >= nextFirstRoundingOutcome) {
result.push({
payout: clamp((0, BigIntUtils_1.toBigInt)(currentPayout)),
indexFrom: currentMidRoundedOutcome,
indexTo: nextFirstRoundingOutcome - BigInt(1),
});
currentOutcome = nextFirstRoundingOutcome;
break;
}
if (nextRoundedPayoutBigInt > totalCollateral ||
nextRoundedPayoutBigInt < 0 ||
nextOutcome >= to ||
nextOutcome < 0 // undefined on curve
) {
if (nextMidRoundedOutcome < from || nextMidRoundedOutcome > to) {
result.push({
payout: clamp((0, BigIntUtils_1.toBigInt)(currentPayout)),
indexFrom: currentMidRoundedOutcome,
indexTo: to,
});
}
else {
result.push({
payout: clamp((0, BigIntUtils_1.toBigInt)(currentPayout)),
indexFrom: currentMidRoundedOutcome,
indexTo: nextMidRoundedOutcome,
}, {
payout: clamp(nextRoundedPayoutBigInt),
indexFrom: nextMidRoundedOutcome + BigInt(1),
indexTo: to - BigInt(1),
});
}
currentOutcome = to;
break;
}
result.push({
payout: clamp((0, BigIntUtils_1.toBigInt)(currentPayout)),
indexFrom: currentMidRoundedOutcome,
indexTo: nextMidRoundedOutcome,
});
// Handle case where nextOutcome is invalid
if (nextOutcome < 0) {
// Advance manually if getOutcomeForPayout returns invalid value
currentOutcome = currentMidRoundedOutcome + BigInt(1);
}
else {
currentOutcome = nextOutcome + BigInt(1);
}
currentPayout = nextRoundedPayout;
// Additional safety check: ensure we're making progress
const previousMidRoundedOutcome = currentMidRoundedOutcome;
currentMidRoundedOutcome = nextMidRoundedOutcome + BigInt(1);
if (currentMidRoundedOutcome <= previousMidRoundedOutcome) {
// No progress detected in splitIntoRanges loop, breaking
currentOutcome = nextFirstRoundingOutcome;
break;
}
}
}
// outcome = endpoint_1
result.push({
payout: toPayout,
indexFrom: to,
indexTo: to,
});
// merge neighbouring ranges with same payout
return (0, exports.mergePayouts)(result);
}
exports.splitIntoRanges = splitIntoRanges;
const mergePayouts = (payouts) => {
return payouts.reduce((acc, range) => {
const prev = acc[acc.length - 1];
if (prev) {
if ((prev.indexTo === range.indexFrom ||
prev.indexTo + BigInt(1) === range.indexFrom) &&
prev.payout === range.payout) {
prev.indexTo = range.indexTo;
return acc;
}
}
return [...acc, range];
}, []);
};
exports.mergePayouts = mergePayouts;
function roundPayout(payout, rounding) {
const roundingBN = new bignumber_js_1.default(rounding.toString());
const mod = payout.gte(0)
? payout.mod(roundingBN)
: payout.mod(roundingBN).plus(roundingBN);
const roundedPayout = mod.gte(roundingBN.dividedBy(2))
? payout.plus(roundingBN).minus(mod)
: payout.minus(mod);
return (0, BigIntUtils_1.toBigInt)(roundedPayout);
}
exports.roundPayout = roundPayout;
//# sourceMappingURL=CETCalculator.js.map