From 2227dd6a325b82484341e7b1125d724b9316e6c4 Mon Sep 17 00:00:00 2001 From: ryzhak Date: Tue, 11 Dec 2018 16:45:09 +0300 Subject: [PATCH] added all tests for daico --- contracts/Daico/Daico.sol | 2 +- contracts/Daico/IDaico.sol | 40 ++++- test/Daico.functional.tests.js | 63 +++++++ test/Daico.tests.js | 317 ++++++++++++++++++++++++++++++--- 4 files changed, 389 insertions(+), 33 deletions(-) create mode 100644 test/Daico.functional.tests.js diff --git a/contracts/Daico/Daico.sol b/contracts/Daico/Daico.sol index 352a832..d2a73a5 100644 --- a/contracts/Daico/Daico.sol +++ b/contracts/Daico/Daico.sol @@ -339,8 +339,8 @@ contract Daico is Ownable { function withdrawTapPayment(uint _tapIndex) external validTapIndex(_tapIndex) { // validation require(msg.sender == projectOwner); - require(!tapPayments[_tapIndex].isWithdrawn); require(isTapWithdrawAcceptedByInvestors(_tapIndex)); + require(!tapPayments[_tapIndex].isWithdrawn); // create tap payment TapPayment memory tapPayment; tapPayment.amount = tapAmounts[_tapIndex]; diff --git a/contracts/Daico/IDaico.sol b/contracts/Daico/IDaico.sol index 7e991df..09dcd9a 100644 --- a/contracts/Daico/IDaico.sol +++ b/contracts/Daico/IDaico.sol @@ -10,7 +10,9 @@ import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; * 2. Contract creates initial votings of type ReleaseTap for each tap. * 3. Evercity member transfers DAI tokens to DAICO contract address. * - * Scenarios: + * ================== + * Common scenarios: + * ================== * # Successful voting for tap release * 1. Token holders votes via 'vote()'. * 2. Quorum reached with positive decision. @@ -36,6 +38,27 @@ import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; * 3. One of the investors creates a new voting of type 'TerminateProject' via 'createVotingByInvestor()'. * 4. Quorum reached with positive decision. * 5. Evercity member withdraws left DAI tokens via 'withdrawFunding()'. + * + * =================== + * All scenario steps: + * =================== + * # ReleaseTap/ReleaseTapDecreasedQuorum + * - voting of type ReleaseTap or ReleaseTapDecreasedQuorum finishes with Accept result => project owner withdraws DAI tokens via 'withdrawTapPayment()' + * - voting of type ReleaseTap or ReleaseTapDecreasedQuorum finishes with Decline result => one of the investors creates a voting of type TerminateProject via 'createVotingByInvestor()' + * - voting of type ReleaseTap or ReleaseTapDecreasedQuorum finishes with QuorumNotReached result => evercity member creates a voting of type ReleaseTapDecreasedQuorum via 'createVotingByOwner()' + * - voting of type ReleaseTap or ReleaseTapDecreasedQuorum finishes with NoDecision result => one of the investors creates a voting of type ChangeRoadmap via 'createVotingByInvestor()' + * + * # ChangeRoadmap/ChangeRoadmapDecreasedQuorum + * - voting of type ChangeRoadmap or ChangeRoadmapDecreasedQuorum finishes with Accept result => project owner withdraws DAI tokens via 'withdrawTapPayment()' + * - voting of type ChangeRoadmap or ChangeRoadmapDecreasedQuorum finishes with Decline result => one of the investors creates a voting of type TerminateProject via 'createVotingByInvestor()' + * - voting of type ChangeRoadmap or ChangeRoadmapDecreasedQuorum finishes with QuorumNotReached result => one of the investors creates a voting of type ChangeRoadmapDecreasedQuorum via 'createVotingByInvestor()' + * - voting of type ChangeRoadmap or ChangeRoadmapDecreasedQuorum finishes with NoDecision result => one of the investors creates a voting of type ChangeRoadmapDecreasedQuorum via 'createVotingByInvestor()' + * + * # TerminateProject/TerminateProjectDecreasedQuorum + * - voting of type TerminateProject or TerminateProjectDecreasedQuorum finishes with Accept result => evervity member withdraws DAI tokens via 'withdrawFunding()' + * - voting of type TerminateProject or TerminateProjectDecreasedQuorum finishes with Decline result => one of the investors creates a voting of type ChangeRoadmap via 'createVotingByInvestor()' + * - voting of type TerminateProject or TerminateProjectDecreasedQuorum finishes with QuorumNotReached result => one of the investors creates a voting of type TerminateProjectDecreasedQuorum via 'createVotingByInvestor()' + * - voting of type TerminateProject or TerminateProjectDecreasedQuorum finishes with NoDecision result => one of the investors creates a voting of type TerminateProjectDecreasedQuorum via 'createVotingByInvestor()' */ contract IDaico is Ownable { @@ -120,9 +143,17 @@ contract IDaico is Ownable { */ function getVotingResult(uint _votingIndex) public view returns(VotingResult); + /** + * @dev Checks whether investor already voted in particular voting + * @param _votingIndex voting index + * @param _investorAddress investor address + * @return whether investor has already voted in particular voting + */ + function isInvestorVoted(uint _votingIndex, address _investorAddress) external view returns(bool); + /** * @dev Checks whether project is terminated. - * Project is terminated when the last voting is of type TerminateProject with Accept result. + * Project is terminated when the last voting is of type TerminateProject/TerminateProjectDecreasedQuorum with Accept result. * When project is terminated contract owner(evercity member) can withdraw DAI tokens via 'withdrawFunding()'. * @return is project terminated */ @@ -140,7 +171,7 @@ contract IDaico is Ownable { */ /** - * @dev Creates a new voting by investor. Investor can create 2 types of votings: change roadmap and terminate project + * @dev Creates a new voting by investor. Investors can create votings of 4 types: ChangeRoadmap, ChangeRoadmapDecreasedQuorum, TerminateProject, TerminateProjectDecreasedQuorum. * @param _tapIndex tap index * @param _votingType voting type */ @@ -153,6 +184,7 @@ contract IDaico is Ownable { * - Valid voting index * - Is valid voting period * - Investor hasn't voted earlier for this proposal + * - Project is not terminated * @param _votingIndex voting index * @param _isYes decision, yes or no */ @@ -163,7 +195,7 @@ contract IDaico is Ownable { */ /** - * @dev Creates a new voting by owner. Owner can create 1 type of votings: tap release with decreased quorum rate + * @dev Creates a new voting by owner. Owner can create votings only of type ReleaseTapDecreasedQuorum * @param _tapIndex tap index * @param _votingType voting type */ diff --git a/test/Daico.functional.tests.js b/test/Daico.functional.tests.js new file mode 100644 index 0000000..762551d --- /dev/null +++ b/test/Daico.functional.tests.js @@ -0,0 +1,63 @@ +const moment = require("moment"); + +const Daico = artifacts.require("DaicoTestable"); +const MintableToken = artifacts.require("MintableToken"); + +contract("Daico", (accounts) => { + + const evercityMemberAddress = accounts[0]; + const projectOwnerAddress = accounts[1]; + const inverstorAddress = accounts[2]; + const otherAddress = accounts[3]; + + const minQuorumRate = 70; + const minVoteRate = 70; + const tokenHoldersCount = 5; + + let daico; + let daiToken; + let projectToken; + + beforeEach(async() => { + daiToken = await MintableToken.new(); + projectToken = await MintableToken.new(); + await projectToken.mint(inverstorAddress, 1); + + timestampsFinishAt = [ + moment.unix(web3.eth.getBlock("latest").timestamp).add(1, 'week').unix(), + moment.unix(web3.eth.getBlock("latest").timestamp).add(5, 'weeks').unix() + ]; + daico = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount); + }); + + describe("onlyInvestor()", () => { + it("should revert if method is called not by investor", async() => { + await daico.vote(0, true, {from: otherAddress}).should.be.rejectedWith("revert"); + }); + + it("should call method that can be executed only by investor", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + }); + }); + + describe("validTapIndex()", () => { + it("should revert if tap index does not exist", async() => { + await daico.isTapWithdrawAcceptedByInvestors(2).should.be.rejectedWith("revert"); + }); + + it("should call method with valid tap index", async() => { + await daico.isTapWithdrawAcceptedByInvestors(1).should.be.fulfilled; + }); + }); + + describe("validVotingIndex()", () => { + it("should revert if voting index does not exist", async() => { + await daico.vote(2, true, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should call method with valid tap index", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + }); + }); + +}); diff --git a/test/Daico.tests.js b/test/Daico.tests.js index 966e7a5..d3f4726 100644 --- a/test/Daico.tests.js +++ b/test/Daico.tests.js @@ -12,6 +12,7 @@ contract("Daico", (accounts) => { const inverstorAddress2 = accounts[3]; const inverstorAddress3 = accounts[4]; const inverstorAddress4 = accounts[5]; + const inverstorAddress5 = accounts[6]; const VOTING_TYPE_RELEASE_TAP = 0; const VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM = 1; @@ -25,7 +26,6 @@ contract("Daico", (accounts) => { const VOTING_RESULT_QUORUM_NOT_REACHED = 2; const VOTING_RESULT_NO_DECISION = 3; - const timestampsFinishAt = [moment().add(1, 'week').unix(), moment().add(1, '5 weeks').unix()]; const minQuorumRate = 70; const minVoteRate = 70; const tokenHoldersCount = 5; @@ -33,15 +33,24 @@ contract("Daico", (accounts) => { let daico; let daiToken; let projectToken; + let timestampsFinishAt; beforeEach(async() => { daiToken = await MintableToken.new(); + await daiToken.mint(evercityMemberAddress, 3); + projectToken = await MintableToken.new(); await projectToken.mint(inverstorAddress, 1); await projectToken.mint(inverstorAddress2, 1); await projectToken.mint(inverstorAddress3, 1); await projectToken.mint(inverstorAddress4, 1); + + timestampsFinishAt = [ + moment.unix(web3.eth.getBlock("latest").timestamp).add(1, 'week').unix(), + moment.unix(web3.eth.getBlock("latest").timestamp).add(5, 'weeks').unix() + ]; daico = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount); + await daiToken.transfer(daico.address, 3, {from: evercityMemberAddress}); }); describe("constructor()", () => { @@ -151,6 +160,151 @@ contract("Daico", (accounts) => { it("should revert if last voting is not finished", async() => { await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP, {from: inverstorAddress}).should.be.rejectedWith("revert"); }); + + it("should revert on ChangeRoadmap voting type when last voting is not: ReleaseTap, ReleaseTapDecreasedQuorum, TerminateProject, TerminateProjectDecreasedQuorum", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP, {from: inverstorAddress}).should.be.fulfilled; + await increaseTime(28 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should revert on ChangeRoadmap voting type when last voting is ReleaseTap or ReleaseTapDecreasedQuorum with voting result not NoDecision", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should revert on ChangeRoadmap voting type when last voting is TerminateProject or TerminateProjectDecreasedQuorum with voting result not Decline", async() => { + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + await increaseTime(28 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should create a new voting of type ChangeRoadmap", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP, {from: inverstorAddress}).should.be.fulfilled; + }); + + it("should revert on ChangeRoadmapDecreasedQuorum voting type when last voting is not ChangeRoadmap or ChangeRoadmapDecreasedQuorum", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP_DECREASED_QUORUM, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should revert on ChangeRoadmapDecreasedQuorum voting type when last voting result is not QuorumNotReached or NoDecision", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP, {from: inverstorAddress}).should.be.fulfilled; + await increaseTime(21 * 24 * 60 * 60); + await daico.vote(2, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(28 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP_DECREASED_QUORUM, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should create a new voting of type ChangeRoadmapDecreasedQuorum", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP, {from: inverstorAddress}).should.be.fulfilled; + await increaseTime(21 * 24 * 60 * 60); + await daico.vote(2, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(2, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(2, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(28 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_CHANGE_ROADMAP_DECREASED_QUORUM, {from: inverstorAddress}).should.be.fulfilled; + }); + + it("should revert on TerminateProject voting type when last voting is not: ReleaseTap, ReleaseTapDecreasedQuorum, ChangeRoadmap, ChangeRoadmapDecreasedQuorum", async() => { + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + await increaseTime(14 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should revert on TerminateProject voting type when last voting result is not Decline", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should create a new voting of type TerminateProject", async() => { + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + }); + + it("should revert on TerminateProjectDecreasedQuorum voting type when last voting is not TerminateProject or TerminateProjectDecreasedQuorum", async() => { + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT_DECREASED_QUORUM, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should revert on TerminateProjectDecreasedQuorum voting type when last voting result is not QuorumReached or NoDecision", async() => { + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(2, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(2, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(14 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT_DECREASED_QUORUM, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should create a new voting of type TerminateProjectDecreasedQuorum", async() => { + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(2, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(2, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(14 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT_DECREASED_QUORUM, {from: inverstorAddress}).should.be.fulfilled; + }); }); describe("createVotingByOwner()", () => { @@ -162,26 +316,33 @@ contract("Daico", (accounts) => { await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM, {from: evercityMemberAddress}).should.be.rejectedWith("revert"); }); - // TODO: test 'require(latestVoting.votingType == VotingType.ReleaseTap || latestVoting.votingType == VotingType.ReleaseTapDecreasedQuorum);' - - // TODO: fix increaseTime issue - // it("should revert if latest voting result is not quorum not reached", async() => { - // await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; - // await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; - // await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; - // await daico.vote(0, true, {from: inverstorAddress4}).should.be.fulfilled; - // await increaseTime(7 * 24 * 60 * 60); - // await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM, {from: evercityMemberAddress}).should.be.rejectedWith("revert"); - // }); - - // TODO: fix increaseTime issue - // it("should create a new voting", async() => { - // await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; - // await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; - // await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; - // await increaseTime(7 * 24 * 60 * 60); - // await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM, {from: evercityMemberAddress}).should.be.fulfilled; - // }); + it("should revert if latest voting is not ReleaseTap or ReleaseTapDecreasedQuorum", async() => { + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + await increaseTime(14 * 24 * 60 * 60); + await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM, {from: evercityMemberAddress}).should.be.rejectedWith("revert"); + }); + + it("should revert if latest voting result is not quorum not reached", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM, {from: evercityMemberAddress}).should.be.rejectedWith("revert"); + }); + + it("should create a new voting", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM, {from: evercityMemberAddress}).should.be.fulfilled; + }); }); describe("getVotingResult()", () => { @@ -232,6 +393,26 @@ contract("Daico", (accounts) => { }); }); + describe("isProjectTerminated()", () => { + it("should return false if project is not terminated", async() => { + assert.equal(await daico.isProjectTerminated(), false); + }); + + it("should return true if project is terminated", async() => { + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress4}).should.be.fulfilled; + assert.equal(await daico.isProjectTerminated(), true); + }); + }); + describe("isTapWithdrawAcceptedByInvestors()", () => { it("should return false if investors have not accepted tap release", async() => { assert.equal(await daico.isTapWithdrawAcceptedByInvestors(0), false); @@ -251,14 +432,27 @@ contract("Daico", (accounts) => { await daico.vote(1, true, {from: inverstorAddress}).should.be.rejectedWith("revert"); }); - // TODO: fix increase time issue when timestampsFinishAt not in sync with current blockhain timestamp after 'increaseTime()' - // it("should revert if it is too late to vote", async() => { - // const daicoNew = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount); - // await increaseTime(7 * 24 * 60 * 60); - // await daicoNew.vote(0, true, {from: inverstorAddress}).should.be.rejectedWith("revert"); - // }); + it("should revert if it is too late to vote", async() => { + const daicoNew = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount); + await increaseTime(7 * 24 * 60 * 60); + await daicoNew.vote(0, true, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); - // TODO: test 'require(!isProjectTerminated())' + it("should revert if project is terminated", async() => { + // terminate project + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress4}).should.be.fulfilled; + + await daico.vote(2, false, {from: inverstorAddress5}).should.be.rejectedWith("revert"); + }); it("should revert if investor has already voted", async() => { await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; @@ -284,4 +478,71 @@ contract("Daico", (accounts) => { }); }); + describe("withdrawFunding()", () => { + it("should revert if project is not terminated", async() => { + await daico.withdrawFunding({from: evercityMemberAddress}).should.be.rejectedWith("revert"); + }); + + it("should withdraw DAI tokens", async() => { + // terminate project + await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, false, {from: inverstorAddress4}).should.be.fulfilled; + await increaseTime(7 * 24 * 60 * 60); + await daico.createVotingByInvestor(0, VOTING_TYPE_TERMINATE_PROJECT, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstorAddress4}).should.be.fulfilled; + // withdraw DAI tokens + assert.equal(await daiToken.balanceOf(evercityMemberAddress), 0); + await daico.withdrawFunding({from: evercityMemberAddress}).should.be.fulfilled; + assert.equal(await daiToken.balanceOf(evercityMemberAddress), 3); + }); + }); + + describe("withdrawTapPayment()", () => { + it("should revert if caller is not project owner", async() => { + await daico.withdrawTapPayment(0, {from: inverstorAddress}).should.be.rejectedWith("revert"); + }); + + it("should revert if tap release is not accepted by investors", async() => { + await daico.withdrawTapPayment(0, {from: projectOwnerAddress}).should.be.rejectedWith("revert"); + }); + + it("should revert if tap is already withdrawn", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress4}).should.be.fulfilled; + await daico.withdrawTapPayment(0, {from: projectOwnerAddress}).should.be.fulfilled; + await daico.withdrawTapPayment(0, {from: projectOwnerAddress}).should.be.rejectedWith("revert"); + }); + + it("should create a new tap payment", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress4}).should.be.fulfilled; + await daico.withdrawTapPayment(0, {from: projectOwnerAddress}).should.be.fulfilled; + + const tapPayment = await daico.tapPayments(0); + assert.equal(tapPayment[0], 1); + assert.notEqual(tapPayment[1], 0); + assert.equal(tapPayment[2], true); + }); + + it("should transfer DAI tokens to project owner", async() => { + await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress3}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstorAddress4}).should.be.fulfilled; + + assert.equal(await daiToken.balanceOf(projectOwnerAddress), 0); + await daico.withdrawTapPayment(0, {from: projectOwnerAddress}).should.be.fulfilled; + assert.equal(await daiToken.balanceOf(projectOwnerAddress), 1); + }); + }); + });