diff --git a/contracts/mocks/MessageHelper.sol b/contracts/mocks/MessageHelper.sol index b44b6496a..97caf9899 100644 --- a/contracts/mocks/MessageHelper.sol +++ b/contracts/mocks/MessageHelper.sol @@ -4,12 +4,22 @@ pragma solidity ^0.4.21; contract MessageHelper { event Show(bytes32 b32, uint256 number, string text); + event Buy(bytes32 b32, uint256 number, string text, uint256 value); function showMessage( bytes32 message, uint256 number, string text ) public returns (bool) { emit Show(message, number, text); return true; } + function buyMessage( bytes32 message, uint256 number, string text ) public payable returns (bool) { + emit Buy( + message, + number, + text, + msg.value); + return true; + } + function fail() public { require(false); } diff --git a/contracts/token/ERC827/ERC827.sol b/contracts/token/ERC827/ERC827.sol index 83187f90f..a3ec481ea 100644 --- a/contracts/token/ERC827/ERC827.sol +++ b/contracts/token/ERC827/ERC827.sol @@ -12,8 +12,8 @@ import "../ERC20/ERC20.sol"; * @dev approvals. */ contract ERC827 is ERC20 { - function approveAndCall( address _spender, uint256 _value, bytes _data) public returns (bool); - function transferAndCall( address _to, uint256 _value, bytes _data) public returns (bool); + function approveAndCall( address _spender, uint256 _value, bytes _data) public payable returns (bool); + function transferAndCall( address _to, uint256 _value, bytes _data) public payable returns (bool); function transferFromAndCall( address _from, address _to, @@ -21,5 +21,6 @@ contract ERC827 is ERC20 { bytes _data ) public + payable returns (bool); } diff --git a/contracts/token/ERC827/ERC827Token.sol b/contracts/token/ERC827/ERC827Token.sol index 28e15d08f..c8c7eee84 100644 --- a/contracts/token/ERC827/ERC827Token.sol +++ b/contracts/token/ERC827/ERC827Token.sol @@ -34,12 +34,13 @@ contract ERC827Token is ERC827, StandardToken { * * @return true if the call function was executed successfully */ - function approveAndCall(address _spender, uint256 _value, bytes _data) public returns (bool) { + function approveAndCall(address _spender, uint256 _value, bytes _data) public payable returns (bool) { require(_spender != address(this)); super.approve(_spender, _value); - require(_spender.call(_data)); + // solium-disable-next-line security/no-call-value + require(_spender.call.value(msg.value)(_data)); return true; } @@ -54,12 +55,13 @@ contract ERC827Token is ERC827, StandardToken { * * @return true if the call function was executed successfully */ - function transferAndCall(address _to, uint256 _value, bytes _data) public returns (bool) { + function transferAndCall(address _to, uint256 _value, bytes _data) public payable returns (bool) { require(_to != address(this)); super.transfer(_to, _value); - require(_to.call(_data)); + // solium-disable-next-line security/no-call-value + require(_to.call.value(msg.value)(_data)); return true; } @@ -80,13 +82,14 @@ contract ERC827Token is ERC827, StandardToken { uint256 _value, bytes _data ) - public returns (bool) + public payable returns (bool) { require(_to != address(this)); super.transferFrom(_from, _to, _value); - require(_to.call(_data)); + // solium-disable-next-line security/no-call-value + require(_to.call.value(msg.value)(_data)); return true; } @@ -103,12 +106,13 @@ contract ERC827Token is ERC827, StandardToken { * @param _addedValue The amount of tokens to increase the allowance by. * @param _data ABI-encoded contract call to call `_spender` address. */ - function increaseApprovalAndCall(address _spender, uint _addedValue, bytes _data) public returns (bool) { + function increaseApprovalAndCall(address _spender, uint _addedValue, bytes _data) public payable returns (bool) { require(_spender != address(this)); super.increaseApproval(_spender, _addedValue); - require(_spender.call(_data)); + // solium-disable-next-line security/no-call-value + require(_spender.call.value(msg.value)(_data)); return true; } @@ -126,12 +130,13 @@ contract ERC827Token is ERC827, StandardToken { * @param _subtractedValue The amount of tokens to decrease the allowance by. * @param _data ABI-encoded contract call to call `_spender` address. */ - function decreaseApprovalAndCall(address _spender, uint _subtractedValue, bytes _data) public returns (bool) { + function decreaseApprovalAndCall(address _spender, uint _subtractedValue, bytes _data) public payable returns (bool) { require(_spender != address(this)); super.decreaseApproval(_spender, _subtractedValue); - require(_spender.call(_data)); + // solium-disable-next-line security/no-call-value + require(_spender.call.value(msg.value)(_data)); return true; } diff --git a/test/token/ERC827/ERC827Token.js b/test/token/ERC827/ERC827Token.js index 7302d3a2d..3cfed9683 100644 --- a/test/token/ERC827/ERC827Token.js +++ b/test/token/ERC827/ERC827Token.js @@ -110,6 +110,196 @@ contract('ERC827 Token', function (accounts) { }); describe('Test ERC827 methods', function () { + it( + 'should allow payment through transfer' + , async function () { + const message = await Message.new(); + + const extraData = message.contract.buyMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + const transaction = await token.transferAndCall( + message.contract.address, 100, extraData, { from: accounts[0], value: 1000 } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(100).should.be.bignumber.equal( + await token.balanceOf(message.contract.address) + ); + new BigNumber(1000).should.be.bignumber.equal( + await web3.eth.getBalance(message.contract.address) + ); + }); + + it( + 'should allow payment through approve' + , async function () { + const message = await Message.new(); + + const extraData = message.contract.buyMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + const transaction = await token.approveAndCall( + message.contract.address, 100, extraData, { from: accounts[0], value: 1000 } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(100).should.be.bignumber.equal( + await token.allowance(accounts[0], message.contract.address) + ); + new BigNumber(1000).should.be.bignumber.equal( + await web3.eth.getBalance(message.contract.address) + ); + }); + + it( + 'should allow payment through increaseApproval' + , async function () { + const message = await Message.new(); + + const extraData = message.contract.buyMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + await token.approve(message.contract.address, 10); + new BigNumber(10).should.be.bignumber.equal( + await token.allowance(accounts[0], message.contract.address) + ); + + const transaction = await token.increaseApprovalAndCall( + message.contract.address, 50, extraData, { from: accounts[0], value: 1000 } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(60).should.be.bignumber.equal( + await token.allowance(accounts[0], message.contract.address) + ); + new BigNumber(1000).should.be.bignumber.equal( + await web3.eth.getBalance(message.contract.address) + ); + }); + + it( + 'should allow payment through decreaseApproval' + , async function () { + const message = await Message.new(); + + await token.approve(message.contract.address, 100); + + new BigNumber(100).should.be.bignumber.equal( + await token.allowance(accounts[0], message.contract.address) + ); + + const extraData = message.contract.buyMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + const transaction = await token.decreaseApprovalAndCall( + message.contract.address, 60, extraData, { from: accounts[0], value: 1000 } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(40).should.be.bignumber.equal( + await token.allowance(accounts[0], message.contract.address) + ); + new BigNumber(1000).should.be.bignumber.equal( + await web3.eth.getBalance(message.contract.address) + ); + }); + + it( + 'should allow payment through transferFrom' + , async function () { + const message = await Message.new(); + + const extraData = message.contract.buyMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + await token.approve(accounts[1], 100, { from: accounts[0] }); + + new BigNumber(100).should.be.bignumber.equal( + await token.allowance(accounts[0], accounts[1]) + ); + + const transaction = await token.transferFromAndCall( + accounts[0], message.contract.address, 100, extraData, { from: accounts[1], value: 1000 } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(100).should.be.bignumber.equal( + await token.balanceOf(message.contract.address) + ); + new BigNumber(1000).should.be.bignumber.equal( + await web3.eth.getBalance(message.contract.address) + ); + }); + + it('should revert funds of failure inside approve (with data)', async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + await token.approveAndCall( + message.contract.address, 10, extraData, { from: accounts[0], value: 1000 } + ).should.be.rejectedWith(EVMRevert); + + // approval should not have gone through so allowance is still 0 + new BigNumber(0).should.be.bignumber + .equal(await token.allowance(accounts[1], message.contract.address)); + new BigNumber(0).should.be.bignumber + .equal(await web3.eth.getBalance(message.contract.address)); + }); + + it('should revert funds of failure inside transfer (with data)', async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + await token.transferAndCall( + message.contract.address, 10, extraData, { from: accounts[0], value: 1000 } + ).should.be.rejectedWith(EVMRevert); + + // transfer should not have gone through, so balance is still 0 + new BigNumber(0).should.be.bignumber + .equal(await token.balanceOf(message.contract.address)); + new BigNumber(0).should.be.bignumber + .equal(await web3.eth.getBalance(message.contract.address)); + }); + + it('should revert funds of failure inside transferFrom (with data)', async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + await token.approve(accounts[1], 10, { from: accounts[2] }); + + await token.transferFromAndCall( + accounts[2], message.contract.address, 10, extraData, { from: accounts[2], value: 1000 } + ).should.be.rejectedWith(EVMRevert); + + // transferFrom should have failed so balance is still 0 but allowance is 10 + new BigNumber(10).should.be.bignumber + .equal(await token.allowance(accounts[2], accounts[1])); + new BigNumber(0).should.be.bignumber + .equal(await token.balanceOf(message.contract.address)); + new BigNumber(0).should.be.bignumber + .equal(await web3.eth.getBalance(message.contract.address)); + }); + it( 'should return correct balances after transfer (with data) and show the event on receiver contract' , async function () {