diff --git a/contracts/token/SafeERC20.sol b/contracts/token/SafeERC20.sol new file mode 100644 index 000000000..cffa8b9f8 --- /dev/null +++ b/contracts/token/SafeERC20.sol @@ -0,0 +1,24 @@ +pragma solidity ^0.4.11; + +import './ERC20Basic.sol'; +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 { + 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..46b9ad1c0 --- /dev/null +++ b/test/SafeERC20.js @@ -0,0 +1,38 @@ +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); + }); + + 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 new file mode 100644 index 000000000..693af0934 --- /dev/null +++ b/test/helpers/SafeERC20Helper.sol @@ -0,0 +1,84 @@ +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 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 failing; + ERC20 succeeding; + + function SafeERC20Helper() { + failing = new ERC20FailingMock(); + succeeding = new ERC20SucceedingMock(); + } + + function doFailingTransfer() { + failing.safeTransfer(0, 0); + } + + function doFailingTransferFrom() { + failing.safeTransferFrom(0, 0, 0); + } + + function doFailingApprove() { + failing.safeApprove(0, 0); + } + + function doSucceedingTransfer() { + succeeding.safeTransfer(0, 0); + } + + function doSucceedingTransferFrom() { + succeeding.safeTransferFrom(0, 0, 0); + } + + function doSucceedingApprove() { + succeeding.safeApprove(0, 0); + } +}