Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved SafeERC20 allowance handling #1407

Merged
merged 28 commits into from Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f2e5ed1
signing prefix added
Aniket-Engg Mar 10, 2018
ebdae17
Merge branch 'master' into master
Aniket-Engg Mar 13, 2018
2bf341c
Minor improvement
Aniket-Engg Mar 13, 2018
27c614a
Merge branch 'master' of https://github.com/Aniket-Engg/zeppelin-soli…
Aniket-Engg Mar 13, 2018
bc7ea7a
Tests changed
Aniket-Engg Mar 13, 2018
5a72021
Successfully tested
Aniket-Engg Mar 13, 2018
de0d2d4
Minor improvements
Aniket-Engg Mar 13, 2018
7f32e5c
Minor improvements
Aniket-Engg Mar 13, 2018
c3fb8a8
minor changes
Aniket-Engg Oct 1, 2018
68de840
Revert "Dangling commas are now required. (#1359)"
Aniket-Engg Oct 1, 2018
92b725f
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 1, 2018
1fe2cdf
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 1, 2018
75bc310
updates
Aniket-Engg Oct 1, 2018
9d9a5a0
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 3, 2018
10b1898
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 4, 2018
3858a0c
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 4, 2018
9484daa
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 5, 2018
5139019
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 8, 2018
3097a48
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 9, 2018
a46e176
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 10, 2018
fc9d1a7
Merge remote-tracking branch 'upstream/master'
Aniket-Engg Oct 11, 2018
23df893
fixes #1404
Aniket-Engg Oct 11, 2018
d8dd5ec
approve failing test
Aniket-Engg Oct 11, 2018
ae96caa
suggested changes done
Aniket-Engg Oct 16, 2018
e2fb20a
ISafeERC20 removed
Aniket-Engg Oct 16, 2018
7f20627
allowance methods in library
Aniket-Engg Oct 17, 2018
dbf529e
Improved SafeERC20 tests.
nventuro Oct 17, 2018
09ef341
Fixed test coverage.
nventuro Oct 18, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 39 additions & 19 deletions contracts/mocks/SafeERC20Helper.sol
Expand Up @@ -3,10 +3,8 @@ pragma solidity ^0.4.24;
import "../token/ERC20/IERC20.sol";
import "../token/ERC20/SafeERC20.sol";

