Skip to main content

Math Library

The Math library provides standard mathematical utilities missing in Solidity, based on OpenZeppelin's implementation. It includes safe arithmetic operations, rounding functions, and advanced mathematical operations with overflow protection.

Overview

Library Type: Pure functions
License: MIT (OpenZeppelin)
Import Path: @evvm/testnet-contracts/library/primitives/Math.sol
Version: Based on OpenZeppelin v5.0.0

Key Features

  • Overflow-safe operations with try* functions
  • Rounding control for division operations
  • Min/max comparisons
  • Advanced operations (sqrt, log, mulDiv)
  • Gas-optimized implementations

Enums

Rounding

enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}

Controls rounding behavior for division and advanced operations.

Basic Arithmetic (Try Functions)

These functions return (bool success, uint256 result) to handle overflows gracefully.

tryAdd

function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256)

Description: Safe addition with overflow check

Returns:

  • success: true if no overflow, false otherwise
  • result: Sum if successful, 0 if overflow

Example:

(bool success, uint256 sum) = Math.tryAdd(100, 50);
if (success) {
// sum = 150
}

(bool overflow, uint256 invalid) = Math.tryAdd(type(uint256).max, 1);
// overflow = false, invalid = 0

trySub

function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256)

Description: Safe subtraction with underflow check

Returns:

  • success: true if a >= b, false otherwise
  • result: Difference if successful, 0 if underflow

Example:

(bool success, uint256 diff) = Math.trySub(100, 50);
// success = true, diff = 50

(bool underflow, uint256 invalid) = Math.trySub(50, 100);
// underflow = false, invalid = 0

tryMul

function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256)

Description: Safe multiplication with overflow check

Example:

(bool success, uint256 product) = Math.tryMul(10, 20);
// success = true, product = 200

(bool overflow, ) = Math.tryMul(type(uint256).max, 2);
// overflow = false

tryDiv

function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256)

Description: Safe division with zero-check

Returns:

  • success: true if b != 0, false if division by zero
  • result: Quotient if successful, 0 if division by zero

Example:

(bool success, uint256 quotient) = Math.tryDiv(100, 5);
// success = true, quotient = 20

(bool divByZero, ) = Math.tryDiv(100, 0);
// divByZero = false

tryMod

function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256)

Description: Safe modulo with zero-check

Example:

(bool success, uint256 remainder) = Math.tryMod(100, 30);
// success = true, remainder = 10

Comparison Functions

max

function max(uint256 a, uint256 b) internal pure returns (uint256)

Description: Returns the larger of two numbers

Example:

uint256 maxValue = Math.max(100, 200);
// maxValue = 200

min

function min(uint256 a, uint256 b) internal pure returns (uint256)

Description: Returns the smaller of two numbers

Example:

uint256 minValue = Math.min(100, 200);
// minValue = 100

average

function average(uint256 a, uint256 b) internal pure returns (uint256)

Description: Returns the average of two numbers, rounded towards zero

Implementation: (a & b) + (a ^ b) / 2 (prevents overflow)

Example:

uint256 avg = Math.average(100, 200);
// avg = 150

uint256 avgOdd = Math.average(100, 201);
// avgOdd = 150 (rounded down)

Advanced Operations

sqrt

function sqrt(uint256 a) internal pure returns (uint256)
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256)

Description: Calculates the square root with optional rounding

Example:

uint256 root = Math.sqrt(144);
// root = 12

uint256 rootFloor = Math.sqrt(150, Math.Rounding.Floor);
// rootFloor = 12

uint256 rootCeil = Math.sqrt(150, Math.Rounding.Ceil);
// rootCeil = 13

log10

function log10(uint256 value) internal pure returns (uint256)
function log10(uint256 value, Rounding rounding) internal pure returns (uint256)

Description: Calculates log₁₀(value) with optional rounding

Example:

uint256 log = Math.log10(1000);
// log = 3

uint256 logFloor = Math.log10(1500, Math.Rounding.Floor);
// logFloor = 3

uint256 logCeil = Math.log10(1500, Math.Rounding.Ceil);
// logCeil = 4

log2

function log2(uint256 value) internal pure returns (uint256)
function log2(uint256 value, Rounding rounding) internal pure returns (uint256)

Description: Calculates log₂(value) with optional rounding

