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); + } +}