From c7636bdc4c20b8193825e48b6d476dca0e06a671 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Mon, 28 Aug 2017 17:30:59 -0300 Subject: [PATCH 1/3] add safe ERC20 helpers --- contracts/token/SafeERC20.sol | 22 +++++++++++++++ test/SafeERC20.js | 27 ++++++++++++++++++ test/helpers/SafeERC20Helper.sol | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 contracts/token/SafeERC20.sol create mode 100644 test/SafeERC20.js create mode 100644 test/helpers/SafeERC20Helper.sol diff --git a/contracts/token/SafeERC20.sol b/contracts/token/SafeERC20.sol new file mode 100644 index 000000000..405a7617b --- /dev/null +++ b/contracts/token/SafeERC20.sol @@ -0,0 +1,22 @@ +pragma solidity ^0.4.11; + +import './ERC20Basic.sol'; +import './ERC20.sol'; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure. + */ +library SafeERC20 { + function safeTransfer(ERC20Basic token, address to, uint256 value) internal { + assert(token.transfer(to, value)); + } + + function safeTransferFrom(ERC20 token, address from, address to, uint256 value) internal { + assert(token.transferFrom(from, to, value)); + } + + function safeApprove(ERC20 token, address spender, uint256 value) internal { + assert(token.approve(spender, value)); + } +} diff --git a/test/SafeERC20.js b/test/SafeERC20.js new file mode 100644 index 000000000..b2cf67d50 --- /dev/null +++ b/test/SafeERC20.js @@ -0,0 +1,27 @@ +import EVMThrow from './helpers/EVMThrow'; + +require('chai') + .use(require('chai-as-promised')) + .should(); + +const SafeERC20Helper = artifacts.require('./helpers/SafeERC20Helper.sol'); + +contract('SafeERC20', function () { + + beforeEach(async function () { + this.helper = await SafeERC20Helper.new(); + }); + + it('should throw on failed transfer', async function () { + await this.helper.doFailingTransfer().should.be.rejectedWith(EVMThrow); + }); + + it('should throw on failed transferFrom', async function () { + await this.helper.doFailingTransferFrom().should.be.rejectedWith(EVMThrow); + }); + + it('should throw on failed approve', async function () { + await this.helper.doFailingApprove().should.be.rejectedWith(EVMThrow); + }); + +}); diff --git a/test/helpers/SafeERC20Helper.sol b/test/helpers/SafeERC20Helper.sol new file mode 100644 index 000000000..b0d6994c8 --- /dev/null +++ b/test/helpers/SafeERC20Helper.sol @@ -0,0 +1,48 @@ +pragma solidity ^0.4.11; + +import '../../contracts/token/ERC20.sol'; +import '../../contracts/token/SafeERC20.sol'; + +contract ERC20FailingMock is ERC20 { + function transfer(address, uint256) returns (bool) { + return false; + } + + function transferFrom(address, address, uint256) returns (bool) { + return false; + } + + function approve(address, uint256) returns (bool) { + return false; + } + + function balanceOf(address) constant returns (uint256) { + return 0; + } + + function allowance(address, address) constant returns (uint256) { + return 0; + } +} + +contract SafeERC20Helper { + using SafeERC20 for ERC20; + + ERC20 token; + + function SafeERC20Helper() { + token = new ERC20FailingMock(); + } + + function doFailingTransfer() { + token.safeTransfer(0, 0); + } + + function doFailingTransferFrom() { + token.safeTransferFrom(0, 0, 0); + } + + function doFailingApprove() { + token.safeApprove(0, 0); + } +} From 7b463769a5d88fb1e312b10ce5bf810d39853289 Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Mon, 28 Aug 2017 17:34:25 -0300 Subject: [PATCH 2/3] explain how to use SafeERC20 --- contracts/token/SafeERC20.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/token/SafeERC20.sol b/contracts/token/SafeERC20.sol index 405a7617b..cffa8b9f8 100644 --- a/contracts/token/SafeERC20.sol +++ b/contracts/token/SafeERC20.sol @@ -6,6 +6,8 @@ import './ERC20.sol'; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure. + * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { function safeTransfer(ERC20Basic token, address to, uint256 value) internal { From c37c233d22ccab2e599cb6ccbc25e1e11710994f Mon Sep 17 00:00:00 2001 From: Francisco Giordano Date: Mon, 28 Aug 2017 17:47:48 -0300 Subject: [PATCH 3/3] add SafeERC20 tests for succeeding operations --- test/SafeERC20.js | 11 ++++++++ test/helpers/SafeERC20Helper.sol | 46 ++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/test/SafeERC20.js b/test/SafeERC20.js index b2cf67d50..46b9ad1c0 100644 --- a/test/SafeERC20.js +++ b/test/SafeERC20.js @@ -24,4 +24,15 @@ contract('SafeERC20', function () { await this.helper.doFailingApprove().should.be.rejectedWith(EVMThrow); }); + it('should not throw on succeeding transfer', async function () { + await this.helper.doSucceedingTransfer().should.be.fulfilled; + }); + + it('should not throw on succeeding transferFrom', async function () { + await this.helper.doSucceedingTransferFrom().should.be.fulfilled; + }); + + it('should not throw on succeeding approve', async function () { + await this.helper.doSucceedingApprove().should.be.fulfilled; + }); }); diff --git a/test/helpers/SafeERC20Helper.sol b/test/helpers/SafeERC20Helper.sol index b0d6994c8..693af0934 100644 --- a/test/helpers/SafeERC20Helper.sol +++ b/test/helpers/SafeERC20Helper.sol @@ -25,24 +25,60 @@ contract ERC20FailingMock is ERC20 { } } +contract ERC20SucceedingMock is ERC20 { + function transfer(address, uint256) returns (bool) { + return true; + } + + function transferFrom(address, address, uint256) returns (bool) { + return true; + } + + function approve(address, uint256) returns (bool) { + return true; + } + + function balanceOf(address) constant returns (uint256) { + return 0; + } + + function allowance(address, address) constant returns (uint256) { + return 0; + } +} + contract SafeERC20Helper { using SafeERC20 for ERC20; - ERC20 token; + ERC20 failing; + ERC20 succeeding; function SafeERC20Helper() { - token = new ERC20FailingMock(); + failing = new ERC20FailingMock(); + succeeding = new ERC20SucceedingMock(); } function doFailingTransfer() { - token.safeTransfer(0, 0); + failing.safeTransfer(0, 0); } function doFailingTransferFrom() { - token.safeTransferFrom(0, 0, 0); + failing.safeTransferFrom(0, 0, 0); } function doFailingApprove() { - token.safeApprove(0, 0); + failing.safeApprove(0, 0); + } + + function doSucceedingTransfer() { + succeeding.safeTransfer(0, 0); + } + + function doSucceedingTransferFrom() { + succeeding.safeTransferFrom(0, 0, 0); + } + + function doSucceedingApprove() { + succeeding.safeApprove(0, 0); } }