diff --git a/README.md b/README.md index 0cae1a74552..b9deaddfa2c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ mkdir myproject && cd myproject truffle init ``` -To install the OpenZeppelin library, run: +To install the OpenZeppelin library, run the following in your Solidity project root directory: ```sh npm init npm install zeppelin-solidity diff --git a/contracts/lifecycle/TokenDestructible.sol b/contracts/lifecycle/TokenDestructible.sol index 0bca30e7b82..131e57a5797 100644 --- a/contracts/lifecycle/TokenDestructible.sol +++ b/contracts/lifecycle/TokenDestructible.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; import "../ownership/Ownable.sol"; -import "../token/ERC20Basic.sol"; +import "../token/ERC20/ERC20Basic.sol"; /** diff --git a/contracts/mocks/DetailedERC20Mock.sol b/contracts/mocks/DetailedERC20Mock.sol index 83fc96b3802..81aca044afe 100644 --- a/contracts/mocks/DetailedERC20Mock.sol +++ b/contracts/mocks/DetailedERC20Mock.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; import "../token/StandardToken.sol"; -import "../token/DetailedERC20.sol"; +import "../token/ERC20/DetailedERC20.sol"; contract DetailedERC20Mock is StandardToken, DetailedERC20 { diff --git a/contracts/mocks/ERC23TokenMock.sol b/contracts/mocks/ERC223TokenMock.sol similarity index 57% rename from contracts/mocks/ERC23TokenMock.sol rename to contracts/mocks/ERC223TokenMock.sol index d785a72d987..259faf2ab87 100644 --- a/contracts/mocks/ERC23TokenMock.sol +++ b/contracts/mocks/ERC223TokenMock.sol @@ -4,20 +4,19 @@ pragma solidity ^0.4.18; import "../token/BasicToken.sol"; -contract ERC23ContractInterface { +contract ERC223ContractInterface { function tokenFallback(address _from, uint256 _value, bytes _data) external; } +contract ERC223TokenMock is BasicToken { -contract ERC23TokenMock is BasicToken { - - function ERC23TokenMock(address initialAccount, uint256 initialBalance) public { + function ERC223TokenMock(address initialAccount, uint256 initialBalance) public { balances[initialAccount] = initialBalance; totalSupply = initialBalance; } - // ERC23 compatible transfer function (except the name) - function transferERC23(address _to, uint256 _value, bytes _data) public + // ERC223 compatible transfer function (except the name) + function transferERC223(address _to, uint256 _value, bytes _data) public returns (bool success) { transfer(_to, _value); @@ -26,7 +25,7 @@ contract ERC23TokenMock is BasicToken { is_contract := not(iszero(extcodesize(_to))) } if (is_contract) { - ERC23ContractInterface receiver = ERC23ContractInterface(_to); + ERC223ContractInterface receiver = ERC223ContractInterface(_to); receiver.tokenFallback(msg.sender, _value, _data); } return true; diff --git a/contracts/mocks/ERC827TokenMock.sol b/contracts/mocks/ERC827TokenMock.sol new file mode 100644 index 00000000000..9ac16cc6399 --- /dev/null +++ b/contracts/mocks/ERC827TokenMock.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.4.13; + + +import "../token/ERC827/ERC827Token.sol"; + + +// mock class using ERC827 Token +contract ERC827TokenMock is ERC827Token { + + function ERC827TokenMock(address initialAccount, uint256 initialBalance) public { + balances[initialAccount] = initialBalance; + totalSupply = initialBalance; + } + +} diff --git a/contracts/mocks/MessageHelper.sol b/contracts/mocks/MessageHelper.sol new file mode 100644 index 00000000000..f990241dab0 --- /dev/null +++ b/contracts/mocks/MessageHelper.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.4.11; + +contract MessageHelper { + + event Show(bytes32 b32, uint256 number, string text); + + function showMessage( bytes32 message, uint256 number, string text ) public returns (bool) { + Show(message, number, text); + return true; + } + + function fail() public { + require(false); + } + + function call(address to, bytes data) public returns (bool) { + if (to.call(data)) + return true; + else + return false; + } + +} diff --git a/contracts/mocks/SafeERC20Helper.sol b/contracts/mocks/SafeERC20Helper.sol index d41e7a5e1aa..1a9bcdfc2ba 100644 --- a/contracts/mocks/SafeERC20Helper.sol +++ b/contracts/mocks/SafeERC20Helper.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; -import "../token/ERC20.sol"; -import "../token/SafeERC20.sol"; +import "../token/ERC20/ERC20.sol"; +import "../token/ERC20/SafeERC20.sol"; contract ERC20FailingMock is ERC20 { diff --git a/contracts/ownership/CanReclaimToken.sol b/contracts/ownership/CanReclaimToken.sol index ab85e173652..69fb95c6e21 100644 --- a/contracts/ownership/CanReclaimToken.sol +++ b/contracts/ownership/CanReclaimToken.sol @@ -1,8 +1,8 @@ pragma solidity ^0.4.18; import "./Ownable.sol"; -import "../token/ERC20Basic.sol"; -import "../token/SafeERC20.sol"; +import "../token/ERC20/ERC20Basic.sol"; +import "../token/ERC20/SafeERC20.sol"; /** diff --git a/contracts/ownership/HasNoTokens.sol b/contracts/ownership/HasNoTokens.sol index 9a4e7aabe67..ee9531bb7c6 100644 --- a/contracts/ownership/HasNoTokens.sol +++ b/contracts/ownership/HasNoTokens.sol @@ -6,14 +6,14 @@ import "./CanReclaimToken.sol"; /** * @title Contracts that should not own Tokens * @author Remco Bloemen - * @dev This blocks incoming ERC23 tokens to prevent accidental loss of tokens. + * @dev This blocks incoming ERC223 tokens to prevent accidental loss of tokens. * Should tokens (any ERC20Basic compatible) end up in the contract, it allows the * owner to reclaim the tokens. */ contract HasNoTokens is CanReclaimToken { /** - * @dev Reject all ERC23 compatible tokens + * @dev Reject all ERC223 compatible tokens * @param from_ address The address that is transferring the tokens * @param value_ uint256 the amount of the specified token * @param data_ Bytes The data passed from the caller. diff --git a/contracts/token/BasicToken.sol b/contracts/token/BasicToken.sol index 469fedbccaf..46e8bf87016 100644 --- a/contracts/token/BasicToken.sol +++ b/contracts/token/BasicToken.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; -import "./ERC20Basic.sol"; +import "./ERC20/ERC20Basic.sol"; import "../math/SafeMath.sol"; diff --git a/contracts/token/DetailedERC20.sol b/contracts/token/ERC20/DetailedERC20.sol similarity index 100% rename from contracts/token/DetailedERC20.sol rename to contracts/token/ERC20/DetailedERC20.sol diff --git a/contracts/token/ERC20.sol b/contracts/token/ERC20/ERC20.sol similarity index 100% rename from contracts/token/ERC20.sol rename to contracts/token/ERC20/ERC20.sol diff --git a/contracts/token/ERC20Basic.sol b/contracts/token/ERC20/ERC20Basic.sol similarity index 100% rename from contracts/token/ERC20Basic.sol rename to contracts/token/ERC20/ERC20Basic.sol diff --git a/contracts/token/SafeERC20.sol b/contracts/token/ERC20/SafeERC20.sol similarity index 100% rename from contracts/token/SafeERC20.sol rename to contracts/token/ERC20/SafeERC20.sol diff --git a/contracts/token/ERC827/ERC827.sol b/contracts/token/ERC827/ERC827.sol new file mode 100644 index 00000000000..31a17cd809f --- /dev/null +++ b/contracts/token/ERC827/ERC827.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.4.13; + + +import "../ERC20/ERC20.sol"; + + +/** + @title ERC827 interface, an extension of ERC20 token standard + + Interface of a ERC827 token, following the ERC20 standard with extra + methods to transfer value and data and execute calls in transfers and + approvals. + */ +contract ERC827 is ERC20 { + + function approve( address _spender, uint256 _value, bytes _data ) public returns (bool); + function transfer( address _to, uint256 _value, bytes _data ) public returns (bool); + function transferFrom( address _from, address _to, uint256 _value, bytes _data ) public returns (bool); + +} diff --git a/contracts/token/ERC827/ERC827Token.sol b/contracts/token/ERC827/ERC827Token.sol new file mode 100644 index 00000000000..f027c80ff3d --- /dev/null +++ b/contracts/token/ERC827/ERC827Token.sol @@ -0,0 +1,126 @@ +pragma solidity ^0.4.13; + +import "./ERC827.sol"; +import "../StandardToken.sol"; + +/** + @title ERC827, an extension of ERC20 token standard + + Implementation the ERC827, following the ERC20 standard with extra + methods to transfer value and data and execute calls in transfers and + approvals. + Uses OpenZeppelin StandardToken. + */ +contract ERC827Token is ERC827, StandardToken { + + /** + @dev Addition to ERC20 token methods. It allows to + approve the transfer of value and execute a call with the sent data. + + Beware that changing an allowance with this method brings the risk that + someone may use both the old and the new allowance by unfortunate + transaction ordering. One possible solution to mitigate this race condition + is to first reduce the spender's allowance to 0 and set the desired value + afterwards: + https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + + @param _spender The address that will spend the funds. + @param _value The amount of tokens to be spent. + @param _data ABI-encoded contract call to call `_to` address. + + @return true if the call function was executed successfully + */ + function approve(address _spender, uint256 _value, bytes _data) public returns (bool) { + require(_spender != address(this)); + + super.approve(_spender, _value); + + require(_spender.call(_data)); + + return true; + } + + /** + @dev Addition to ERC20 token methods. Transfer tokens to a specified + address and execute a call with the sent data on the same transaction + + @param _to address The address which you want to transfer to + @param _value uint256 the amout of tokens to be transfered + @param _data ABI-encoded contract call to call `_to` address. + + @return true if the call function was executed successfully + */ + function transfer(address _to, uint256 _value, bytes _data) public returns (bool) { + require(_to != address(this)); + + super.transfer(_to, _value); + + require(_to.call(_data)); + return true; + } + + /** + @dev Addition to ERC20 token methods. Transfer tokens from one address to + another and make a contract call on the same transaction + + @param _from The address which you want to send tokens from + @param _to The address which you want to transfer to + @param _value The amout of tokens to be transferred + @param _data ABI-encoded contract call to call `_to` address. + + @return true if the call function was executed successfully + */ + function transferFrom(address _from, address _to, uint256 _value, bytes _data) public returns (bool) { + require(_to != address(this)); + + super.transferFrom(_from, _to, _value); + + require(_to.call(_data)); + return true; + } + + /** + * @dev Addition to StandardToken methods. Increase the amount of tokens that + * an owner allowed to a spender and execute a call with the sent data. + * + * approve should be called when allowed[_spender] == 0. To increment + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _addedValue The amount of tokens to increase the allowance by. + * @param _data ABI-encoded contract call to call `_spender` address. + */ + function increaseApproval(address _spender, uint _addedValue, bytes _data) public returns (bool) { + require(_spender != address(this)); + + super.increaseApproval(_spender, _addedValue); + + require(_spender.call(_data)); + + return true; + } + + /** + * @dev Addition to StandardToken methods. Decrease the amount of tokens that + * an owner allowed to a spender and execute a call with the sent data. + * + * approve should be called when allowed[_spender] == 0. To decrement + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _subtractedValue The amount of tokens to decrease the allowance by. + * @param _data ABI-encoded contract call to call `_spender` address. + */ + function decreaseApproval(address _spender, uint _subtractedValue, bytes _data) public returns (bool) { + require(_spender != address(this)); + + super.decreaseApproval(_spender, _subtractedValue); + + require(_spender.call(_data)); + + return true; + } + +} diff --git a/contracts/token/StandardToken.sol b/contracts/token/StandardToken.sol index 1aa10d3f912..a3a1c34457a 100644 --- a/contracts/token/StandardToken.sol +++ b/contracts/token/StandardToken.sol @@ -2,7 +2,7 @@ pragma solidity ^0.4.18; import "./BasicToken.sol"; -import "./ERC20.sol"; +import "./ERC20/ERC20.sol"; /** diff --git a/contracts/token/TokenTimelock.sol b/contracts/token/TokenTimelock.sol index 3fd0103a518..2562b094018 100644 --- a/contracts/token/TokenTimelock.sol +++ b/contracts/token/TokenTimelock.sol @@ -1,7 +1,6 @@ pragma solidity ^0.4.18; -import "./ERC20Basic.sol"; -import "../token/SafeERC20.sol"; +import "./ERC20/SafeERC20.sol"; /** diff --git a/contracts/token/TokenVesting.sol b/contracts/token/TokenVesting.sol index 01bdff34e7c..a8b45c01e25 100644 --- a/contracts/token/TokenVesting.sol +++ b/contracts/token/TokenVesting.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.18; -import "./ERC20Basic.sol"; -import "./SafeERC20.sol"; +import "./ERC20/ERC20Basic.sol"; +import "./ERC20/SafeERC20.sol"; import "../ownership/Ownable.sol"; import "../math/SafeMath.sol"; diff --git a/package.json b/package.json index f15f6397b2b..6d93e41e1c5 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "truffle-hdwallet-provider": "0.0.3" }, "dependencies": { - "dotenv": "^4.0.0" + "dotenv": "^4.0.0", + "ethjs-abi": "^0.2.1" } } diff --git a/test/SimpleToken.test.js b/test/examples/SimpleToken.test.js similarity index 90% rename from test/SimpleToken.test.js rename to test/examples/SimpleToken.test.js index 9a1870f2047..8d4c330918e 100644 --- a/test/SimpleToken.test.js +++ b/test/examples/SimpleToken.test.js @@ -1,5 +1,5 @@ -import decodeLogs from './helpers/decodeLogs'; -const SimpleToken = artifacts.require('examples/SimpleToken.sol'); +import decodeLogs from '../helpers/decodeLogs'; +const SimpleToken = artifacts.require('SimpleToken.sol'); contract('SimpleToken', accounts => { let token; @@ -23,7 +23,7 @@ contract('SimpleToken', accounts => { const decimals = await token.decimals(); assert(decimals.eq(18)); }); - + it('assigns the initial total supply to the creator', async function () { const totalSupply = await token.totalSupply(); const creatorBalance = await token.balanceOf(creator); diff --git a/test/ECRecovery.test.js b/test/library/ECRecovery.test.js similarity index 92% rename from test/ECRecovery.test.js rename to test/library/ECRecovery.test.js index 927441f4bc7..ae69f42cf46 100644 --- a/test/ECRecovery.test.js +++ b/test/library/ECRecovery.test.js @@ -1,7 +1,7 @@ -var ECRecoveryMock = artifacts.require('../contracts/mocks/ECRecoveryMock.sol'); -var ECRecoveryLib = artifacts.require('../contracts/ECRecovery.sol'); +var ECRecoveryMock = artifacts.require('ECRecoveryMock.sol'); +var ECRecoveryLib = artifacts.require('ECRecovery.sol'); -var hashMessage = require('./helpers/hashMessage.js'); +var hashMessage = require('../helpers/hashMessage.js'); contract('ECRecovery', function (accounts) { let ecrecovery; diff --git a/test/Math.test.js b/test/library/Math.test.js similarity index 89% rename from test/Math.test.js rename to test/library/Math.test.js index 7378610b61c..b112646a0f8 100644 --- a/test/Math.test.js +++ b/test/library/Math.test.js @@ -1,4 +1,4 @@ -var MathMock = artifacts.require('./mocks/MathMock.sol'); +var MathMock = artifacts.require('../mocks/MathMock.sol'); contract('Math', function (accounts) { let math; diff --git a/test/MerkleProof.test.js b/test/library/MerkleProof.test.js similarity index 94% rename from test/MerkleProof.test.js rename to test/library/MerkleProof.test.js index f55d6ebc8b5..9d464401f5a 100644 --- a/test/MerkleProof.test.js +++ b/test/library/MerkleProof.test.js @@ -1,8 +1,8 @@ -import MerkleTree from './helpers/merkleTree.js'; +import MerkleTree from '../helpers/merkleTree.js'; import { sha3, bufferToHex } from 'ethereumjs-util'; -var MerkleProof = artifacts.require('./MerkleProof.sol'); +var MerkleProof = artifacts.require('MerkleProof.sol'); contract('MerkleProof', function (accounts) { let merkleProof; diff --git a/test/ownership/HasNoTokens.test.js b/test/ownership/HasNoTokens.test.js index e6622a16dff..ac3690fd37e 100644 --- a/test/ownership/HasNoTokens.test.js +++ b/test/ownership/HasNoTokens.test.js @@ -2,7 +2,7 @@ import expectThrow from '../helpers/expectThrow'; const HasNoTokens = artifacts.require('../contracts/lifecycle/HasNoTokens.sol'); -const ERC23TokenMock = artifacts.require('mocks/ERC23TokenMock.sol'); +const ERC223TokenMock = artifacts.require('mocks/ERC223TokenMock.sol'); contract('HasNoTokens', function (accounts) { let hasNoTokens = null; @@ -11,7 +11,7 @@ contract('HasNoTokens', function (accounts) { beforeEach(async () => { // Create contract and token hasNoTokens = await HasNoTokens.new(); - token = await ERC23TokenMock.new(accounts[0], 100); + token = await ERC223TokenMock.new(accounts[0], 100); // Force token into contract await token.transfer(hasNoTokens.address, 10); @@ -19,8 +19,8 @@ contract('HasNoTokens', function (accounts) { assert.equal(startBalance, 10); }); - it('should not accept ERC23 tokens', async function () { - await expectThrow(token.transferERC23(hasNoTokens.address, 10, '')); + it('should not accept ERC223 tokens', async function () { + await expectThrow(token.transferERC223(hasNoTokens.address, 10, '')); }); it('should allow owner to reclaim tokens', async function () { diff --git a/test/token/DetailedERC20.test.js b/test/token/ERC20/DetailedERC20.test.js similarity index 91% rename from test/token/DetailedERC20.test.js rename to test/token/ERC20/DetailedERC20.test.js index 16155a4b87b..cfb1d1e714f 100644 --- a/test/token/DetailedERC20.test.js +++ b/test/token/ERC20/DetailedERC20.test.js @@ -5,7 +5,7 @@ require('chai') .use(require('chai-bignumber')(BigNumber)) .should(); -const DetailedERC20Mock = artifacts.require('mocks/DetailedERC20Mock.sol'); +const DetailedERC20Mock = artifacts.require('DetailedERC20Mock.sol'); contract('DetailedERC20', accounts => { let detailedERC20 = null; diff --git a/test/token/SafeERC20.test.js b/test/token/ERC20/SafeERC20.test.js similarity index 90% rename from test/token/SafeERC20.test.js rename to test/token/ERC20/SafeERC20.test.js index 0602e08ffba..40a43e1a749 100644 --- a/test/token/SafeERC20.test.js +++ b/test/token/ERC20/SafeERC20.test.js @@ -1,10 +1,10 @@ -import EVMThrow from '../helpers/EVMThrow'; +import EVMThrow from '../../helpers/EVMThrow'; require('chai') .use(require('chai-as-promised')) .should(); -const SafeERC20Helper = artifacts.require('mocks/SafeERC20Helper.sol'); +const SafeERC20Helper = artifacts.require('SafeERC20Helper.sol'); contract('SafeERC20', function () { beforeEach(async function () { diff --git a/test/token/ERC827/ERC827Token.js b/test/token/ERC827/ERC827Token.js new file mode 100644 index 00000000000..302cca7dd95 --- /dev/null +++ b/test/token/ERC827/ERC827Token.js @@ -0,0 +1,374 @@ + +import EVMRevert from '../../helpers/EVMRevert'; +var Message = artifacts.require('MessageHelper.sol'); +var ERC827TokenMock = artifacts.require('ERC827TokenMock.sol'); + +var BigNumber = web3.BigNumber; +var _ = require('lodash'); +var ethjsABI = require('ethjs-abi'); +require('chai') + .use(require('chai-as-promised')) + .use(require('chai-bignumber')(BigNumber)) + .should(); + +contract('ERC827 Token', function (accounts) { + let token; + + function findMethod (abi, name, args) { + for (var i = 0; i < abi.length; i++) { + const methodArgs = _.map(abi[i].inputs, 'type').join(','); + if ((abi[i].name === name) && (methodArgs === args)) { + return abi[i]; + } + } + } + + beforeEach(async function () { + token = await ERC827TokenMock.new(accounts[0], 100); + }); + + it('should return the correct totalSupply after construction', async function () { + let totalSupply = await token.totalSupply(); + + assert.equal(totalSupply, 100); + }); + + it('should return the correct allowance amount after approval', async function () { + let token = await ERC827TokenMock.new(); + await token.approve(accounts[1], 100); + let allowance = await token.allowance(accounts[0], accounts[1]); + + assert.equal(allowance, 100); + }); + + it('should return correct balances after transfer', async function () { + await token.transfer(accounts[1], 100); + let balance0 = await token.balanceOf(accounts[0]); + assert.equal(balance0, 0); + + let balance1 = await token.balanceOf(accounts[1]); + assert.equal(balance1, 100); + }); + + it('should throw an error when trying to transfer more than balance', async function () { + await token.transfer(accounts[1], 101).should.be.rejectedWith(EVMRevert); + }); + + it('should return correct balances after transfering from another account', async function () { + await token.approve(accounts[1], 100); + await token.transferFrom(accounts[0], accounts[2], 100, { from: accounts[1] }); + + let balance0 = await token.balanceOf(accounts[0]); + assert.equal(balance0, 0); + + let balance1 = await token.balanceOf(accounts[2]); + assert.equal(balance1, 100); + + let balance2 = await token.balanceOf(accounts[1]); + assert.equal(balance2, 0); + }); + + it('should throw an error when trying to transfer more than allowed', async function () { + await token.approve(accounts[1], 99); + await token.transferFrom( + accounts[0], accounts[2], 100, + { from: accounts[1] } + ).should.be.rejectedWith(EVMRevert); + }); + + it('should throw an error when trying to transferFrom more than _from has', async function () { + let balance0 = await token.balanceOf(accounts[0]); + await token.approve(accounts[1], 99); + await token.transferFrom( + accounts[0], accounts[2], balance0 + 1, + { from: accounts[1] } + ).should.be.rejectedWith(EVMRevert); + }); + + describe('validating allowance updates to spender', function () { + let preApproved; + + it('should start with zero', async function () { + preApproved = await token.allowance(accounts[0], accounts[1]); + assert.equal(preApproved, 0); + }); + + it('should increase by 50 then decrease by 10', async function () { + const abiMethod = findMethod(token.abi, 'increaseApproval', 'address,uint256'); + const increaseApprovalData = ethjsABI.encodeMethod(abiMethod, + [accounts[1], 50] + ); + await token.sendTransaction( + { from: accounts[0], data: increaseApprovalData } + ); + let postIncrease = await token.allowance(accounts[0], accounts[1]); + preApproved.plus(50).should.be.bignumber.equal(postIncrease); + await token.decreaseApproval(accounts[1], 10); + let postDecrease = await token.allowance(accounts[0], accounts[1]); + postIncrease.minus(10).should.be.bignumber.equal(postDecrease); + }); + }); + + it('should increase by 50 then set to 0 when decreasing by more than 50', async function () { + await token.approve(accounts[1], 50); + await token.decreaseApproval(accounts[1], 60); + let postDecrease = await token.allowance(accounts[0], accounts[1]); + postDecrease.should.be.bignumber.equal(0); + }); + + it('should throw an error when trying to transfer to 0x0', async function () { + await token.transfer(0x0, 100).should.be.rejectedWith(EVMRevert); + }); + + it('should throw an error when trying to transferFrom to 0x0', async function () { + await token.approve(accounts[1], 100); + await token.transferFrom(accounts[0], 0x0, 100, { from: accounts[1] }) + .should.be.rejectedWith(EVMRevert); + }); + + describe('Test ERC827 methods', function () { + it( + 'should return correct balances after transfer (with data) and show the event on receiver contract' + , async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + const abiMethod = findMethod(token.abi, 'transfer', 'address,uint256,bytes'); + const transferData = ethjsABI.encodeMethod(abiMethod, + [message.contract.address, 100, extraData] + ); + const transaction = await token.sendTransaction( + { from: accounts[0], data: transferData } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(100).should.be.bignumber.equal( + await token.balanceOf(message.contract.address) + ); + }); + + it( + 'should return correct allowance after approve (with data) and show the event on receiver contract' + , async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + const abiMethod = findMethod(token.abi, 'approve', 'address,uint256,bytes'); + const approveData = ethjsABI.encodeMethod(abiMethod, + [message.contract.address, 100, extraData] + ); + const transaction = await token.sendTransaction( + { from: accounts[0], data: approveData } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(100).should.be.bignumber.equal( + await token.allowance(accounts[0], message.contract.address) + ); + }); + + it( + 'should return correct allowance after increaseApproval (with data) and show the event on receiver contract' + , async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.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 abiMethod = findMethod(token.abi, 'increaseApproval', 'address,uint256,bytes'); + const increaseApprovalData = ethjsABI.encodeMethod(abiMethod, + [message.contract.address, 50, extraData] + ); + const transaction = await token.sendTransaction( + { from: accounts[0], data: increaseApprovalData } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(60).should.be.bignumber.equal( + await token.allowance(accounts[0], message.contract.address) + ); + }); + + it( + 'should return correct allowance after decreaseApproval (with data) and show the event on receiver contract' + , 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.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + const abiMethod = findMethod(token.abi, 'decreaseApproval', 'address,uint256,bytes'); + const decreaseApprovalData = ethjsABI.encodeMethod(abiMethod, + [message.contract.address, 60, extraData] + ); + const transaction = await token.sendTransaction( + { from: accounts[0], data: decreaseApprovalData } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(40).should.be.bignumber.equal( + await token.allowance(accounts[0], message.contract.address) + ); + }); + + it( + 'should return correct balances after transferFrom (with data) and show the event on receiver contract' + , async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.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 abiMethod = findMethod(token.abi, 'transferFrom', 'address,address,uint256,bytes'); + const transferFromData = ethjsABI.encodeMethod(abiMethod, + [accounts[0], message.contract.address, 100, extraData] + ); + const transaction = await token.sendTransaction( + { from: accounts[1], data: transferFromData } + ); + + assert.equal(2, transaction.receipt.logs.length); + + new BigNumber(100).should.be.bignumber.equal( + await token.balanceOf(message.contract.address) + ); + }); + + it('should fail inside approve (with data)', async function () { + const message = await Message.new(); + + const extraData = message.contract.fail.getData(); + + const abiMethod = findMethod(token.abi, 'approve', 'address,uint256,bytes'); + const approveData = ethjsABI.encodeMethod(abiMethod, + [message.contract.address, 10, extraData] + ); + await token.sendTransaction( + { from: accounts[0], data: approveData } + ).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)); + }); + + it('should fail inside transfer (with data)', async function () { + const message = await Message.new(); + + const extraData = message.contract.fail.getData(); + + const abiMethod = findMethod(token.abi, 'transfer', 'address,uint256,bytes'); + const transferData = ethjsABI.encodeMethod(abiMethod, + [message.contract.address, 10, extraData] + ); + await token.sendTransaction( + { from: accounts[0], data: transferData } + ).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)); + }); + + it('should fail inside transferFrom (with data)', async function () { + const message = await Message.new(); + + const extraData = message.contract.fail.getData(); + + await token.approve(accounts[1], 10, { from: accounts[2] }); + + const abiMethod = findMethod(token.abi, 'transferFrom', 'address,address,uint256,bytes'); + const transferFromData = ethjsABI.encodeMethod(abiMethod, + [accounts[2], message.contract.address, 10, extraData] + ); + await token.sendTransaction( + { from: accounts[1], data: transferFromData } + ).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)); + }); + + it('should fail approve (with data) when using token contract address as receiver', async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + const abiMethod = findMethod(token.abi, 'approve', 'address,uint256,bytes'); + const approveData = ethjsABI.encodeMethod(abiMethod, + [token.contract.address, 100, extraData] + ); + await token.sendTransaction( + { from: accounts[0], data: approveData } + ).should.be.rejectedWith(EVMRevert); + }); + + it('should fail transfer (with data) when using token contract address as receiver', async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + const abiMethod = findMethod(token.abi, 'transfer', 'address,uint256,bytes'); + const transferData = ethjsABI.encodeMethod(abiMethod, + [token.contract.address, 100, extraData] + ); + await token.sendTransaction( + { from: accounts[0], data: transferData } + ).should.be.rejectedWith(EVMRevert); + }); + + it('should fail transferFrom (with data) when using token contract address as receiver', async function () { + const message = await Message.new(); + + const extraData = message.contract.showMessage.getData( + web3.toHex(123456), 666, 'Transfer Done' + ); + + await token.approve(accounts[1], 1, { from: accounts[0] }); + + const abiMethod = findMethod(token.abi, 'transferFrom', 'address,address,uint256,bytes'); + const transferFromData = ethjsABI.encodeMethod(abiMethod, + [accounts[0], token.contract.address, 1, extraData] + ); + await token.sendTransaction( + { from: accounts[1], data: transferFromData } + ).should.be.rejectedWith(EVMRevert); + }); + }); +});