Skip to content

Commit

Permalink
Refactor SafeMath to avoid memory leaks (#2462)
Browse files Browse the repository at this point in the history
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
  • Loading branch information
Amxx and frangio authored Jan 18, 2021
1 parent 5a58fd2 commit c342114
Show file tree
Hide file tree
Showing 7 changed files with 584 additions and 133 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
* `ERC20Permit`: added an implementation of the ERC20 permit extension for gasless token approvals. ([#2237](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2237))
* Presets: added token presets with preminted fixed supply `ERC20PresetFixedSupply` and `ERC777PresetFixedSupply`. ([#2399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2399))
* `Address`: added `functionDelegateCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333))
* `Context`: moved from `contracts/GSN` to `contracts/utils`. ([#2453](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2453))
* `Context`: moved from `contracts/GSN` to `contracts/utils`. ([#2453](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2453))
* `PaymentSplitter`: replace usage of `.transfer()` with `Address.sendValue` for improved compatibility with smart wallets. ([#2455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2455))
* `UpgradeableProxy`: bubble revert reasons from initialization calls. ([#2454](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2454))
* `UpgradeableProxy`: bubble revert reasons from initialization calls. ([#2454](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2454))
* `SafeMath`: fix a memory allocation issue by adding new `SafeMath.tryOp(uint,uint)→(bool,uint)` functions. `SafeMath.op(uint,uint,string)→uint` are now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462))
* `EnumerableMap`: fix a memory allocation issue by adding new `EnumerableMap.tryGet(uint)→(bool,address)` functions. `EnumerableMap.get(uint)→string` is now deprecated. ([#2462](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2462))

## 3.3.0 (2020-11-26)

Expand Down
141 changes: 93 additions & 48 deletions contracts/math/SafeMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,52 @@ pragma solidity >=0.6.0 <0.8.0;
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}

/**
* @dev Returns the substraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b > a) return (false, 0);
return (true, a - b);
}

/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
// 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) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}

/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a / b);
}

/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
if (b == 0) return (false, 0);
return (true, a % b);
}

/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
Expand All @@ -29,7 +75,6 @@ library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");

return c;
}

Expand All @@ -44,24 +89,8 @@ library SafeMath {
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}

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

return c;
require(b <= a, "SafeMath: subtraction overflow");
return a - b;
}

/**
Expand All @@ -75,21 +104,14 @@ library SafeMath {
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// 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) {
return 0;
}

if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");

return c;
}

/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* @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. Note: this function uses a
Expand All @@ -101,48 +123,71 @@ library SafeMath {
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
require(b > 0, "SafeMath: division by zero");
return a / b;
}

/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* reverting when dividing by 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).
* 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 div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: modulo by zero");
return a % b;
}

return c;
/**
* @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) {
require(b <= a, errorMessage);
return a - b;
}

/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
* @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. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
* CAUTION: This function is deprecated because it requires allocating memory for the error
* message unnecessarily. For custom revert reasons use {tryDiv}.
*
* 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 mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b > 0, errorMessage);
return a / b;
}

/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
* 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
Expand All @@ -153,7 +198,7 @@ library SafeMath {
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
require(b > 0, errorMessage);
return a % b;
}
}
8 changes: 8 additions & 0 deletions contracts/mocks/EnumerableMapMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ contract EnumerableMapMock {
}


function tryGet(uint256 key) public view returns (bool, address) {
return _map.tryGet(key);
}

function get(uint256 key) public view returns (address) {
return _map.get(key);
}

function getWithMessage(uint256 key, string calldata errorMessage) public view returns (address) {
return _map.get(key, errorMessage);
}
}
90 changes: 84 additions & 6 deletions contracts/mocks/SafeMathMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,101 @@ pragma solidity >=0.6.0 <0.8.0;
import "../math/SafeMath.sol";

contract SafeMathMock {
function mul(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.mul(a, b);
function tryAdd(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.tryAdd(a, b);
}

function div(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.div(a, b);
function trySub(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.trySub(a, b);
}

function sub(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.sub(a, b);
function tryMul(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.tryMul(a, b);
}

function tryDiv(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.tryDiv(a, b);
}

function tryMod(uint256 a, uint256 b) public pure returns (bool flag, uint256 value) {
return SafeMath.tryMod(a, b);
}

function add(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.add(a, b);
}

function sub(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.sub(a, b);
}

function mul(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.mul(a, b);
}

function div(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.div(a, b);
}

function mod(uint256 a, uint256 b) public pure returns (uint256) {
return SafeMath.mod(a, b);
}

function subWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) {
return SafeMath.sub(a, b, errorMessage);
}

function divWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) {
return SafeMath.div(a, b, errorMessage);
}

function modWithMessage(uint256 a, uint256 b, string memory errorMessage) public pure returns (uint256) {
return SafeMath.mod(a, b, errorMessage);
}

function addMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.add(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}

function subMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.sub(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}

function mulMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.mul(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}

function divMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.div(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}

function modMemoryCheck() public pure returns (uint256 mem) {
uint256 length = 32;
// solhint-disable-next-line no-inline-assembly
assembly { mem := mload(0x40) }
for (uint256 i = 0; i < length; ++i) { SafeMath.mod(1, 1); }
// solhint-disable-next-line no-inline-assembly
assembly { mem := sub(mload(0x40), mem) }
}

}
Loading

0 comments on commit c342114

Please sign in to comment.