OpenZeppelin / openzeppelin-contracts

OpenZeppelin Contracts is a library for secure smart contract development.
https://openzeppelin.com/contracts
MIT License
25.02k stars 11.82k forks source link

optimize SafeMath contract #4076

Open codeislight1 opened 1 year ago

codeislight1 commented 1 year ago

Brief: I was reviewing the contract, i noticed that there are couple of gas optimizations that are worth implementing:

Implementation:

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 c) {
        unchecked {
            c = a + b;
            if (c < a) c = 0;
            else success = true;
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 c) {
        unchecked {
            if (b <= a) {
                c = a - b;
                success = true;
            }
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 c) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) success = true;
            else {
                c = a * b;
                if (c / a != b) {
                    c = 0;
                } else {
                    success = true;
                }
            }
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 c) {
        unchecked {
            if (b > 0) {
                c = a / b;
                success = true;
            }
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 c) {
        unchecked {
            if (b > 0) {
                c = a % b;
                success = true;
            }
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
        unchecked {
            c = a + b;
            require(c >= a, "SafeMath: addition overflow");
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256 c) {
        unchecked {
            require(b <= a, "SafeMath: subtraction overflow");
            c = a - b;
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
        unchecked {
            c = a * b;
            require(c / a == b, "SafeMath: multiplication overflow");
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256 c) {
        unchecked {
            require(b > 0, "SafeMath: division by zero");
            c = a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256 c) {
        unchecked {
            require(b > 0, "SafeMath: modulo by zero");
            return a % b;
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256 c) {
        unchecked {
            require(b <= a, errorMessage);
            c = a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256 c) {
        unchecked {
            require(b > 0, errorMessage);
            c = a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256 c) {
        unchecked {
            require(b > 0, errorMessage);
            c = a % b;
        }
    }
}
Amxx commented 1 year ago

Hello @co

Have you measured the gas savings this would bring ?

codeislight1 commented 1 year ago

I have tested it on Remix, the following are the gas savings:

// add saves 136 - 87 = 49
// sub saves 136 - 82 = 54
// mul saves 152 - 111 = 41
// div saves 121 - 108 = 13
// mod saves 121 - 108 = 13

// tryAdd saves 112 - 103 = 9 
// trySub saves 101 - 97 = 4
// tryMul saves 159 - 155 = 4
// tryDiv saves 127 - 123 = 4
// tryMod saves 127 - 123 = 4

The following is the script used to profile the gas savings:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
import "./SafeMath.sol";

abstract contract gasProfiler{
    event Gas(uint gas);
    event EmitMsg(string message);
    event Gas(string message, uint gas);
    modifier profile(){
        uint gas = gasleft();
        _;
        emit Gas(gas - gasleft());
    }
}
contract Test is gasProfiler {
    using SafeMath for uint256;
    // 0 appended functions are the original one, while 1 refers for the optimized version
    function testAdd0(uint a, uint b) internal profile() { // 136
        a.add(b);
    }
    function testAdd1(uint a, uint b) internal profile() { // 87
        a.add1(b);
    }
    function testSub0(uint a, uint b) internal profile() { // 136
        a.sub(b);
    }
    function testSub1(uint a, uint b) internal profile() { // 82
        a.sub1(b);
    }
    function testMul0(uint a, uint b) internal profile() { // 152
        a.mul(b);
    }
    function testMul1(uint a, uint b) internal profile() { // 111
        a.mul1(b);
    }
    function testDiv0(uint a, uint b) internal profile() { // 121
        a.div(b);
    }
    function testDiv1(uint a, uint b) internal profile() { // 108
        a.div1(b);
    }
    function testMod0(uint a, uint b) internal profile() { // 121
        a.mod(b);
    }
    function testMod1(uint a, uint b) internal profile() { // 108
        a.mod1(b);
    }

    function testTryAdd0(uint a, uint b) internal profile() { // 112
        a.tryAdd(b);
    }
    function testTryAdd1(uint a, uint b) internal profile() { // 103
        a.tryAdd1(b);
    }
    function testTrySub0(uint a, uint b) internal profile() { // 101
        a.trySub(b);
    }
    function testTrySub1(uint a, uint b) internal profile() { // 97
        a.trySub1(b);
    }
    function testTryMul0(uint a, uint b) internal profile() { // 159
        a.tryMul(b);
    }
    function testTryMul1(uint a, uint b) internal profile() { // 155
        a.tryMul1(b);
    }
    function testTryDiv0(uint a, uint b) internal profile() { // 127
        a.tryDiv(b);
    }
    function testTryDiv1(uint a, uint b) internal profile() { // 123
        a.tryDiv1(b);
    }
    function testTryMod0(uint a, uint b) internal profile() { // 127
        a.tryMod(b);
    }
    function testTryMod1(uint a, uint b) internal profile() { // 123
        a.tryMod1(b);
    }

    enum Operations {
        add, sub, mul, div, mod, tryAdd, trySub, tryMul, tryDiv, tryMod
    }

    function run(uint a, uint b, Operations op) external {
        if(op == Operations.add){
            testAdd0(a,b);
            testAdd1(a,b);
        } else if(op == Operations.sub){
            testSub0(a,b);
            testSub1(a,b);
        } else if(op == Operations.mul){
            testMul0(a,b);
            testMul1(a,b);
        } else if(op == Operations.div){
            testDiv0(a,b);
            testDiv1(a,b);
        } else if(op == Operations.mod){
            testMod0(a,b);
            testMod1(a,b);
        } else if(op == Operations.tryAdd){
            testTryAdd0(a,b);
            testTryAdd1(a,b);
        } else if(op == Operations.trySub){
            testTrySub0(a,b);
            testTrySub1(a,b);
        } else if(op == Operations.tryMul){
            testTryMul0(a,b);
            testTryMul1(a,b);
        } else if(op == Operations.tryDiv){
            testTryDiv0(a,b);
            testTryDiv1(a,b);
        } else if(op == Operations.tryMod){
            testTryMod0(a,b);
            testTryMod1(a,b);
        }
    }
}