contract ERC20FailingMock is IERC20 {
function totalSupply() public view returns (uint256) {
return 0;
}
contract ERC20FailingMock {
uint256 private _allowance;

function transfer(address, uint256) public returns (bool) {
return false;
Expand All @@ -20,19 +18,13 @@ contract ERC20FailingMock is IERC20 {
return false;
}

function balanceOf(address) public view returns (uint256) {
return 0;
}

function allowance(address, address) public view returns (uint256) {
return 0;
}
}

contract ERC20SucceedingMock is IERC20 {
function totalSupply() public view returns (uint256) {
return 0;
}
contract ERC20SucceedingMock {
uint256 private _allowance;

function transfer(address, uint256) public returns (bool) {
return true;
Expand All @@ -46,12 +38,12 @@ contract ERC20SucceedingMock is IERC20 {
return true;
}

function balanceOf(address) public view returns (uint256) {
return 0;
function setAllowance(uint256 allowance_) public {
_allowance = allowance_;
}

function allowance(address, address) public view returns (uint256) {
return 0;
return _allowance;
}
}

Expand All @@ -62,10 +54,12 @@ contract SafeERC20Helper {
IERC20 private _succeeding;

constructor() public {
_failing = new ERC20FailingMock();
_succeeding = new ERC20SucceedingMock();
_failing = IERC20(new ERC20FailingMock());
_succeeding = IERC20(new ERC20SucceedingMock());
}

// Using _failing

function doFailingTransfer() public {
_failing.safeTransfer(address(0), 0);
}
Expand All @@ -78,6 +72,16 @@ contract SafeERC20Helper {
_failing.safeApprove(address(0), 0);
}

function doFailingIncreaseAllowance() public {
_failing.safeIncreaseAllowance(address(0), 0);
}

function doFailingDecreaseAllowance() public {
_failing.safeDecreaseAllowance(address(0), 0);
}

// Using _succeeding

function doSucceedingTransfer() public {
_succeeding.safeTransfer(address(0), 0);
}
Expand All @@ -86,7 +90,23 @@ contract SafeERC20Helper {
_succeeding.safeTransferFrom(address(0), address(0), 0);
}

function doSucceedingApprove() public {
_succeeding.safeApprove(address(0), 0);
function doSucceedingApprove(uint256 amount) public {
_succeeding.safeApprove(address(0), amount);
}

function doSucceedingIncreaseAllowance(uint256 amount) public {
_succeeding.safeIncreaseAllowance(address(0), amount);
}

function doSucceedingDecreaseAllowance(uint256 amount) public {
_succeeding.safeDecreaseAllowance(address(0), amount);
}

function setAllowance(uint256 allowance_) public {
ERC20SucceedingMock(_succeeding).setAllowance(allowance_);
}

function allowance() public view returns (uint256) {
return _succeeding.allowance(address(0), address(0));
}
}
29 changes: 29 additions & 0 deletions contracts/token/ERC20/SafeERC20.sol
Expand Up @@ -10,6 +10,9 @@ import "./IERC20.sol";
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {

using SafeMath for uint256;

function safeTransfer(
IERC20 token,
address to,
Expand Down Expand Up @@ -38,6 +41,32 @@ library SafeERC20 {
)
internal
{
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require((value == 0) || (token.allowance(msg.sender, spender) == 0));
Aniket-Engg marked this conversation as resolved.
Show resolved Hide resolved
require(token.approve(spender, value));
}

function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
)
internal
{
uint256 newAllowance = token.allowance(address(this), spender).add(value);
require(token.approve(spender, newAllowance));
}

function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
)
internal
{
uint256 newAllowance = token.allowance(address(this), spender).sub(value);
require(token.approve(spender, newAllowance));
}
}
90 changes: 74 additions & 16 deletions test/token/ERC20/SafeERC20.test.js
Expand Up @@ -10,27 +10,85 @@ contract('SafeERC20', function () {
this.helper = await SafeERC20Helper.new();
});

it('should throw on failed transfer', async function () {
await shouldFail.reverting(this.helper.doFailingTransfer());
});
describe('with token that returns false on all calls', function () {
it('reverts on transfer', async function () {
await shouldFail.reverting(this.helper.doFailingTransfer());
});

it('should throw on failed transferFrom', async function () {
await shouldFail.reverting(this.helper.doFailingTransferFrom());
});
it('reverts on transferFrom', async function () {
await shouldFail.reverting(this.helper.doFailingTransferFrom());
});

it('should throw on failed approve', async function () {
await shouldFail.reverting(this.helper.doFailingApprove());
});
it('reverts on approve', async function () {
await shouldFail.reverting(this.helper.doFailingApprove());
});

it('should not throw on succeeding transfer', async function () {
await this.helper.doSucceedingTransfer();
});
it('reverts on increaseAllowance', async function () {
await shouldFail.reverting(this.helper.doFailingIncreaseAllowance());
});

it('should not throw on succeeding transferFrom', async function () {
await this.helper.doSucceedingTransferFrom();
it('reverts on decreaseAllowance', async function () {
await shouldFail.reverting(this.helper.doFailingDecreaseAllowance());
});
});

it('should not throw on succeeding approve', async function () {
await this.helper.doSucceedingApprove();
describe('with token that returns true on all calls', function () {
it('doesn\'t revert on transfer', async function () {
await this.helper.doSucceedingTransfer();
});

it('doesn\'t revert on transferFrom', async function () {
await this.helper.doSucceedingTransferFrom();
});

describe('approvals', function () {
context('with zero allowance', function () {
beforeEach(async function () {
await this.helper.setAllowance(0);
});

it('doesn\'t revert when approving a non-zero allowance', async function () {
await this.helper.doSucceedingApprove(100);
});

it('doesn\'t revert when approving a zero allowance', async function () {
await this.helper.doSucceedingApprove(0);
});

it('doesn\'t revert when increasing the allowance', async function () {
await this.helper.doSucceedingIncreaseAllowance(10);
});

it('reverts when decreasing the allowance', async function () {
await shouldFail.reverting(this.helper.doSucceedingDecreaseAllowance(10));
});
});

context('with non-zero allowance', function () {
beforeEach(async function () {
await this.helper.setAllowance(100);
});

it('reverts when approving a non-zero allowance', async function () {
await shouldFail.reverting(this.helper.doSucceedingApprove(20));
});

it('doesn\'t revert when approving a zero allowance', async function () {
await this.helper.doSucceedingApprove(0);
});

it('doesn\'t revert when increasing the allowance', async function () {
await this.helper.doSucceedingIncreaseAllowance(10);
});

it('doesn\'t revert when decreasing the allowance to a positive value', async function () {
await this.helper.doSucceedingDecreaseAllowance(50);
});

it('reverts when decreasing the allowance to a negative value', async function () {
await shouldFail.reverting(this.helper.doSucceedingDecreaseAllowance(200));
});
});
});
});
});