-
-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: major refactor, compute expected values dynamically
build: add "mathjs" and "@types/mathjs" as deps refactor: delete stale constants refactor: turn BigNumber constants into strings test: new ethers.math module for ethers bn math extensions test: new math helper functions, which build on top of math.js
- Loading branch information
Showing
52 changed files
with
1,440 additions
and
1,488 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,20 @@ | ||
import { BigNumber } from "@ethersproject/bignumber"; | ||
|
||
import { fp, maxInt, maxUint, minInt, solidityModByScale } from "./numbers"; | ||
import { bn, fp } from "./numbers"; | ||
|
||
export const E: BigNumber = fp("2.718281828459045235"); | ||
export const LN_E: BigNumber = fp("0.999999999999999990"); | ||
export const LN_MAX_SD59x18: BigNumber = fp("135.305999368893231615"); | ||
export const LN_MAX_UD60x18: BigNumber = fp("135.999146549453176925"); | ||
export const LOG10_MAX_SD59x18: BigNumber = fp("587.626488943152049156"); | ||
export const LOG10_MAX_UD60x18: BigNumber = fp("590.636788899791861115"); | ||
export const LOG2_MAX_SD59x18: BigNumber = fp("195.205294292027477728"); | ||
export const LOG2_MAX_UD60x18: BigNumber = fp("196.205294292027477728"); | ||
export const HALF_SCALE: BigNumber = fp("0.5"); | ||
export const MAX_SD59x18: BigNumber = maxInt(256); // Equivalent to max int256 | ||
export const MAX_UD60x18: BigNumber = maxUint(256); // Equivalent to max uint256 | ||
export const MAX_WHOLE_SD59x18: BigNumber = MAX_SD59x18.sub(solidityModByScale(MAX_SD59x18)); | ||
export const MAX_WHOLE_UD60x18: BigNumber = MAX_UD60x18.sub(solidityModByScale(MAX_UD60x18)); | ||
export const MIN_SD59x18: BigNumber = minInt(256); // Equivalent to min int256 | ||
export const MIN_WHOLE_SD59x18: BigNumber = MIN_SD59x18.sub(solidityModByScale(MIN_SD59x18)); | ||
export const PI: BigNumber = fp("3.141592653589793238"); | ||
export const SCALE: BigNumber = BigNumber.from(10).pow(18); | ||
export const SQRT_2 = fp("1.414213562373095048"); | ||
export const SQRT_MAX_SD59x18 = fp("240615969168004511545033772477.625056927114980741"); | ||
export const SQRT_MAX_UD60x18 = fp("340282366920938463463374607431.768211455999999999"); | ||
export const SQRT_MAX_SD59x18_DIV_BY_SCALE = fp("240615969168004511545.033772477625056927"); // biggest number whose square fits within int256 | ||
export const SQRT_MAX_UD60x18_DIV_BY_SCALE = fp("340282366920938463463.374607431768211455"); // biggest number whose square fits within uint256 | ||
export const ZERO = BigNumber.from(0); | ||
export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; | ||
export const E: string = "2.718281828459045235"; | ||
export const EPSILON: BigNumber = fp("1e-6"); | ||
export const EPSILON_MAGNITUDE: BigNumber = bn("1e6"); | ||
export const HALF_SCALE: string = "0.5"; | ||
export const MAX_SD59x18: string = "57896044618658097711785492504343953926634992332820282019728.792003956564819967"; | ||
export const MAX_UD60x18: string = "115792089237316195423570985008687907853269984665640564039457.584007913129639935"; | ||
export const MAX_WHOLE_SD59x18: string = "57896044618658097711785492504343953926634992332820282019728"; | ||
export const MAX_WHOLE_UD60x18: string = "115792089237316195423570985008687907853269984665640564039457"; | ||
export const MIN_SD59x18: string = "-57896044618658097711785492504343953926634992332820282019728.792003956564819968"; | ||
export const MIN_WHOLE_SD59x18: string = "-57896044618658097711785492504343953926634992332820282019728"; | ||
export const PI: string = "3.141592653589793238"; | ||
export const SCALE: string = "1"; | ||
export const SQRT_MAX_SD59x18: string = "240615969168004511545033772477.625056927114980741"; | ||
export const SQRT_MAX_UD60x18: string = "340282366920938463463374607431.768211455999999999"; | ||
export const SQRT_MAX_SD59x18_DIV_BY_SCALE: string = "240615969168004511545.033772477625056927"; // biggest number whose square fits within int256 | ||
export const SQRT_MAX_UD60x18_DIV_BY_SCALE: string = "340282366920938463463.374607431768211455"; // biggest number whose square fits within uint256 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { BigNumber } from "@ethersproject/bignumber"; | ||
|
||
import { SCALE } from "./constants"; | ||
import { fp } from "./numbers"; | ||
|
||
const foo: BigNumber = fp(SCALE).mul(fp(SCALE)); | ||
console.log({ foo: foo.toString() }); | ||
|
||
const bar: BigNumber = BigNumber.from(10).pow(18).mul(BigNumber.from(10).pow(18)); | ||
console.log({ bar: bar.toString() }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { BigNumber } from "@ethersproject/bignumber"; | ||
|
||
import { HALF_SCALE, SCALE } from "./constants"; | ||
import { fp } from "./numbers"; | ||
|
||
export function avg(x: BigNumber, y: BigNumber): BigNumber { | ||
let result: BigNumber = x.div(2).add(y.div(2)); | ||
if (x.mod(2).eq(1) && y.mod(2).eq(1)) { | ||
result = result.add(1); | ||
} | ||
return result; | ||
} | ||
|
||
export function frac(x: BigNumber): BigNumber { | ||
return solidityModByScale(x); | ||
} | ||
|
||
export function inv(x: BigNumber): BigNumber { | ||
return fp(SCALE).mul(fp(SCALE)).div(x); | ||
} | ||
|
||
export function max(x: BigNumber, y: BigNumber): BigNumber { | ||
if (x.gte(y)) { | ||
return x; | ||
} else { | ||
return y; | ||
} | ||
} | ||
|
||
export function mul(x: BigNumber, y: BigNumber): BigNumber { | ||
const doubleScaledProduct = x.mul(y); | ||
let doubleScaledProductWithHalfScale: BigNumber; | ||
if (doubleScaledProduct.isNegative()) { | ||
doubleScaledProductWithHalfScale = doubleScaledProduct.sub(fp(HALF_SCALE)); | ||
} else { | ||
doubleScaledProductWithHalfScale = doubleScaledProduct.add(fp(HALF_SCALE)); | ||
} | ||
const result: BigNumber = doubleScaledProductWithHalfScale.div(fp(SCALE)); | ||
return result; | ||
} | ||
|
||
export function solidityMod(x: BigNumber, n: BigNumber): BigNumber { | ||
let result = x.mod(n); | ||
if (!result.isZero() && x.isNegative()) { | ||
result = result.sub(n); | ||
} | ||
return result; | ||
} | ||
|
||
// See https://github.com/ethers-io/ethers.js/discussions/1408. | ||
export function solidityModByScale(x: BigNumber): BigNumber { | ||
const scale: BigNumber = BigNumber.from(10).pow(18); | ||
return solidityMod(x, scale); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
import { all, create } from "mathjs"; | ||
import { BigNumber as MathjsBigNumber } from "mathjs"; | ||
|
||
const config = { | ||
number: "BigNumber", | ||
precision: 79, | ||
}; | ||
|
||
const math = create(all, config)!; | ||
const mbn = math.bignumber!; | ||
|
||
export function ceil(x: string): MathjsBigNumber { | ||
return <MathjsBigNumber>mbn(x).ceil(); | ||
} | ||
|
||
export function exp(x: string): MathjsBigNumber { | ||
return <MathjsBigNumber>mbn(x).exp(); | ||
} | ||
|
||
export function exp2(x: string): MathjsBigNumber { | ||
return <MathjsBigNumber>math.pow!(mbn("2"), mbn(x)); | ||
} | ||
|
||
export function floor(x: string): MathjsBigNumber { | ||
return <MathjsBigNumber>mbn(x).floor(); | ||
} | ||
|
||
export function gm(x: string, y: string): MathjsBigNumber { | ||
return <MathjsBigNumber>math.sqrt!(mbn(x).mul(mbn(y))); | ||
} | ||
|
||
export function ln(x: string): MathjsBigNumber { | ||
return <MathjsBigNumber>math.log!(mbn(x)); | ||
} | ||
|
||
export function log10(x: string): MathjsBigNumber { | ||
return <MathjsBigNumber>math.log10!(mbn(x)); | ||
} | ||
|
||
export function log2(x: string): MathjsBigNumber { | ||
return <MathjsBigNumber>math.log2!(mbn(x)); | ||
} | ||
|
||
export function pow(x: string, y: string): MathjsBigNumber { | ||
return <MathjsBigNumber>math.pow!(mbn(x), mbn(y)); | ||
} | ||
|
||
export function sqrt(x: string): MathjsBigNumber { | ||
return <MathjsBigNumber>math.sqrt!(mbn(x)); | ||
} | ||
|
||
export { mbn }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,42 @@ | ||
import { BigNumber, parseFixed } from "@ethersproject/bignumber"; | ||
import fromExponential from "from-exponential"; | ||
import { BigNumber as MathjsBigNumber } from "mathjs"; | ||
|
||
export function bn(x: string): BigNumber { | ||
return BigNumber.from(x); | ||
} | ||
|
||
export function fp(x: string): BigNumber { | ||
// Check if x is either a whole number with up to 60 digits or a fixed-point number with up to 60 digits and up to 18 decimals. | ||
if (!/^[-+]?(\d{1,60}|(?=\d+\.\d+)\d{1,60}\.\d{1,18})$/.test(x)) { | ||
throw new Error(`Unknown format for fixed-point number: ${x}`); | ||
let xs: string = x; | ||
if (x.includes("e")) { | ||
xs = fromExponential(x); | ||
} | ||
|
||
const precision: number = 18; | ||
return parseFixed(x, precision); | ||
} | ||
|
||
export function sfp(x: string): BigNumber { | ||
// Check if the input is in scientific notation. | ||
if (!/^(-?\d+)(\.\d+)?(e|e-)(\d+)$/.test(x)) { | ||
throw new Error(`Unknown format for fixed-point number in scientific notation: ${x}`); | ||
return BigNumber.from(xs); | ||
} | ||
|
||
/// 1. Get the stringified value of x, but limit the number of decimals to 18. | ||
/// 2. Check if x is a whole number with up to 60 digits or a fixed-point number with up to 60 digits and up to 18 | ||
/// decimals. If yes, convert the number to fixed-point representation and return it. | ||
/// 3. Otherwise, throw an error. | ||
export function fp(x: string | MathjsBigNumber): BigNumber { | ||
let xs: string; | ||
if (typeof x === "string") { | ||
xs = x; | ||
if (xs.includes("e")) { | ||
xs = fromExponential(xs); | ||
} | ||
} else { | ||
xs = String(x); | ||
if (xs.includes("e")) { | ||
xs = fromExponential(xs); | ||
} | ||
if (xs.includes(".")) { | ||
const parts: string[] = xs.split("."); | ||
parts[1] = parts[1].slice(0, 18); | ||
xs = parts[0] + "." + parts[1]; | ||
} | ||
} | ||
|
||
const precision: number = 18; | ||
return parseFixed(fromExponential(x), precision); | ||
} | ||
|
||
export function fpPowOfTwo(exp: number | BigNumber): BigNumber { | ||
const scale: BigNumber = BigNumber.from(10).pow(18); | ||
return powOfTwo(exp).mul(scale); | ||
} | ||
|
||
export function maxInt(exp: number): BigNumber { | ||
return powOfTwo(exp - 1).sub(1); | ||
} | ||
|
||
export function minInt(exp: number): BigNumber { | ||
return powOfTwo(exp - 1).mul(-1); | ||
} | ||
|
||
export function maxUint(exp: number): BigNumber { | ||
return BigNumber.from(2).pow(exp).sub(1); | ||
} | ||
|
||
export function powOfTwo(exp: number | BigNumber): BigNumber { | ||
return BigNumber.from(2).pow(BigNumber.from(exp)); | ||
} | ||
|
||
export function solidityMod(x: BigNumber, n: BigNumber): BigNumber { | ||
let result = x.mod(n); | ||
if (x.isNegative()) { | ||
result = result.sub(n); | ||
if (/^[-+]?(\d{1,60}|(?=\d+\.\d+)\d{1,60}\.\d{1,18})$/.test(xs)) { | ||
return parseFixed(xs, precision); | ||
} else { | ||
throw new Error(`Unknown format for fixed-point number: ${xs}`); | ||
} | ||
return result; | ||
} | ||
|
||
// See https://github.com/ethers-io/ethers.js/issues/1402. | ||
export function solidityModByScale(x: BigNumber): BigNumber { | ||
const scale: BigNumber = BigNumber.from(10).pow(18); | ||
return solidityMod(x, scale); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.