From 57529f6c392b8b436dee5ff834de825b1b754a8b Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 20 Nov 2017 14:58:28 +1300 Subject: [PATCH 1/6] claim tokens checks for all end cases. hasended checks for emergency finalize --- contracts/PolyMathTokenOffering.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/PolyMathTokenOffering.sol b/contracts/PolyMathTokenOffering.sol index 18c888e..a7bbd17 100644 --- a/contracts/PolyMathTokenOffering.sol +++ b/contracts/PolyMathTokenOffering.sol @@ -184,7 +184,7 @@ contract PolyMathTokenOffering is Ownable { function hasEnded() public constant returns (bool) { bool capReached = weiRaised >= cap; bool passedEndTime = getBlockTimestamp() > endTime; - return passedEndTime || capReached; + return isFinalized || passedEndTime || capReached; } function getBlockTimestamp() internal constant returns (uint256) { @@ -208,7 +208,7 @@ contract PolyMathTokenOffering is Ownable { // Allows the owner to take back the tokens that are assigned to the sale contract. event TokensRefund(uint256 _amount); function refund() external onlyOwner returns (bool) { - require(isFinalized); + require(!hasEnded()); uint256 tokens = token.balanceOf(address(this)); if (tokens == 0) { From b13790d34ee92670ae9fb96daf27f10b511e09c5 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 21 Nov 2017 16:41:01 +1300 Subject: [PATCH 2/6] Removed issue tokens, tokens now locked for 7 days after corwdsale --- contracts/PolyMathToken.sol | 27 +++++++++++++++++++-------- contracts/PolyMathTokenOffering.sol | 12 ++++++++---- test/auditTests.js | 20 +++++++++++++++++--- test/helpers/PolyMathTokenMock.sol | 20 ++++++++++++++++++++ test/pauseTokenTests.js | 9 +++++++-- test/refundTest.js | 2 +- 6 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 test/helpers/PolyMathTokenMock.sol diff --git a/contracts/PolyMathToken.sol b/contracts/PolyMathToken.sol index d96aa01..0bd8d5b 100644 --- a/contracts/PolyMathToken.sol +++ b/contracts/PolyMathToken.sol @@ -2,6 +2,7 @@ pragma solidity ^0.4.13; import 'zeppelin-solidity/contracts/token/PausableToken.sol'; import 'zeppelin-solidity/contracts/token/BurnableToken.sol'; +import './PolyMathTokenOffering.sol'; contract PolyMathToken is PausableToken, BurnableToken { @@ -22,10 +23,14 @@ contract PolyMathToken is PausableToken, BurnableToken { uint256 public constant ADVISOR_SUPPLY = 25000000 * token_factor; uint256 public constant RESERVE_SUPPLY = 500000000 * token_factor; - bool private crowdsaleInitialized = false; + address private crowdsale; + + function isCrowdsaleAddressSet() public constant returns (bool) { + return (address(crowdsale) != address(0)); + } modifier crowdsaleNotInitialized() { - require(crowdsaleInitialized == false); + require(!isCrowdsaleAddressSet()); _; } @@ -41,17 +46,23 @@ contract PolyMathToken is PausableToken, BurnableToken { } function initializeCrowdsale(address _crowdsale) onlyOwner crowdsaleNotInitialized { - crowdsaleInitialized = true; transfer(_crowdsale, PUBLICSALE_SUPPLY); + crowdsale = _crowdsale; pause(); transferOwnership(_crowdsale); } - function issueTokens(address _to, uint256 _value) onlyOwner returns (bool) { - balances[owner] = balances[owner].sub(_value); - balances[_to] = balances[_to].add(_value); - Transfer(owner, _to, _value); - return true; + function getBlockTimestamp() internal constant returns (uint256) { + return block.timestamp; + } + + // Override - lifecycle/Pausable.sol + function unpause() public { + if (PolyMathTokenOffering(crowdsale).hasEnded()) { + // Tokens should be locked until 7 days after the crowdsale + require(getBlockTimestamp() >= (PolyMathTokenOffering(crowdsale).endTime() + 7 days)); + } + super.unpause(); } // Don't accept calls to the contract address; must call a method. diff --git a/contracts/PolyMathTokenOffering.sol b/contracts/PolyMathTokenOffering.sol index a7bbd17..e66a8d6 100644 --- a/contracts/PolyMathTokenOffering.sol +++ b/contracts/PolyMathTokenOffering.sol @@ -143,6 +143,7 @@ contract PolyMathTokenOffering is Ownable { uint256 weiToReturn = msg.value.sub(weiAmount); uint256 tokens = ethToTokens(weiAmount); + token.unpause(); weiRaised = weiRaised.add(weiAmount); forwardFunds(weiAmount); @@ -153,7 +154,8 @@ contract PolyMathTokenOffering is Ownable { } // send tokens to purchaser TokenPurchase(msg.sender, beneficiary, weiAmount, tokens); - token.issueTokens(beneficiary, tokens); + token.transfer(beneficiary, tokens); + token.pause(); TokenRedeem(beneficiary, tokens); checkFinalize(); } @@ -182,9 +184,12 @@ contract PolyMathTokenOffering is Ownable { // @return true if crowdsale event has ended or cap reached function hasEnded() public constant returns (bool) { + if (isFinalized) { + return true; + } bool capReached = weiRaised >= cap; bool passedEndTime = getBlockTimestamp() > endTime; - return isFinalized || passedEndTime || capReached; + return passedEndTime || capReached; } function getBlockTimestamp() internal constant returns (uint256) { @@ -201,14 +206,13 @@ contract PolyMathTokenOffering is Ownable { require(!isFinalized); Finalized(); isFinalized = true; - token.unpause(); token.transferOwnership(owner); } // Allows the owner to take back the tokens that are assigned to the sale contract. event TokensRefund(uint256 _amount); function refund() external onlyOwner returns (bool) { - require(!hasEnded()); + require(hasEnded()); uint256 tokens = token.balanceOf(address(this)); if (tokens == 0) { diff --git a/test/auditTests.js b/test/auditTests.js index bdcd61a..c4ca21a 100644 --- a/test/auditTests.js +++ b/test/auditTests.js @@ -1,6 +1,6 @@ 'use strict'; var TokenOffering = artifacts.require('./helpers/PolyMathTokenOfferingMock.sol'); -var POLYToken = artifacts.require('PolyMathToken.sol'); +var POLYToken = artifacts.require('./helpers/PolyMathTokenMock.sol'); import { latestTime, duration } from './helpers/latestTime'; const BigNumber = require("bignumber.js"); @@ -37,7 +37,7 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p it('Tokens should not be able to be sent to the null address from the token contract', async function () { tokenDeployed = await POLYToken.new(presale_wallet); - await assertFail(async () => { await tokenDeployed.issueTokens(0x0, tokenDeployed.address) }); + await assertFail(async () => { await tokenDeployed.transfer(0x0, tokenDeployed.address) }); }); describe('Deploy Contracts', async function () { @@ -78,10 +78,24 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p await assertFail(async () => { await tokenOfferingDeployed.refund() }); }); - it('Unsold tokens should be refundable after the crowdsale is finished', async function () { + it('Tokens should be locked until 7 days after crowdsale ends', async function () { await tokenOfferingDeployed.setBlockTimestamp(endTime + 1); await tokenOfferingDeployed.checkFinalize(); + await assertFail(async () => { await tokenOfferingDeployed.refund() }); + await assertFail(async () => { await tokenDeployed.unpause() }); + await assertFail(async () => { await tokenOfferingDeployed.refund() }); + await tokenDeployed.setBlockTimestamp(endTime + duration.days(7)); + await tokenDeployed.unpause(); + await tokenOfferingDeployed.refund(); + assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 0, "The Crowdsale should have no balance after a refund"); + }); + + it('Unsold tokens should be refundable after the crowdsale is finished and 7 days pass', async function () { + await tokenOfferingDeployed.setBlockTimestamp(endTime + duration.days(7)); + await tokenDeployed.setBlockTimestamp(endTime + duration.days(7)); + await tokenOfferingDeployed.checkFinalize(); assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 150000000 * 10 ** DECIMALS, "The Crowdsale should have 150mil"); + await tokenDeployed.unpause(); await tokenOfferingDeployed.refund(); assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 0, "The Crowdsale should have no balance after a refund"); }); diff --git a/test/helpers/PolyMathTokenMock.sol b/test/helpers/PolyMathTokenMock.sol new file mode 100644 index 0000000..f6e3382 --- /dev/null +++ b/test/helpers/PolyMathTokenMock.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.4.13; + +import '../../contracts/PolyMathToken.sol'; + +contract PolyMathTokenMock is PolyMathToken { + uint256 public timeStamp = block.timestamp; + + function setBlockTimestamp(uint256 _timeStamp) public { + timeStamp = _timeStamp; + } + + function getBlockTimestamp() internal constant returns (uint256) { + return timeStamp; + } + + function PolyMathTokenMock(address _presale_wallet) PolyMathToken(_presale_wallet) + { + } + +} diff --git a/test/pauseTokenTests.js b/test/pauseTokenTests.js index 81c631d..8c360d7 100644 --- a/test/pauseTokenTests.js +++ b/test/pauseTokenTests.js @@ -1,7 +1,7 @@ 'use strict'; let TokenOffering = artifacts.require('./helpers/PolyMathTokenOfferingMock.sol'); -let POLYToken = artifacts.require('PolyMathToken.sol'); +let POLYToken = artifacts.require('./helpers/PolyMathTokenMock.sol'); const BigNumber = require("bignumber.js"); const assertFail = require("./helpers/assertFail"); @@ -84,6 +84,9 @@ contract('polyTokenPause', async function([miner, owner, investor, investor2, wa await tokenOfferingDeployed.sendTransaction({ from: investor, value: value }); balance = await tokenDeployed.balanceOf(investor); assert.equal(balance.toNumber(), 1200 * 10 ** DECIMALS, 'balanceOf is 1200 for investor who just bought tokens'); + await tokenOfferingDeployed.setBlockTimestamp(endTime + duration.days(7)); + await tokenDeployed.setBlockTimestamp(endTime + duration.days(7)); + await tokenDeployed.unpause(); // Token should be unpaused, can now transfer await tokenDeployed.transfer(investor2, 600 * 10 ** DECIMALS, { from: investor }); balance = await tokenDeployed.balanceOf(investor); @@ -103,9 +106,11 @@ contract('polyTokenPause', async function([miner, owner, investor, investor2, wa assert.equal(balance.toNumber(), 600 * 10 ** DECIMALS, 'balanceOf is 600 for investor who just bought tokens'); await assertFail(async () => { await tokenDeployed.transfer(investor2, 10, { from: investor }) }); - await tokenOfferingDeployed.setBlockTimestamp(endTime + 1); + await tokenOfferingDeployed.setBlockTimestamp(endTime + duration.days(7)); + await tokenDeployed.setBlockTimestamp(endTime + duration.days(7)); await tokenOfferingDeployed.checkFinalize(); + await tokenDeployed.unpause(); let isFinalized = await tokenOfferingDeployed.isFinalized(); assert.isTrue(isFinalized, "isFinalized should be true"); diff --git a/test/refundTest.js b/test/refundTest.js index 52d46f3..00da2f1 100644 --- a/test/refundTest.js +++ b/test/refundTest.js @@ -1,6 +1,6 @@ 'use strict'; var TokenOffering = artifacts.require('./helpers/PolyMathTokenOfferingMock.sol'); -var POLYToken = artifacts.require('PolyMathToken.sol'); +var POLYToken = artifacts.require('./helpers/PolyMathTokenMock.sol'); const assertFail = require("./helpers/assertFail"); import { latestTime, duration } from './helpers/latestTime'; From bb13cb9bd9e08a94c6a4f2c1d66dd34eaa088c3b Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 21 Nov 2017 17:44:13 +1300 Subject: [PATCH 3/6] added claim tokens, updated supply amounts --- contracts/PolyMathToken.sol | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/contracts/PolyMathToken.sol b/contracts/PolyMathToken.sol index 0bd8d5b..4441070 100644 --- a/contracts/PolyMathToken.sol +++ b/contracts/PolyMathToken.sol @@ -16,12 +16,12 @@ contract PolyMathToken is PausableToken, BurnableToken { // 1 billion POLY tokens in units divisible up to 18 decimals. uint256 public constant INITIAL_SUPPLY = 1000 * (10**6) * token_factor; - uint256 public constant PRESALE_SUPPLY = 150000000 * token_factor; - uint256 public constant PUBLICSALE_SUPPLY = 150000000 * token_factor; + uint256 public constant PRESALE_SUPPLY = 200000000 * token_factor; + uint256 public constant PUBLICSALE_SUPPLY = 120000000 * token_factor; uint256 public constant FOUNDER_SUPPLY = 150000000 * token_factor; - uint256 public constant BDMARKET_SUPPLY = 25000000 * token_factor; + uint256 public constant BDMARKET_SUPPLY = 55000000 * token_factor; uint256 public constant ADVISOR_SUPPLY = 25000000 * token_factor; - uint256 public constant RESERVE_SUPPLY = 500000000 * token_factor; + uint256 public constant RESERVE_SUPPLY = 450000000 * token_factor; address private crowdsale; @@ -70,4 +70,15 @@ contract PolyMathToken is PausableToken, BurnableToken { revert(); } + function claimTokens(address _token) public onlyOwner { + if (_token == 0x0) { + owner.transfer(this.balance); + return; + } + + ERC20Basic token = ERC20Basic(_token); + uint256 balance = token.balanceOf(this); + token.transfer(owner, balance); + } + } From 4b1f095aaf0be6be9a39e3bdf87d7ce96b5840a4 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 22 Nov 2017 10:50:38 +1300 Subject: [PATCH 4/6] added general claimtokens/eth function for stuck tokens. updated tests for new vesting amounts: --- contracts/PolyMathTokenOffering.sol | 14 ++++++++++++++ test/auditTests.js | 12 ++++++------ test/tokenTests.js | 6 +++--- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/contracts/PolyMathTokenOffering.sol b/contracts/PolyMathTokenOffering.sol index e66a8d6..b50bd7b 100644 --- a/contracts/PolyMathTokenOffering.sol +++ b/contracts/PolyMathTokenOffering.sol @@ -2,6 +2,7 @@ pragma solidity ^0.4.13; import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; import 'zeppelin-solidity/contracts/math/SafeMath.sol'; +import 'zeppelin-solidity/contracts/token/ERC20Basic.sol'; import './PolyMathToken.sol'; /** @@ -225,4 +226,17 @@ contract PolyMathTokenOffering is Ownable { return true; } + + function claimTokens(address _token) public onlyOwner { + require(hasEnded()); + if (_token == 0x0) { + owner.transfer(this.balance); + return; + } + + ERC20Basic refundToken = ERC20Basic(_token); + uint256 balance = refundToken.balanceOf(this); + refundToken.transfer(owner, balance); + TokensRefund(balance); + } } diff --git a/test/auditTests.js b/test/auditTests.js index c4ca21a..1c34f79 100644 --- a/test/auditTests.js +++ b/test/auditTests.js @@ -56,15 +56,15 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p }); it('After deploying the Token and the Crowdsale, the balances should all be correct', async function () { - assert.equal((await tokenDeployed.balanceOf(deployer)).toNumber(), 850000000 * 10 ** DECIMALS, "The Token deployer should hold 850mil"); + assert.equal((await tokenDeployed.balanceOf(deployer)).toNumber(), 800000000 * 10 ** DECIMALS, "The Token deployer should hold 800mil"); assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 0, "The Crowdsale should have no balance"); - assert.equal((await tokenDeployed.balanceOf(presale_wallet)).toNumber(), 150000000 * 10 ** DECIMALS, "The Presale should hold 150mil"); + assert.equal((await tokenDeployed.balanceOf(presale_wallet)).toNumber(), 200000000 * 10 ** DECIMALS, "The Presale should hold 200mil"); await tokenDeployed.initializeCrowdsale(tokenOfferingDeployed.address); - assert.equal((await tokenDeployed.balanceOf(deployer)).toNumber(), 700000000 * 10 ** DECIMALS, "The Token deployer should hold 700mil"); - assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 150000000 * 10 ** DECIMALS, "The Crowdsale should hold 150mil"); - assert.equal((await tokenDeployed.balanceOf(presale_wallet)).toNumber(), 150000000 * 10 ** DECIMALS, "The Presale should hold 150mil"); + assert.equal((await tokenDeployed.balanceOf(deployer)).toNumber(), 680000000 * 10 ** DECIMALS, "The Token deployer should hold 680mil"); + assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 120000000 * 10 ** DECIMALS, "The Crowdsale should hold 120mil"); + assert.equal((await tokenDeployed.balanceOf(presale_wallet)).toNumber(), 200000000 * 10 ** DECIMALS, "The Presale should hold 200mil"); }); describe('Initialize crowdsale', async function () { @@ -94,7 +94,7 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p await tokenOfferingDeployed.setBlockTimestamp(endTime + duration.days(7)); await tokenDeployed.setBlockTimestamp(endTime + duration.days(7)); await tokenOfferingDeployed.checkFinalize(); - assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 150000000 * 10 ** DECIMALS, "The Crowdsale should have 150mil"); + assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 120000000 * 10 ** DECIMALS, "The Crowdsale should have 120mil"); await tokenDeployed.unpause(); await tokenOfferingDeployed.refund(); assert.equal((await tokenDeployed.balanceOf(tokenOfferingDeployed.address)).toNumber(), 0, "The Crowdsale should have no balance after a refund"); diff --git a/test/tokenTests.js b/test/tokenTests.js index 1d8dac1..a8bf83a 100644 --- a/test/tokenTests.js +++ b/test/tokenTests.js @@ -89,9 +89,9 @@ contract('polyToken', async function(accounts) { // ############## ERC20 TESTS ############## - let amount = new web3.BigNumber(850000000000000000000000000); + let amount = new web3.BigNumber(800000000000000000000000000); // TRANSERS - it("transfers: should transfer 850000000 to accounts[1] with accounts[0] having 850000000", async () => { + it("transfers: should transfer 800000000 to accounts[1] with accounts[0] having 800000000", async () => { watcher = polyToken.Transfer(); await polyToken.transfer(accounts[1], amount, { from: accounts[0] @@ -108,7 +108,7 @@ contract('polyToken', async function(accounts) { ); }); - it("transfers: should fail when trying to transfer 850000010 to accounts[1] with accounts[0] having 850000000", async () => { + it("transfers: should fail when trying to transfer 800000010 to accounts[1] with accounts[0] having 800000000", async () => { await assertFail(async () => { await polyToken.transfer(accounts[1], amount + 10, { from: accounts[0] From b95b59ad98d3f9c49820da036a56cd36b3b0cf7f Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 22 Nov 2017 11:38:37 +1300 Subject: [PATCH 5/6] added erc20basic import for clarity --- contracts/PolyMathToken.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/PolyMathToken.sol b/contracts/PolyMathToken.sol index 4441070..df82b5c 100644 --- a/contracts/PolyMathToken.sol +++ b/contracts/PolyMathToken.sol @@ -2,6 +2,7 @@ pragma solidity ^0.4.13; import 'zeppelin-solidity/contracts/token/PausableToken.sol'; import 'zeppelin-solidity/contracts/token/BurnableToken.sol'; +import 'zeppelin-solidity/contracts/token/ERC20Basic.sol'; import './PolyMathTokenOffering.sol'; contract PolyMathToken is PausableToken, BurnableToken { From 72127385616c97266ae4cabc74447c2b8ed100ef Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 22 Nov 2017 16:46:26 +1300 Subject: [PATCH 6/6] added more tests. updated truffle deployment --- migrations/2_deploy_contracts.js | 9 ++-- test/auditTests.js | 86 +++++++++++++++++++++++++++++++- test/crowdsaleTests.js | 54 +++++++++++++++++++- test/refundTest.js | 12 +++++ 4 files changed, 155 insertions(+), 6 deletions(-) diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index e269e42..7dfee77 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -16,25 +16,26 @@ const duration = { }; module.exports = function(deployer, network) { - + const startTime = latestTime() + duration.minutes(5); const endTime = startTime + duration.weeks(1); const rate = new web3.BigNumber(1000); const wallet = web3.eth.accounts[0]; + const presale_wallet = web3.eth.accounts[1]; const goal = new web3.BigNumber(3000 * Math.pow(10, 18)); const cap = new web3.BigNumber(15000 * Math.pow(10, 18)); if(network !== 'development'){ deployer.deploy(POLYToken, wallet).then(async function() { - let tokenDeployed = await POLYToken.deployed(); + let tokenDeployed = await POLYToken.deployed(presale_wallet); const encodedPoly = abiEncoder.rawEncode(['address'], [ wallet]); console.log('encodedPoly ENCODED: \n', encodedPoly.toString('hex')); // function PolyMathTokenOffering(address _token, uint256 _startTime, uint256 _endTime, uint256 _cap, address _wallet) { await deployer.deploy(POLYTokenOffering, POLYToken.address, startTime, endTime, cap, wallet); const encodedPOLYTokenOffering = abiEncoder.rawEncode(['address', 'uint256', 'uint256', 'uint256', 'address'], [POLYToken.address, startTime.toString(10), endTime.toString(10), cap.toString(10), wallet]); console.log('encodedPOLYTokenOffering ENCODED: \n', encodedPOLYTokenOffering.toString('hex')); - await tokenDeployed.setOwner(POLYTokenOffering.address); - + await tokenDeployed.initializeCrowdsale(POLYTokenOffering.address); + }); } }; diff --git a/test/auditTests.js b/test/auditTests.js index 1c34f79..81089bc 100644 --- a/test/auditTests.js +++ b/test/auditTests.js @@ -30,9 +30,87 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p }); }); + it('Cannot deploy token if the presale wallet address is null', async function () { + await assertFail(async () => { + tokenDeployed = await POLYToken.new(0x0); + }); + }); + + it('Cannot deploy crowdsale if the cap is 0', async function () { + tokenDeployed = await POLYToken.new(presale_wallet); + await assertFail(async () => { + tokenOfferingDeployed = await TokenOffering.new( + tokenDeployed.address, + latestTime() + duration.seconds(17), + latestTime() + duration.weeks(3), + 0, + presale_wallet + ) + }); + }); + + it('Cannot deploy crowdsale if the token is null', async function () { + await assertFail(async () => { + tokenOfferingDeployed = await TokenOffering.new( + 0x0, + latestTime() + duration.seconds(15), + latestTime() + duration.weeks(6), + 12345, + presale_wallet + ) + }); + }); + + it('Cannot deploy crowdsale if the token is null', async function () { + tokenDeployed = await POLYToken.new(presale_wallet); + await assertFail(async () => { + tokenOfferingDeployed = await TokenOffering.new( + tokenDeployed.address, + latestTime() + duration.seconds(19), + latestTime() + duration.weeks(2), + 12345, + 0x0 + ) + }); + }); + + it('Cannot deploy crowdsale if the start time is in the past', async function () { + tokenDeployed = await POLYToken.new(presale_wallet); + await assertFail(async () => { + tokenOfferingDeployed = await TokenOffering.new( + tokenDeployed.address, + latestTime() - duration.seconds(2), + latestTime() + duration.weeks(1), + 12345, + presale_wallet + ) + }); + }); + + it('Cannot deploy crowdsale if the end time is before the start time', async function () { + tokenDeployed = await POLYToken.new(presale_wallet); + await assertFail(async () => { + tokenOfferingDeployed = await TokenOffering.new( + tokenDeployed.address, + latestTime() + duration.weeks(10), + latestTime() + duration.weeks(1), + 12345, + presale_wallet + ) + }); + }); + it('Cap should not be able to exceed balance of crowdsale contract', async function () { tokenDeployed = await POLYToken.new(presale_wallet); - await assertFail(async () => { await TokenOffering.new(tokenDeployed.address, latestTime() + duration.seconds(20), latestTime() + duration.weeks(1), web3.toWei(150000001, 'ether'), crowdsale_wallet) }); + await assertFail(async () => { + await TokenOffering.new( + tokenDeployed.address, + latestTime() + duration.seconds(20), + latestTime() + duration.weeks(1), + web3.toWei(150000001, 'ether'), + presale_wallet + ) + }); }); it('Tokens should not be able to be sent to the null address from the token contract', async function () { @@ -50,6 +128,12 @@ contract('Audit Tests', async function ([deployer, investor, crowdsale_wallet, p tokenOfferingDeployed = await TokenOffering.new(tokenDeployed.address, startTime, endTime, cap, crowdsale_wallet); }); + it('Calling an invalid function on the token triggers the fallback and reverts', async function () { + await assertFail(async () => { + await tokenDeployed.sendTransaction({ from: investor }) + }); + }); + it('Crowdsale should only be able to be initialized once', async function () { await tokenDeployed.initializeCrowdsale(tokenOfferingDeployed.address); await assertFail(async () => { await tokenDeployed.initializeCrowdsale(tokenOfferingDeployed.address) });; diff --git a/test/crowdsaleTests.js b/test/crowdsaleTests.js index cc1c823..fc9944a 100644 --- a/test/crowdsaleTests.js +++ b/test/crowdsaleTests.js @@ -2,11 +2,12 @@ var TokenOffering = artifacts.require('./helpers/PolyMathTokenOfferingMock.sol'); var POLYToken = artifacts.require('PolyMathToken.sol'); +const assertFail = require("./helpers/assertFail"); import { latestTime, duration } from './helpers/latestTime'; const DECIMALS = 18; -contract('TokenOffering', async function ([miner, owner, investor, wallet, presale_wallet]) { +contract('TokenOffering', async function ([miner, owner, investor, investor2, wallet, presale_wallet]) { let tokenOfferingDeployed; let tokenDeployed; let startTime; @@ -134,5 +135,56 @@ contract('TokenOffering', async function ([miner, owner, investor, wallet, pres assert.equal(balance.toNumber(), 1000 * 10 ** DECIMALS, 'balanceOf is 1000 for investor who just bought tokens'); }); + it('tokens cannot be purchased after the crowdsale is finalized', async function () { + await tokenOfferingDeployed.whitelistAddresses([investor], true); + await tokenOfferingDeployed.emergencyFinalize(); + const value = web3.toWei(1, 'ether'); + await assertFail(async () => { + await tokenOfferingDeployed.sendTransaction({ from: investor, value: value }); + }); + }); + + it('cannot purchase tokens for the null address', async function () { + // No whitelist for purchaser, or beneficiary (0x0) + await assertFail(async () => { + await tokenOfferingDeployed.buyTokens(0x0, { from: investor, value: value }); + }); + // Whitelist for purchaser + await tokenOfferingDeployed.whitelistAddresses([investor], true); + await assertFail(async () => { + await tokenOfferingDeployed.buyTokens(0x0, { from: investor, value: value }); + }); + // Whitelist for purchaser and beneficiary (0x0) + // Should still fail even if someone whitelists the null address + await tokenOfferingDeployed.whitelistAddresses([0x0], true); + await assertFail(async () => { + await tokenOfferingDeployed.buyTokens(0x0, { from: investor, value: value }); + }); + }); + + it('cannot purchase tokens if the beneficiary is not whitelisted, even if you are', async function () { + const value = web3.toWei(1, 'ether'); + await tokenOfferingDeployed.whitelistAddresses([investor], true); + await assertFail(async () => { + await tokenOfferingDeployed.buyTokens(investor2, { from: investor, value: value }); + }); + // Should work once the beneficiary has been whitelisted + await tokenOfferingDeployed.whitelistAddresses([investor2], true); + await tokenOfferingDeployed.buyTokens(investor2, { from: investor, value: value }); + let balance = await tokenDeployed.balanceOf(investor2); + assert.equal(balance.toNumber(), 1200 * 10 ** DECIMALS, 'balanceOf is 1200 for investor who just bought tokens'); + }); + + it('cannot purchase tokens if the beneficiary is not whitelisted', async function () { + const value = web3.toWei(1, 'ether'); + await assertFail(async () => { + await tokenOfferingDeployed.sendTransaction({ from: investor, value: value }); + }); + // Should work once the beneficiary has been whitelisted + await tokenOfferingDeployed.whitelistAddresses([investor], true); + await tokenOfferingDeployed.sendTransaction({ from: investor, value: value }); + let balance = await tokenDeployed.balanceOf(investor); + assert.equal(balance.toNumber(), 1200 * 10 ** DECIMALS, 'balanceOf is 1200 for investor who just bought tokens'); + }); }) }); diff --git a/test/refundTest.js b/test/refundTest.js index 00da2f1..991937c 100644 --- a/test/refundTest.js +++ b/test/refundTest.js @@ -69,6 +69,18 @@ contract('TokenOfferingRefund', async function ([miner, owner, investor, wallet, assert.equal(balance.toNumber(), 1200 * 10 ** DECIMALS); }); + it('trying to whitelist an address twice doesn\'t change it\'s state', async function () { + let investorStatus = await tokenOfferingDeployed.whitelist(investor); + assert.isFalse(investorStatus); + await tokenOfferingDeployed.whitelistAddresses([investor], true); + investorStatus = await tokenOfferingDeployed.whitelist(investor); + assert.isTrue(investorStatus); + + await tokenOfferingDeployed.whitelistAddresses([investor], true); + investorStatus = await tokenOfferingDeployed.whitelist(investor); + assert.isTrue(investorStatus); + }); + it('refund excess ETH if cap has been exceeded (day 1)', async function () { await tokenOfferingDeployed.setBlockTimestamp(startTime + 1); await tokenOfferingDeployed.whitelistAddresses([investor], true);