2.4.3 • Published 2 years ago

prb-math v2.4.3

Weekly downloads
-
License
Unlicense
Repository
github
Last release
2 years ago

PRBMath Coverage Status Styled with Prettier Commitizen Friendly license: Unlicense

Smart contract library for advanced fixed-point math that operates with signed 59.18-decimal fixed-point and unsigned 60.18-decimal fixed-point numbers. The name of the number formats stems from the fact that there can be up to 59/60 digits in the integer part and up to 18 decimals in the fractional part. The numbers are bound by the minimum and the maximum values permitted by the Solidity types int256 and uint256.

  • Operates with signed and unsigned denary fixed-point numbers, with 18 trailing decimals
  • Offers advanced math functions like logarithms, exponentials, powers and square roots
  • Gas efficient, but still user-friendly
  • Bakes in overflow-safe multiplication and division
  • Reverts with custom errors instead of reason strings
  • Well-documented via NatSpec comments
  • Thoroughly tested with Hardhat and Waffle

I created this because I wanted a fixed-point math library that is at the same time practical, intuitive and efficient. I looked at ABDKMath64x64, which is fast, but I didn't like that it operates with binary numbers and it limits the precision to int128. I then looked at Fixidity, which operates with denary numbers and has wide precision, but is slow and susceptible to phantom overflow.

Install

With yarn:

$ yarn add prb-math

Or npm:

$ npm install prb-math

Usage

PRBMath comes in four flavors: basic signed, typed signed, basic unsigned and typed unsigned.

PRBMathSD59x18.sol

// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.4;

import "prb-math/contracts/PRBMathSD59x18.sol";

contract SignedConsumer {
  using PRBMathSD59x18 for int256;

  function signedLog2(int256 x) external pure returns (int256 result) {
    result = x.log2();
  }

  /// @notice Calculates x*y÷1e18 while handling possible intermediary overflow.
  /// @dev Try this with x = type(int256).max and y = 5e17.
  function signedMul(int256 x, int256 y) external pure returns (int256 result) {
    result = x.mul(y);
  }

  /// @dev Assuming that 1e18 = 100% and 1e16 = 1%.
  function signedYield(int256 principal, int256 apr) external pure returns (int256 result) {
    result = principal.mul(apr);
  }
}

PRBMathSD59x18Typed.sol

// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.4;

import "prb-math/contracts/PRBMathSD59x18Typed.sol";

contract SignedConsumerTyped {
  using PRBMathSD59x18Typed for PRBMath.SD59x18;

  function signedLog2(int256 x) external pure returns (int256 result) {
    PRBMath.SD59x18 memory xsd = PRBMath.SD59x18({ value: x });
    result = xsd.log2().value;
  }

  /// @notice Calculates x*y÷1e18 while handling possible intermediary overflow.
  /// @dev Try this with x = type(int256).max and y = 5e17.
  function signedMul(int256 x, int256 y) external pure returns (int256 result) {
    PRBMath.SD59x18 memory xsd = PRBMath.SD59x18({ value: x });
    PRBMath.SD59x18 memory ysd = PRBMath.SD59x18({ value: y });
    result = xsd.mul(ysd).value;
  }

  /// @dev Assuming that 1e18 = 100% and 1e16 = 1%.
  function signedYield(int256 principal, int256 apr) external pure returns (int256 result) {
    PRBMath.SD59x18 memory principalSd = PRBMath.SD59x18({ value: principal });
    PRBMath.SD59x18 memory aprSd = PRBMath.SD59x18({ value: apr });
    result = principalSd.mul(aprSd).value;
  }
}

PRBMathUD60x18.sol

// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.4;

import "prb-math/contracts/PRBMathUD60x18.sol";

contract UnsignedConsumer {
  using PRBMathUD60x18 for uint256;

  /// @dev Note that "x" must be greater than or equal to 1e18, lest the result would be negative, and negative
  /// numbers are not supported by the unsigned 60.18-decimal fixed-point representation.
  function unsignedLog2(uint256 x) external pure returns (uint256 result) {
    result = x.log2();
  }

  /// @notice Calculates x*y÷1e18 while handling possible intermediary overflow.
  /// @dev Try this with x = type(uint256).max and y = 5e17.
  function unsignedMul(uint256 x, uint256 y) external pure returns (uint256 result) {
    result = x.mul(y);
  }

  /// @dev Assuming that 1e18 = 100% and 1e16 = 1%.
  function unsignedYield(uint256 principal, uint256 apr) external pure returns (uint256 result) {
    result = principal.mul(apr);
  }
}

PRBMathUD60x18Typed.sol

// SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.4;

import "prb-math/contracts/PRBMathUD60x18Typed.sol";

