UnsafeMath for Solidity 0.8.0+

math

UnsafeMath is a Solidity library used to perform unchecked, or “unsafe”, math operations. Prior to version 0.8.0 all math was unchecked meaning that subtracting 1 from 0 would underflow and result in the max uint256 value. This behavior led many contracts to use the OpenZeppelin SafeMath library to performed checked math – using the prior example subtracting 1 from 0 would throw an exception as a uint256 is unsigned and therefore cannot be negative. In Solidity 0.8.0+ all math operations became checked, but at a cost of more gas used per operation.

Unchecked Math Library

The UnsafeMath library allows you to perform unchecked math operations where you are confident the result will not be an underflow or an overflow of the uint256 space – saving gas in your contracts where checked math is not needed.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// solhint-disable func-name-mixedcase

library UnsafeMath {
    function unsafe_add(uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            return a + b;
        }
    }

    function unsafe_sub(uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            return a - b;
        }
    }

    function unsafe_div(uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            uint256 result;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                result := div(a, b)
            }
            return result;
        }
    }

    function unsafe_mul(uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            return a * b;
        }
    }

    function unsafe_increment(uint256 a) internal pure returns (uint256) {
        unchecked {
            return ++a;
        }
    }

    function unsafe_decrement(uint256 a) internal pure returns (uint256) {
        unchecked {
            return --a;
        }
    }
}

Gas Usage Tests

This test contract uses the UnsafeMath.unsafe_decrement() and Unsafe.unsafe_decrement() functions alongside their checked counterparts to test the difference in gas used between the different methods.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import './UnsafeMath.sol';

contract TestUnsafeMath {
    using UnsafeMath for uint256;

    uint256 private _s_foobar;

    function safeDecrement(uint256 count) public {
        for (uint256 i = count; i > 0; --i) {
            _s_foobar = i;
        }
    }

    function safeIncrement(uint256 count) public {
        for (uint256 i = 0; i < count; ++i) {
            _s_foobar = i;
        }
    }

    function unsafeDecrement(uint256 count) public {
        for (uint256 i = count; i > 0; i = i.unsafe_decrement()) {
            _s_foobar = i;
        }
    }

    function unsafeIncrement(uint256 count) public {
        for (uint256 i = 0; i < count; i = i.unsafe_increment()) {
            _s_foobar = i;
        }
    }
}

Using a simple Mocha setup, our tests will call each of the contract functions with an argument for 100 iterations.

import { ethers } from 'hardhat';
import { ContractFactory } from '@ethersproject/contracts';
import { TestUnsafeMath } from '../sdk/types';

describe('UnsafeMath', () => {
  let testUnsafeMathDeploy: ContractFactory, testUnsafeMathContract: TestUnsafeMath;

  beforeEach(async () => {
    testUnsafeMathDeploy = await ethers.getContractFactory('TestUnsafeMath', {});
    testUnsafeMathContract = (await testUnsafeMathDeploy.deploy()) as TestUnsafeMath;
  });

  describe('Gas Used', async () => {
    it('safeDecrement gas used', async () => {
      const tx = await testUnsafeMathContract.safeDecrement(100);
      // const receipt = await tx.wait();
      // console.log(receipt.gasUsed.toString(), 'gasUsed');
    });
    
    it('safeIncrement gas used', async () => {
      const tx = await testUnsafeMathContract.safeIncrement(100);
      // const receipt = await tx.wait();
      // console.log(receipt.gasUsed.toString(), 'gasUsed');
    });
    
    it('unsafeDecrement gas used', async () => {
      const tx = await testUnsafeMathContract.unsafeDecrement(100);
      // const receipt = await tx.wait();
      // console.log(receipt.gasUsed.toString(), 'gasUsed');
    });
    
    it('unsafeIncrement gas used', async () => {
      const tx = await testUnsafeMathContract.unsafeIncrement(100);
      // const receipt = await tx.wait();
      // console.log(receipt.gasUsed.toString(), 'gasUsed');
    });
  });
});

The results show that a checked incrementing loop used 60276 gas, checked decrementing used 59424 gas, unchecked incrementing used 58117 gas, and unchecked decrementing came in at 57473 gas.

That’s a savings of 2803 gas on a 100 iteration loop, or 4.55% of the total gas used.

  UnsafeMath
    Gas Used
      ✓ safeDecrement gas used
      ✓ safeIncrement gas used
      ✓ unsafeDecrement gas used
      ✓ unsafeIncrement gas used

·--------------------------------------|---------------------------|----------------|-----------------------------·
|         Solc version: 0.8.15         ·  Optimizer enabled: true  ·  Runs: 999999  ·  Block limit: 30000000 gas  │
·······································|···························|················|······························
|  Methods                                                                                                        │
···················|···················|·············|·············|················|···············|··············
|  Contract        ·  Method           ·  Min        ·  Max        ·  Avg           ·  # calls      ·  usd (avg)  │
···················|···················|·············|·············|················|···············|··············
|  TestUnsafeMath  ·  safeDecrement    ·          -  ·          -  ·         59424  ·            1  ·          -  │
···················|···················|·············|·············|················|···············|··············
|  TestUnsafeMath  ·  safeIncrement    ·          -  ·          -  ·         60276  ·            1  ·          -  │
···················|···················|·············|·············|················|···············|··············
|  TestUnsafeMath  ·  unsafeDecrement  ·          -  ·          -  ·         57473  ·            1  ·          -  │
···················|···················|·············|·············|················|···············|··············
|  TestUnsafeMath  ·  unsafeIncrement  ·          -  ·          -  ·         58117  ·            1  ·          -  │
···················|···················|·············|·············|················|···············|··············
|  Deployments                         ·                                            ·  % of limit   ·             │
·······································|·············|·············|················|···············|··············
|  TestUnsafeMath                      ·          -  ·          -  ·        188806  ·        0.6 %  ·          -  │
·--------------------------------------|-------------|-------------|----------------|---------------|-------------·

  4 passing (2s)

You may also like...

Leave a Reply

Your email address will not be published.