"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var bytes_1 = require("@ethersproject/bytes"); var logger_1 = require("@ethersproject/logger"); var _version_1 = require("./_version"); var logger = new logger_1.Logger(_version_1.version); var bignumber_1 = require("./bignumber"); var _constructorGuard = {}; var Zero = bignumber_1.BigNumber.from(0); var NegativeOne = bignumber_1.BigNumber.from(-1); function throwFault(message, fault, operation, value) { var params = { fault: fault, operation: operation }; if (value !== undefined) { params.value = value; } return logger.throwError(message, logger_1.Logger.errors.NUMERIC_FAULT, params); } // Constant to pull zeros from for multipliers var zeros = "0"; while (zeros.length < 256) { zeros += zeros; } // Returns a string "1" followed by decimal "0"s function getMultiplier(decimals) { if (typeof (decimals) !== "number") { try { decimals = bignumber_1.BigNumber.from(decimals).toNumber(); } catch (e) { } } if (typeof (decimals) === "number" && decimals >= 0 && decimals <= 256 && !(decimals % 1)) { return ("1" + zeros.substring(0, decimals)); } return logger.throwArgumentError("invalid decimal size", "decimals", decimals); } function formatFixed(value, decimals) { if (decimals == null) { decimals = 0; } var multiplier = getMultiplier(decimals); // Make sure wei is a big number (convert as necessary) value = bignumber_1.BigNumber.from(value); var negative = value.lt(Zero); if (negative) { value = value.mul(NegativeOne); } var fraction = value.mod(multiplier).toString(); while (fraction.length < multiplier.length - 1) { fraction = "0" + fraction; } // Strip training 0 fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1]; var whole = value.div(multiplier).toString(); value = whole + "." + fraction; if (negative) { value = "-" + value; } return value; } exports.formatFixed = formatFixed; function parseFixed(value, decimals) { if (decimals == null) { decimals = 0; } var multiplier = getMultiplier(decimals); if (typeof (value) !== "string" || !value.match(/^-?[0-9.,]+$/)) { logger.throwArgumentError("invalid decimal value", "value", value); } if (multiplier.length - 1 === 0) { return bignumber_1.BigNumber.from(value); } // Is it negative? var negative = (value.substring(0, 1) === "-"); if (negative) { value = value.substring(1); } if (value === ".") { logger.throwArgumentError("missing value", "value", value); } // Split it into a whole and fractional part var comps = value.split("."); if (comps.length > 2) { logger.throwArgumentError("too many decimal points", "value", value); } var whole = comps[0], fraction = comps[1]; if (!whole) { whole = "0"; } if (!fraction) { fraction = "0"; } // Prevent underflow if (fraction.length > multiplier.length - 1) { throwFault("fractional component exceeds decimals", "underflow", "parseFixed"); } // Fully pad the string with zeros to get to wei while (fraction.length < multiplier.length - 1) { fraction += "0"; } var wholeValue = bignumber_1.BigNumber.from(whole); var fractionValue = bignumber_1.BigNumber.from(fraction); var wei = (wholeValue.mul(multiplier)).add(fractionValue); if (negative) { wei = wei.mul(NegativeOne); } return wei; } exports.parseFixed = parseFixed; var FixedFormat = /** @class */ (function () { function FixedFormat(constructorGuard, signed, width, decimals) { this.signed = signed; this.width = width; this.decimals = decimals; this.name = (signed ? "" : "u") + "fixed" + String(width) + "x" + String(decimals); this._multiplier = getMultiplier(decimals); Object.freeze(this); } FixedFormat.from = function (value) { if (value instanceof FixedFormat) { return value; } var signed = true; var width = 128; var decimals = 18; if (typeof (value) === "string") { if (value === "fixed") { // defaults... } else if (value === "ufixed") { signed = false; } else if (value != null) { var match = value.match(/^(u?)fixed([0-9]+)x([0-9]+)$/); if (!match) { logger.throwArgumentError("invalid fixed format", "format", value); } signed = (match[1] !== "u"); width = parseInt(match[2]); decimals = parseInt(match[3]); } } else if (value) { var check = function (key, type, defaultValue) { if (value[key] == null) { return defaultValue; } if (typeof (value[key]) !== type) { logger.throwArgumentError("invalid fixed format (" + key + " not " + type + ")", "format." + key, value[key]); } return value[key]; }; signed = check("signed", "boolean", signed); width = check("width", "number", width); decimals = check("decimals", "number", decimals); } if (width % 8) { logger.throwArgumentError("invalid fixed format width (not byte aligned)", "format.width", width); } if (decimals > 80) { logger.throwArgumentError("invalid fixed format (decimals too large)", "format.decimals", decimals); } return new FixedFormat(_constructorGuard, signed, width, decimals); }; return FixedFormat; }()); exports.FixedFormat = FixedFormat; var FixedNumber = /** @class */ (function () { function FixedNumber(constructorGuard, hex, value, format) { var _newTarget = this.constructor; logger.checkNew(_newTarget, FixedNumber); this.format = format; this._hex = hex; this._value = value; this._isFixedNumber = true; Object.freeze(this); } FixedNumber.prototype._checkFormat = function (other) { if (this.format.name !== other.format.name) { logger.throwArgumentError("incompatible format; use fixedNumber.toFormat", "other", other); } }; FixedNumber.prototype.addUnsafe = function (other) { this._checkFormat(other); var a = parseFixed(this._value, this.format.decimals); var b = parseFixed(other._value, other.format.decimals); return FixedNumber.fromValue(a.add(b), this.format.decimals, this.format); }; FixedNumber.prototype.subUnsafe = function (other) { this._checkFormat(other); var a = parseFixed(this._value, this.format.decimals); var b = parseFixed(other._value, other.format.decimals); return FixedNumber.fromValue(a.sub(b), this.format.decimals, this.format); }; FixedNumber.prototype.mulUnsafe = function (other) { this._checkFormat(other); var a = parseFixed(this._value, this.format.decimals); var b = parseFixed(other._value, other.format.decimals); return FixedNumber.fromValue(a.mul(b).div(this.format._multiplier), this.format.decimals, this.format); }; FixedNumber.prototype.divUnsafe = function (other) { this._checkFormat(other); var a = parseFixed(this._value, this.format.decimals); var b = parseFixed(other._value, other.format.decimals); return FixedNumber.fromValue(a.mul(this.format._multiplier).div(b), this.format.decimals, this.format); }; // @TODO: Support other rounding algorithms FixedNumber.prototype.round = function (decimals) { if (decimals == null) { decimals = 0; } if (decimals < 0 || decimals > 80 || (decimals % 1)) { logger.throwArgumentError("invalid decimal count", "decimals", decimals); } // If we are already in range, we're done var comps = this.toString().split("."); if (comps[1].length <= decimals) { return this; } // Bump the value up by the 0.00...0005 var bump = "0." + zeros.substring(0, decimals) + "5"; comps = this.addUnsafe(FixedNumber.fromString(bump, this.format))._value.split("."); // Now it is safe to truncate return FixedNumber.fromString(comps[0] + "." + comps[1].substring(0, decimals)); }; FixedNumber.prototype.toString = function () { return this._value; }; FixedNumber.prototype.toHexString = function (width) { if (width == null) { return this._hex; } if (width % 8) { logger.throwArgumentError("invalid byte width", "width", width); } var hex = bignumber_1.BigNumber.from(this._hex).fromTwos(this.format.width).toTwos(width).toHexString(); return bytes_1.hexZeroPad(hex, width / 8); }; FixedNumber.prototype.toUnsafeFloat = function () { return parseFloat(this.toString()); }; FixedNumber.prototype.toFormat = function (format) { return FixedNumber.fromString(this._value, format); }; FixedNumber.fromValue = function (value, decimals, format) { // If decimals looks more like a format, and there is no format, shift the parameters if (format == null && decimals != null && !bignumber_1.isBigNumberish(decimals)) { format = decimals; decimals = null; } if (decimals == null) { decimals = 0; } if (format == null) { format = "fixed"; } return FixedNumber.fromString(formatFixed(value, decimals), FixedFormat.from(format)); }; FixedNumber.fromString = function (value, format) { if (format == null) { format = "fixed"; } var fixedFormat = FixedFormat.from(format); var numeric = parseFixed(value, fixedFormat.decimals); if (!fixedFormat.signed && numeric.lt(Zero)) { throwFault("unsigned value cannot be negative", "overflow", "value", value); } var hex = null; if (fixedFormat.signed) { hex = numeric.toTwos(fixedFormat.width).toHexString(); } else { hex = numeric.toHexString(); hex = bytes_1.hexZeroPad(hex, fixedFormat.width / 8); } var decimal = formatFixed(numeric, fixedFormat.decimals); return new FixedNumber(_constructorGuard, hex, decimal, fixedFormat); }; FixedNumber.fromBytes = function (value, format) { if (format == null) { format = "fixed"; } var fixedFormat = FixedFormat.from(format); if (bytes_1.arrayify(value).length > fixedFormat.width / 8) { throw new Error("overflow"); } var numeric = bignumber_1.BigNumber.from(value); if (fixedFormat.signed) { numeric = numeric.fromTwos(fixedFormat.width); } var hex = numeric.toTwos((fixedFormat.signed ? 0 : 1) + fixedFormat.width).toHexString(); var decimal = formatFixed(numeric, fixedFormat.decimals); return new FixedNumber(_constructorGuard, hex, decimal, fixedFormat); }; FixedNumber.from = function (value, format) { if (typeof (value) === "string") { return FixedNumber.fromString(value, format); } if (bytes_1.isBytes(value)) { return FixedNumber.fromBytes(value, format); } try { return FixedNumber.fromValue(value, 0, format); } catch (error) { // Allow NUMERIC_FAULT to bubble up if (error.code !== logger_1.Logger.errors.INVALID_ARGUMENT) { throw error; } } return logger.throwArgumentError("invalid FixedNumber value", "value", value); }; FixedNumber.isFixedNumber = function (value) { return !!(value && value._isFixedNumber); }; return FixedNumber; }()); exports.FixedNumber = FixedNumber;