Functions in SafeMath contract overloaded to accept custom error messages (#1828)
* Imporvement: functions in SafeMath contract overloaded to accept custom error messages. * CHANGELOG updated, custom error messages added to ERC20, ERC721 and ERC777 for subtraction related exceptions. * SafeMath overloads for 'add' and 'mul' removed. * Error messages modified. * Update CHANGELOG.md
This commit is contained in:
committed by
Nicolás Venturo
parent
377431bc4c
commit
5d34dbecea
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
### Improvements:
|
### Improvements:
|
||||||
* `Address.isContract`: switched from `extcodesize` to `extcodehash` for less gas usage. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802))
|
* `Address.isContract`: switched from `extcodesize` to `extcodehash` for less gas usage. ([#1802](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1802))
|
||||||
|
* `SafeMath`: added custom error messages support for `sub`, `div` and `mod` functions. `ERC20` and `ERC777` updated to throw custom errors on subtraction overflows. ([#1828](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1828))
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,20 @@ library SafeMath {
|
|||||||
* - Subtraction cannot overflow.
|
* - Subtraction cannot overflow.
|
||||||
*/
|
*/
|
||||||
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
require(b <= a, "SafeMath: subtraction overflow");
|
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;
|
uint256 c = a - b;
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
@ -81,8 +94,23 @@ library SafeMath {
|
|||||||
* - The divisor cannot be zero.
|
* - The divisor cannot be zero.
|
||||||
*/
|
*/
|
||||||
function div(uint256 a, uint256 b) internal pure returns (uint256) {
|
function div(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
|
return div(a, b, "SafeMath: division by zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the integer division of two unsigned integers. Reverts 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) {
|
||||||
// Solidity only automatically asserts when dividing by 0
|
// Solidity only automatically asserts when dividing by 0
|
||||||
require(b > 0, "SafeMath: division by zero");
|
require(b > 0, errorMessage);
|
||||||
uint256 c = a / b;
|
uint256 c = a / b;
|
||||||
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
|
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
|
||||||
|
|
||||||
@ -101,7 +129,22 @@ library SafeMath {
|
|||||||
* - The divisor cannot be zero.
|
* - The divisor cannot be zero.
|
||||||
*/
|
*/
|
||||||
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
|
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||||
require(b != 0, "SafeMath: modulo by zero");
|
return mod(a, b, "SafeMath: modulo by zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
|
||||||
|
* Reverts with custom message 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, string memory errorMessage) internal pure returns (uint256) {
|
||||||
|
require(b != 0, errorMessage);
|
||||||
return a % b;
|
return a % b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,7 +96,7 @@ contract ERC20 is IERC20 {
|
|||||||
*/
|
*/
|
||||||
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
|
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
|
||||||
_transfer(sender, recipient, amount);
|
_transfer(sender, recipient, amount);
|
||||||
_approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
|
_approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ contract ERC20 is IERC20 {
|
|||||||
* `subtractedValue`.
|
* `subtractedValue`.
|
||||||
*/
|
*/
|
||||||
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
|
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
|
||||||
_approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
|
_approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ contract ERC20 is IERC20 {
|
|||||||
require(sender != address(0), "ERC20: transfer from the zero address");
|
require(sender != address(0), "ERC20: transfer from the zero address");
|
||||||
require(recipient != address(0), "ERC20: transfer to the zero address");
|
require(recipient != address(0), "ERC20: transfer to the zero address");
|
||||||
|
|
||||||
_balances[sender] = _balances[sender].sub(amount);
|
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
|
||||||
_balances[recipient] = _balances[recipient].add(amount);
|
_balances[recipient] = _balances[recipient].add(amount);
|
||||||
emit Transfer(sender, recipient, amount);
|
emit Transfer(sender, recipient, amount);
|
||||||
}
|
}
|
||||||
@ -190,8 +190,8 @@ contract ERC20 is IERC20 {
|
|||||||
function _burn(address account, uint256 value) internal {
|
function _burn(address account, uint256 value) internal {
|
||||||
require(account != address(0), "ERC20: burn from the zero address");
|
require(account != address(0), "ERC20: burn from the zero address");
|
||||||
|
|
||||||
|
_balances[account] = _balances[account].sub(value, "ERC20: burn amount exceeds balance");
|
||||||
_totalSupply = _totalSupply.sub(value);
|
_totalSupply = _totalSupply.sub(value);
|
||||||
_balances[account] = _balances[account].sub(value);
|
|
||||||
emit Transfer(account, address(0), value);
|
emit Transfer(account, address(0), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,6 +224,6 @@ contract ERC20 is IERC20 {
|
|||||||
*/
|
*/
|
||||||
function _burnFrom(address account, uint256 amount) internal {
|
function _burnFrom(address account, uint256 amount) internal {
|
||||||
_burn(account, amount);
|
_burn(account, amount);
|
||||||
_approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
|
_approve(account, msg.sender, _allowances[account][msg.sender].sub(amount, "ERC20: burn amount exceeds allowance"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ library SafeERC20 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
|
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
|
||||||
uint256 newAllowance = token.allowance(address(this), spender).sub(value);
|
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
|
||||||
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
|
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -285,7 +285,7 @@ contract ERC777 is IERC777, IERC20 {
|
|||||||
_callTokensToSend(spender, holder, recipient, amount, "", "");
|
_callTokensToSend(spender, holder, recipient, amount, "", "");
|
||||||
|
|
||||||
_move(spender, holder, recipient, amount, "", "");
|
_move(spender, holder, recipient, amount, "", "");
|
||||||
_approve(holder, spender, _allowances[holder][spender].sub(amount));
|
_approve(holder, spender, _allowances[holder][spender].sub(amount, "ERC777: transfer amount exceeds allowance"));
|
||||||
|
|
||||||
_callTokensReceived(spender, holder, recipient, amount, "", "", false);
|
_callTokensReceived(spender, holder, recipient, amount, "", "", false);
|
||||||
|
|
||||||
@ -383,8 +383,8 @@ contract ERC777 is IERC777, IERC20 {
|
|||||||
_callTokensToSend(operator, from, address(0), amount, data, operatorData);
|
_callTokensToSend(operator, from, address(0), amount, data, operatorData);
|
||||||
|
|
||||||
// Update state variables
|
// Update state variables
|
||||||
|
_balances[from] = _balances[from].sub(amount, "ERC777: burn amount exceeds balance");
|
||||||
_totalSupply = _totalSupply.sub(amount);
|
_totalSupply = _totalSupply.sub(amount);
|
||||||
_balances[from] = _balances[from].sub(amount);
|
|
||||||
|
|
||||||
emit Burned(operator, from, amount, data, operatorData);
|
emit Burned(operator, from, amount, data, operatorData);
|
||||||
emit Transfer(from, address(0), amount);
|
emit Transfer(from, address(0), amount);
|
||||||
@ -400,7 +400,7 @@ contract ERC777 is IERC777, IERC20 {
|
|||||||
)
|
)
|
||||||
private
|
private
|
||||||
{
|
{
|
||||||
_balances[from] = _balances[from].sub(amount);
|
_balances[from] = _balances[from].sub(amount, "ERC777: transfer amount exceeds balance");
|
||||||
_balances[to] = _balances[to].add(amount);
|
_balances[to] = _balances[to].add(amount);
|
||||||
|
|
||||||
emit Sent(operator, from, to, amount, userData, operatorData);
|
emit Sent(operator, from, to, amount, userData, operatorData);
|
||||||
|
|||||||
@ -88,7 +88,7 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip
|
|||||||
|
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevert(this.token.transferFrom(
|
await expectRevert(this.token.transferFrom(
|
||||||
tokenOwner, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer amount exceeds balance`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -104,7 +104,7 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip
|
|||||||
|
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevert(this.token.transferFrom(
|
await expectRevert(this.token.transferFrom(
|
||||||
tokenOwner, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer amount exceeds allowance`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -114,7 +114,7 @@ function shouldBehaveLikeERC20 (errorPrefix, initialSupply, initialHolder, recip
|
|||||||
|
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevert(this.token.transferFrom(
|
await expectRevert(this.token.transferFrom(
|
||||||
tokenOwner, to, amount, { from: spender }), 'SafeMath: subtraction overflow'
|
tokenOwner, to, amount, { from: spender }), `${errorPrefix}: transfer amount exceeds balance`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -166,7 +166,7 @@ function shouldBehaveLikeERC20Transfer (errorPrefix, from, to, balance, transfer
|
|||||||
|
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevert(transfer.call(this, from, to, amount),
|
await expectRevert(transfer.call(this, from, to, amount),
|
||||||
'SafeMath: subtraction overflow'
|
`${errorPrefix}: transfer amount exceeds balance`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -27,7 +27,7 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
|
|||||||
describe('when there was no approved amount before', function () {
|
describe('when there was no approved amount before', function () {
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevert(this.token.decreaseAllowance(
|
await expectRevert(this.token.decreaseAllowance(
|
||||||
spender, amount, { from: initialHolder }), 'SafeMath: subtraction overflow'
|
spender, amount, { from: initialHolder }), 'ERC20: decreased allowance below zero'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -63,7 +63,7 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
|
|||||||
it('reverts when more than the full allowance is removed', async function () {
|
it('reverts when more than the full allowance is removed', async function () {
|
||||||
await expectRevert(
|
await expectRevert(
|
||||||
this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }),
|
this.token.decreaseAllowance(spender, approvedAmount.addn(1), { from: initialHolder }),
|
||||||
'SafeMath: subtraction overflow'
|
'ERC20: decreased allowance below zero'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -88,7 +88,7 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
|
|||||||
|
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevert(this.token.decreaseAllowance(
|
await expectRevert(this.token.decreaseAllowance(
|
||||||
spender, amount, { from: initialHolder }), 'SafeMath: subtraction overflow'
|
spender, amount, { from: initialHolder }), 'ERC20: decreased allowance below zero'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -221,7 +221,7 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
|
|||||||
describe('for a non zero account', function () {
|
describe('for a non zero account', function () {
|
||||||
it('rejects burning more than balance', async function () {
|
it('rejects burning more than balance', async function () {
|
||||||
await expectRevert(this.token.burn(
|
await expectRevert(this.token.burn(
|
||||||
initialHolder, initialSupply.addn(1)), 'SafeMath: subtraction overflow'
|
initialHolder, initialSupply.addn(1)), 'ERC20: burn amount exceeds balance'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -276,13 +276,13 @@ contract('ERC20', function ([_, initialHolder, recipient, anotherAccount]) {
|
|||||||
describe('for a non zero account', function () {
|
describe('for a non zero account', function () {
|
||||||
it('rejects burning more than allowance', async function () {
|
it('rejects burning more than allowance', async function () {
|
||||||
await expectRevert(this.token.burnFrom(initialHolder, allowance.addn(1)),
|
await expectRevert(this.token.burnFrom(initialHolder, allowance.addn(1)),
|
||||||
'SafeMath: subtraction overflow'
|
'ERC20: burn amount exceeds allowance'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects burning more than balance', async function () {
|
it('rejects burning more than balance', async function () {
|
||||||
await expectRevert(this.token.burnFrom(initialHolder, initialSupply.addn(1)),
|
await expectRevert(this.token.burnFrom(initialHolder, initialSupply.addn(1)),
|
||||||
'SafeMath: subtraction overflow'
|
'ERC20: burn amount exceeds balance'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ function shouldOnlyRevertOnErrors () {
|
|||||||
it('reverts when decreasing the allowance', async function () {
|
it('reverts when decreasing the allowance', async function () {
|
||||||
await expectRevert(
|
await expectRevert(
|
||||||
this.wrapper.decreaseAllowance(10),
|
this.wrapper.decreaseAllowance(10),
|
||||||
'SafeMath: subtraction overflow'
|
'SafeERC20: decreased allowance below zero'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -125,7 +125,7 @@ function shouldOnlyRevertOnErrors () {
|
|||||||
it('reverts when decreasing the allowance to a negative value', async function () {
|
it('reverts when decreasing the allowance to a negative value', async function () {
|
||||||
await expectRevert(
|
await expectRevert(
|
||||||
this.wrapper.decreaseAllowance(200),
|
this.wrapper.decreaseAllowance(200),
|
||||||
'SafeMath: subtraction overflow'
|
'SafeERC20: decreased allowance below zero'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -38,7 +38,7 @@ function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) {
|
|||||||
|
|
||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await expectRevert(this.token.burn(amount, { from: owner }),
|
await expectRevert(this.token.burn(amount, { from: owner }),
|
||||||
'SafeMath: subtraction overflow'
|
'ERC20: burn amount exceeds balance'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -87,7 +87,7 @@ function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) {
|
|||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await this.token.approve(burner, amount, { from: owner });
|
await this.token.approve(burner, amount, { from: owner });
|
||||||
await expectRevert(this.token.burnFrom(owner, amount, { from: burner }),
|
await expectRevert(this.token.burnFrom(owner, amount, { from: burner }),
|
||||||
'SafeMath: subtraction overflow'
|
'ERC20: burn amount exceeds balance'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -98,7 +98,7 @@ function shouldBehaveLikeERC20Burnable (owner, initialBalance, [burner]) {
|
|||||||
it('reverts', async function () {
|
it('reverts', async function () {
|
||||||
await this.token.approve(burner, allowance, { from: owner });
|
await this.token.approve(burner, allowance, { from: owner });
|
||||||
await expectRevert(this.token.burnFrom(owner, allowance.addn(1), { from: burner }),
|
await expectRevert(this.token.burnFrom(owner, allowance.addn(1), { from: burner }),
|
||||||
'SafeMath: subtraction overflow'
|
'ERC20: burn amount exceeds allowance'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user