Example:

uint256 log = Math.log2(256);
// log = 8

uint256 log = Math.log2(100);
// log = 6 (2^6 = 64, 2^7 = 128)

log256

function log256(uint256 value) internal pure returns (uint256)
function log256(uint256 value, Rounding rounding) internal pure returns (uint256)

Description: Calculates log₂₅₆(value) with optional rounding

mulDiv

function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256)
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256)

Description: Calculates (x * y) / denominator with full precision (512-bit intermediate result)

Reverts: MathOverflowedMulDiv() if result overflows or denominator is zero

Use Case: Prevents overflow in (a * b) / c calculations

Example:

// Calculate percentage: (amount * 15) / 100
uint256 result = Math.mulDiv(1000 ether, 15, 100);
// result = 150 ether

// With rounding
uint256 resultCeil = Math.mulDiv(1000 ether, 15, 100, Math.Rounding.Ceil);

Common Use Cases

Use Case 1: Safe Arithmetic in Services

contract Service {
function calculateReward(uint256 baseReward, uint256 multiplier) public pure returns (uint256) {
(bool success, uint256 reward) = Math.tryMul(baseReward, multiplier);
require(success, "Reward overflow");
return reward;
}
}

Use Case 2: Percentage Calculations

function calculateFee(uint256 amount, uint256 feePercentage) internal pure returns (uint256) {
// Calculate fee with precision: (amount * feePercentage) / 10000
// Supports up to 0.01% precision
return Math.mulDiv(amount, feePercentage, 10000);
}

// Example: 2.5% fee on 1000 tokens
uint256 fee = calculateFee(1000, 250); // 250 = 2.5% * 100
// fee = 25

Use Case 3: Finding Min/Max Values

function clampValue(uint256 value, uint256 minVal, uint256 maxVal) internal pure returns (uint256) {
return Math.min(Math.max(value, minVal), maxVal);
}

uint256 clamped = clampValue(150, 100, 120);
// clamped = 120 (capped at max)

Use Case 4: Logarithmic Scaling

function calculateTier(uint256 amount) internal pure returns (uint256) {
if (amount == 0) return 0;

// Tier increases logarithmically with amount
return Math.log10(amount, Math.Rounding.Floor);
}

uint256 tier1 = calculateTier(100); // tier = 2
uint256 tier2 = calculateTier(1000); // tier = 3
uint256 tier3 = calculateTier(10000); // tier = 4

Error Handling

MathOverflowedMulDiv

error MathOverflowedMulDiv();

Thrown by: mulDiv functions
Reason: Result overflows uint256 or denominator is zero

Example:

try Math.mulDiv(type(uint256).max, 2, 1) returns (uint256) {
// Won't reach here
} catch {
// Catches MathOverflowedMulDiv
}

Best Practices

1. Use Try Functions for User Input

// Good - handle overflow gracefully
function addUserAmounts(uint256 a, uint256 b) external pure returns (uint256) {
(bool success, uint256 sum) = Math.tryAdd(a, b);
require(success, "Amount overflow");
return sum;
}

// Bad - unchecked addition can overflow
function addUserAmounts(uint256 a, uint256 b) external pure returns (uint256) {
return a + b; // Can silently overflow
}

2. Use mulDiv for Precision

// Good - maintains precision
uint256 result = Math.mulDiv(largeAmount, percentage, 100);

// Bad - can overflow or lose precision
uint256 result = (largeAmount * percentage) / 100; // Overflow risk

3. Choose Appropriate Rounding

// For user-favorable rounding (fees)
uint256 fee = Math.mulDiv(amount, feeRate, 10000, Math.Rounding.Floor);

// For protocol-favorable rounding (rewards)
uint256 reward = Math.mulDiv(stake, rate, 10000, Math.Rounding.Ceil);

Gas Considerations

  • Try functions: ~50-100 gas overhead vs unchecked operations
  • mulDiv: More expensive than simple division but prevents overflow
  • log functions: ~200-500 gas depending on input size
  • sqrt: ~150-300 gas depending on input size

Integration with EVVM

The Math library is used internally by:

  • AdvancedStrings: log10 for uint to string conversion
  • Staking: mulDiv for reward calculations
  • Treasury: Safe arithmetic for fee calculations

See Also