contract UnsignedConsumerTyped {
  using PRBMathUD60x18Typed for PRBMath.UD60x18;

  function unsignedLog2(uint256 x) external pure returns (uint256 result) {
    PRBMath.UD60x18 memory xud = PRBMath.UD60x18({ value: x });
    result = xud.log2().value;
  }

  /// @notice Calculates x*y÷1e18 while handling possible intermediary overflow.
  /// @dev Try this with x = type(uint256).max and y = 5e17.
  function unsignedMul(uint256 x, uint256 y) external pure returns (uint256 result) {
    PRBMath.UD60x18 memory xud = PRBMath.UD60x18({ value: x });
    PRBMath.UD60x18 memory yud = PRBMath.UD60x18({ value: y });
    result = xud.mul(yud).value;
  }

  /// @dev Assuming that 1e18 = 100% and 1e16 = 1%.
  function unsignedYield(uint256 principal, uint256 apr) external pure returns (uint256 result) {
    PRBMath.UD60x18 memory principalUd = PRBMath.UD60x18({ value: principal });
    PRBMath.UD60x18 memory aprUd = PRBMath.UD60x18({ value: apr });
    result = principalUd.mul(aprUd).value;
  }
}

JavaScript SDK

PRBMath is accompanied by a JavaScript SDK. Whatever functions there are in the Solidity library, you should find them replicated in the SDK. The only exception are fromUint and toUint, for which you can use evm-bn.

Here's an example for how to calculate the binary logarithm:

import type { BigNumber } from "@ethersproject/bignumber";
import { fromBn, toBn } from "evm-bn";
import { log2 } from "prb-math";

(async function () {
  const x: BigNumber = toBn("16");
  const result: BigNumber = log2(x);
  console.log({ result: fromBn(result) });
})();

Pro tip: see how the SDK is used in the tests for PRBMath.

Gas Efficiency

The typeless PRBMath library is faster than ABDKMath for abs, exp, exp2, gm, inv, ln, log2. Conversely, it is slower than ABDKMath for avg, div, mul, powu and sqrt. There are two technical reasons why PRBMath lags behind ABDKMath's mul and div functions:

  1. PRBMath operates with 256-bit word sizes, so it has to account for possible intermediary overflow. ABDKMath operates with 128-bit word sizes.
  2. PRBMath rounds up instead of truncating in certain cases (see listing 6 and text above it in this article), which makes it slightly more precise than ABDKMath but comes at a gas cost.

PRBMath

Based on the v2.0.1 of the library.

SD59x18MinMaxAvgUD60x18MinMaxAvg
abs687270n/an/an/an/a
avg575757avg575757
ceil82117101ceil787878
div431483451div205205205
exp3827972263exp187427422244
exp26326782104exp2178426522156
floor82117101floor434343
frac232323frac232323
fromInt838383fromUint494949
gm26892690gm26893691
inv404040inv404040
ln46373064724ln41969023814
log1010490744337log1050386954571
log237772414243log233068253426
mul455463459mul219275247
pow64113388518pow64106376635
powu293247455681powu83245355471
sqrt140839716sqrt114846710
toInt232323toUint232323

PRBMathTyped

Based on the v2.0.1 of the library.

SD59x18MinMaxAvgUD60x18MinMaxAvg
abs128132130n/an/an/an/a
add221221221add979797
avg120120120avg120120120
ceil95166141ceil132132132
div524582546div259259259
exp8230762617exp208629542456
exp211027682233exp2184027082212
floor95171143floor979797
fromInt118118118fromUint848484
frac828282frac777777
gm67947741gm67948743
inv828282inv828282
ln64575035041ln62671244029
log1018292874337log10241489127280
log242272854701log240769104108
mul538546542mul273336305
pow115118248471pow115111297346
powu479252135931powu132244264207
sqrt195918798sqrt153903769
sub218218218sub989898
toInt292929toUint292929

ABDKMath64x64

Based on v3.0 of the library. See abdk-gas-estimations.

MethodMinMaxAvg
abs889290
avg414141
div168168168
exp7737802687
exp27736002746
gavg166875719
inv157157157
ln707471647126
log2697270627024
mul111111111
pow30347401792
sqrt129809699

Security

While I set a high bar for code quality and test coverage, you shouldn't assume that this project is completely safe to use. The contracts have not been audited by a security researcher.

Caveat Emptor

This is experimental software and is provided on an "as is" and "as available" basis. I do not give any warranties and will not be liable for any loss, direct or indirect through continued use of this codebase.

Contact

If you discover any security issues, please report them via Keybase.

Acknowledgements

License

Unlicense © Paul Razvan Berg