From fc2ea04e93119bce58a35b15d6330b20a330c01f Mon Sep 17 00:00:00 2001 From: enkogu Date: Tue, 22 Jan 2019 20:31:54 +0700 Subject: [PATCH 1/6] functional test; @thetta/core removed from rep --- .../0-FineGrainedPermissions.sol | 54 - contracts/0-FineGrainedPermissions/README.md | 15 - .../1-HierarchyDao/HierarchyDaoFactory.sol | 91 - contracts/1-HierarchyDao/HierarcyDao.sol | 8 - contracts/1-HierarchyDao/README.md | 4 - contracts/2-BoD/BodDao.sol | 8 - contracts/2-BoD/BodDaoFactory.sol | 80 - contracts/2-BoD/README.md | 4 - contracts/3-DevZenDao/DevZenDao.sol | 119 -- contracts/3-DevZenDao/DevZenDaoAuto.sol | 71 - .../3-DevZenDao/DevZenDaoAutoTestable.sol | 58 - contracts/3-DevZenDao/DevZenDaoCore.sol | 303 --- contracts/3-DevZenDao/DevZenDaoFactory.sol | 129 -- .../3-DevZenDao/DevZenDaoFactoryTestable.sol | 117 -- contracts/3-DevZenDao/DevZenDaoTestable.sol | 133 -- .../3-DevZenDao/DevZenDaoWithUnpackers.sol | 43 - .../DevZenDaoWithUnpackersTestable.sol | 44 - contracts/3-DevZenDao/README.md | 23 - contracts/Daico/Daico.sol | 62 +- contracts/Daico/DaicoAuto.sol | 20 - contracts/Daico/DaicoFactory.sol | 76 - contracts/Daico/DaicoOld.sol | 31 - contracts/Daico/DaicoProject.sol | 66 - contracts/Daico/DaicoTestable.sol | 8 +- contracts/Daico/DaicoWithUnpackers.sol | 30 - migrations/2_deploy_contracts_libraries.js | 20 - migrations/3_deploy_devZenDao.js | 92 - migrations/4_deploy_HierarchyDao.js | 54 - migrations/5_deploy_BodDao.js | 57 - package-lock.json | 1809 +---------------- package.json | 1 - test/BoD.functional.tests.js | 93 - test/Daico.functional.tests.js | 126 +- test/Daico.tests.js | 84 +- test/DevZenDao.functional.tests.js | 321 --- test/DevZenDao.testable.functional.tests.js | 471 ----- test/HierarchyDao.functional.tests.js | 84 - 37 files changed, 181 insertions(+), 4628 deletions(-) delete mode 100644 contracts/0-FineGrainedPermissions/0-FineGrainedPermissions.sol delete mode 100644 contracts/0-FineGrainedPermissions/README.md delete mode 100644 contracts/1-HierarchyDao/HierarchyDaoFactory.sol delete mode 100644 contracts/1-HierarchyDao/HierarcyDao.sol delete mode 100644 contracts/1-HierarchyDao/README.md delete mode 100644 contracts/2-BoD/BodDao.sol delete mode 100644 contracts/2-BoD/BodDaoFactory.sol delete mode 100644 contracts/2-BoD/README.md delete mode 100644 contracts/3-DevZenDao/DevZenDao.sol delete mode 100644 contracts/3-DevZenDao/DevZenDaoAuto.sol delete mode 100644 contracts/3-DevZenDao/DevZenDaoAutoTestable.sol delete mode 100644 contracts/3-DevZenDao/DevZenDaoCore.sol delete mode 100644 contracts/3-DevZenDao/DevZenDaoFactory.sol delete mode 100644 contracts/3-DevZenDao/DevZenDaoFactoryTestable.sol delete mode 100644 contracts/3-DevZenDao/DevZenDaoTestable.sol delete mode 100644 contracts/3-DevZenDao/DevZenDaoWithUnpackers.sol delete mode 100644 contracts/3-DevZenDao/DevZenDaoWithUnpackersTestable.sol delete mode 100644 contracts/3-DevZenDao/README.md delete mode 100644 contracts/Daico/DaicoAuto.sol delete mode 100644 contracts/Daico/DaicoFactory.sol delete mode 100644 contracts/Daico/DaicoOld.sol delete mode 100644 contracts/Daico/DaicoProject.sol delete mode 100644 contracts/Daico/DaicoWithUnpackers.sol delete mode 100644 migrations/2_deploy_contracts_libraries.js delete mode 100644 migrations/3_deploy_devZenDao.js delete mode 100644 migrations/4_deploy_HierarchyDao.js delete mode 100644 migrations/5_deploy_BodDao.js delete mode 100644 test/BoD.functional.tests.js delete mode 100644 test/DevZenDao.functional.tests.js delete mode 100644 test/DevZenDao.testable.functional.tests.js delete mode 100644 test/HierarchyDao.functional.tests.js diff --git a/contracts/0-FineGrainedPermissions/0-FineGrainedPermissions.sol b/contracts/0-FineGrainedPermissions/0-FineGrainedPermissions.sol deleted file mode 100644 index ad4f84e..0000000 --- a/contracts/0-FineGrainedPermissions/0-FineGrainedPermissions.sol +++ /dev/null @@ -1,54 +0,0 @@ -pragma solidity ^0.4.24; - -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/DaoClient.sol"; - -// This is an example of a more fine grained permissions. -// DaoBase allows us to permit 'issueTokens'/'burnTokens' only for all tokens at once. -// -// So if you have 3 tokens (governance, rep, repdev) -> any member that can issueTokens -// for 1st, will be able to issueTokens for the 2nd, etc. -// -// Imagine you want: -// 1. create your own more fine grained permissions - issueTokensGovr, issueTokensRep, issueTokensRepDev -// 2. to permit issueTokensGovr to CEO -// 3. to permit issueTokensRep tnd issueTokensRepDev to Managers -// -// This contract shows how to do that: -contract ExampleOfFineGrainedPerms is DaoClient { - StdDaoToken public tokenGovernance; - // Generic reputation - StdDaoToken public tokenReputation; - // Reputation for Devs - StdDaoToken public tokenReputationDev; - -//// - constructor(IDaoBase _daoBase) public DaoClient(_daoBase){ - // 1 - create tokens - tokenGovernance = new StdDaoToken("YourDaoGovToken","GOV",18, true, false, 10**25); - tokenReputation = new StdDaoToken("YourDaoRepToken","REP",18, true, true, 10**25); - tokenReputationDev = new StdDaoToken("YourDaoRepDevToken","REPD",18, true, false, 10**25); - - // 2 - transfer ownership to the Dao - tokenGovernance.transferOwnership(daoBase); - tokenReputation.transferOwnership(daoBase); - tokenReputationDev.transferOwnership(daoBase); - } - -// ACTIONS: - function issueTokensGovr(address _to, uint _amount) external isCanDo("CUSTOM_issueTokensGovr"){ - // you should grant issueTokens permission to THIS contract - daoBase.issueTokens(tokenGovernance, _to, _amount); - } - - function issueTokensRep(address _to, uint _amount) external isCanDo("CUSTOM_issueTokensRep"){ - // you should grant issueTokens permission to THIS contract - daoBase.issueTokens(tokenReputation, _to, _amount); - } - - function issueTokensRepDev(address _to, uint _amount) external isCanDo("CUSTOM_issueTokensRepDev"){ - // you should grant issueTokens permission to THIS contract - daoBase.issueTokens(tokenReputationDev, _to, _amount); - } -} diff --git a/contracts/0-FineGrainedPermissions/README.md b/contracts/0-FineGrainedPermissions/README.md deleted file mode 100644 index b3470e1..0000000 --- a/contracts/0-FineGrainedPermissions/README.md +++ /dev/null @@ -1,15 +0,0 @@ - # FineGrainedPermissions - --- - ## Description -This is an example of a more fine grained permissions. -DaoBase allows us to permit 'issueTokens'/'burnTokens' only for all tokens at once. - -So if you have 3 tokens (governance, rep, repdev) -> any member that can issueTokens -for 1st, will be able to issueTokens for the 2nd, etc. - -Imagine you want: -1. create your own more fine grained permissions - issueTokensGovr, issueTokensRep, issueTokensRepDev -2. to permit issueTokensGovr to CEO -3. to permit issueTokensRep tnd issueTokensRepDev to Managers - -This contract shows how to do that. \ No newline at end of file diff --git a/contracts/1-HierarchyDao/HierarchyDaoFactory.sol b/contracts/1-HierarchyDao/HierarchyDaoFactory.sol deleted file mode 100644 index a71f804..0000000 --- a/contracts/1-HierarchyDao/HierarchyDaoFactory.sol +++ /dev/null @@ -1,91 +0,0 @@ -pragma solidity ^0.4.24; - -import "@thetta/core/contracts/DaoBase.sol"; -import "@thetta/core/contracts/DaoBaseWithUnpackers.sol"; -import "@thetta/core/contracts/DaoBaseAuto.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/DaoStorage.sol"; -import "@thetta/core/contracts/utils/UtilsLib.sol"; -import "@thetta/core/contracts/governance/InformalProposal.sol"; -import "./HierarcyDao.sol"; - -contract HierarchyDaoFactory { - DaoStorage public store; - StdDaoToken public token; - DaoBaseWithUnpackers public daoBase; - HierarchyDao public hierarcyDao; - DaoBaseAuto public hierarchyDaoAuto; - - constructor( - address _boss, - address[] _managers, - address[] _employees, - address[] _outsiders - ) public { - createDao(_boss, _managers, _employees, _outsiders); - setupHierarchyDaoAuto(); - daoBase.renounceOwnership(); - } - - function createDao( - address _boss, - address[] _managers, - address[] _employees, - address[] _outsiders - ) internal { - // 1 - create - token = new StdDaoToken("StdToken", "STDT", 18, true, false, 10**25); - address[] tokens; - tokens.push(address(token)); - store = new DaoStorage(tokens); - daoBase = new DaoBaseWithUnpackers(store); - hierarcyDao = new HierarchyDao(IDaoBase(daoBase)); - - store.allowActionByAddress(daoBase.MANAGE_GROUPS(), address(this)); - store.transferOwnership(daoBase); - token.transferOwnership(daoBase); - - setPermissions(_boss, _managers, _employees); - } - - function setPermissions(address _boss, address[] _managers, address[] _employees) internal { - - // 1 - grant all permissions to the boss - daoBase.addGroupMember("Managers", _boss); - daoBase.addGroupMember("Employees", _boss); - - daoBase.allowActionByAddress(daoBase.ISSUE_TOKENS(), _boss); - daoBase.allowActionByAddress(daoBase.UPGRADE_DAO_CONTRACT(), _boss); - - // 2 - set managers group permission - daoBase.allowActionByAnyMemberOfGroup(daoBase.ADD_NEW_PROPOSAL(), "Managers"); - - // 4 - the rest is by voting only (requires addNewProposal permission) - // so accessable by Managers only even with voting - daoBase.allowActionByVoting(daoBase.MANAGE_GROUPS(), token); - - // 5 - populate groups - uint i = 0; - for(i = 0; i < _managers.length; ++i) { - daoBase.addGroupMember("Managers", _managers[i]); - } - for(i = 0; i < _employees.length; ++i){ - daoBase.addGroupMember("Employees", _employees[i]); - } - - } - - function setupHierarchyDaoAuto() internal { - hierarchyDaoAuto = new DaoBaseAuto(daoBase); - - // set voring params 1 person 1 vote - uint8 VOTING_TYPE_1P1V = 1; - hierarchyDaoAuto.setVotingParams(daoBase.MANAGE_GROUPS(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("Managers"), bytes32(50), bytes32(50), 0); - - daoBase.allowActionByAddress(daoBase.ADD_NEW_PROPOSAL(), hierarchyDaoAuto); - daoBase.allowActionByAddress(daoBase.MANAGE_GROUPS(), hierarchyDaoAuto); - - hierarchyDaoAuto.transferOwnership(msg.sender); - } -} \ No newline at end of file diff --git a/contracts/1-HierarchyDao/HierarcyDao.sol b/contracts/1-HierarchyDao/HierarcyDao.sol deleted file mode 100644 index 69d45f4..0000000 --- a/contracts/1-HierarchyDao/HierarcyDao.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.4.24; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/DaoClient.sol"; - -contract HierarchyDao is DaoClient { - constructor(IDaoBase _daoBase)public DaoClient(_daoBase){ - } -} diff --git a/contracts/1-HierarchyDao/README.md b/contracts/1-HierarchyDao/README.md deleted file mode 100644 index d3b79ba..0000000 --- a/contracts/1-HierarchyDao/README.md +++ /dev/null @@ -1,4 +0,0 @@ - # HierarchyDao - --- - ## Description -Hierarchy DAO with boss => managers => employees. \ No newline at end of file diff --git a/contracts/2-BoD/BodDao.sol b/contracts/2-BoD/BodDao.sol deleted file mode 100644 index f52347f..0000000 --- a/contracts/2-BoD/BodDao.sol +++ /dev/null @@ -1,8 +0,0 @@ -pragma solidity ^0.4.24; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/DaoClient.sol"; - -contract BodDao is DaoClient { - constructor(IDaoBase _daoBase)public DaoClient(_daoBase){ - } -} diff --git a/contracts/2-BoD/BodDaoFactory.sol b/contracts/2-BoD/BodDaoFactory.sol deleted file mode 100644 index c813de6..0000000 --- a/contracts/2-BoD/BodDaoFactory.sol +++ /dev/null @@ -1,80 +0,0 @@ -pragma solidity ^0.4.24; - -import "@thetta/core/contracts/DaoBase.sol"; -import "@thetta/core/contracts/DaoBaseWithUnpackers.sol"; -import "@thetta/core/contracts/DaoBaseAuto.sol"; -import "@thetta/core/contracts/DaoStorage.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; -import "@thetta/core/contracts/utils/UtilsLib.sol"; -import "@thetta/core/contracts/tasks/WeiGenericTask.sol"; - -import "./BodDao.sol"; - -contract BodDaoFactory { - DaoBaseWithUnpackers public daoBase; - DaoBaseAuto public bodDaoAuto; - DaoStorage public store; - StdDaoToken public token; - BodDao public bodDao; - - address[] tokens; - - constructor(address _creator, address[] _directors, address[] _employees) public { - createDao(_creator, _directors, _employees); - setupBodDaoAuto(); - daoBase.renounceOwnership(); - } - - function createDao(address _creator, address[] _directors, address[] _employees) internal { - // 1 - create - token = new StdDaoToken("StdToken", "STDT", 18, true, true, 10**25); - tokens.push(address(token)); - store = new DaoStorage(tokens); - daoBase = new DaoBaseWithUnpackers(store); - bodDao = new BodDao(IDaoBase(daoBase)); - - store.allowActionByAddress(daoBase.MANAGE_GROUPS(), address(this)); - - token.transferOwnership(daoBase); - store.transferOwnership(daoBase); - // 2 - setup - - // 1 - creator is in BoD initially - daoBase.addGroupMember("BoD", _creator); - daoBase.addGroupMember("Employees", _creator); - - // 2 - set BoD group permissions - daoBase.allowActionByAnyMemberOfGroup(daoBase.ADD_NEW_PROPOSAL(), "BoD"); - - // 4 - the rest is by voting only (requires addNewProposal permission) - daoBase.allowActionByVoting(daoBase.MANAGE_GROUPS(), token); - daoBase.allowActionByVoting(daoBase.ISSUE_TOKENS(), token); - daoBase.allowActionByVoting(daoBase.UPGRADE_DAO_CONTRACT(), token); - - // 5 - populate groups - uint i = 0; - for(i = 0; i < _directors.length; ++i){ - daoBase.addGroupMember("BoD", _directors[i]); - } - - for(i = 0; i < _employees.length; ++i){ - daoBase.addGroupMember("Employees", _employees[i]); - } - } - - function setupBodDaoAuto() internal { - bodDaoAuto = new DaoBaseAuto(daoBase); - - uint VOTING_TYPE_1P1V = 1; - bodDaoAuto.setVotingParams(daoBase.MANAGE_GROUPS(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("BoD"), bytes32(49), bytes32(49), 0); - bodDaoAuto.setVotingParams(daoBase.ISSUE_TOKENS(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("BoD"), bytes32(49), bytes32(49), 0); - bodDaoAuto.setVotingParams(daoBase.UPGRADE_DAO_CONTRACT(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("BoD"), bytes32(49), bytes32(49), 0); - - daoBase.allowActionByAddress(daoBase.MANAGE_GROUPS(), bodDaoAuto); - daoBase.allowActionByAddress(daoBase.ISSUE_TOKENS(), bodDaoAuto); - daoBase.allowActionByAddress(daoBase.ADD_NEW_PROPOSAL(), bodDaoAuto); - - bodDaoAuto.transferOwnership(msg.sender); - } - -} diff --git a/contracts/2-BoD/README.md b/contracts/2-BoD/README.md deleted file mode 100644 index 025e898..0000000 --- a/contracts/2-BoD/README.md +++ /dev/null @@ -1,4 +0,0 @@ - # Board of Directors DAO - --- - ## Description -DAO of directors to manage the company. \ No newline at end of file diff --git a/contracts/3-DevZenDao/DevZenDao.sol b/contracts/3-DevZenDao/DevZenDao.sol deleted file mode 100644 index bd271f4..0000000 --- a/contracts/3-DevZenDao/DevZenDao.sol +++ /dev/null @@ -1,119 +0,0 @@ -pragma solidity ^0.4.24; - -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoBase.sol"; -import "@thetta/core/contracts/DaoBaseImpersonated.sol"; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; - -import "./DevZenDaoCore.sol"; - -contract DevZenDao is DevZenDaoCore { - - constructor( - IDaoBase _daoBase, - address[] _tokens - ) public DevZenDaoCore(_daoBase, _tokens) {} - - // --------------------------------------------- - // These methods should be called by DevZen team: - //---------------------------------------------- - - /** - * @dev Change the DAO parameters - * This method should require voting! - * Notice: DevZen_updateDaoParams is a custom action! - */ - function setParam(bytes32 _param, uint _value) public isCanDo(DEV_ZEN_UPDATE_DAO_PARAMS) { - super._setParam(_param, _value); - } - - /** - * @dev Withdraw all collected ETH to the _output. - * This method should require voting! - * Notice: DevZen_withdrawEther is a custom action! - */ - function withdrawEther(address _output) public isCanDo(DEV_ZEN_WITHDRAW_ETHER) { - super._withdrawEther(_output); - } - - /** - * @dev Select next episode's host - * This method should require voting! - * Notice: DevZen_selectNextHost is a custom action! - */ - function selectNextHost(address _nextHost) public isCanDo(DEV_ZEN_SELECT_NEXT_HOST) { - super._selectNextHost(_nextHost); - } - - /** - * @dev Changes the guest in "legal" way - * @param _guest New guest address - * When guest is changed via this function we ensure that stake is returned to previous guest. - */ - function changeTheGuest(address _guest) public isCanDo(DEV_ZEN_CHANGE_GUEST) { - super._changeTheGuest(_guest); - } - - /** - * @dev Set the guest (emergency) - * In normal circumst. people should use 'becomeTheNextShowGuest' method. - * However, sometimes DevZen team should be able to fix the next guest! - * Notice: DevZen_emergencyChangeGuest is a custom action! - */ - function emergency_ChangeTheGuest(address _guest) public isCanDo(DEV_ZEN_EMERGENCY_CHANGE_GUEST) { - super._emergency_ChangeTheGuest(_guest); - } - - /** - * @dev Move to next episode - * @param _guestHasCome Whether the guest(initual or emergency) has come to the show - * Should be called right AFTER the recording of the current episode - * Notice: DevZen_moveToNextEpisode is a custom action! - */ - function moveToNextEpisode(bool _guestHasCome) public isCanDo(DEV_ZEN_MOVE_TO_NEXT_EPISODE) { - super._moveToNextEpisode(_guestHasCome); - } - - // ------------------------------------------------------ - // These methods should be called by DevZen token holders - // ------------------------------------------------------ - // Any patron (DevZen token holder) can use DevZen tokens to run ads: Burn k tokens to add your add into the slot (linear, no priority). - function runAdsInTheNextEpisode(string _adText) public { - super._runAdsInTheNextEpisode(_adText); - } - - /** - * @dev Become the next guest. - * To become a guest sender should buy 5 DZT and approve dao to put them at stake. Sender will get back tokens after the show. - */ - function becomeTheNextShowGuest() public { - super._becomeTheNextShowGuest(); - } - - // ---------------------------------------------- - // These methods should be called by any address: - // ---------------------------------------------- - - /** - * @dev Any listener can get a ERC20 “devzen” tokens by sending X ETHers to the DevZen DAO and becomes a “patron” (i.e. token holder). - */ - function buyTokens() external payable { - super._buyTokens(); - } - - /** - * @dev Check that 1 week has passed since the last episode - * @return true if 1 week has passed else false - */ - function isOneWeekPassed() public view returns(bool) { - return super._isOneWeekPassed(); - } - - // do not allow to send ETH here. Instead use buyTokens method - function() { - revert(); - } -} diff --git a/contracts/3-DevZenDao/DevZenDaoAuto.sol b/contracts/3-DevZenDao/DevZenDaoAuto.sol deleted file mode 100644 index ac5bc72..0000000 --- a/contracts/3-DevZenDao/DevZenDaoAuto.sol +++ /dev/null @@ -1,71 +0,0 @@ -pragma solidity ^0.4.24; -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoBaseAuto.sol"; - -import "./DevZenDao.sol"; -import "./DevZenDaoCore.sol"; -import "./DevZenDaoWithUnpackers.sol"; - -contract DevZenDaoAuto is DaoBaseAuto{ - event UpdateDaoParamsAuto(); - event WithdrawEtherAuto(); - event SelectNextHostAuto(); - event ChangeTheGuestAuto(); - event Emergency_ChangeTheGuestAuto(); - event MoveToNextEpisodeAuto(); - - DevZenDaoWithUnpackers devZenDao; - constructor(IDaoBase _daoBase, DevZenDaoWithUnpackers _devZenDao) public DaoBaseAuto(_daoBase){ - devZenDao = _devZenDao; - } - - function updateDaoParamsAuto(bytes32 _param, uint _value) public returns(address proposalOut) { - emit UpdateDaoParamsAuto(); - bytes32[] memory params = new bytes32[](2); - params[0] = _param; - params[1] = bytes32(_value); - return doAction(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), devZenDao, msg.sender, "updateDaoParamsGeneric(bytes32[])", params); - } - - function withdrawEtherAuto(address _output) public returns(address proposalOut) { - emit WithdrawEtherAuto(); - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_output); - return doAction(devZenDao.DEV_ZEN_WITHDRAW_ETHER(), devZenDao, msg.sender, "withdrawEtherGeneric(bytes32[])", params); - } - - function selectNextHostAuto(address _nextHost) public returns(address proposalOut) { - emit SelectNextHostAuto(); - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_nextHost); - return doAction(devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), devZenDao, msg.sender, "selectNextHostGeneric(bytes32[])", params); - } - - function changeTheGuestAuto(address _guest) public returns(address proposalOut) { - emit ChangeTheGuestAuto(); - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_guest); - return doAction(devZenDao.DEV_ZEN_CHANGE_GUEST(), devZenDao, msg.sender, "changeTheGuestGeneric(bytes32[])", params); - } - - function emergency_ChangeTheGuestAuto(address _guest) public returns(address proposalOut) { - emit Emergency_ChangeTheGuestAuto(); - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_guest); - return doAction(devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), devZenDao, msg.sender, "emergency_ChangeTheGuestGeneric(bytes32[])", params); - } - - function moveToNextEpisodeAuto(bool _guestHasCome) public returns(address proposalOut) { - emit MoveToNextEpisodeAuto(); - bytes32[] memory params = new bytes32[](1); - if(_guestHasCome){ - params[0] = bytes32(1); - }else{ - params[0] = bytes32(0); - } - return doAction(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), devZenDao, msg.sender, "moveToNextEpisodeGeneric(bytes32[])", params); - } - -} diff --git a/contracts/3-DevZenDao/DevZenDaoAutoTestable.sol b/contracts/3-DevZenDao/DevZenDaoAutoTestable.sol deleted file mode 100644 index ae694c0..0000000 --- a/contracts/3-DevZenDao/DevZenDaoAutoTestable.sol +++ /dev/null @@ -1,58 +0,0 @@ -pragma solidity ^0.4.24; -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoBaseAuto.sol"; - -import "./DevZenDao.sol"; -import "./DevZenDaoCore.sol"; -import "./DevZenDaoWithUnpackersTestable.sol"; - -contract DevZenDaoAutoTestable is DaoBaseAuto{ - DevZenDaoWithUnpackersTestable devZenDao; - constructor(IDaoBase _daoBase, DevZenDaoWithUnpackersTestable _devZenDao) public DaoBaseAuto(_daoBase){ - devZenDao = _devZenDao; - } - - function updateDaoParamsAuto(bytes32 _param, uint _value) public returns(address proposalOut) { - bytes32[] memory params = new bytes32[](2); - params[0] = _param; - params[1] = bytes32(_value); - return doAction(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), daoBase, msg.sender, "updateDaoParamsGeneric(bytes32[])", params); - } - - function withdrawEtherAuto(address _output) public returns(address proposalOut) { - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_output); - return doAction(devZenDao.DEV_ZEN_WITHDRAW_ETHER(), devZenDao, msg.sender, "withdrawEtherGeneric(bytes32[])", params); - } - - function selectNextHostAuto(address _nextHost) public returns(address proposalOut) { - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_nextHost); - return doAction(devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), devZenDao, msg.sender, "selectNextHostGeneric(bytes32[])", params); - } - - function changeTheGuestAuto(address _guest) public returns(address proposalOut) { - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_guest); - return doAction(devZenDao.DEV_ZEN_CHANGE_GUEST(), devZenDao, msg.sender, "changeTheGuestGeneric(bytes32[])", params); - } - - function emergency_ChangeTheGuestAuto(address _guest) public returns(address proposalOut) { - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_guest); - return doAction(devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), devZenDao, msg.sender, "emergency_ChangeTheGuestGeneric(bytes32[])", params); - } - - function moveToNextEpisodeAuto(bool _guestHasCome) public returns(address proposalOut) { - bytes32[] memory params = new bytes32[](1); - if(_guestHasCome){ - params[0] = bytes32(1); - }else{ - params[0] = bytes32(0); - } - return doAction(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), devZenDao, msg.sender, "moveToNextEpisodeGeneric(bytes32[])", params); - } - -} diff --git a/contracts/3-DevZenDao/DevZenDaoCore.sol b/contracts/3-DevZenDao/DevZenDaoCore.sol deleted file mode 100644 index af53c67..0000000 --- a/contracts/3-DevZenDao/DevZenDaoCore.sol +++ /dev/null @@ -1,303 +0,0 @@ -pragma solidity ^0.4.24; - -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoBase.sol"; -import "@thetta/core/contracts/DaoClient.sol"; -import "@thetta/core/contracts/DaoStorage.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; -import "zeppelin-solidity/contracts/ownership/Ownable.sol"; -/** - * @title DevZenDaoCore - * @dev This is the DAO for russian most famous "for hard-core developers only" podcast. - * I was a guest of show #198 (July 30, 2018) - * We discussed how Thetta can be applied to their structure. You can read the blog post here - TODO. - * - * ## Requirements: - * - * 1) Any listener can get a ERC20 “devzen” tokens by sending X ETHers to the DevZen DAO and becomes a “patron” (i.e. token holder). - * 2) Any patron can use DevZen tokens to run ads: Burn k tokens to add your add into the slot (linear, no priority). - * 3) Any team member can use Reputation to govern the DAO, i.e., change the parameters. Also, reputation is used in the votes to select the next host and to add or remove moderator. - * 4) To become a guest, a listener has to become a patron first (i.e., they have to buy some DevZen tokens), then they must stake S tokens for D days. After the show has ended, S tokens are returned to the patron. If the guest missed the show (that is bad), the tokens are burned. - * - * ## Token model (example): - * - * DevZen tokens are minted each week: - * 10 tokens for 5 ads slots - * 0 free floating tokens - * DevZen tokens are burned: - * 2 tokens per 1 ads slot (if ads is running in the current episode) - * Reputation tokens are minted each week: - * 2 tokens as reputation incentive for 1 host - * 2 tokens as reputation incentive for 4 moderators - * 1 tokens as incentive for 1 guest -*/ -contract DevZenDaoCore is DaoClient { - StdDaoToken public devZenToken; - StdDaoToken public repToken; - NextEpisode public nextEpisode; - - bytes32 public constant DEV_ZEN_UPDATE_DAO_PARAMS = keccak256("DevZen_updateDaoParams"); - bytes32 public constant DEV_ZEN_WITHDRAW_ETHER = keccak256("DevZen_withdrawEther"); - bytes32 public constant DEV_ZEN_SELECT_NEXT_HOST = keccak256("DevZen_selectNextHost"); - bytes32 public constant DEV_ZEN_CHANGE_GUEST = keccak256("DevZen_changeGuest"); - bytes32 public constant DEV_ZEN_EMERGENCY_CHANGE_GUEST = keccak256("DevZen_emergencyChangeGuest"); - bytes32 public constant DEV_ZEN_MOVE_TO_NEXT_EPISODE = keccak256("DevZen_moveToNextEpisode"); - - bytes32 public constant MINT_TOKENS_PER_WEEK_AMOUNT = 0xc9b51a76ddd807905ae4f432305a7941a6eeed3018a217456051bf48a64b23cc; - bytes32 public constant MINT_REPUTATION_TOKENS_PER_WEEK_AMOUNT = 0xf62293a5f827624aae2cb3ccf2a626acfb00192eb976ac25c0b6fcfe9099f109; - bytes32 public constant ONE_AD_SLOT_PRICE = 0x2fc30c0260b9c2120dbb43e5716b23b323cb0059511c89856fe497bcaf93cbe0; - bytes32 public constant ONE_TOKEN_PRICE_IN_WEI = 0x1d18f55c54a96a26d4aaa596d526372f95c7cef9f217e9bbe766cea168596907; - bytes32 public constant BECOME_GUEST_STAKE = 0x3016d4c48f6a1e0a92b321085915d5914a9ab2c36783443e2b6066054b37f7c4; - bytes32 public constant REP_TOKENS_REWARD_HOST = 0x91a208a4a1aa03cdcf19de90fdf6add60ea8d103a63e151f2b189cc77dfc8cf7; - bytes32 public constant REP_TOKENS_REWARD_GUEST = 0x9cf4c579edc10b766d99450c69f14a06144239d4923ded8d12e0a7d6ec69a048; - bytes32 public constant REP_TOKENS_REWARD_TEAM_MEMBERS = 0x8097db39df04019c8a72c19c6369ebda43741c8e2a45d27badc3e4ff8ecc3d0b; - - event DevZenDaoCore_WithdrawEther(address _output); - event DevZenDaoCore_SelectNextHost(address _nextHost); - event DevZenDaoCore_ChangeTheGuest(address _guest); - event DevZenDaoCore_BurnGuestStake(); - event DevZenDaoCore_Emergency_ChangeTheGuest(address _guest); - event DevZenDaoCore_MoveToNextEpisode(bool _guestHasCome); - event DevZenDaoCore_RunAdsInTheNextEpisode(string _adText); - event DevZenDaoCore_BecomeTheNextShowGuest(); - event DevZenDaoCore_BuyTokens(); - event DevZenDaoCore_IsOneWeekPassed(); - event DevZenDaoCore_SetGuest(address _guest); - - event SetParam(bytes32 _param, uint _value); - - mapping (bytes32 => uint) public params; - - // this is changed each week in 'moveToNextEpisode' method - struct NextEpisode { - address nextShowHost; - address nextShowGuest; - address prevShowHost; - address prevShowGuest; - string[] adSlots; - uint usedSlots; - uint createdAt; - bool isEmergencyGuest; - } - - constructor(IDaoBase _daoBase, address[] _tokens) public DaoClient(_daoBase){ - devZenToken = StdDaoToken(_tokens[0]); - repToken = StdDaoToken(_tokens[1]); - } - // --------------------------------------------- - // These methods should be called by DevZen team: - //---------------------------------------------- - /** - * @dev Change the DAO parameters - */ - function _setParam(bytes32 _param, uint _value) internal { - emit SetParam(_param, _value); - params[_param] = _value; - } - - /** - * @dev Withdraw all collected ETH to the _output. - */ - function _withdrawEther(address _output) internal { - emit DevZenDaoCore_WithdrawEther(_output); - // TODO: better to use moneyflow instead of _output - // Specifying _output can lead to hacks and money loss! - _output.transfer(address(this).balance); - } - - /** - * @dev Select next episode's host - */ - function _selectNextHost(address _nextHost) internal { - emit DevZenDaoCore_SelectNextHost(_nextHost); - // 1 - check if host is still not selected - require(0x0==nextEpisode.nextShowHost); - // 2 - select next host - nextEpisode.nextShowHost = _nextHost; - } - - /** - * @dev Guest did not appear -> penalize him) - */ - function _burnGuestStake() internal { - emit DevZenDaoCore_BurnGuestStake(); - daoBase.burnTokens(devZenToken, address(this), params[BECOME_GUEST_STAKE]); - } - - /** - * @dev Changes the guest in "legal" way - * @param _guest New guest address - * When guest is changed via this function we ensure that stake is returned to previous guest. - */ - function _changeTheGuest(address _guest) internal { - emit DevZenDaoCore_ChangeTheGuest(_guest); - // 0 - check that next show guest exists - require(0x0 != nextEpisode.nextShowGuest); - // 1 - save previous guest address for future use - address prevGuest = nextEpisode.nextShowGuest; - // 2 - set the new guest - _setGuest(_guest); - // 3 - if previous guest is not emergency guest then return stake - if(!nextEpisode.isEmergencyGuest) { - devZenToken.transfer(prevGuest, params[BECOME_GUEST_STAKE]); - } - // 4 - mark guest as legal - nextEpisode.isEmergencyGuest = false; - } - - /** - * @dev Set the guest (emergency) - * In normal circumst. people should use 'becomeTheNextShowGuest' method. - * However, sometimes DevZen team should be able to fix the next guest! - */ - function _emergency_ChangeTheGuest(address _guest) internal { - // emit DevZenDaoCore_Emergency_ChangeTheGuest(_guest); - // 1 - check that next show guest exists - require(nextEpisode.nextShowGuest != 0x0); - // 2 - set next show guest - nextEpisode.nextShowGuest = _guest; - // 3 - mark guest as emergency guest - nextEpisode.isEmergencyGuest = true; - } - - /** - * @dev Move to next episode - * @param _guestHasCome Whether the guest(initual or emergency) has come to the show - * Should be called right AFTER the recording of the current episode - */ - function _moveToNextEpisode(bool _guestHasCome) internal { - emit DevZenDaoCore_MoveToNextEpisode(_guestHasCome); - // 1 - check if 1 week is passed - require(_isOneWeekPassed()); - // 2 - mint tokens - // We are minting X tokens to this address (to the DevZen DAO contract itself) - // Current contract is the owner of the devZenToken contract, so it can do anything with it (mint/burn tokens) - daoBase.issueTokens(address(devZenToken), address(this), params[MINT_TOKENS_PER_WEEK_AMOUNT]); - daoBase.issueTokens(address(repToken), address(this), params[MINT_REPUTATION_TOKENS_PER_WEEK_AMOUNT]); - // 3 - clear next host and next guest - nextEpisode.prevShowHost = nextEpisode.nextShowHost; - nextEpisode.prevShowGuest = nextEpisode.nextShowGuest; - nextEpisode.nextShowHost = 0x0; - nextEpisode.nextShowGuest = 0x0; - nextEpisode.usedSlots = 0; - nextEpisode.createdAt = now; - // 4 - mint DZTREP tokens to the Guest - if(_guestHasCome) { - daoBase.issueTokens(address(repToken), nextEpisode.prevShowGuest, params[REP_TOKENS_REWARD_GUEST]); - } - // 5 - mint some reputation tokens to the Host - daoBase.issueTokens(address(repToken), nextEpisode.prevShowHost, params[REP_TOKENS_REWARD_HOST]); - // TODO: - // 6 - mint some reputation tokens to the rest of the DevZen team! - uint teamMembers = daoBase.getMembersCount("DevZenTeam"); - assert(teamMembers>=1); - uint perMember = params[REP_TOKENS_REWARD_TEAM_MEMBERS] / (teamMembers - 1); - address member; - for(uint i=0; i= tokensToPurchase); - // 3 - if ok -> transfer tokens to the msg.sender - devZenToken.transfer(msg.sender, tokensToPurchase); - } - - /** - * @dev Check that 1 week has passed since the last episode - * @return true if 1 week has passed else false - */ - function _isOneWeekPassed() internal view returns(bool) { - emit DevZenDaoCore_IsOneWeekPassed(); - // return true if this is the 1st episode - if(nextEpisode.createdAt == 0) return true; - return nextEpisode.createdAt + 7 days <= now; - } - - // do not allow to send ETH here. Instead use buyTokens method - function(){ - revert(); - } - - //----------------------------------------------- - // These are helper methods for usage in contract - //----------------------------------------------- - /** - * @dev Sets the guest for next show in "legal" way - * @param _guest New guest address - * New guest should have enough DZT and should allow current contract to transfer his stake - */ - function _setGuest(address _guest) internal { - // emit DevZenDaoCore_SetGuest(_guest); - // 0 - check that guest has required amount of tokens - require(devZenToken.balanceOf(_guest) >= params[BECOME_GUEST_STAKE]); - // 1 - check that guest has allowed current contract to put 5 DZT at stake - require(devZenToken.allowance(_guest, address(this)) >= params[BECOME_GUEST_STAKE]); - // 3 - lock tokens, transfer tokens from guest to current contract - devZenToken.transferFrom(_guest, address(this), params[BECOME_GUEST_STAKE]); - // 4 - select next guest - - nextEpisode.nextShowGuest = _guest; - } -} diff --git a/contracts/3-DevZenDao/DevZenDaoFactory.sol b/contracts/3-DevZenDao/DevZenDaoFactory.sol deleted file mode 100644 index 9b77a50..0000000 --- a/contracts/3-DevZenDao/DevZenDaoFactory.sol +++ /dev/null @@ -1,129 +0,0 @@ -pragma solidity ^0.4.22; - -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoBaseWithUnpackers.sol"; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/DaoStorage.sol"; -import "@thetta/core/contracts/DaoBaseAuto.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; -import "@thetta/core/contracts/utils/UtilsLib.sol"; - -import "./DevZenDao.sol"; -import "./DevZenDaoAuto.sol"; -import "./DevZenDaoCore.sol"; - -import "./DevZenDaoWithUnpackers.sol"; - -// DevZen tokens: -// 10 tokens for 5 ads slots -// 0 free floating tokens -// Reputation tokens: -// 2 tokens as reputation incentive for 1 host -// 2 tokens as reputation incentive for 4 moderators -// 1 tokens as incentive for 1 guest - -contract DevZenDaoFactory { - DevZenDaoWithUnpackers public devZenDao; - DaoBaseWithUnpackers public daoBase; - DaoStorage store; - DevZenDaoAuto public devZenDaoAuto; - - constructor(address _boss, address[] _devZenTeam) public{ - createDao(_boss, _devZenTeam); - setupDevZenDaoAuto(); - daoBase.renounceOwnership(); - } - - function createDao(address _boss, address[] _devZenTeam) internal returns(address) { - StdDaoToken devZenToken = new StdDaoToken("DevZenToken", "DZT", 18, true, true, 10**25); - StdDaoToken repToken = new StdDaoToken("DevZenRepToken", "DZTREP", 18, true, true, 10**25); - - address[] tokens; - tokens.push(address(devZenToken)); - tokens.push(address(repToken)); - store = new DaoStorage(tokens); - daoBase = new DaoBaseWithUnpackers(store); - - createNewContract(IDaoBase(daoBase), tokens); - - store.allowActionByAddress(daoBase.MANAGE_GROUPS(),address(this)); - store.allowActionByAddress(daoBase.ISSUE_TOKENS(),address(devZenDao)); - store.allowActionByAddress(daoBase.BURN_TOKENS(),address(devZenDao)); - store.allowActionByAddress(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), _boss); - store.allowActionByAddress(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), address(this)); - store.transferOwnership(daoBase); - - devZenDao.setParam(devZenDao.MINT_TOKENS_PER_WEEK_AMOUNT(), 10 * 10**18); - devZenDao.setParam(devZenDao.MINT_REPUTATION_TOKENS_PER_WEEK_AMOUNT(), 5 * 10**18); - devZenDao.setParam(devZenDao.ONE_AD_SLOT_PRICE(), 2 * 10**18); // Current ETH price is ~$450. One token will be worth ~$45. One ad will cost ~$90 (2 tokens) - devZenDao.setParam(devZenDao.ONE_TOKEN_PRICE_IN_WEI(), 10**17); //) To become a guest user should put 5 tokens at stake - devZenDao.setParam(devZenDao.BECOME_GUEST_STAKE(), 5 * 10**18); - devZenDao.setParam(devZenDao.REP_TOKENS_REWARD_HOST(), 2 * 10**18); - devZenDao.setParam(devZenDao.REP_TOKENS_REWARD_GUEST(), 1 * 10**18); - devZenDao.setParam(devZenDao.REP_TOKENS_REWARD_TEAM_MEMBERS(), 2 * 10**18); - - devZenToken.transferOwnership(daoBase); - repToken.transferOwnership(daoBase); - - // 2 - setup - daoBase.addGroupMember("DevZenTeam", _boss); - - uint i = 0; - for(i=0; i<_devZenTeam.length; ++i){ - daoBase.addGroupMember("DevZenTeam", _devZenTeam[i]); - } - - // 1 - set DevZenTeam group permissions - daoBase.allowActionByAnyMemberOfGroup(daoBase.ADD_NEW_PROPOSAL(),"DevZenTeam"); - daoBase.allowActionByVoting(daoBase.MANAGE_GROUPS(), devZenDao.repToken()); - daoBase.allowActionByVoting(daoBase.UPGRADE_DAO_CONTRACT(), devZenDao.repToken()); - - // 2 - set custom DevZenTeam permissions - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_WITHDRAW_ETHER(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_CHANGE_GUEST(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), devZenDao.repToken()); - - // 3 - return - // - return devZenDao; - } - - function createNewContract(IDaoBase _daoBase, address[] _tokens) internal { - devZenDao = new DevZenDaoWithUnpackers(_daoBase, _tokens); - } - - function setupDevZenDaoAuto() internal { - // TODO: add all custom actions to the DaoBaseAuto derived contract - - devZenDaoAuto = new DevZenDaoAuto(IDaoBase(daoBase), devZenDao); - - daoBase.allowActionByAddress(daoBase.ADD_NEW_PROPOSAL(), devZenDaoAuto); - daoBase.allowActionByAddress(daoBase.MANAGE_GROUPS(), devZenDaoAuto); - daoBase.allowActionByAddress(daoBase.UPGRADE_DAO_CONTRACT(), devZenDaoAuto); - - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_WITHDRAW_ETHER(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_CHANGE_GUEST(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), devZenDaoAuto); - - uint VOTING_TYPE_1P1V = 1; - devZenDaoAuto.setVotingParams(daoBase.MANAGE_GROUPS(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(daoBase.REMOVE_GROUP_MEMBER(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(daoBase.UPGRADE_DAO_CONTRACT(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_WITHDRAW_ETHER(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_CHANGE_GUEST(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - - devZenDaoAuto.transferOwnership(daoBase); - } -} diff --git a/contracts/3-DevZenDao/DevZenDaoFactoryTestable.sol b/contracts/3-DevZenDao/DevZenDaoFactoryTestable.sol deleted file mode 100644 index 0ecbbe9..0000000 --- a/contracts/3-DevZenDao/DevZenDaoFactoryTestable.sol +++ /dev/null @@ -1,117 +0,0 @@ -pragma solidity ^0.4.24; - -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/DaoBaseWithUnpackers.sol"; - -import "./DevZenDaoFactory.sol"; -import "./DevZenDaoTestable.sol"; -import "./DevZenDaoAutoTestable.sol"; -import "./DevZenDaoWithUnpackersTestable.sol"; -import "./DevZenDaoCore.sol"; - -contract DevZenDaoFactoryTestable { - DevZenDaoWithUnpackersTestable public devZenDao; - DaoBaseWithUnpackers public daoBase; - DaoStorage store; - DevZenDaoAutoTestable public devZenDaoAuto; - - constructor(address _boss, address[] _devZenTeam) public{ - createDao(_boss, _devZenTeam); - setupDevZenDaoAuto(); - daoBase.renounceOwnership(); - } - - function createDao(address _boss, address[] _devZenTeam) internal returns(address) { - StdDaoToken devZenToken = new StdDaoToken("DevZenToken", "DZT", 18, true, true, 10**25); - StdDaoToken repToken = new StdDaoToken("DevZenRepToken", "DZTREP", 18, true, true, 10**25); - - address[] tokens; - tokens.push(address(devZenToken)); - tokens.push(address(repToken)); - store = new DaoStorage(tokens); - daoBase = new DaoBaseWithUnpackers(store); - - createNewContract(IDaoBase(daoBase), tokens); - - store.allowActionByAddress(daoBase.MANAGE_GROUPS(),address(this)); - store.allowActionByAddress(daoBase.ISSUE_TOKENS(),address(devZenDao)); - store.allowActionByAddress(daoBase.BURN_TOKENS(),address(devZenDao)); - store.allowActionByAddress(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), _boss); - store.transferOwnership(daoBase); - - devZenDao.setParam(devZenDao.MINT_TOKENS_PER_WEEK_AMOUNT(), 10 * 10**18); - devZenDao.setParam(devZenDao.MINT_REPUTATION_TOKENS_PER_WEEK_AMOUNT(), 5 * 10**18); - devZenDao.setParam(devZenDao.ONE_AD_SLOT_PRICE(), 2 * 10**18); // Current ETH price is ~$450. One token will be worth ~$45. One ad will cost ~$90 (2 tokens) - devZenDao.setParam(devZenDao.ONE_TOKEN_PRICE_IN_WEI(), 10**17); //) To become a guest user should put 5 tokens at stake - devZenDao.setParam(devZenDao.BECOME_GUEST_STAKE(), 5 * 10**18); - devZenDao.setParam(devZenDao.REP_TOKENS_REWARD_HOST(), 2 * 10**18); - devZenDao.setParam(devZenDao.REP_TOKENS_REWARD_GUEST(), 1 * 10**18); - devZenDao.setParam(devZenDao.REP_TOKENS_REWARD_TEAM_MEMBERS(), 2 * 10**18); - - devZenToken.transferOwnership(daoBase); - repToken.transferOwnership(daoBase); - - // 2 - setup - daoBase.addGroupMember("DevZenTeam", _boss); - - uint i = 0; - for(i=0; i<_devZenTeam.length; ++i){ - daoBase.addGroupMember("DevZenTeam", _devZenTeam[i]); - } - - // 1 - set DevZenTeam group permissions - daoBase.allowActionByAnyMemberOfGroup(daoBase.ADD_NEW_PROPOSAL(),"DevZenTeam"); - daoBase.allowActionByVoting(daoBase.MANAGE_GROUPS(), devZenDao.repToken()); - daoBase.allowActionByVoting(daoBase.UPGRADE_DAO_CONTRACT(), devZenDao.repToken()); - - // 2 - set custom DevZenTeam permissions - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_WITHDRAW_ETHER(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_CHANGE_GUEST(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), devZenDao.repToken()); - daoBase.allowActionByVoting(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), devZenDao.repToken()); - - // 3 - return - // - return devZenDao; - } - - function createNewContract(IDaoBase _daoBase, address[] _tokens) internal { - devZenDao = new DevZenDaoWithUnpackersTestable(_daoBase, _tokens); - } - - function setupDevZenDaoAuto() internal { - // TODO: add all custom actions to the DaoBaseAuto derived contract - - devZenDaoAuto = new DevZenDaoAutoTestable(IDaoBase(daoBase), devZenDao); - - daoBase.allowActionByAddress(daoBase.ADD_NEW_PROPOSAL(), devZenDaoAuto); - daoBase.allowActionByAddress(daoBase.MANAGE_GROUPS(), devZenDaoAuto); - daoBase.allowActionByAddress(daoBase.UPGRADE_DAO_CONTRACT(), devZenDaoAuto); - - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_WITHDRAW_ETHER(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_CHANGE_GUEST(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), devZenDaoAuto); - daoBase.allowActionByAddress(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), devZenDaoAuto); - - uint VOTING_TYPE_1P1V = 1; - devZenDaoAuto.setVotingParams(daoBase.MANAGE_GROUPS(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(daoBase.REMOVE_GROUP_MEMBER(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(daoBase.UPGRADE_DAO_CONTRACT(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_WITHDRAW_ETHER(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_CHANGE_GUEST(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - devZenDaoAuto.setVotingParams(devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("DevZenTeam"), bytes32(65), bytes32(65), 0); - - devZenDaoAuto.transferOwnership(daoBase); - } - -} \ No newline at end of file diff --git a/contracts/3-DevZenDao/DevZenDaoTestable.sol b/contracts/3-DevZenDao/DevZenDaoTestable.sol deleted file mode 100644 index c235b75..0000000 --- a/contracts/3-DevZenDao/DevZenDaoTestable.sol +++ /dev/null @@ -1,133 +0,0 @@ - -pragma solidity ^0.4.24; - -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoBase.sol"; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; - -import "./DevZenDaoCore.sol"; - -/** - * @title DevZenDaoTestable - * @dev DevZenDaoTestable is for testing DevZenDaoCore. - * The difference between DevZenDao and DevZenDaoTestable is that there are no Thetta's isCanDo() modifiers in DevZenDaoTestable - * which helps in testing only the logic. - */ -contract DevZenDaoTestable is DevZenDaoCore { - - constructor(IDaoBase _daoBase, address[] _tokens) public - DevZenDaoCore(_daoBase, _tokens) {} - - // --------------------------------------------- - // These methods should be called by DevZen team: - //---------------------------------------------- - - /** - * @dev Change the DAO parameters - */ - function setParam(bytes32 _param, uint _value) public { - super._setParam(_param, _value); - } - - /** - * @dev Withdraw all collected ETH to the _output. - */ - function withdrawEther(address _output) public { - super._withdrawEther(_output); - } - - /** - * @dev Select next episode's host - */ - function selectNextHost(address _nextHost) public { - super._selectNextHost(_nextHost); - } - - /** - * @dev Guest did not appear -> penalize him) - */ - function burnGuestStake() public { - super._burnGuestStake(); - } - - /** - * @dev Changes the guest in "legal" way - */ - function changeTheGuest(address _guest) public { - super._changeTheGuest(_guest); - } - - /** - * @dev Set the guest (emergency) - * In normal circumst. people should use 'becomeTheNextShowGuest' method. - * However, sometimes DevZen team should be able to fix the next guest! - */ - function emergency_ChangeTheGuest(address _guest) public { - super._emergency_ChangeTheGuest(_guest); - } - - /** - * @dev Move to next episode - * @param _guestHasCome Whether the guest(initual or emergency) has come to the show - * Should be called right AFTER the recording of the current episode - */ - function moveToNextEpisode(bool _guestHasCome) public { - super._moveToNextEpisode(_guestHasCome); - } - - // ------------------------------------------------------ - // These methods should be called by DevZen token holders - // ------------------------------------------------------ - - // Any patron (DevZen token holder) can use DevZen tokens to run ads: Burn k tokens to add your add into the slot (linear, no priority). - function runAdsInTheNextEpisode(string _adText) public { - super._runAdsInTheNextEpisode(_adText); - } - - /** - * @dev Become the next guest. - * To become a guest sender should buy 5 DZT and approve dao to put them at stake. Sender will get back tokens after the show. - */ - function becomeTheNextShowGuest() public { - super._becomeTheNextShowGuest(); - } - - // ---------------------------------------------- - // These methods should be called by any address: - // ---------------------------------------------- - - /** - * @dev Any listener can get a ERC20 “devzen” tokens by sending X ETHers to the DevZen DAO and becomes a “patron” (i.e. token holder). - */ - function buyTokens() external payable { - super._buyTokens(); - } - - /** - * @dev Check that 1 week has passed since the last episode - * @return true if 1 week has passed else false - */ - function isOneWeekPassed() public view returns(bool) { - return super._isOneWeekPassed(); - } - - // do not allow to send ETH here. Instead use buyTokens method - function() { - revert(); - } - - //----------------------------------------------- - // These are helper methods for usage in contract - //----------------------------------------------- - - /** - * @dev Sets the guest for next show in "legal" way - */ - function setGuest(address _guest) public { - super._setGuest(_guest); - } - -} \ No newline at end of file diff --git a/contracts/3-DevZenDao/DevZenDaoWithUnpackers.sol b/contracts/3-DevZenDao/DevZenDaoWithUnpackers.sol deleted file mode 100644 index 03d8f7c..0000000 --- a/contracts/3-DevZenDao/DevZenDaoWithUnpackers.sol +++ /dev/null @@ -1,43 +0,0 @@ -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/utils/UtilsLib.sol"; -import "@thetta/core/contracts/DaoBase.sol"; -import "./DevZenDao.sol"; - -contract DevZenDaoWithUnpackers is DevZenDao { - constructor(IDaoBase _daoBase, address[] _tokens) public - DevZenDao(_daoBase, _tokens){} - - function updateDaoParamsGeneric(bytes32[] _params) external { - bytes32 param = _params[0]; - uint value = uint(_params[1]); - setParam(param, value); - } - - function withdrawEtherGeneric(bytes32[] _params) external { - address output = address(_params[0]); - withdrawEther(output); - } - - function selectNextHostGeneric(bytes32[] _params) external { - address nextHost = address(_params[0]); - selectNextHost(nextHost); - } - - function changeTheGuestGeneric(bytes32[] _params) external { - address guest = address(_params[0]); - changeTheGuest(guest); - } - - function emergency_ChangeTheGuestGeneric(bytes32[] _params) external { - address guest = address(_params[0]); - emergency_ChangeTheGuest(guest); - } - - function moveToNextEpisodeGeneric(bytes32[] _params) external { - uint guestHasCome = uint(_params[0]); // TypeError: Explicit type conversion not allowed from "bytes32" to "bool" - moveToNextEpisode(guestHasCome==1); - } - -} diff --git a/contracts/3-DevZenDao/DevZenDaoWithUnpackersTestable.sol b/contracts/3-DevZenDao/DevZenDaoWithUnpackersTestable.sol deleted file mode 100644 index 7334917..0000000 --- a/contracts/3-DevZenDao/DevZenDaoWithUnpackersTestable.sol +++ /dev/null @@ -1,44 +0,0 @@ -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/utils/UtilsLib.sol"; -import "@thetta/core/contracts/DaoBase.sol"; -import "./DevZenDao.sol"; -import "./DevZenDaoTestable.sol"; - -contract DevZenDaoWithUnpackersTestable is DevZenDaoTestable { - constructor(IDaoBase _daoBase, address[] _tokens) public - DevZenDaoTestable(_daoBase, _tokens){} - - function updateDaoParamsGeneric(bytes32[] _params) external { - bytes32 param = _params[0]; - uint value = uint(_params[1]); - setParam(param, value); - } - - function withdrawEtherGeneric(bytes32[] _params) external { - address output = address(_params[0]); - withdrawEther(output); - } - - function selectNextHostGeneric(bytes32[] _params) external { - address nextHost = address(_params[0]); - selectNextHost(nextHost); - } - - function changeTheGuestGeneric(bytes32[] _params) external { - address guest = address(_params[0]); - changeTheGuest(guest); - } - - function emergency_ChangeTheGuestGeneric(bytes32[] _params) external { - address guest = address(_params[0]); - emergency_ChangeTheGuest(guest); - } - - function moveToNextEpisodeGeneric(bytes32[] _params) external { - uint guestHasCome = uint(_params[0]); // TypeError: Explicit type conversion not allowed from "bytes32" to "bool" - moveToNextEpisode(guestHasCome==1); - } - -} diff --git a/contracts/3-DevZenDao/README.md b/contracts/3-DevZenDao/README.md deleted file mode 100644 index 15dc51f..0000000 --- a/contracts/3-DevZenDao/README.md +++ /dev/null @@ -1,23 +0,0 @@ - # DevZenDao - --- - ## Description - This is the DAO for russian most famous "for hard-core developers only" podcast. - I was a guest of show #198 (July 30, 2018) - We discussed how Thetta can be applied to their structure. You can read the blog post here - TODO. -## Requirements -1) Any listener can get a ERC20 “devzen” tokens by sending X ETHers to the DevZen DAO and becomes a “patron” (i.e. token holder). -2) Any patron can use DevZen tokens to run ads: Burn k tokens to add your add into the slot (linear, no priority). -3) Any team member can use Reputation to govern the DAO, i.e., change the parameters. Also, reputation is used in the votes to select the next host and to add or remove moderator. -4) To become a guest, a listener has to become a patron first (i.e., they have to buy some DevZen tokens), then they must stake S tokens for D days. After the show has ended, S tokens are returned to the patron. If the guest missed the show (that is bad), the tokens are burned. -## Token model (example) -DevZen tokens are minted each week: - - 10 tokens for 5 ads slots - - 0 free floating tokens - -DevZen tokens are burned: - - 2 tokens per 1 ads slot (if ads is running in the current episode) - -Reputation tokens are minted each week: -- 2 tokens as reputation incentive for 1 host -- 2 tokens as reputation incentive for 4 moderators -- 1 tokens as incentive for 1 guest \ No newline at end of file diff --git a/contracts/Daico/Daico.sol b/contracts/Daico/Daico.sol index d2a73a5..3fa7188 100644 --- a/contracts/Daico/Daico.sol +++ b/contracts/Daico/Daico.sol @@ -15,11 +15,11 @@ contract Daico is Ownable { ERC20 public projectToken; address public projectOwner; + address public returnAddress; uint public minQuorumRate; uint public minVoteRate; uint public tapsCount; - uint public tokenHoldersCount; uint[] public tapAmounts; uint[] public tapTimestampsFinishAt; @@ -42,13 +42,14 @@ contract Daico is Ownable { struct Voting { uint tapIndex; - uint yesVotesCount; - uint noVotesCount; + uint tokenAmountToAccept; + uint tokenAmountToReject; uint quorumRate; uint createdAt; uint finishAt; VotingType votingType; - mapping(address => bool) voted; + mapping(address => uint) votedByAmount; + address[] voters; } /** @@ -89,18 +90,17 @@ contract Daico is Ownable { * @param _tapTimestampsFinishAt array of deadline timestamps, project should get payment before each deadline timestamp * @param _minQuorumRate min quorum rate, 100 == 100% * @param _minVoteRate min vote rate for proposal to be accepted/declined, 100 == 100% - * @param _tokenHoldersCount amount of token holders */ constructor( address _daiTokenAddress, address _projectTokenAddress, address _projectOwnerAddress, + address _returnAddress, uint _tapsCount, uint[] _tapAmounts, uint[] _tapTimestampsFinishAt, uint _minQuorumRate, - uint _minVoteRate, - uint _tokenHoldersCount + uint _minVoteRate ) public { // validation require(_daiTokenAddress != address(0)); @@ -111,17 +111,16 @@ contract Daico is Ownable { require(_tapTimestampsFinishAt.length == _tapsCount); require(_minQuorumRate > 0); require(_minVoteRate > 0); - require(_tokenHoldersCount > 0); // setting contract properties daiToken = ERC20(_daiTokenAddress); projectToken = ERC20(_projectTokenAddress); projectOwner = _projectOwnerAddress; + returnAddress = _returnAddress; tapsCount = _tapsCount; tapAmounts = _tapAmounts; tapTimestampsFinishAt = _tapTimestampsFinishAt; minQuorumRate = _minQuorumRate; minVoteRate = _minVoteRate; - tokenHoldersCount = _tokenHoldersCount; // create initial ReleaseTap votings for all taps for(uint i = 0; i < tapsCount; i++) { uint createdAt = tapTimestampsFinishAt[i] - 7 days; @@ -140,17 +139,18 @@ contract Daico is Ownable { */ function getVotingResult(uint _votingIndex) public view validVotingIndex(_votingIndex) returns(VotingResult) { Voting memory voting = votings[_votingIndex]; - uint totalVotesCount = voting.yesVotesCount.add(voting.noVotesCount); + uint tokenAmountOfAllVoters = voting.tokenAmountToAccept.add(voting.tokenAmountToReject); + uint totalSupply = projectToken.totalSupply(); // check whether quorum is reached - if(totalVotesCount.mul(100) <= tokenHoldersCount.mul(voting.quorumRate)) { + if(tokenAmountOfAllVoters.mul(100) <= totalSupply.mul(voting.quorumRate)) { return VotingResult.QuorumNotReached; } // check whether voting result is strongly accepted - if(voting.yesVotesCount.mul(100) >= totalVotesCount.mul(minVoteRate)) { + if(voting.tokenAmountToAccept.mul(100) >= tokenAmountOfAllVoters.mul(minVoteRate)) { return VotingResult.Accept; } // check whether voting result is strongly declined - if(voting.noVotesCount.mul(100) >= totalVotesCount.mul(minVoteRate)) { + if(voting.tokenAmountToReject.mul(100) >= tokenAmountOfAllVoters.mul(minVoteRate)) { return VotingResult.Decline; } // by default return no decision result @@ -165,7 +165,7 @@ contract Daico is Ownable { */ function isInvestorVoted(uint _votingIndex, address _investorAddress) external view validVotingIndex(_votingIndex) returns(bool) { require(_investorAddress != address(0)); - return votings[_votingIndex].voted[_investorAddress]; + return (votings[_votingIndex].votedByAmount[_investorAddress] > 0); } /** @@ -275,15 +275,17 @@ contract Daico is Ownable { function vote(uint _votingIndex, bool _isYes) external onlyInvestor validVotingIndex(_votingIndex) { // validation require(now >= votings[_votingIndex].createdAt); - require(now < votings[_votingIndex].finishAt); - require(!votings[_votingIndex].voted[msg.sender]); + // require(now < votings[_votingIndex].finishAt); + require(votings[_votingIndex].votedByAmount[msg.sender]==0); require(!isProjectTerminated()); // vote - votings[_votingIndex].voted[msg.sender] = true; + uint256 senderBalance = projectToken.balanceOf(msg.sender); + votings[_votingIndex].votedByAmount[msg.sender] = senderBalance; + votings[_votingIndex].voters.push(msg.sender); if(_isYes) { - votings[_votingIndex].yesVotesCount = votings[_votingIndex].yesVotesCount.add(1); + votings[_votingIndex].tokenAmountToAccept = votings[_votingIndex].tokenAmountToAccept.add(senderBalance); } else { - votings[_votingIndex].noVotesCount = votings[_votingIndex].noVotesCount.add(1); + votings[_votingIndex].tokenAmountToReject = votings[_votingIndex].tokenAmountToReject.add(senderBalance); } } @@ -298,7 +300,10 @@ contract Daico is Ownable { */ function createVotingByOwner(uint _tapIndex, VotingType _votingType) external onlyOwner validTapIndex(_tapIndex) { // validation - require(_votingType == VotingType.ReleaseTapDecreasedQuorum); + require( + _votingType == VotingType.ReleaseTapDecreasedQuorum || + _votingType == VotingType.ReleaseTap + ); uint latestVotingIndex = tapVotings[_tapIndex][tapVotingsCount[_tapIndex].sub(1)]; Voting memory latestVoting = votings[latestVotingIndex]; // check that latest voting is finished @@ -306,9 +311,15 @@ contract Daico is Ownable { // check that latest voting is of type ReleaseTap or ReleaseTapDecreasedQuorum require(latestVoting.votingType == VotingType.ReleaseTap || latestVoting.votingType == VotingType.ReleaseTapDecreasedQuorum); // check that latest voting result is quorum not reached - require(getVotingResult(latestVotingIndex) == VotingResult.QuorumNotReached); + // create a new voting - _createVoting(_tapIndex, 50, now, now + 7 days, VotingType.ReleaseTapDecreasedQuorum); + if(_votingType == VotingType.ReleaseTapDecreasedQuorum) { + require(getVotingResult(latestVotingIndex) == VotingResult.QuorumNotReached); + _createVoting(_tapIndex, 50, now, now + 7 days, VotingType.ReleaseTapDecreasedQuorum); + } else { + require(getVotingResult(latestVotingIndex) == VotingResult.QuorumNotReached); + _createVoting(_tapIndex, 50, now, now + 7 days, VotingType.ReleaseTap); + } } /** @@ -324,8 +335,8 @@ contract Daico is Ownable { amountToWithdraw = amountToWithdraw.add(tapAmounts[i]); } } - // transfer DAI tokens to owner - daiToken.transfer(owner, amountToWithdraw); + // transfer DAI tokens to returnAddress + daiToken.transfer(returnAddress, amountToWithdraw); } /** @@ -339,7 +350,7 @@ contract Daico is Ownable { function withdrawTapPayment(uint _tapIndex) external validTapIndex(_tapIndex) { // validation require(msg.sender == projectOwner); - require(isTapWithdrawAcceptedByInvestors(_tapIndex)); + // require(isTapWithdrawAcceptedByInvestors(_tapIndex)); TODO: FIX require(!tapPayments[_tapIndex].isWithdrawn); // create tap payment TapPayment memory tapPayment; @@ -381,5 +392,4 @@ contract Daico is Ownable { tapVotingsCount[_tapIndex] = tapVotingsCount[_tapIndex].add(1); votingsCount = votingsCount.add(1); } - } diff --git a/contracts/Daico/DaicoAuto.sol b/contracts/Daico/DaicoAuto.sol deleted file mode 100644 index a2d123b..0000000 --- a/contracts/Daico/DaicoAuto.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity ^0.4.24; - -import "@thetta/core/contracts/utils/GenericCaller.sol"; -import "./DaicoWithUnpackers.sol"; - - -contract DaicoAuto is GenericCaller { - DaicoWithUnpackers public daico; - - constructor(IDaoBase _daoBase, DaicoWithUnpackers _daico) public GenericCaller(_daoBase){ - daico = _daico; - } - - function nextStageAuto(uint _projectNum) public returns(address) { - // require(projects[_projectNum].projectState()==DaicoProject.ProjectState.Basic); - bytes32[] memory params = new bytes32[](1); - params[0] = bytes32(_projectNum); - return doAction(daico.NEXT_STAGE(), daico, msg.sender, "nextStageGeneric(bytes32[])", params); - } -} \ No newline at end of file diff --git a/contracts/Daico/DaicoFactory.sol b/contracts/Daico/DaicoFactory.sol deleted file mode 100644 index fa767a2..0000000 --- a/contracts/Daico/DaicoFactory.sol +++ /dev/null @@ -1,76 +0,0 @@ -pragma solidity ^0.4.24; - -// to enable Params passing to constructor and method -// pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoBase.sol"; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/DaoStorage.sol"; -import "@thetta/core/contracts/DaoBaseAuto.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; -import "@thetta/core/contracts/utils/UtilsLib.sol"; - -// import "./DaicoProject.sol"; -// import "./Daico.sol"; -import "./DaicoWithUnpackers.sol"; -import "./DaicoAuto.sol"; - - -contract DaicoFactory { - DaicoWithUnpackers public daico; - DaoBase public daoBase; - DaoStorage public store; - DaicoAuto public daicoAuto; - - constructor(address[] _investors) { - createDaico(_investors); - // setupAutoMethods(); - // daoBase.renounceOwnership(); - } - - function createDaico(address[] _investors) { - StdDaoToken daicoToken = new StdDaoToken("DaicoToken", "DAICO", 18, true, true, 10**25); - - address[] tokens; - tokens.push(address(daicoToken)); - // store = new DaoStorage(tokens); - // daoBase = new DaoBase(store); - - // daico = new DaicoWithUnpackers(IDaoBase(daoBase), _investors); - - // store.allowActionByAddress(daoBase.MANAGE_GROUPS(),address(this)); - // store.allowActionByAddress(daoBase.ISSUE_TOKENS(),address(daico)); - // store.allowActionByAddress(daoBase.BURN_TOKENS(),address(daico)); - // store.transferOwnership(daoBase); - - // daicoToken.transferOwnership(daoBase); - - // for(uint i=0; i<_investors.length; ++i){ - // daoBase.addGroupMember("Investors", _investors[i]); - // } - - // // 1 - set investors group permissions - // daoBase.allowActionByAnyMemberOfGroup(daoBase.ADD_NEW_PROPOSAL(),"Investors"); - // daoBase.allowActionByVoting(daoBase.MANAGE_GROUPS(), daicoToken); - - // // 2 - set custom investors permissions - // daoBase.allowActionByVoting(daico.NEXT_STAGE(), daico.daicoToken()); - } - - function setupAutoMethods() internal { - // TODO: add all custom actions to the DaoBaseAuto derived contract - // daicoAuto = new DaicoAuto(IDaoBase(daoBase), daico); - - // daoBase.allowActionByAddress(daoBase.ADD_NEW_PROPOSAL(), daicoAuto); - // daoBase.allowActionByAddress(daoBase.MANAGE_GROUPS(), daicoAuto); - // daoBase.allowActionByAddress(daoBase.UPGRADE_DAO_CONTRACT(), daicoAuto); - // // daoBase.allowActionByAddress(daico.NEXT_STAGE(), daicoAuto); - - // uint VOTING_TYPE_1P1V = 1; - // daicoAuto.setVotingParams(daoBase.MANAGE_GROUPS(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("Investors"), bytes32(70), bytes32(70), bytes32(24*7)); - // daicoAuto.setVotingParams(daoBase.UPGRADE_DAO_CONTRACT(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("Investors"), bytes32(70), bytes32(70), bytes32(24*7)); - // daicoAuto.setVotingParams(daico.NEXT_STAGE(), VOTING_TYPE_1P1V, bytes32(0), UtilsLib.stringToBytes32("Investors"), bytes32(0), bytes32(0), bytes32(24*7)); - - // daicoAuto.transferOwnership(daoBase); - } -} \ No newline at end of file diff --git a/contracts/Daico/DaicoOld.sol b/contracts/Daico/DaicoOld.sol deleted file mode 100644 index b8ce08a..0000000 --- a/contracts/Daico/DaicoOld.sol +++ /dev/null @@ -1,31 +0,0 @@ -pragma solidity ^0.4.24; - -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoClient.sol"; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; -import "./DaicoProject.sol"; - - -contract DaicoOld is DaoClient { - mapping(uint => DaicoProject) public projects; - uint projectsCount; - address[] public investors; - - bytes32 public NEXT_STAGE = keccak256("nextStage"); - - constructor(IDaoBase _daoBase, address[] _investors) DaoClient(_daoBase) { - investors = _investors; - } - - function addNewProject(uint _stagesCount, uint _stageAmount) { - DaicoProject project = new DaicoProject(_stagesCount, _stageAmount, msg.sender, address(this)); - projects[projectsCount] = project; - projectsCount++; - } - - function addAmountToFund() public payable {} - -} \ No newline at end of file diff --git a/contracts/Daico/DaicoProject.sol b/contracts/Daico/DaicoProject.sol deleted file mode 100644 index 076b55b..0000000 --- a/contracts/Daico/DaicoProject.sol +++ /dev/null @@ -1,66 +0,0 @@ -pragma solidity ^0.4.24; - -// to enable Params passing to constructor and method -pragma experimental ABIEncoderV2; - -import "@thetta/core/contracts/DaoClient.sol"; -import "@thetta/core/contracts/IDaoBase.sol"; -import "@thetta/core/contracts/tokens/StdDaoToken.sol"; -import "@thetta/core/contracts/moneyflow/ether/WeiAbsoluteExpense.sol"; - -// DaicoProject is funds owner -contract DaicoProject { - mapping(uint => WeiAbsoluteExpense) stages; - uint public stageAmount; - uint public stagesCount; - address public projectOwner; - address public daicoAddress; - uint public currentStage = 0; - - uint public blockUntil = 0; - bool public isRemoved = false; - - modifier onlyDaico() { - require(msg.sender==daicoAddress); - _; - } - - modifier onlyProjectOwner() { - require(msg.sender==projectOwner); - _; - } - - constructor(uint _stagesCount, uint _stageAmount, address _projectOwner, address _daicoAddress) { - stageAmount = _stageAmount; - stagesCount = _stagesCount; - projectOwner = _projectOwner; - daicoAddress = _daicoAddress; - - for(uint i=0; i<_stagesCount; i++) { - WeiAbsoluteExpense stage = new WeiAbsoluteExpense(_stageAmount); - - stages[i] = stage; - } - } - - function flushFundsFromStage(uint _stageNum) onlyProjectOwner { - stages[_stageNum].flushTo(projectOwner); - } - - function goToNextStage() onlyDaico { - require(currentStage70) { - projects[projNum].goToNextStage(); - }else if(yesPercent>20) { - uint blockUntil = uint(now) + 30*24*3600*1000; - projects[projNum].setBlock(blockUntil); - } else { - projects[projNum].removeProject(); - } - } -} \ No newline at end of file diff --git a/migrations/2_deploy_contracts_libraries.js b/migrations/2_deploy_contracts_libraries.js deleted file mode 100644 index 8c22957..0000000 --- a/migrations/2_deploy_contracts_libraries.js +++ /dev/null @@ -1,20 +0,0 @@ -var migrateLibs = require('@thetta/core/scripts/migrateLibs'); - -module.exports = function (deployer, network, accounts) { - let additionalContracts = [ "./BodDaoFactory" - , "./DevZenDaoAuto" - , "./DevZenDaoAutoTestable" - , "./DevZenDaoFactory" - , "./DevZenDaoFactoryTestable" - , "./DevZenDaoTestable" - , "./DevZenDaoWithUnpackers" - , "./DevZenDaoWithUnpackersTestable" - , "./HierarchyDaoFactory" - , "./DaicoFactory" - , "./Daico" - , "./DaicoAuto" - , "./DaicoWithUnpackers" - ] - - return migrateLibs(artifacts, additionalContracts, deployer, network, accounts); -}; diff --git a/migrations/3_deploy_devZenDao.js b/migrations/3_deploy_devZenDao.js deleted file mode 100644 index 17673b6..0000000 --- a/migrations/3_deploy_devZenDao.js +++ /dev/null @@ -1,92 +0,0 @@ -var DevZenDaoFactory = artifacts.require("DevZenDaoFactory"); -var StdDaoToken = artifacts.require("StdDaoToken"); -var DaoStorage = artifacts.require("DaoStorage"); -var DaoBaseWithUnpackers = artifacts.require("DaoBaseWithUnpackers"); -var IDaoBase = artifacts.require("IDaoBase"); -var DaoBaseAuto = artifacts.require("DaoBaseAuto"); -var DevZenDao = artifacts.require("DevZenDao"); -var DevZenDaoAuto = artifacts.require("DevZenDaoAuto"); -var DevZenDaoCore = artifacts.require("DevZenDaoCore"); -var DevZenDaoWithUnpackers = artifacts.require("DevZenDaoWithUnpackers"); -const { uintToBytes32, padToBytes, fromUtf8 } = require("../test/utils/helpers"); - -let emp1 = '0x7EaD9f71ef8a32D351ce1966b281300114bF2eab'; -let emp2 = '0x1f27a8F4a8A50898C5735221982eefA80c070073'; -let emp3 = '0xC86d4De6dC26d73BE76a526D951d194BF13C605c'; - -module.exports = function(deployer, network, accounts) { - return deployer.then(async () => { - let devZenToken = await deployer.deploy(StdDaoToken, "DevZenToken", "DZT", 18, true, true, 100000000000000000000); - let repToken = await deployer.deploy(StdDaoToken, "DevZenRepToken", "DZTREP", 18, true, true, 100000000000000000000); - let store = await deployer.deploy(DaoStorage, [devZenToken.address, repToken.address]); - let daoBase = await deployer.deploy(DaoBaseWithUnpackers, store.address); - let devZenDao = await deployer.deploy(DevZenDaoWithUnpackers, daoBase.address, [devZenToken.address, repToken.address]); - - await store.allowActionByAddress(await daoBase.MANAGE_GROUPS(),accounts[0]); - await store.allowActionByAddress(await daoBase.ISSUE_TOKENS(),devZenDao.address); - await store.allowActionByAddress(await daoBase.BURN_TOKENS(),devZenDao.address); - await store.allowActionByAddress(await devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), accounts[0]); - await store.allowActionByAddress(await devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), accounts[0]); - - // await await 2 - setup - await store.addGroupMember(web3.sha3("DevZenTeam"), accounts[0]); - await store.addGroupMember(web3.sha3("DevZenTeam"), emp1); - await store.addGroupMember(web3.sha3("DevZenTeam"), emp2); - await store.addGroupMember(web3.sha3("DevZenTeam"), emp3); - - await store.transferOwnership(daoBase.address); - await devZenDao.setParam(await devZenDao.MINT_TOKENS_PER_WEEK_AMOUNT(), 10 * 1e18); - await devZenDao.setParam(await devZenDao.MINT_REPUTATION_TOKENS_PER_WEEK_AMOUNT(), 5 * 1e18); - await devZenDao.setParam(await devZenDao.ONE_AD_SLOT_PRICE(), 2 * 1e18); // Current ETH price is ~$450. One token will be worth ~$45. One ad will cost ~$90 (2 tokens) - await devZenDao.setParam(await devZenDao.ONE_TOKEN_PRICE_IN_WEI(), 1e17); //) To become a guest user should put 5 tokens at stake - - await devZenDao.setParam(await devZenDao.BECOME_GUEST_STAKE(), 5 * 1e18); - await devZenDao.setParam(await devZenDao.REP_TOKENS_REWARD_HOST(), 2 * 1e18); - await devZenDao.setParam(await devZenDao.REP_TOKENS_REWARD_GUEST(), 1 * 1e18); - await devZenDao.setParam(await devZenDao.REP_TOKENS_REWARD_TEAM_MEMBERS(), 2 * 1e18); - - await devZenToken.transferOwnership(daoBase.address); - await repToken.transferOwnership(daoBase.address); - - // 1 - set DevZenTeam group permissions - await daoBase.allowActionByAnyMemberOfGroup(await daoBase.ADD_NEW_PROPOSAL(),"DevZenTeam"); - await daoBase.allowActionByVoting(await daoBase.MANAGE_GROUPS(), repToken.address); - await daoBase.allowActionByVoting(await daoBase.UPGRADE_DAO_CONTRACT(), repToken.address); - - // 2 - set custom DevZenTeam permissions - await daoBase.allowActionByVoting(await devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), repToken.address); - await daoBase.allowActionByVoting(await devZenDao.DEV_ZEN_WITHDRAW_ETHER(), repToken.address); - await daoBase.allowActionByVoting(await devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), repToken.address); - await daoBase.allowActionByVoting(await devZenDao.DEV_ZEN_CHANGE_GUEST(), repToken.address); - await daoBase.allowActionByVoting(await devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), repToken.address); - await daoBase.allowActionByVoting(await devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), repToken.address); - - let devZenDaoAuto = await deployer.deploy(DevZenDaoAuto, daoBase.address, devZenDao.address); - - await daoBase.allowActionByAddress(await daoBase.ADD_NEW_PROPOSAL(), devZenDaoAuto.address); - await daoBase.allowActionByAddress(await daoBase.MANAGE_GROUPS(), devZenDaoAuto.address); - await daoBase.allowActionByAddress(await daoBase.UPGRADE_DAO_CONTRACT(), devZenDaoAuto.address); - - await daoBase.allowActionByAddress(await devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), devZenDaoAuto.address); - await daoBase.allowActionByAddress(await devZenDao.DEV_ZEN_WITHDRAW_ETHER(), devZenDaoAuto.address); - await daoBase.allowActionByAddress(await devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), devZenDaoAuto.address); - await daoBase.allowActionByAddress(await devZenDao.DEV_ZEN_CHANGE_GUEST(), devZenDaoAuto.address); - await daoBase.allowActionByAddress(await devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), devZenDaoAuto.address); - await daoBase.allowActionByAddress(await devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), devZenDaoAuto.address); - - let VOTING_TYPE_1P1V = 1; - await devZenDaoAuto.setVotingParams(await daoBase.MANAGE_GROUPS(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - await devZenDaoAuto.setVotingParams(await daoBase.REMOVE_GROUP_MEMBER(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - await devZenDaoAuto.setVotingParams(await daoBase.UPGRADE_DAO_CONTRACT(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - await devZenDaoAuto.setVotingParams(await devZenDao.DEV_ZEN_UPDATE_DAO_PARAMS(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - await devZenDaoAuto.setVotingParams(await devZenDao.DEV_ZEN_WITHDRAW_ETHER(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - await devZenDaoAuto.setVotingParams(await devZenDao.DEV_ZEN_SELECT_NEXT_HOST(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - await devZenDaoAuto.setVotingParams(await devZenDao.DEV_ZEN_CHANGE_GUEST(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - await devZenDaoAuto.setVotingParams(await devZenDao.DEV_ZEN_EMERGENCY_CHANGE_GUEST(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - await devZenDaoAuto.setVotingParams(await devZenDao.DEV_ZEN_MOVE_TO_NEXT_EPISODE(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("DevZenTeam"), uintToBytes32(65), uintToBytes32(65), 0); - - await devZenDaoAuto.transferOwnership(daoBase.address); - - await daoBase.renounceOwnership(); - }); -}; \ No newline at end of file diff --git a/migrations/4_deploy_HierarchyDao.js b/migrations/4_deploy_HierarchyDao.js deleted file mode 100644 index b15fb66..0000000 --- a/migrations/4_deploy_HierarchyDao.js +++ /dev/null @@ -1,54 +0,0 @@ -var StdDaoToken = artifacts.require("StdDaoToken"); -var DaoStorage = artifacts.require("DaoStorage"); -var DaoBase = artifacts.require("DaoBase"); -var IDaoBase = artifacts.require("IDaoBase"); -var DaoBaseAuto = artifacts.require("DaoBaseAuto"); -var HierarchyDao = artifacts.require("HierarchyDao"); -const { uintToBytes32, padToBytes, fromUtf8 } = require("../test/utils/helpers"); - -let mng1 = '0x5a2203e516d8f025eaa37d1f6d7f114ac654da05'; -let mng2 = '0x9dc108ae0579a1c856b8eff862fcab76c6e6ee15'; -let mng3 = '0x564dcf23922de39970b2b442b1a2de8d2fd25330'; -let emp1 = '0xfac20ad5f3bfc1748235edf919d473272ca0fd55'; -let emp2 = '0x38ed1a11e4f2fd85995a058e1f65d41a483a662a'; -let emp3 = '0x92bc71cd9a9a6ad3a1dcacc2b8c9eab13f4d547e'; - -module.exports = function(deployer, network, accounts) { - return deployer.then(async () => { - let token = await deployer.deploy(StdDaoToken, "StdToken", "STD", 18, true, true, 100000000000000000000); - let store = await deployer.deploy(DaoStorage, [token.address]); - let daoBase = await deployer.deploy(DaoBase, store.address); - let hierarchyDao = await deployer.deploy(HierarchyDao, daoBase.address); - - await store.allowActionByAddress(await daoBase.MANAGE_GROUPS(), accounts[0]); - await store.transferOwnership(daoBase.address); - await token.transferOwnership(daoBase.address); - - await daoBase.addGroupMember("Managers", accounts[0]); - await daoBase.addGroupMember("Employees", accounts[0]); - - await daoBase.allowActionByAddress(await daoBase.ISSUE_TOKENS(), accounts[0]); - await daoBase.allowActionByAddress(await daoBase.UPGRADE_DAO_CONTRACT(), accounts[0]); - await daoBase.allowActionByAnyMemberOfGroup(await daoBase.ADD_NEW_PROPOSAL(), "Managers"); - await daoBase.allowActionByVoting(await daoBase.MANAGE_GROUPS(), token.address); - - await daoBase.addGroupMember("Managers", mng1); - await daoBase.addGroupMember("Managers", mng2); - await daoBase.addGroupMember("Managers", mng3); - await daoBase.addGroupMember("Employees", emp1); - await daoBase.addGroupMember("Employees", emp2); - await daoBase.addGroupMember("Employees", emp3); - - let hierarchyDaoAuto = await deployer.deploy(DaoBaseAuto, daoBase.address); - - // set voring params 1 person 1 vote - let VOTING_TYPE_1P1V = 1; - await hierarchyDaoAuto.setVotingParams(await daoBase.MANAGE_GROUPS(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("Managers"), uintToBytes32(50), uintToBytes32(50), 0); - - await daoBase.allowActionByAddress(await daoBase.ADD_NEW_PROPOSAL(), hierarchyDaoAuto.address); - await daoBase.allowActionByAddress(await daoBase.MANAGE_GROUPS(), hierarchyDaoAuto.address); - - await hierarchyDaoAuto.transferOwnership(daoBase.address); - await daoBase.renounceOwnership(); - }); -}; \ No newline at end of file diff --git a/migrations/5_deploy_BodDao.js b/migrations/5_deploy_BodDao.js deleted file mode 100644 index 8368a93..0000000 --- a/migrations/5_deploy_BodDao.js +++ /dev/null @@ -1,57 +0,0 @@ -var StdDaoToken = artifacts.require("StdDaoToken"); -var DaoStorage = artifacts.require("DaoStorage"); -var DaoBase = artifacts.require("DaoBase"); -var IDaoBase = artifacts.require("IDaoBase"); -var DaoBaseAuto = artifacts.require("DaoBaseAuto"); -var BodDao = artifacts.require("BodDao"); -const { uintToBytes32, padToBytes, fromUtf8 } = require("../test/utils/helpers"); - -let dir1 = '0x5a2203e516d8f025eaa37d1f6d7f114ac654da05'; -let dir2 = '0x9dc108ae0579a1c856b8eff862fcab76c6e6ee15'; -let dir3 = '0x564dcf23922de39970b2b442b1a2de8d2fd25330'; -let emp1 = '0xfac20ad5f3bfc1748235edf919d473272ca0fd55'; -let emp2 = '0x38ed1a11e4f2fd85995a058e1f65d41a483a662a'; -let emp3 = '0x92bc71cd9a9a6ad3a1dcacc2b8c9eab13f4d547e'; - -module.exports = function(deployer, network, accounts) { - return deployer.then(async () => { - let token = await deployer.deploy(StdDaoToken, "StdToken", "STD", 18, true, true, 100000000000000000000); - let store = await deployer.deploy(DaoStorage, [token.address]); - let daoBase = await deployer.deploy(DaoBase, store.address); - let bodDao = await deployer.deploy(BodDao, daoBase.address); - - await store.allowActionByAddress(await daoBase.MANAGE_GROUPS(), accounts[0]); - - await token.transferOwnership(daoBase.address); - await store.transferOwnership(daoBase.address); - - await daoBase.addGroupMember("BoD", accounts[0]); - await daoBase.addGroupMember("Employees", accounts[0]); - - daoBase.allowActionByAnyMemberOfGroup(await daoBase.ADD_NEW_PROPOSAL(), "BoD"); - daoBase.allowActionByVoting(await daoBase.MANAGE_GROUPS(), token.address); - daoBase.allowActionByVoting(await daoBase.ISSUE_TOKENS(), token.address); - daoBase.allowActionByVoting(await daoBase.UPGRADE_DAO_CONTRACT(), token.address); - - await daoBase.addGroupMember("BoD", dir1); - await daoBase.addGroupMember("BoD", dir2); - await daoBase.addGroupMember("BoD", dir3); - await daoBase.addGroupMember("Employees", emp1); - await daoBase.addGroupMember("Employees", emp2); - await daoBase.addGroupMember("Employees", emp3); - - let bodDaoAuto = await deployer.deploy(DaoBaseAuto, daoBase.address); - - let VOTING_TYPE_1P1V = 1; - await bodDaoAuto.setVotingParams(await daoBase.MANAGE_GROUPS(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("BoD"), uintToBytes32(49), uintToBytes32(49), 0); - await bodDaoAuto.setVotingParams(await daoBase.ISSUE_TOKENS(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("BoD"), uintToBytes32(49), uintToBytes32(49), 0); - await bodDaoAuto.setVotingParams(await daoBase.UPGRADE_DAO_CONTRACT(), VOTING_TYPE_1P1V, uintToBytes32(0), fromUtf8("BoD"), uintToBytes32(49), uintToBytes32(49), 0); - - await daoBase.allowActionByAddress(await daoBase.MANAGE_GROUPS(), bodDaoAuto.address); - await daoBase.allowActionByAddress(await daoBase.ISSUE_TOKENS(), bodDaoAuto.address); - await daoBase.allowActionByAddress(await daoBase.ADD_NEW_PROPOSAL(), bodDaoAuto.address); - - await bodDaoAuto.transferOwnership(daoBase.address); - await daoBase.renounceOwnership(); - }); -}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a476247..b26fa73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -125,152 +125,11 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" }, - "@thetta/core": { - "version": "git+https://github.com/Thetta/Thetta-DAO-Framework.git#1ebae3a32a2b6fec0c6770b28983bc0f6061c5f8", - "from": "git+https://github.com/Thetta/Thetta-DAO-Framework.git#genericCaller", - "requires": { - "babel-eslint": "^8.2.6", - "babel-polyfill": "^6.26.0", - "babel-register": "^6.26.0", - "chai": "4.1.2", - "chai-as-promised": "7.1.1", - "chai-bignumber": "2.0.2", - "coveralls": "^3.0.2", - "docusaurus-init": "^1.0.2", - "eslint": "^5.4.0", - "eslint-config-standard": "^10.2.1", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-node": "^5.2.1", - "eslint-plugin-promise": "^3.8.0", - "eslint-plugin-standard": "^3.1.0", - "eth-gas-reporter": "^0.1.10", - "ganache-cli": "^6.1.8", - "growl": "^1.10.5", - "install": "^0.12.1", - "solc": "^0.4.24", - "solidity-coverage": "^0.5.7", - "solidity-docgen": "^0.1.0", - "solium": "^1.1.8", - "truffle": "^4.1.14", - "zeppelin-solidity": "^1.12.0" - }, - "dependencies": { - "babel-eslint": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.6.tgz", - "integrity": "sha512-aCdHjhzcILdP8c9lej7hvXKvQieyRt20SF102SIGyY4cUIiw6UaAtK4j2o3dXX74jEmy0TJ0CEhv4fTIM3SzcA==", - "requires": { - "@babel/code-frame": "7.0.0-beta.44", - "@babel/traverse": "7.0.0-beta.44", - "@babel/types": "7.0.0-beta.44", - "babylon": "7.0.0-beta.44", - "eslint-scope": "3.7.1", - "eslint-visitor-keys": "^1.0.0" - } - }, - "ethereumjs-testrpc-sc": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/ethereumjs-testrpc-sc/-/ethereumjs-testrpc-sc-6.1.6.tgz", - "integrity": "sha512-iv2qiGBFgk9mn5Nq2enX8dG5WQ7Lk+FCqpnxfPfH4Ns8KLPwttmNOy264nh3SXDJJvcQwz/XnlLteDQVILotbg==", - "requires": { - "source-map-support": "^0.5.3" - } - }, - "ganache-cli": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/ganache-cli/-/ganache-cli-6.1.8.tgz", - "integrity": "sha512-yXzteu4SIgUL31mnpm9j+x6dpHUw0p/nsRVkcySKq0w+1vDxH9jMErP1QhZAJuTVE6ni4nfvGSNkaQx5cD3jfg==", - "requires": { - "source-map-support": "^0.5.3" - } - }, - "solidity-coverage": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.5.11.tgz", - "integrity": "sha512-qikdsSi6+9XbfvwA0aI7HUVpF9fIFNqRWTw23M89GMDY+b6Gj0wWU9IngJS0fimoZIAdEp3bfChxvpfVcrUesg==", - "requires": { - "death": "^1.1.0", - "ethereumjs-testrpc-sc": "6.1.6", - "istanbul": "^0.4.5", - "keccakjs": "^0.2.1", - "req-cwd": "^1.0.1", - "shelljs": "^0.7.4", - "sol-explore": "^1.6.2", - "solidity-parser-sc": "0.4.11", - "tree-kill": "^1.2.0", - "web3": "^0.18.4" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "truffle": { - "version": "4.1.14", - "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.14.tgz", - "integrity": "sha512-e7tTLvKP3bN9dE7MagfWyFjy4ZgoEGbeujECy1me1ENBzbj/aO/+45gs72qsL3+3IkCNNcWNOJjjrm8BYZZNNg==", - "requires": { - "mocha": "^4.1.0", - "original-require": "1.0.1", - "solc": "0.4.24" - } - }, - "zeppelin-solidity": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/zeppelin-solidity/-/zeppelin-solidity-1.12.0.tgz", - "integrity": "sha512-dgjPPnTmx14hAbTeOpTKemDeDCDdwglS0nquOAJG8h5o9zlb43FZafQSrMlIUUSp1EisDZfehrp5loGEYXHZBA==" - } - } - }, - "@types/concat-stream": { - "version": "1.6.0", - "resolved": "http://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-OU2+C7X+5Gs42JZzXoto7yOQ0A0=", - "requires": { - "@types/node": "*" - } - }, - "@types/form-data": { - "version": "0.0.33", - "resolved": "http://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=", - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "9.6.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.35.tgz", - "integrity": "sha512-h5zvHS8wXHGa+Gcqs9K8vqCgOtqjr0+NqG/DDJmQIX1wpR9HivAfgV8bjcD3mGM4bPfQw5Aneb2Pn8355L83jA==" - }, - "@types/qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-mNhVdZHdtKHMMxbqzNK3RzkBcN1cux3AvuCYGTvjEIQT2uheH3eCAyYsbMbh2Bq8nXkeOWs1kyDiF7geWRFQ4Q==" - }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" }, - "abi-decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/abi-decoder/-/abi-decoder-1.2.0.tgz", - "integrity": "sha512-y2OKSEW4gf2838Eavc56vQY9V46zaXkf3Jl1WpTfUBbzAVrXSr4JRZAAWv55Tv9s5WNz1rVgBgz5d2aJIL1QCg==", - "requires": { - "web3": "^0.18.4" - } - }, "abstract-leveldown": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", @@ -279,32 +138,11 @@ "xtend": "~4.0.0" } }, - "acorn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", - "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==" - }, - "acorn-jsx": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.0.tgz", - "integrity": "sha512-XkB50fn0MURDyww9+UYL3c1yLbOBz0ZFvrdYlGB8l+Ije1oSC75qAqrzSPjYQbdnQUzhlUGNKuesryAv0gxZOg==" - }, "aes-js": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-0.2.4.tgz", "integrity": "sha1-lLiBq3FyhtAV+iGeCPtmcJ3aWj0=" }, - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", @@ -343,89 +181,6 @@ "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==" }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - } - } - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -477,11 +232,6 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -515,11 +265,6 @@ "lodash": "^4.17.10" } }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" - }, "async-eventemitter": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", @@ -1518,11 +1263,6 @@ "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", "from": "bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" }, - "binary-extensions": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", - "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==" - }, "binaryextensions": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.1.tgz", @@ -1708,19 +1448,6 @@ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" - }, "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", @@ -1768,11 +1495,6 @@ "check-error": "^1.0.2" } }, - "chai-bignumber": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/chai-bignumber/-/chai-bignumber-2.0.2.tgz", - "integrity": "sha512-BIdRNjRaoRj4bMsZLKbIZPMNKqmwnzNiyxqBYDSs6dFOCs9w8OHPuUE8e1bH60i1IhOzT0NjLtCD+lKEWB1KTQ==" - }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -1788,11 +1510,6 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -1806,45 +1523,6 @@ "functional-red-black-tree": "^1.0.1" } }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -1854,11 +1532,6 @@ "safe-buffer": "^5.0.1" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -1908,16 +1581,6 @@ } } }, - "cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - } - }, "cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -2080,11 +1743,6 @@ "typedarray": "^0.0.6" } }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=" - }, "convert-source-map": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", @@ -2169,11 +1827,6 @@ "which": "^1.2.9" } }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, "crypto-js": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", @@ -2320,35 +1973,6 @@ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - } - } - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2396,23 +2020,6 @@ } } }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "requires": { - "esutils": "^2.0.2" - } - }, - "docusaurus-init": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/docusaurus-init/-/docusaurus-init-1.0.2.tgz", - "integrity": "sha512-vfGstxIL5gK8/PCcGS+gY8MG4iw3fvnzpb3zOsG1kxHTJRekvebKd2x2Y/SPFt38jX15ZjDezrwi7YZX7/DggA==", - "requires": { - "chalk": "^2.1.0", - "shelljs": "^0.7.8" - } - }, "dom-walk": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", @@ -2507,11 +2114,6 @@ "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-5.10.0.tgz", "integrity": "sha512-rXbzXWvnQxy+TcqZlARbWVQwgGVVouVJgFZhLVN5htjLxl1thstrP2ZGi0pXC309AbK7gVOPU+ulz/tmpCI7iw==" }, - "eol": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", - "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==" - }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -2597,358 +2199,25 @@ } } }, - "eslint": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.8.0.tgz", - "integrity": "sha512-Zok6Bru3y2JprqTNm14mgQ15YQu/SMDkWdnmHfFg770DIUlmMFd/gqqzCHekxzjHZJxXv3tmTpH0C1icaYJsRQ==", - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.5.3", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", - "ignore": "^4.0.6", - "imurmurhash": "^0.1.4", - "inquirer": "^6.1.0", - "is-resolvable": "^1.1.0", - "js-yaml": "^3.12.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.5", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.0.2", - "text-table": "^0.2.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "requires": { - "ms": "^2.1.1" - } - }, - "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" - }, - "inquirer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", - "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.0", - "figures": "^2.0.0", - "lodash": "^4.17.10", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.1.0", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "requires": { - "tslib": "^1.9.0" - } - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "eslint-config-standard": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", - "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=" - }, - "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", - "requires": { - "debug": "^2.6.9", - "resolve": "^1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - } - } - }, - "eslint-module-utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", - "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", - "requires": { - "debug": "^2.6.8", - "pkg-dir": "^1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "requires": { - "find-up": "^1.0.0" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", - "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", - "requires": { - "contains-path": "^0.1.0", - "debug": "^2.6.8", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - } - } - }, - "eslint-plugin-node": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.2.1.tgz", - "integrity": "sha512-xhPXrh0Vl/b7870uEbaumb2Q+LxaEcOQ3kS1jtIXanBAwpMre1l5q/l2l/hESYJGEFKuI78bp6Uw50hlpr7B+g==", - "requires": { - "ignore": "^3.3.6", - "minimatch": "^3.0.4", - "resolve": "^1.3.3", - "semver": "5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" - } - } - }, - "eslint-plugin-promise": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz", - "integrity": "sha512-JiFL9UFR15NKpHyGii1ZcvmtIqa3UTwiDAGb8atSffe43qJ3+1czVGN6UtkklpcJ2DVnqvTMzEKRaJdBkAL2aQ==" - }, - "eslint-plugin-standard": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", - "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==" - }, - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" } }, - "eslint-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", - "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==" - }, "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==" }, - "espree": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", - "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", - "requires": { - "acorn": "^6.0.2", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - } - }, "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "requires": { - "estraverse": "^4.0.0" - } - }, "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", @@ -2997,43 +2266,6 @@ } } }, - "eth-gas-reporter": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.1.12.tgz", - "integrity": "sha512-Ao5uiXSA5Ep5fi/YvGCsFJMelMKj0fMJkAvWYzPVe1h3Mg9Z7X3Rs51ovG9izFZH7wSqnqydiC6SKDhZWpxK2g==", - "requires": { - "abi-decoder": "^1.0.8", - "cli-table3": "^0.5.0", - "colors": "^1.1.2", - "lodash": "^4.17.4", - "mocha": "^4.1.0", - "req-cwd": "^2.0.0", - "request": "^2.83.0", - "request-promise-native": "^1.0.5", - "sha1": "^1.1.1", - "shelljs": "^0.7.8", - "solidity-parser-antlr": "^0.2.10", - "sync-request": "^6.0.0" - }, - "dependencies": { - "req-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", - "integrity": "sha1-1AgrTURZgDZkD7c93qAe1T20nrw=", - "requires": { - "req-from": "^2.0.0" - } - }, - "req-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", - "integrity": "sha1-10GI5H+TeW9Kpx327jWuaJ8+DnA=", - "requires": { - "resolve-from": "^3.0.0" - } - } - } - }, "eth-json-rpc-infura": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-3.1.2.tgz", @@ -3679,11 +2911,6 @@ "checkpoint-store": "^1.1.0" } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, "fast-glob": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", @@ -3734,15 +2961,6 @@ "escape-string-regexp": "^1.0.5" } }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -3785,17 +3003,6 @@ "readable-stream": "^2.0.2" } }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" - } - }, "flow-parser": { "version": "0.76.0", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.76.0.tgz", @@ -3876,468 +3083,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", - "optional": true, - "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.5.1", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.21", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": "^2.1.0" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "minipass": { - "version": "2.2.4", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.1.0", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.2.0", - "bundled": true, - "optional": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.1.10", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.5.0", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.1", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true - } - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4382,11 +3127,6 @@ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" - }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -4872,32 +3612,11 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.1.tgz", "integrity": "sha512-Ba4+0M4YvIDUUsprMjhVTU1yN9F2/LJSAl69ZpzaLT4l4j5mwTS6jqqW9Ojvj6lKz/veqPzpJBqGbXspOb533A==" }, - "http-basic": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-7.0.0.tgz", - "integrity": "sha1-gvClBr6UJzLsje6+6A50bvVzbbo=", - "requires": { - "@types/concat-stream": "^1.6.0", - "@types/node": "^9.4.1", - "caseless": "~0.12.0", - "concat-stream": "^1.4.6", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - } - }, "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" }, - "http-response-object": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.1.tgz", - "integrity": "sha512-6L0Fkd6TozA8kFSfh9Widst0wfza3U1Ex2RjJ6zNDK0vR1U1auUR6jY4Nn2Xl7CCy0ikFmxW1XcspVpb9RvwTg==", - "requires": { - "@types/node": "^9.3.0" - } - }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -5002,11 +3721,6 @@ } } }, - "install": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/install/-/install-0.12.2.tgz", - "integrity": "sha512-+7thTb4Rpvs9mnlhHKGZFJbGOO6kyMgy+gg0sgM5vFzIFK0wrCYXqdlaM71Bi289DTuPHf61puMFsaZBcwDIrg==" - }, "interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", @@ -5047,14 +3761,6 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "requires": { - "binary-extensions": "^1.0.0" - } - }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -5193,27 +3899,6 @@ } } }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "requires": { - "path-is-inside": "^1.0.1" - } - }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -5250,11 +3935,6 @@ "has": "^1.0.1" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, "is-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -5410,11 +4090,6 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.3.1.tgz", "integrity": "sha1-hhIoAhQvCChQKg0d7h2V4lO7AkM=" }, - "js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" - }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -5589,11 +4264,6 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, "json-stable-stringify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", @@ -5602,11 +4272,6 @@ "jsonify": "~0.0.0" } }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" - }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -6030,17 +4695,6 @@ } } }, - "load-json-file": { - "version": "2.0.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, "loader-utils": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", @@ -6528,11 +5182,6 @@ "minimatch": "^3.0.0" } }, - "mustache": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", - "integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==" - }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -6568,11 +5217,6 @@ } } }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" - }, "neo-async": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", @@ -6933,11 +5577,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, - "parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha1-juqz5U+laSD+Fro493+iGqzC104=" - }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -7006,11 +5645,6 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -7021,14 +5655,6 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "requires": { - "pify": "^2.0.0" - } - }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", @@ -7082,11 +5708,6 @@ "find-up": "^2.1.0" } }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==" - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -7132,19 +5753,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, - "progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", - "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==" - }, - "promise": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.2.tgz", - "integrity": "sha512-EIyzM39FpVOMbqgzEHhxdrEhtOSDOtjMZQ0M6iVfCE+kWNgCkAyOdnuCWqfmflylftfadU6FkiMgHZA2kUzwRw==", - "requires": { - "asap": "~2.0.6" - } - }, "promise-to-callback": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/promise-to-callback/-/promise-to-callback-1.0.0.tgz", @@ -7154,15 +5762,6 @@ "set-immediate-shim": "^1.0.1" } }, - "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", - "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -7173,11 +5772,6 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -7223,28 +5817,6 @@ "safe-buffer": "^5.1.0" } }, - "react": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.6.0.tgz", - "integrity": "sha512-zJPnx/jKtuOEXCbQ9BKaxDMxR0001/hzxXwYxG8septeyYGfsgAei6NgfbVgOhbY1WOP2o3VPs/E9HaN+9hV3Q==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.10.0" - } - }, - "react-dom": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.0.tgz", - "integrity": "sha512-Stm2D9dXEUUAQdvpvhvFj/DEXwC2PAL/RwEMhoN4dvvD2ikTlJegEXf97xryg88VIAU22ZAP7n842l+9BTz6+w==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.10.0" - } - }, "read-chunk": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", @@ -7261,25 +5833,6 @@ } } }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -7294,16 +5847,6 @@ "util-deprecate": "~1.0.1" } }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, "recast": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/recast/-/recast-0.15.2.tgz", @@ -7367,11 +5910,6 @@ "safe-regex": "^1.1.0" } }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==" - }, "regexpu-core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", @@ -7480,24 +6018,6 @@ "uuid": "^3.1.0" } }, - "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", - "requires": { - "lodash": "^4.13.1" - } - }, - "request-promise-native": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", - "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", - "requires": { - "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7513,22 +6033,6 @@ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" - } - } - }, "resolve": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", @@ -7664,15 +6168,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "scheduler": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.10.0.tgz", - "integrity": "sha512-+TSTVTCBAA3h8Anei3haDc1IRwMeDmtI/y/o3iBe3Mjl2vwYF9DtPDt929HyRmV/e7au7CLu8sc4C4W0VOs29w==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, "scoped-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", @@ -7768,15 +6263,6 @@ "safe-buffer": "^5.0.1" } }, - "sha1": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", - "integrity": "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=", - "requires": { - "charenc": ">= 0.0.1", - "crypt": ">= 0.0.1" - } - }, "sha3": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/sha3/-/sha3-1.2.2.tgz", @@ -7928,11 +6414,6 @@ "kind-of": "^3.2.0" } }, - "sol-digger": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/sol-digger/-/sol-digger-0.0.2.tgz", - "integrity": "sha1-QGxKnTHiaef4jrHC6hATGOXgkCU=" - }, "sol-explore": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/sol-explore/-/sol-explore-1.6.2.tgz", @@ -8108,72 +6589,6 @@ "web3": "^0.18.4" } }, - "solidity-docgen": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/solidity-docgen/-/solidity-docgen-0.1.0.tgz", - "integrity": "sha512-F7ufNWmlP5c5hIi66Ijv9tc+HNosyO7ijWq6pRtyBR1WqyJBH/0DJkD6QZI8HkE8p6LEXiPKxGBWbAeVT9Nu9g==", - "requires": { - "commander": "^2.14.1", - "lodash": "^4.17.5", - "mocha": "^5.0.1", - "mustache": "^2.3.0", - "react": "^16.2.0", - "react-dom": "^16.2.0", - "shelljs": "^0.8.1" - }, - "dependencies": { - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" - }, - "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" - } - } - }, - "shelljs": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", - "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - } - } - }, - "solidity-parser-antlr": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/solidity-parser-antlr/-/solidity-parser-antlr-0.2.15.tgz", - "integrity": "sha512-EzRI8/TR/ljTXkZAyAjb0w4G20wH2XM7pcNf87ifdV825AbUv7DkY7HmiZCTj6NeKtIx8Y1s0NZWPbj+JTp8Zw==" - }, "solidity-parser-sc": { "version": "0.4.11", "resolved": "https://registry.npmjs.org/solidity-parser-sc/-/solidity-parser-sc-0.4.11.tgz", @@ -8325,68 +6740,6 @@ } } }, - "solium": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/solium/-/solium-1.1.8.tgz", - "integrity": "sha512-fn0lusM6of14CytIDDHK73SGjn6NsVTaCVJjaKCKJyqKhT00rH/hDtvnIeZ2ZTD9z/xaXd4Js2brW3az6AV9RA==", - "requires": { - "ajv": "^5.2.2", - "chokidar": "^1.6.0", - "colors": "^1.1.2", - "commander": "^2.9.0", - "eol": "^0.9.1", - "js-string-escape": "^1.0.1", - "lodash": "^4.14.2", - "sol-digger": "0.0.2", - "sol-explore": "1.6.1", - "solium-plugin-security": "0.1.1", - "solparse": "2.2.5", - "text-table": "^0.2.0" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "sol-explore": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/sol-explore/-/sol-explore-1.6.1.tgz", - "integrity": "sha1-tZ8HPGn+MyVg1aEMMrqMp/KYbPs=" - }, - "solparse": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/solparse/-/solparse-2.2.5.tgz", - "integrity": "sha512-t7tvtR6KU6QfPYLMv1nlCh9DA8HYIu5tbjHpKu0fhGFZ1NuSp0KKDHfFHv07g6v1xgcuUY3rVqNFjZt5b9+5qA==", - "requires": { - "mocha": "^4.0.1", - "pegjs": "^0.10.0", - "yargs": "^10.0.3" - } - } - } - }, - "solium-plugin-security": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/solium-plugin-security/-/solium-plugin-security-0.1.1.tgz", - "integrity": "sha512-kpLirBwIq4mhxk0Y/nn5cQ6qdJTI+U1LO3gpoNIcqNaW+sI058moXBe2UiHs+9wvF9IzYD49jcKhFTxcR9u9SQ==" - }, "sort-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", @@ -8501,11 +6854,6 @@ } } }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", @@ -8608,11 +6956,6 @@ "is-hex-prefixed": "1.0.0" } }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, "supports-color": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", @@ -8626,50 +6969,6 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" }, - "sync-request": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.0.0.tgz", - "integrity": "sha512-jGNIAlCi9iU4X3Dm4oQnNQshDD3h0/1A7r79LyqjbjUnj69sX6mShAXlhRXgImsfVKtTcnra1jfzabdZvp+Lmw==", - "requires": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - } - }, - "sync-rpc": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.4.tgz", - "integrity": "sha512-Iug+t1ICVFenUcTnDu8WXFnT+k8IVoLKGh8VA3eXUtl2Rt9SjKX3YEv33OenABqpTPL9QEaHv1+CNn2LK8vMow==", - "requires": { - "get-port": "^3.1.0" - } - }, - "table": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/table/-/table-5.1.0.tgz", - "integrity": "sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg==", - "requires": { - "ajv": "^6.5.3", - "lodash": "^4.17.10", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "requires": { - "is-fullwidth-code-point": "^2.0.0" - } - } - } - }, "tape": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/tape/-/tape-4.9.1.tgz", @@ -8724,31 +7023,6 @@ "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.2.0.tgz", "integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==" }, - "then-request": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.0.tgz", - "integrity": "sha512-xA+7uEMc+jsQIoyySJ93Ad08Kuqnik7u6jLS5hR91Z3smAoCfL3M8/MqMlobAa9gzBfO9pA88A/AntfepkkMJQ==", - "requires": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^7.0.0", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "dependencies": { - "@types/node": { - "version": "8.10.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.36.tgz", - "integrity": "sha512-SL6KhfM7PTqiFmbCW3eVNwVBZ+88Mrzbuvn9olPsfv43mbiWaFY+nRcz/TGGku0/lc2FepdMbImdMY1JrQ+zbw==" - } - } - }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -9063,14 +7337,6 @@ "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==" }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -9552,14 +7818,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "requires": { - "mkdirp": "^0.5.1" - } - }, "write-file-atomic": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", @@ -9614,65 +7872,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, - "yargs": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.1.2.tgz", - "integrity": "sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==", - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^8.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "yargs-parser": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", - "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", - "requires": { - "camelcase": "^4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - } - } - }, "yeoman-environment": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.3.0.tgz", diff --git a/package.json b/package.json index 18a2748..3f49c55 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ }, "homepage": "https://web.thetta.io", "dependencies": { - "@thetta/core": "https://github.com/Thetta/Thetta-DAO-Framework.git#genericCaller", "babel-eslint": "^8.2.3", "babel-polyfill": "^6.26.0", "babel-register": "^6.26.0", diff --git a/test/BoD.functional.tests.js b/test/BoD.functional.tests.js deleted file mode 100644 index fbc5847..0000000 --- a/test/BoD.functional.tests.js +++ /dev/null @@ -1,93 +0,0 @@ -const CheckExceptions = require("./utils/checkexceptions"); -const should = require("./utils/helpers"); - -const BodDaoFactory = artifacts.require("BodDaoFactory"); -const DaoBaseAuto = artifacts.require("DaoBaseAuto"); -const DaoBase = artifacts.require("DaoBase"); -const DaoBaseWithUnpackers = artifacts.require("DaoBaseWithUnpackers"); -const DaoStorage = artifacts.require("DaoStorage"); -const GenericProposal = artifacts.require("GenericProposal"); -const InformalProposal = artifacts.require("InformalProposal"); -const Voting = artifacts.require("Voting"); -const WeiTask = artifacts.require("WeiGenericTask"); - -contract("BodDaoFactory", (accounts) => { - - const creator = accounts[0]; - const director1 = accounts[1]; - const director2 = accounts[2]; - const director3 = accounts[3]; - const employee1 = accounts[4]; - const employee2 = accounts[5]; - - var bodDaoFactory; - let store; - let daoBase; - let bodDaoAuto; - let informalProposal; - let weiTask; - let voting; - - beforeEach(async () => { - bodDaoFactory = await BodDaoFactory.new(creator, [director1, director2, director3], [employee1, employee2]); - daoBase = DaoBaseWithUnpackers.at(await bodDaoFactory.daoBase()); - store = DaoStorage.at(await bodDaoFactory.store()); - bodDaoAuto = DaoBaseAuto.at(await bodDaoFactory.bodDaoAuto()); - informalProposal = await InformalProposal.new("ANY_TEXT"); - weiTask = await WeiTask.new(daoBase.address, "ANY_CAPTION", "ANY_DESC", true, false, 100, Math.floor(Date.now() / 1000), Math.floor(Date.now() / 1000)); - - // adding test proposal which creates a voting - await bodDaoAuto.addGroupMemberAuto("ANY_GROUP", employee1, { from: director1 }).should.be.fulfilled; - const proposalsCount = await store.getProposalsCount(); - const proposalAddress = await store.getProposalAtIndex(proposalsCount - 1); - const genericProposal = GenericProposal.at(proposalAddress); - const votingAddress = await genericProposal.getVoting(); - voting = Voting.at(votingAddress); - }); - - it("BoD member should be able to add new proposal", async () => { - await daoBase.addNewProposal(informalProposal.address, { from: director1 }).should.be.fulfilled; - }); - - it("employee should not be able to add new proposal", async () => { - await CheckExceptions.checkContractThrows( - daoBase.addNewProposal, [informalProposal.address, { from: employee1 }] - ); - }); - - it("BoD member should be able to manage groups only by voting", async () => { - await bodDaoAuto.addGroupMemberAuto("ANY_GROUP", employee1, { from: director1 }).should.be.fulfilled; - }); - - it("BoD member should be able to vote", async () => { - await voting.vote(true, { from: director2 }).should.be.fulfilled; - }); - - it("employee should not be able to vote", async () => { - await CheckExceptions.checkContractThrows( - voting.vote, [true, { from: employee1 }] - ); - }); - - it("50% of persons in a group are required to vote to finish the voting", async () => { - - let isFinished = await voting.isFinished(); - assert.isFalse(isFinished, "half of the users in group has not voted yet"); - - await voting.vote(false, { from: director2 }).should.be.fulfilled; - - isFinished = await voting.isFinished(); - assert.isTrue(isFinished, "half of the users should have voted"); - }); - - it("50% of 'yes' required to finish the voting with 'yes' result", async () => { - - let isYes = await voting.isYes(); - assert.isFalse(isYes, "half of the users in group has not voted for yes"); - - await voting.vote(true, { from: director2 }).should.be.fulfilled; - - isYes = await voting.isYes(); - assert.isTrue(isYes, "half of the users should have voted for yes"); - }); -}); diff --git a/test/Daico.functional.tests.js b/test/Daico.functional.tests.js index 762551d..3a039a5 100644 --- a/test/Daico.functional.tests.js +++ b/test/Daico.functional.tests.js @@ -3,61 +3,107 @@ const moment = require("moment"); const Daico = artifacts.require("DaicoTestable"); const MintableToken = artifacts.require("MintableToken"); -contract("Daico", (accounts) => { +const { increaseTime } = require("./utils/helpers"); - const evercityMemberAddress = accounts[0]; - const projectOwnerAddress = accounts[1]; - const inverstorAddress = accounts[2]; - const otherAddress = accounts[3]; + +contract("Daico functional tests", (accounts) => { + const evercityMember1 = accounts[0]; + const evercityMember2 = accounts[9]; + const projectOwner = accounts[1]; + const inverstor1 = accounts[2]; + const inverstor2 = accounts[3]; + const inverstor3 = accounts[4]; + const inverstor4 = accounts[5]; + const inverstor5 = accounts[6]; + const returnFunds = accounts[7]; + const other = accounts[8]; + + const VOTING_TYPE_RELEASE_TAP = 0; + const VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM = 1; + const VOTING_TYPE_CHANGE_ROADMAP = 2; + const VOTING_TYPE_CHANGE_ROADMAP_DECREASED_QUORUM = 3; + const VOTING_TYPE_TERMINATE_PROJECT = 4; + const VOTING_TYPE_TERMINATE_PROJECT_DECREASED_QUORUM = 5; + + const VOTING_RESULT_ACCEPT = 0; + const VOTING_RESULT_DECLINE = 1; + const VOTING_RESULT_QUORUM_NOT_REACHED = 2; + const VOTING_RESULT_NO_DECISION = 3; const minQuorumRate = 70; const minVoteRate = 70; - const tokenHoldersCount = 5; + const days = 24 * 60 * 60; let daico; let daiToken; let projectToken; + let timestampsFinishAt; - 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"); - }); + describe("Different scenarios", () => { + beforeEach(async() => { + // 1.1. evercityMember деплоит daiToken + daiToken = await MintableToken.new({from: evercityMember1}); + + // 1.2. У двух evercityMember появляются daiToken + await daiToken.mint(evercityMember1, 100, {from: evercityMember1}); + await daiToken.mint(evercityMember2, 150, {from: evercityMember1}); + + // 1.3. projectOwner деплоит projectToken + projectToken = await MintableToken.new({from: projectOwner}); + + // 1.4. projectOwner продает эти токены инвесторам (вне приложения, возможно за фиат) + await projectToken.mint(inverstor1, 200, {from: projectOwner}); + await projectToken.mint(inverstor2, 501, {from: projectOwner}); + await projectToken.mint(inverstor3, 150, {from: projectOwner}); + await projectToken.mint(inverstor4, 149, {from: projectOwner}); - it("should call method that can be executed only by investor", async() => { - await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + // 1.5. projectOwner: + // согласует значения minVoteRate, minQuorumRate (или ставит произвольные), + // узнает daiToken, returnFunds + // деплоит Daico контракт + // evercityMember1 и evercityMember2 проверяют все значения (например, что returnFunds тот самый, иначе projectOwner может смошенничать) + timestampsFinishAt = [ + moment.unix(web3.eth.getBlock("latest").timestamp).add(1, 'week').unix(), + moment.unix(web3.eth.getBlock("latest").timestamp).add(1, 'week').unix(), + moment.unix(web3.eth.getBlock("latest").timestamp).add(1, 'week').unix() + ]; + daico = await Daico.new(daiToken.address, projectToken.address, projectOwner, returnFunds, 3, [50, 100, 100], timestampsFinishAt, minVoteRate, minQuorumRate, {from: projectOwner}); + + // 1.6. evercityMember1 и evercityMember2 переводят токены на Daico адрес, который им дал projectOwner + await daiToken.transfer(daico.address, 100, {from: evercityMember1}); + await daiToken.transfer(daico.address, 150, {from: evercityMember2}); }); - }); - describe("validTapIndex()", () => { - it("should revert if tap index does not exist", async() => { - await daico.isTapWithdrawAcceptedByInvestors(2).should.be.rejectedWith("revert"); - }); + it("1. Стандартный сценарий: все идет хорошо", async() => { + // 1.7. Первая стадия голосования наступает автоматически, создавать голосование не требуется + // minVoteRate == minQuorumRate == 70%, то есть голосов inverstor1 (200/1000) и inverstor2 (501/1000) достаточно, чтобы перейти на следующий stage + await daico.vote(0, true, {from: inverstor1}).should.be.fulfilled; + await daico.vote(0, true, {from: inverstor2}).should.be.fulfilled; + + // 1.8. Проходит неделя и projectOwner снимает часть средств. + assert.equal(await daiToken.balanceOf(projectOwner), 0); + await daico.withdrawTapPayment(0, {from: projectOwner}).should.be.fulfilled; + assert.equal(new web3.BigNumber(await daiToken.balanceOf(projectOwner)).toNumber(), 50); - it("should call method with valid tap index", async() => { - await daico.isTapWithdrawAcceptedByInvestors(1).should.be.fulfilled; - }); - }); + // 1.9. Проходит неделя, projectOwner создает новое голосование и инвесторы голосуют по новой. + await increaseTime(7 * days); + await daico.createVotingByOwner(1, VOTING_TYPE_RELEASE_TAP, {from: projectOwner}); + + await daico.vote(1, true, {from: inverstor2}).should.be.fulfilled; + await daico.vote(1, true, {from: inverstor3}).should.be.fulfilled; + await daico.vote(1, true, {from: inverstor4}).should.be.fulfilled; + await daico.withdrawTapPayment(1, {from: projectOwner}).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"); - }); + // 1.10. И Снова + await increaseTime(7 * days); + await daico.createVotingByOwner(2, VOTING_TYPE_RELEASE_TAP, {from: projectOwner}); + await daico.vote(2, true, {from: inverstor2}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstor3}).should.be.fulfilled; + await daico.vote(2, true, {from: inverstor4}).should.be.fulfilled; + await daico.withdrawTapPayment(2, {from: projectOwner}).should.be.fulfilled; - it("should call method with valid tap index", async() => { - await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; + // Проверяем баланс – projectOwner должен был получить все daiToken, которые проинвестировали + assert.equal(await daiToken.balanceOf(projectOwner), 250); }); }); - }); diff --git a/test/Daico.tests.js b/test/Daico.tests.js index d3f4726..13a8ea4 100644 --- a/test/Daico.tests.js +++ b/test/Daico.tests.js @@ -4,8 +4,7 @@ const { increaseTime } = require("./utils/helpers"); const Daico = artifacts.require("DaicoTestable"); const MintableToken = artifacts.require("MintableToken"); -contract("Daico", (accounts) => { - +contract("Daico unit tests", (accounts) => { const evercityMemberAddress = accounts[0]; const projectOwnerAddress = accounts[1]; const inverstorAddress = accounts[2]; @@ -13,6 +12,8 @@ contract("Daico", (accounts) => { const inverstorAddress3 = accounts[4]; const inverstorAddress4 = accounts[5]; const inverstorAddress5 = accounts[6]; + const returnAddress = accounts[7]; + const otherAddress = accounts[8]; const VOTING_TYPE_RELEASE_TAP = 0; const VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM = 1; @@ -28,7 +29,6 @@ contract("Daico", (accounts) => { const minQuorumRate = 70; const minVoteRate = 70; - const tokenHoldersCount = 5; let daico; let daiToken; @@ -49,49 +49,45 @@ contract("Daico", (accounts) => { 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); + daico = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate); await daiToken.transfer(daico.address, 3, {from: evercityMemberAddress}); }); describe("constructor()", () => { it("should revert if DAI token address is 0x00", async() => { - await Daico.new(0x00, projectToken.address, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount).should.be.rejectedWith("revert"); + await Daico.new(0x00, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); }); it("should revert if project token address is 0x00", async() => { - await Daico.new(daiToken.address, 0x00, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount).should.be.rejectedWith("revert"); + await Daico.new(daiToken.address, 0x00, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); }); it("should revert if project owner address is 0x00", async() => { - await Daico.new(daiToken.address, projectToken.address, 0x00, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount).should.be.rejectedWith("revert"); + await Daico.new(daiToken.address, projectToken.address, 0x00, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); }); it("should revert if taps count is 0", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 0, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount).should.be.rejectedWith("revert"); + await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 0, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); }); it("should revert if tap amounts array length not equal to taps count", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [], timestampsFinishAt, minVoteRate, minQuorumRate, tokenHoldersCount).should.be.rejectedWith("revert"); + await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); }); it("should revert if tap timestamps finish at array length not equal to taps count", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], [], minVoteRate, minQuorumRate, tokenHoldersCount).should.be.rejectedWith("revert"); + await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], [], minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); }); it("should revert if min quorum rate is 0", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, 0, minVoteRate, tokenHoldersCount).should.be.rejectedWith("revert"); + await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, 0, minVoteRate).should.be.rejectedWith("revert"); }); it("should revert if min vote rate is 0", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, minQuorumRate, 0, tokenHoldersCount).should.be.rejectedWith("revert"); - }); - - it("should revert if token holders count is 0", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], timestampsFinishAt, minQuorumRate, minVoteRate, 0).should.be.rejectedWith("revert"); + await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, 0, minQuorumRate).should.be.rejectedWith("revert"); }); it("should set contract properties", async() => { - const daicoNew = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, 2, [1, 2], [3,4], 3, 4, 5).should.be.fulfilled; + const daicoNew = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], [3,4], 3, 4).should.be.fulfilled; assert.equal(await daicoNew.daiToken(), daiToken.address); assert.equal(await daicoNew.projectToken(), projectToken.address); assert.equal(await daicoNew.projectOwner(), projectOwnerAddress); @@ -102,7 +98,7 @@ contract("Daico", (accounts) => { assert.equal(await daicoNew.tapTimestampsFinishAt(1), 4); assert.equal(await daicoNew.minQuorumRate(), 3); assert.equal(await daicoNew.minVoteRate(), 4); - assert.equal(await daicoNew.tokenHoldersCount(), 5); + }); it("should create initial votings of type ReleaseTap", async() => { @@ -126,7 +122,7 @@ contract("Daico", (accounts) => { await daico.createVoting(0, 1, 1, 0, VOTING_TYPE_RELEASE_TAP).should.be.rejectedWith("revert"); }); - it("should create a new voting", async() => { + it("should create a new voting (daico.createVoting)", async() => { await daico.createVoting(0, 1, 2, 3, VOTING_TYPE_RELEASE_TAP).should.be.fulfilled; const votingsCount = await daico.votingsCount(); const voting = await daico.votings(votingsCount.sub(1)); @@ -336,10 +332,9 @@ contract("Daico", (accounts) => { await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM, {from: evercityMemberAddress}).should.be.rejectedWith("revert"); }); - it("should create a new voting", async() => { + it("should create a new voting createVotingByOwner", 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; }); @@ -349,8 +344,7 @@ contract("Daico", (accounts) => { it("should return 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; - assert.equal(await daico.getVotingResult(0), VOTING_RESULT_QUORUM_NOT_REACHED); + assert.equal(new web3.BigNumber(await daico.getVotingResult(0)).toNumber(), VOTING_RESULT_QUORUM_NOT_REACHED); }); it("should return accept", async() => { @@ -358,7 +352,7 @@ contract("Daico", (accounts) => { 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 daico.getVotingResult(0), VOTING_RESULT_ACCEPT); + assert.equal(new web3.BigNumber(await daico.getVotingResult(0)).toNumber(), VOTING_RESULT_ACCEPT); }); it("should return decline", async() => { @@ -402,13 +396,11 @@ contract("Daico", (accounts) => { 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); }); }); @@ -433,7 +425,7 @@ contract("Daico", (accounts) => { }); 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); + const daicoNew = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate); await increaseTime(7 * 24 * 60 * 60); await daicoNew.vote(0, true, {from: inverstorAddress}).should.be.rejectedWith("revert"); }); @@ -443,13 +435,11 @@ contract("Daico", (accounts) => { 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"); }); @@ -488,17 +478,15 @@ contract("Daico", (accounts) => { 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); + assert.equal(await daiToken.balanceOf(returnAddress), 0); await daico.withdrawFunding({from: evercityMemberAddress}).should.be.fulfilled; - assert.equal(await daiToken.balanceOf(evercityMemberAddress), 3); + assert.equal(await daiToken.balanceOf(returnAddress), 3); }); }); @@ -545,4 +533,34 @@ contract("Daico", (accounts) => { }); }); + 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/DevZenDao.functional.tests.js b/test/DevZenDao.functional.tests.js deleted file mode 100644 index a9712a0..0000000 --- a/test/DevZenDao.functional.tests.js +++ /dev/null @@ -1,321 +0,0 @@ -const { increaseTime } = require("./utils/helpers"); - -const DevZenDaoFactory = artifacts.require("DevZenDaoFactory"); -const DevZenDaoAuto = artifacts.require("DevZenDaoAuto"); -const DevZenDao = artifacts.require("DevZenDao"); -const DaoBase = artifacts.require("DaoBase"); -const IProposal = artifacts.require("IProposal"); -const IVoting = artifacts.require("IVoting"); - -const DevZenDaoWithUnpackers = artifacts.require("DevZenDaoWithUnpackers"); -const StdDaoToken = artifacts.require("StdDaoToken"); - -const getVoting = async(daoBase,i) => { - let pa = await daoBase.getProposalAtIndex(i); - let proposal = await IProposal.at(pa); - let votingAddress = await proposal.getVoting(); - let voting = await IVoting.at(votingAddress); - return voting; -} - -const checkVoting = async(voting, yes, no, finished, isYes) => { - const r1 = await voting.getVotingStats(); - assert.equal(r1[0].toNumber(),yes,'yes'); - assert.equal(r1[1].toNumber(),no,'no'); - assert.strictEqual(await voting.isFinished(),finished,'Voting is still not finished'); - assert.strictEqual(await voting.isYes(),isYes,'Voting is still not finished'); -} - -contract("DevZenDaoAuto", (accounts) => { - - const boss = accounts[0]; - const newBoss = accounts[1] - const guest1 = accounts[2]; - const guest2 = accounts[3]; - const guest3 = accounts[4]; - const teamMember1 = accounts[5]; - const teamMember2 = accounts[6]; - const patron = accounts[7]; - - let devZenDaoFactory; - let devZenDao; - let devZenToken; - let repToken; - let devZenDaoAuto; - let daoBase; - - beforeEach(async () => { - devZenDaoFactory = await DevZenDaoFactory.new(boss, [teamMember1, teamMember2]); - devZenDao = DevZenDao.at(await devZenDaoFactory.devZenDao()); - daoBase = DaoBase.at(await devZenDaoFactory.daoBase()); - devZenToken = StdDaoToken.at(await devZenDao.devZenToken()); - repToken = StdDaoToken.at(await devZenDao.repToken()); - devZenDaoAuto = DevZenDaoAuto.at(await devZenDaoFactory.devZenDaoAuto()); - }); - - describe("addGroupMemberAuto", () => { - it("should add a new group member on successful voting", async() => { - await devZenDaoAuto.addGroupMemberAuto("DevZenTeam", guest1, {from: boss}).should.be.fulfilled; - const voting = await getVoting(daoBase, 0); - - const membersBefore = await daoBase.getGroupMembers("DevZenTeam"); - assert.equal(membersBefore.length, 3); - - await voting.vote(true, {from: teamMember1}).should.be.fulfilled; - - const membersAfter = await daoBase.getGroupMembers("DevZenTeam"); - assert.equal(membersAfter.length, 4); - assert.equal(membersAfter[3], guest1); - }); - - it("should not add a new group member on failed voting", async() => { - await devZenDaoAuto.addGroupMemberAuto("DevZenTeam", guest1, {from: boss}).should.be.fulfilled; - const voting = await getVoting(daoBase, 0); - - const membersBefore = await daoBase.getGroupMembers("DevZenTeam"); - assert.equal(membersBefore.length, 3); - - await voting.vote(false, {from: teamMember1}).should.be.fulfilled; - - const membersAfter = await daoBase.getGroupMembers("DevZenTeam"); - assert.equal(membersAfter.length, 3); - }); - }); - - describe("withdrawEtherAuto", () => { - it("should withdraw ether to specified address", async() => { - await devZenDao.moveToNextEpisode(false, {from:boss}).should.be.fulfilled; - - const initialBalance = web3.eth.getBalance(patron); - const value = web3.toWei(1, "ether"); - await devZenDao.buyTokens({ value: value, from: patron }).should.be.fulfilled; - - const balanceAfterTokensBought = web3.eth.getBalance(patron); - assert.isTrue(initialBalance.toNumber() - balanceAfterTokensBought.toNumber() > value, 'patron should spend 1 ETH on tokens'); - - await devZenDaoAuto.withdrawEtherAuto(patron,{from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,0); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - const balanceAfterWithdraw = web3.eth.getBalance(patron); - assert.isTrue(balanceAfterWithdraw.toNumber() > balanceAfterTokensBought.toNumber(), '1 ETH should be withdrawn to patron'); - }); - }); - - describe("selectNextHostAuto", () => { - it("should set next episode's host if it is not yet selected", async() => { - await devZenDaoAuto.selectNextHostAuto(boss, {from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,0); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - - const nextEpisode = await devZenDao.nextEpisode(); - const nextShowHostIndex = 0; - assert.equal(nextEpisode[nextShowHostIndex], boss); - }); - - }); - - describe("changeTheGuestAuto", () => { - it("should set the new guest", async() => { - await devZenDao.moveToNextEpisode(false,{from:boss}).should.be.fulfilled; - const value = web3.toWei("0.5", "ether"); - - // guest1 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guest1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guest1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guest1 }).should.be.fulfilled; - - const nextShowGuestIndex = 1; - let nextEpisode = await devZenDao.nextEpisode(); - assert.equal(nextEpisode[nextShowGuestIndex], guest1, "guest1 is now guest because he has paid for it"); - - // guest2 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guest2 }).should.be.fulfilled; - // guest2 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guest2 }); - - // manually change the guest to guest2 - await devZenDaoAuto.changeTheGuestAuto(guest2, {from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,0); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - - nextEpisode = await devZenDao.nextEpisode(); - assert.equal(nextEpisode[nextShowGuestIndex], guest2, "guest2 is now guest because he was selected manually"); - }); - - it("should return stake to previous guest", async() => { - await devZenDao.moveToNextEpisode(false, {from:boss}).should.be.fulfilled; - const value = web3.toWei("0.5", "ether"); - - // guest1 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guest1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guest1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guest1 }).should.be.fulfilled; - - // guest2 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guest2 }).should.be.fulfilled; - // guest2 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guest2 }); - - const guest1BalanceBefore = await devZenToken.balanceOf(guest1); - assert.equal(guest1BalanceBefore.toNumber(), 0, "should be 0 because guest1 bought 5 DZT and put them at stake to become a guest") - - // manually change the guest to guest2 - await devZenDaoAuto.changeTheGuestAuto(guest2,{from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,0); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - - const guest1BalanceAfter = await devZenToken.balanceOf(guest1); - assert.equal(guest1BalanceAfter.toNumber(), 5e18, "should be 5 because stake is returned to guest1"); - }); - - it("should not return stake to previous guest if it was an emergency guest", async() => { - await devZenDao.moveToNextEpisode(false,{from:boss}).should.be.fulfilled; - const value = web3.toWei("0.5", "ether"); - - // guest1 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guest1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guest1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guest1 }).should.be.fulfilled; - - // host sets guest2 an emergency guest - await devZenDaoAuto.emergency_ChangeTheGuestAuto(guest2,{from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,0); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - - // guest3 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guest3 }).should.be.fulfilled; - // guest3 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guest3 }); - - const balanceGuest2Before = await devZenToken.balanceOf(guest2); - assert.equal(balanceGuest2Before.toNumber(), 0, "should be 0 because it is an emergency guest"); - - // host sets "legal" guest - await devZenDaoAuto.changeTheGuestAuto(guest3,{from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,1); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - - const balanceGuest2After = await devZenToken.balanceOf(guest2); - assert.equal(balanceGuest2After.toNumber(), 0, "should be 0 because emergency guest put nothing at stake"); - }); - }); - - describe("moveToNextEpisodeAuto", () => { - - it("should mint DZTREP to guest if he came", async() => { - await devZenDaoAuto.moveToNextEpisodeAuto(false,{from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,0); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guest1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guest1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guest1 }).should.be.fulfilled; - - const repBalanceBefore = await repToken.balanceOf(guest1); - assert.equal(repBalanceBefore.toNumber(), 0); - - // 7 days passed and guest came - await increaseTime(60 * 60 * 24 * 7); - await devZenDao.moveToNextEpisode(true,{from:boss}).should.be.fulfilled; - - const repTokensRewardGuest = await devZenDao.params(web3.sha3("RepTokensReward_Guest")); - const repTokensRewardGuestIndex = 6; - - const repBalanceAfter = await repToken.balanceOf(guest1); - assert.equal(repBalanceAfter.toNumber(), repTokensRewardGuest.toNumber()); - }); - - it("should transfer guest's stake back if initial guest has come", async() => { - await devZenDaoAuto.moveToNextEpisodeAuto(false,{from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,0); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guest1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guest1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guest1 }).should.be.fulfilled; - - const dztBalanceBefore = await devZenToken.balanceOf(guest1); - assert.equal(dztBalanceBefore.toNumber(), 0, "guest's 5 DZT were transfered to contract"); - - // 7 days passed and guest came - await increaseTime(60 * 60 * 24 * 7); - await devZenDao.moveToNextEpisode(true,{from:boss}).should.be.fulfilled; - - const dztBalanceAfter = await devZenToken.balanceOf(guest1); - assert.equal(dztBalanceAfter.toNumber(), 5e18, "guest's 5 DZT were tansfered back to guest"); - }); - }); - - describe("removeGroupMemberAuto", () => { - it("should remove group member on successful voting", async() => { - await devZenDaoAuto.removeGroupMemberAuto("DevZenTeam", teamMember2, {from: boss}).should.be.fulfilled; - const voting = await getVoting(daoBase, 0); - - const membersBefore = await daoBase.getGroupMembers("DevZenTeam"); - assert.equal(membersBefore.length, 3); - - await voting.vote(true, {from: teamMember1}).should.be.fulfilled; - - const membersAfter = await daoBase.getGroupMembers("DevZenTeam"); - assert.equal(membersAfter.length, 2); - }); - - it("should not remove group member on failed voting", async() => { - await devZenDaoAuto.removeGroupMemberAuto("DevZenTeam", teamMember2, {from: boss}).should.be.fulfilled; - const voting = await getVoting(daoBase, 0); - - const membersBefore = await daoBase.getGroupMembers("DevZenTeam"); - assert.equal(membersBefore.length, 3); - - await voting.vote(false, {from: teamMember1}).should.be.fulfilled; - - const membersAfter = await daoBase.getGroupMembers("DevZenTeam"); - assert.equal(membersAfter.length, 3); - }); - }); - - describe("updateDaoParamsAuto", () => { - it("should update param", async() => { - let paramHash = await devZenDao.MINT_TOKENS_PER_WEEK_AMOUNT(); - let paramValueBefore = await devZenDao.params(paramHash); - await devZenDaoAuto.updateDaoParamsAuto(paramHash, paramValueBefore.toNumber()*2, {from:teamMember1}).should.be.fulfilled; - voting = await getVoting(daoBase,0); - await checkVoting(voting, 1, 0, false, false); - await voting.vote(true,{from:teamMember2}); - await checkVoting(voting, 2, 0, true, true); - - let paramValueAfter = await devZenDao.params(paramHash); - assert.equal(paramValueBefore.toNumber()*2, paramValueAfter.toNumber()); - }); - }); -}); \ No newline at end of file diff --git a/test/DevZenDao.testable.functional.tests.js b/test/DevZenDao.testable.functional.tests.js deleted file mode 100644 index cdc3c0c..0000000 --- a/test/DevZenDao.testable.functional.tests.js +++ /dev/null @@ -1,471 +0,0 @@ -const { increaseTime } = require("./utils/helpers"); - -const DevZenDaoFactory = artifacts.require("DevZenDaoFactory"); -const DevZenDaoFactoryTestable = artifacts.require("DevZenDaoFactoryTestable"); -const DevZenDaoTestable = artifacts.require("DevZenDaoTestable"); - -const DevZenDao = artifacts.require("DevZenDao"); -const StdDaoToken = artifacts.require("StdDaoToken"); - -contract("DevZenDaoCore", (accounts) => { - const patronAddr1 = accounts[0]; - const patronAddr2 = accounts[1]; - const hostAddr1 = accounts[2]; - const hostAddr2 = accounts[3]; - const guestAddr1 = accounts[4]; - const guestAddr2 = accounts[5]; - const guestAddr3 = accounts[6]; - const teamMemberAddr1 = accounts[7]; - const teamMemberAddr2 = accounts[8]; - - let devZenDaoFactory; - let devZenDao; - let devZenToken; - let repToken; - - beforeEach(async () => { - devZenDaoFactory = await DevZenDaoFactoryTestable.new(hostAddr1, [teamMemberAddr1, teamMemberAddr2]); - - const devZenDaoAddr = await devZenDaoFactory.devZenDao(); - devZenDao = DevZenDaoTestable.at(devZenDaoAddr); - - const devZenTokenAddr = await devZenDao.devZenToken(); - devZenToken = StdDaoToken.at(devZenTokenAddr); - - const repTokenAddr = await devZenDao.repToken(); - repToken = StdDaoToken.at(repTokenAddr); - }); - - describe("withdrawEther", () => { - it("should withdraw ether to specified address", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - - const initialBalance = web3.eth.getBalance(patronAddr1); - const value = web3.toWei(1, "ether"); - await devZenDao.buyTokens({ value: value, from: patronAddr1 }).should.be.fulfilled; - - const balanceAfterTokensBought = web3.eth.getBalance(patronAddr1); - assert.isTrue(initialBalance.toNumber() - balanceAfterTokensBought.toNumber() > value, 'patron should spend 1 ETH on tokens'); - - await devZenDao.withdrawEther(patronAddr1).should.be.fulfilled; - - const balanceAfterWithdraw = web3.eth.getBalance(patronAddr1); - assert.isTrue(balanceAfterWithdraw.toNumber() > balanceAfterTokensBought.toNumber(), '1 ETH should be withdrawn to patron'); - }); - }); - - describe("selectNextHost", () => { - it("should set next episode's host if it is not yet selected", async() => { - await devZenDao.selectNextHost(hostAddr1).should.be.fulfilled; - const nextEpisode = await devZenDao.nextEpisode(); - const nextShowHostIndex = 0; - assert.equal(nextEpisode[nextShowHostIndex], hostAddr1); - }); - - it("should should throw if next episode's host is already selected", async() => { - await devZenDao.selectNextHost(hostAddr1).should.be.fulfilled; - await devZenDao.selectNextHost(hostAddr2).should.be.rejectedWith("revert"); - }); - }); - - describe("burnGuestStake", () => { - it("should burn guest's stake", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - - const balanceBeforeBurn = await devZenToken.balanceOf(devZenDao.address); - assert.equal(balanceBeforeBurn.toNumber(), 10e18, "on new episode 10 DZT are minted to contract"); - - await devZenDao.burnGuestStake().should.be.fulfilled; - - const balanceAfterBurn = await devZenToken.balanceOf(devZenDao.address); - assert.equal(balanceAfterBurn.toNumber(), 5e18, "burns 5 DZT at guest's stake"); - }); - }); - - describe("changeTheGuest", () => { - it("should throw if next guest is not selected", async() => { - await devZenDao.changeTheGuest(guestAddr1).should.be.rejectedWith("revert"); - }); - - it("should set the new guest", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - const value = web3.toWei("0.5", "ether"); - - // guest1 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - const nextShowGuestIndex = 1; - let nextEpisode = await devZenDao.nextEpisode(); - assert.equal(nextEpisode[nextShowGuestIndex], guestAddr1, "guest1 is now guest because he has paid for it"); - - // guest2 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guestAddr2 }).should.be.fulfilled; - // guest2 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr2 }); - - // manually change the guest to guest2 - await devZenDao.changeTheGuest(guestAddr2).should.be.fulfilled; - - nextEpisode = await devZenDao.nextEpisode(); - assert.equal(nextEpisode[nextShowGuestIndex], guestAddr2, "guest2 is now guest because he was selected manually"); - }); - - it("should return stake to previous guest", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - const value = web3.toWei("0.5", "ether"); - - // guest1 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - // guest2 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guestAddr2 }).should.be.fulfilled; - // guest2 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr2 }); - - const guest1BalanceBefore = await devZenToken.balanceOf(guestAddr1); - assert.equal(guest1BalanceBefore.toNumber(), 0, "should be 0 because guest1 bought 5 DZT and put them at stake to become a guest") - - // manually change the guest to guest2 - await devZenDao.changeTheGuest(guestAddr2).should.be.fulfilled; - - const guest1BalanceAfter = await devZenToken.balanceOf(guestAddr1); - assert.equal(guest1BalanceAfter.toNumber(), 5e18, "should be 5 because stake is returned to guest1"); - }); - - it("should not return stake to previous guest if it was an emergency guest", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - const value = web3.toWei("0.5", "ether"); - - // guest1 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - // host sets guest2 an emergency guest - await devZenDao.emergency_ChangeTheGuest(guestAddr2).should.be.fulfilled; - - // guest3 buys 5 DZT - await devZenDao.buyTokens({ value: value, from: guestAddr3 }).should.be.fulfilled; - // guest3 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr3 }); - - const balanceGuest2Before = await devZenToken.balanceOf(guestAddr2); - assert.equal(balanceGuest2Before.toNumber(), 0, "should be 0 because it is an emergency guest"); - - // host sets "legal" guest - await devZenDao.changeTheGuest(guestAddr3).should.be.fulfilled; - - const balanceGuest2After = await devZenToken.balanceOf(guestAddr2); - assert.equal(balanceGuest2After.toNumber(), 0, "should be 0 because emergency guest put nothing at stake"); - }); - }); - - describe("emergency_ChangeTheGuest", () => { - it("should throw if next show guest is not set", async() => { - await devZenDao.emergency_ChangeTheGuest(guestAddr1).should.be.rejectedWith("revert"); - }); - - it("should change next episode's guest and mark guest as updated", async() => { - const guestHasCome = false; - await devZenDao.moveToNextEpisode(guestHasCome).should.be.fulfilled; - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - await devZenDao.emergency_ChangeTheGuest(guestAddr2).should.be.fulfilled; - const nextEpisode = await devZenDao.nextEpisode(); - const nextShowGuestIndex = 1; - const isGuestUpdatedIndex = 6; - assert.equal(nextEpisode[nextShowGuestIndex], guestAddr2); - assert.isTrue(nextEpisode[isGuestUpdatedIndex]); - }); - }); - - describe("moveToNextEpisode", () => { - it("should mint DZT and DZTREP to contract", async() => { - const dztBefore = await devZenToken.balanceOf(devZenDao.address); - const dztRepBebore = await repToken.balanceOf(devZenDao.address); - assert.equal(dztBefore, 0); - assert.equal(dztRepBebore, 0); - - const guestHasCome = false; - await devZenDao.moveToNextEpisode(guestHasCome).should.be.fulfilled; - - const mintTokensPerWeekAmountIndexParam = await devZenDao.params(web3.sha3("MintTokensPerWeekAmount")); - const mintReputationTokensPerWeekAmountParam = await devZenDao.params(web3.sha3("MintReputationTokensPerWeekAmount")); - const mintTokensPerWeekAmountIndex = 0; - const mintReputationTokensPerWeekAmount = 1; - - const dztAfter = await devZenToken.balanceOf(devZenDao.address); - const dztRepAfter = await repToken.balanceOf(devZenDao.address); - assert.equal(dztAfter, mintTokensPerWeekAmountIndexParam.toNumber()); - assert.equal(dztRepAfter, mintReputationTokensPerWeekAmountParam.toNumber()); - }); - - it("should mint DZTREP to guest if he came", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - const repBalanceBefore = await repToken.balanceOf(guestAddr1); - assert.equal(repBalanceBefore.toNumber(), 0); - - // 7 days passed and guest came - await increaseTime(60 * 60 * 24 * 7); - await devZenDao.moveToNextEpisode(true).should.be.fulfilled; - - const repTokensRewardGuestIndexParam = await devZenDao.params(web3.sha3("RepTokensReward_Guest")); - const repTokensRewardGuestIndex = 6; - - const repBalanceAfter = await repToken.balanceOf(guestAddr1); - assert.equal(repBalanceAfter.toNumber(), repTokensRewardGuestIndexParam.toNumber()); - }); - - it("should mint DZTREP to host", async() => { - await devZenDao.selectNextHost(hostAddr1).should.be.fulfilled; - - const repBalanceBefore = await repToken.balanceOf(hostAddr1); - assert.equal(repBalanceBefore.toNumber(), 0); - - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - - const repTokensRewardHostIndexParam = await devZenDao.params(web3.sha3("RepTokensReward_Host")); - const repTokensRewardHostIndex = 5; - - const repBalanceAfter = await repToken.balanceOf(hostAddr1); - assert.equal(repBalanceAfter.toNumber(), repTokensRewardHostIndexParam.toNumber()); - }); - - it("should transfer guest's stake back if initial guest has come", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - const dztBalanceBefore = await devZenToken.balanceOf(guestAddr1); - assert.equal(dztBalanceBefore.toNumber(), 0, "guest's 5 DZT were transfered to contract"); - - // 7 days passed and guest came - await increaseTime(60 * 60 * 24 * 7); - await devZenDao.moveToNextEpisode(true).should.be.fulfilled; - - const dztBalanceAfter = await devZenToken.balanceOf(guestAddr1); - assert.equal(dztBalanceAfter.toNumber(), 5e18, "guest's 5 DZT were tansfered back to guest"); - }); - - it("should burn guest's stake if there was an emergency guest", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - // guest1 becomes the next show guest - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - // emergency, guest2 becomes the guest - await devZenDao.emergency_ChangeTheGuest(guestAddr2).should.be.fulfilled; - - const contractBalanceBefore = await devZenToken.balanceOf(devZenDao.address); - - // 7 days passed and emergency guest came - await increaseTime(60 * 60 * 24 * 7); - await devZenDao.moveToNextEpisode(true).should.be.fulfilled; - - const becomeGuestStakeIndexParam = await devZenDao.params(web3.sha3("BecomeGuestStake")); - const becomeGuestStakeIndex = 4; - - const contractBalanceAfter = await devZenToken.balanceOf(devZenDao.address); - assert.equal(contractBalanceAfter.toNumber() - contractBalanceBefore.toNumber(), becomeGuestStakeIndexParam.toNumber(), "5 DZT should be burnt"); - }); - }); - - describe("runAdsInTheNextEpisode", () => { - it("should throw if all 5 slots are not available", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - // buy 10 DZT - const value = web3.toWei(1, "ether"); - await devZenDao.buyTokens({ value: value, from: patronAddr1 }).should.be.fulfilled; - // post 5 ads - for(let i = 0; i < 5; i++) { - await devZenDao.runAdsInTheNextEpisode("ANY_TEXT", {from: patronAddr1}).should.be.fulfilled; - } - await devZenDao.runAdsInTheNextEpisode("ANY_TEXT", {from: patronAddr1}).should.be.rejectedWith("revert"); - }); - - it("should throw if sender does not have enough DZT", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - await devZenDao.runAdsInTheNextEpisode("ANY_TEXT", {from: patronAddr1}).should.be.rejectedWith("revert"); - }); - - it("should burn sender's tokens if sender buys an ad", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - // buy 10 DZT - const value = web3.toWei(1, "ether"); - await devZenDao.buyTokens({ value: value, from: patronAddr1 }).should.be.fulfilled; - - await devZenDao.runAdsInTheNextEpisode("ANY_TEXT", {from: patronAddr1}).should.be.fulfilled; - - const balanceAfterPurchase = await devZenToken.balanceOf(patronAddr1); - assert.equal(balanceAfterPurchase.toNumber(), 8e18, "sender's balance should move from 10 to 8 DZT"); - }); - - it("should add ad to the slot if sender buys an ad", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - // buy 10 DZT - const value = web3.toWei(1, "ether"); - await devZenDao.buyTokens({ value: value, from: patronAddr1 }).should.be.fulfilled; - - await devZenDao.runAdsInTheNextEpisode("ANY_TEXT", {from: patronAddr1}).should.be.fulfilled; - - const nextEpisode = await devZenDao.nextEpisode(); - const usedSlotsIndex = 4; - assert.equal(nextEpisode[usedSlotsIndex].toNumber(), 1, "used slots number should be increased by 1"); - }); - }); - - describe("becomeTheNextShowGuest", () => { - it("should throw if next guest is already selected", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - // guest1 buys 5 DZT, allows to spend them and becomes the next guest - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - // guest2 buys 5 DZT, allows to spend them and wants to become the next guest - await devZenDao.buyTokens({ value: value, from: guestAddr2 }).should.be.fulfilled; - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr2 }); - await devZenDao.becomeTheNextShowGuest({ from: guestAddr2 }).should.be.rejectedWith("revert"); - }); - }); - - describe("buyTokens", () => { - it("should throw if msg.value = 0", async() => { - await devZenDao.buyTokens().should.be.rejectedWith("revert"); - }); - - it("should throw if there is an insufficient DZT amount in contract", async() => { - const value = web3.toWei(1, "ether"); - await devZenDao.buyTokens({ value: value }).should.be.rejectedWith("revert"); - }); - - it("should transfer tokens to sender if there is a sufficient DZT amount", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - - let balancePatron1 = await devZenToken.balanceOf(patronAddr1); - assert.equal(balancePatron1.toNumber(), 0, "should be zero because patron has not purchased tokens yet"); - - const value = web3.toWei(1, "ether"); - await devZenDao.buyTokens({ value: value, from: patronAddr1 }).should.be.fulfilled; - - balancePatron1 = await devZenToken.balanceOf(patronAddr1); - assert.equal(balancePatron1.toNumber(), 10e18, "should be 10 because 1 token costs 0.1 ETH"); - }); - }); - - describe("isOneWeekPassed", () => { - it("should return true if this is the 1st episode", async() => { - const isOneWeekPassed = await devZenDao.isOneWeekPassed(); - assert.isTrue(isOneWeekPassed, "should be true because this is the 1st episode"); - }); - - it("should return true if 7 days have passed", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - await increaseTime(60 * 60 * 24 * 7); - const isOneWeekPassed = await devZenDao.isOneWeekPassed(); - assert.isTrue(isOneWeekPassed, "should be true because 1 week has passed"); - }); - - it("should return false if 7 days have not passed", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - const isOneWeekPassed = await devZenDao.isOneWeekPassed(); - assert.isFalse(isOneWeekPassed, "should be false because 1 week has not passed"); - }); - }); - - describe("setGuest", () => { - it("should throw if sender does not have enough DZT", async() => { - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.rejectedWith("revert"); - }); - - it("should throw if sender has not allowed dao to put enough DZT at stake", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.rejectedWith("revert"); - }); - - it("should lock tokens", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - - let guestBalance = await devZenToken.balanceOf(guestAddr1); - let contractBalance = await devZenToken.balanceOf(devZenDao.address); - assert.equal(guestBalance, 5e18, "guest balance should be equal 5 DZT"); - assert.equal(contractBalance, 5e18, "contract balance should be equal to 5 DZT, 10 initial DZT - 5 bought by the guest"); - - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - guestBalance = await devZenToken.balanceOf(guestAddr1); - contractBalance = await devZenToken.balanceOf(devZenDao.address); - assert.equal(guestBalance, 0, "guest balance should be 0 because he has put his 5 DZT at stake"); - assert.equal(contractBalance, 10e18, "contract balance should be equal to 10 DZT, 10 initial DZT - 5 bought by the guest + 5 put at stake by the guest"); - }); - - it("should set next show guest", async() => { - await devZenDao.moveToNextEpisode(false).should.be.fulfilled; - // guest1 buys 5 DZT - const value = web3.toWei("0.5", "ether"); - await devZenDao.buyTokens({ value: value, from: guestAddr1 }).should.be.fulfilled; - // guest1 allows to spend his 5 DZT - await devZenToken.approve(devZenDao.address, 5e18, { from: guestAddr1 }); - - await devZenDao.becomeTheNextShowGuest({ from: guestAddr1 }).should.be.fulfilled; - - const nextEpisode = await devZenDao.nextEpisode(); - const nextShowGuestIndex = 1; - assert.equal(nextEpisode[nextShowGuestIndex], guestAddr1, "guest1 should be the next show guest"); - }); - }); - - describe("setParam", () => { - it("should set param", async() => { - let paramHash = await devZenDao.MINT_TOKENS_PER_WEEK_AMOUNT(); - let paramValueBefore = await devZenDao.params(paramHash); - await devZenDao.setParam(paramHash, 2*paramValueBefore.toNumber()).should.be.fulfilled; - let paramValueAfter = await devZenDao.params(paramHash); - assert.equal(paramValueBefore.toNumber()*2, paramValueAfter.toNumber()); - }); - }); -}); \ No newline at end of file diff --git a/test/HierarchyDao.functional.tests.js b/test/HierarchyDao.functional.tests.js deleted file mode 100644 index 3560522..0000000 --- a/test/HierarchyDao.functional.tests.js +++ /dev/null @@ -1,84 +0,0 @@ -const CheckExceptions = require("./utils/checkexceptions"); -const should = require("./utils/helpers"); - -const DaoBaseAuto = artifacts.require("DaoBaseAuto"); -const DaoStorage = artifacts.require("DaoStorage"); -const DaoBaseWithUnpackers = artifacts.require("DaoBaseWithUnpackers"); - -const GenericProposal = artifacts.require("GenericProposal"); -const HierarchyDao = artifacts.require("HierarchyDao"); -const HierarchyDaoFactory = artifacts.require("HierarchyDaoFactory"); -const InformalProposal = artifacts.require("InformalProposal"); -const StdDaoToken = artifacts.require("StdDaoToken"); - -contract('HierarchyDaoFactory', (accounts) => { - const boss = accounts[0]; - const manager1 = accounts[1]; - const manager2 = accounts[2]; - const employee1 = accounts[3]; - const employee2 = accounts[4]; - const outsiderWithTokens = accounts[5]; - const outsiderWithoutTokens = accounts[6]; - - let hierarchyDaoFactory; - let daoBase; - let store; - let hierarchyDaoAuto; - let informalProposal; - let stdDaoToken; - - before(async () => { - hierarchyDaoFactory = await HierarchyDaoFactory.new(boss, [manager1, manager2], [employee1, employee2], [outsiderWithTokens, outsiderWithoutTokens]); - daoBase = DaoBaseWithUnpackers.at(await hierarchyDaoFactory.daoBase()); - store = DaoStorage.at(await hierarchyDaoFactory.store()); - hierarchyDaoAuto = DaoBaseAuto.at(await hierarchyDaoFactory.hierarchyDaoAuto()); - stdDaoToken = StdDaoToken.at(await hierarchyDaoFactory.token()); - informalProposal = await InformalProposal.new("ANY_TEXT"); - }); - - it("boss should be a member of 2 groups: managers and employees", async () => { - const isManager = await daoBase.isGroupMember("Managers", boss); - const isEmployee = await daoBase.isGroupMember("Employees", boss); - - assert.isTrue(isManager, "boss should be in the managers group"); - assert.isTrue(isEmployee, "boss should be in the employees group"); - }); - - it("boss should be able to issue new tokens", async() => { - await daoBase.issueTokens(stdDaoToken.address, employee1, 100, { from: boss }).should.be.fulfilled; - const employee1Balance = await stdDaoToken.balanceOf(employee1); - assert.equal(employee1Balance, 100); - }); - - it("manager should be able to add new proposal", async () => { - await daoBase.addNewProposal(informalProposal.address, { from: manager1 }).should.be.fulfilled; - }); - - it("manager should not be able to issue tokens", async() => { - await CheckExceptions.checkContractThrows( - daoBase.issueTokens, [stdDaoToken.address, employee1, 100, { from: manager1 }] - ); - }); - - it("boss should be able to manage groups only by voting", async () => { - await hierarchyDaoAuto.addGroupMemberAuto("ANY_GROUP", employee1, { from: boss }).should.be.fulfilled; - }); - - it("manager should be able to manage groups only by voting", async () => { - await hierarchyDaoAuto.addGroupMemberAuto("ANY_OTHER_GROUP", employee1, { from: manager1 }).should.be.fulfilled; - }); - - it("outsider (not in groups) with tokens should not be able to add new proposal", async () => { - await daoBase.issueTokens(stdDaoToken.address, outsiderWithTokens, 100, { from: boss }).should.be.fulfilled; - await CheckExceptions.checkContractThrows( - daoBase.addNewProposal, [informalProposal.address, { from: outsiderWithTokens }] - ); - }); - - it("outsider (not in groups) without tokens should not be able to add new proposal", async () => { - await CheckExceptions.checkContractThrows( - daoBase.addNewProposal, [informalProposal.address, { from: outsiderWithoutTokens }] - ); - }); - -}); From 2bfffab296cc2f4b3375b6be2c32c0da0906c32f Mon Sep 17 00:00:00 2001 From: enkogu Date: Thu, 24 Jan 2019 06:03:46 +0700 Subject: [PATCH 2/6] daico reimplemented: no tests --- contracts/Daico/Daico.sol | 701 +++++++++++++++--------------- contracts/Daico/DaicoTestable.sol | 35 -- contracts/Daico/IDaico.sol | 237 +--------- test/Daico.functional.tests.js | 38 +- test/Daico.tests.js | 2 +- 5 files changed, 380 insertions(+), 633 deletions(-) delete mode 100644 contracts/Daico/DaicoTestable.sol diff --git a/contracts/Daico/Daico.sol b/contracts/Daico/Daico.sol index 3fa7188..72186e3 100644 --- a/contracts/Daico/Daico.sol +++ b/contracts/Daico/Daico.sol @@ -1,395 +1,410 @@ pragma solidity ^0.4.24; import "zeppelin-solidity/contracts/math/SafeMath.sol"; -import "zeppelin-solidity/contracts/ownership/Ownable.sol"; import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; -/** - * @title Daico - */ -contract Daico is Ownable { +contract Daico { using SafeMath for uint; + enum TapStage { + Preparing, + Voting, + VotingDQ, + RoadmapPreparing, + RoadmapVoting, + RoadmapVotingDQ, + Success, + Terminated + } - ERC20 public daiToken; - ERC20 public projectToken; - - address public projectOwner; - address public returnAddress; - - uint public minQuorumRate; - uint public minVoteRate; - uint public tapsCount; - uint[] public tapAmounts; - uint[] public tapTimestampsFinishAt; - - enum VotingType { ReleaseTap, ReleaseTapDecreasedQuorum, ChangeRoadmap, ChangeRoadmapDecreasedQuorum, TerminateProject, TerminateProjectDecreasedQuorum } - enum VotingResult { Accept, Decline, QuorumNotReached, NoDecision } - - mapping(uint => mapping(uint => uint)) public tapVotings; - mapping(uint => uint) public tapVotingsCount; - - mapping(uint => Voting) public votings; - uint public votingsCount; + enum VotingResult { + QuorumNotReached, + ConsensusNotReached, + Success, + Decline + } - mapping(uint => TapPayment) public tapPayments; + Project public proj; - struct TapPayment { - uint amount; + struct Project { + bool isActive; + address owner; + MintableToken token; + ERC20 daiToken; uint createdAt; - bool isWithdrawn; + uint startedAt; + uint investDuration; + uint votingDuration; + uint additionalDuration; + uint changeRoadmapDuration; + uint quorumPercent; + uint quorumDecresedPercent; + uint declinePercent; + uint consensusPercent; + Tap[] taps; + Tap[] proposedTaps; + Investor[] investors; } - struct Voting { - uint tapIndex; - uint tokenAmountToAccept; - uint tokenAmountToReject; - uint quorumRate; - uint createdAt; - uint finishAt; - VotingType votingType; - mapping(address => uint) votedByAmount; - address[] voters; + struct Investor { + address addr; + uint invested; } - /** - * Modifiers - */ - - /** - * Modifier checks that method can be called only by investor / project token holder - */ - modifier onlyInvestor() { - require(projectToken.balanceOf(msg.sender) > 0); - _; - } - - /** - * Modifier checks that tap index exists - */ - modifier validTapIndex(uint _tapIndex) { - require(_tapIndex < tapsCount); - _; + struct Tap { + uint funds; + uint duration; + bool isWithdrawed; + mapping(uint => Voting) votings; // 0, 1, 2, 3 – max } - /** - * Modifier checks that voting index exists - */ - modifier validVotingIndex(uint _votingIndex) { - require(_votingIndex < votingsCount); - _; + struct Voting { + uint pro; + uint versus; + address[] voted; + } + + function nonZero(bytes32 _target) public view returns(bool) { + return (_target == bytes32(0)); } - /** - * @dev Contract constructor - * @param _daiTokenAddress address of the DAI token contract, project gets payments in DAI tokens - * @param _projectTokenAddress project token address, investors hold this token - * @param _projectOwnerAddress project owner address who can receive tap payments - * @param _tapsCount how many times project should get payments, NOTICE: we can get taps count from _tapAmounts.length but contract deployer can force so that _tapsCount != _tapAmounts.length - * @param _tapAmounts array of DAI token amounts in wei that describes how many tokens project gets per single stage - * @param _tapTimestampsFinishAt array of deadline timestamps, project should get payment before each deadline timestamp - * @param _minQuorumRate min quorum rate, 100 == 100% - * @param _minVoteRate min vote rate for proposal to be accepted/declined, 100 == 100% - */ - constructor( - address _daiTokenAddress, - address _projectTokenAddress, - address _projectOwnerAddress, - address _returnAddress, - uint _tapsCount, - uint[] _tapAmounts, - uint[] _tapTimestampsFinishAt, - uint _minQuorumRate, - uint _minVoteRate - ) public { - // validation - require(_daiTokenAddress != address(0)); - require(_projectTokenAddress != address(0)); - require(_projectOwnerAddress != address(0)); - require(_tapsCount > 0); - require(_tapAmounts.length == _tapsCount); - require(_tapTimestampsFinishAt.length == _tapsCount); - require(_minQuorumRate > 0); - require(_minVoteRate > 0); - // setting contract properties - daiToken = ERC20(_daiTokenAddress); - projectToken = ERC20(_projectTokenAddress); - projectOwner = _projectOwnerAddress; - returnAddress = _returnAddress; - tapsCount = _tapsCount; - tapAmounts = _tapAmounts; - tapTimestampsFinishAt = _tapTimestampsFinishAt; - minQuorumRate = _minQuorumRate; - minVoteRate = _minVoteRate; - // create initial ReleaseTap votings for all taps - for(uint i = 0; i < tapsCount; i++) { - uint createdAt = tapTimestampsFinishAt[i] - 7 days; - _createVoting(i, minQuorumRate, createdAt, tapTimestampsFinishAt[i], VotingType.ReleaseTap); + // owner создает Daico контракт, куда закладываются следующие параметры: + // 1. daiToken – токен Evercity + // 2. returnAddress - куда вернуть деньги в случае фейла проекта + // 3. tapFunds – массив количества выплат на каждый tap + // 4. tapDurations – массив длительностей каждого tap + // В конструкторе деплоится projectToken, которые будут получать investors в обмен на daiToken + constructor(address _owner, address _daiToken, address _returnAddress, uint[] memory _tapFunds, uint[] memory _tapDurations) public { + require(_tapFunds.length == _tapDurations.length); + // require(nonZero(bytes32(_owner)) && nonZero(bytes32(_daiToken)) && nonZero(bytes32(_returnAddress)) && nonZero(bytes32(_tapFunds.length))); + MintableToken projectToken = new MintableToken(); + proj.token = projectToken; + proj.daiToken = ERC20(_daiToken); + proj.owner = _owner; + proj.createdAt = now; + proj.investDuration = 7 days; + proj.votingDuration = 7 days; + proj.additionalDuration = 7 days; + proj.changeRoadmapDuration = 21 days; + proj.quorumPercent = 70; + proj.quorumDecresedPercent = 50; + proj.declinePercent = 80; + proj.consensusPercent = 70; + + require(_tapFunds.length == _tapDurations.length); + for(uint i = 0; i < _tapFunds.length; i++) { + Tap memory tap; + tap.funds = _tapFunds[i]; + tap.duration = _tapDurations[i]; + proj.taps.push(tap); } } - - /** - * Public methods - */ - - /** - * @dev Returns voting result - * @param _votingIndex voting index - * @return voting result - */ - function getVotingResult(uint _votingIndex) public view validVotingIndex(_votingIndex) returns(VotingResult) { - Voting memory voting = votings[_votingIndex]; - uint tokenAmountOfAllVoters = voting.tokenAmountToAccept.add(voting.tokenAmountToReject); - uint totalSupply = projectToken.totalSupply(); - // check whether quorum is reached - if(tokenAmountOfAllVoters.mul(100) <= totalSupply.mul(voting.quorumRate)) { - return VotingResult.QuorumNotReached; - } - // check whether voting result is strongly accepted - if(voting.tokenAmountToAccept.mul(100) >= tokenAmountOfAllVoters.mul(minVoteRate)) { - return VotingResult.Accept; + + Investor[] public investors; + + // Функция инициации контракта. + // Имеется общее количество daiToken, которое должно поступить на контракт перед началом процесса – Sum(tapFunds) + // Любой может купить любое количество оставшихся projectToken, предварительно сделав approve на то же количество daiToken + // Как только все projectToken проданы, контракт автоматически переходит в первый stage, и owner может снять daiToken за первый tap. + // Если lifetime закончился, а projectToken проданы не все, то можно вызвать returnTokens, которая вернет токены обратно инвесторам. + // 1. Для примера – tap.durations == 1 month, все голосования проходят успешно, тогда Timeline такой: + // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(2TAP: 1 month - 1 week: nothing)---(2TAP: 1 week: voting for a next tap)---... + // 2. Не набран кворум – сразу стартует повторное голосование (7 дней) с пониженным кворумом >50%. + // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(1TAP: 1 additional week: voting for a next tap)---... + // 3. Кворум набран, но пороговый % проголосовавших «за» не пройден. В результате сразу инициируется голосование по пересмотру роадмапа и доработке проекта через месяц. + // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(1TAP: 3 additional weeks: change roadmap)---(1TAP: 1 additional week: voting)---... + + function invest(uint _amount) public { + require(!proj.isActive); + require(nonZero(bytes32(_amount))); + require(totalInvestitions() + _amount <= tapAmountsSum()); + proj.daiToken.transferFrom(msg.sender, address(this), _amount); + proj.token.mint(msg.sender, _amount); + proj.investors.push(Investor(msg.sender, _amount)); + if(totalInvestitions() + _amount == tapAmountsSum()) { + proj.isActive = true; + proj.startedAt = now; } - // check whether voting result is strongly declined - if(voting.tokenAmountToReject.mul(100) >= tokenAmountOfAllVoters.mul(minVoteRate)) { - return VotingResult.Decline; + } + function totalInvestitions() public view returns(uint sum) { + for(uint i = 0; i < proj.investors.length; i++) { + sum += proj.investors[i].invested; } - // by default return no decision result - return VotingResult.NoDecision; } - /** - * @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 validVotingIndex(_votingIndex) returns(bool) { - require(_investorAddress != address(0)); - return (votings[_votingIndex].votedByAmount[_investorAddress] > 0); + function tapAmountsSum() public view returns(uint sum) { + for(uint t = 0; t < proj.taps.length; t++) { + sum += proj.taps[t].funds; + } } - /** - * @dev Checks whether project is terminated - * @return is project terminated - */ - function isProjectTerminated() public view returns(bool) { - bool isTerminated = false; - Voting memory latestVoting = votings[votingsCount.sub(1)]; - // if latest voting is of type TerminateProject or TerminateProjectDecreasedQuorum and result Accept then set isTerminated to true - if(((latestVoting.votingType == VotingType.TerminateProject) || (latestVoting.votingType == VotingType.TerminateProjectDecreasedQuorum)) && (getVotingResult(votingsCount.sub(1)) == VotingResult.Accept)) { - isTerminated = true; + function returnTokens() external { + (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); + require(tapStages[t] == TapStage.Terminated); + uint remainder = 0; + for(uint i = 0; i < proj.taps.length; i++) { + if(!proj.taps[t].isWithdrawed) { + remainder += proj.taps[i].funds; + } } - return isTerminated; - } - /** - * @dev Checks whether tap withdraw is accepted by investors for project owner - * @param _tapIndex tap index - * @return whether withdraw is accepted - */ - function isTapWithdrawAcceptedByInvestors(uint _tapIndex) public view validTapIndex(_tapIndex) returns(bool) { - bool isWithdrawAccepted = false; - // get latest voting for tap - uint latestVotingIndex = tapVotings[_tapIndex][tapVotingsCount[_tapIndex].sub(1)]; - Voting memory voting = votings[latestVotingIndex]; - bool isVotingAccepted = getVotingResult(latestVotingIndex) == VotingResult.Accept; - // if voting is of types: ReleaseTap, ReleaseTapDecreasedQuorum, ChangeRoadmap or ChangeRoadmapDecreasedQuorum then set isWithdrawAccepted to true - if(((voting.votingType != VotingType.TerminateProject) && (voting.votingType != VotingType.TerminateProjectDecreasedQuorum)) && isVotingAccepted) { - isWithdrawAccepted = true; + for(i = 0; i < proj.investors.length; i++) { + proj.daiToken.transfer(proj.investors[i].addr, ((remainder * proj.investors[i].invested)/tapAmountsSum())); } - return isWithdrawAccepted; } - /** - * Investor methods - */ - - /** - * @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 - */ - function createVotingByInvestor(uint _tapIndex, VotingType _votingType) external onlyInvestor validTapIndex(_tapIndex) { - // common validation - require(_votingType == VotingType.ChangeRoadmap || _votingType == VotingType.ChangeRoadmapDecreasedQuorum || _votingType == VotingType.TerminateProject || _votingType == VotingType.TerminateProjectDecreasedQuorum); - uint latestVotingIndex = tapVotings[_tapIndex][tapVotingsCount[_tapIndex].sub(1)]; - Voting memory latestVoting = votings[latestVotingIndex]; - VotingResult votingResult = getVotingResult(latestVotingIndex); - // check that last voting is finished - require(now >= latestVoting.finishAt); - - // if investor wants to create voting of type ChangeRoadmap - if(_votingType == VotingType.ChangeRoadmap) { - // check that latest voting is of types ReleaseTap, ReleaseTapDecreasedQuorum, TerminateProject, TerminateProjectDecreasedQuorum - require(latestVoting.votingType == VotingType.ReleaseTap || latestVoting.votingType == VotingType.ReleaseTapDecreasedQuorum || latestVoting.votingType == VotingType.TerminateProject || latestVoting.votingType == VotingType.TerminateProjectDecreasedQuorum); - // if latest voting is ReleaseTap - if(latestVoting.votingType == VotingType.ReleaseTap || latestVoting.votingType == VotingType.ReleaseTapDecreasedQuorum) { - // check that latest voting result is no decision - require(votingResult == VotingResult.NoDecision); - } - // if latest voting is TerminateProject - if(latestVoting.votingType == VotingType.TerminateProject || latestVoting.votingType == VotingType.TerminateProjectDecreasedQuorum) { - // check that latest voting result is decline - require(votingResult == VotingResult.Decline); - } - // create a new voting - _createVoting(_tapIndex, minQuorumRate, now + 3 weeks, now + 4 weeks, VotingType.ChangeRoadmap); - } + // Функция для снятия средств owner'ом. + function withdrawFundsFromTap(uint _t) external { + require(msg.sender == proj.owner); + + (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); + require(tapStages[_t] == TapStage.Success); + proj.taps[_t].isWithdrawed = true; + proj.daiToken.transfer(proj.owner, proj.taps[_t].funds); + } - // if investor wants to create voting of type ChangeRoadmapDecreasedQuorum - if(_votingType == VotingType.ChangeRoadmapDecreasedQuorum) { - // check that latest voting is of type ChangeRoadmap or ChangeRoadmapDecreasedQuorum - require(latestVoting.votingType == VotingType.ChangeRoadmap || latestVoting.votingType == VotingType.ChangeRoadmapDecreasedQuorum); - // check that latest voting result has not reached quorum or has no decision - require((votingResult == VotingResult.QuorumNotReached) || (votingResult == VotingResult.NoDecision)); - // create a new voting - _createVoting(_tapIndex, 50, now + 3 weeks, now + 4 weeks, VotingType.ChangeRoadmapDecreasedQuorum); + // Функция для голосования. + function vote(bool _vote) external { + require(isInvestor(msg.sender)); + + (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); + require(tapStages[t] == TapStage.Voting + || tapStages[t] == TapStage.VotingDQ + || tapStages[t] == TapStage.RoadmapVoting + || tapStages[t] == TapStage.RoadmapVotingDQ); + + require(!isVoted(proj.taps[t].votings[v].voted, msg.sender)); + + if(_vote) { + proj.taps[t].votings[v].pro += getInvestorAmount(msg.sender); + } else { + proj.taps[t].votings[v].versus += getInvestorAmount(msg.sender); } - // if investor wants to create voting of type TerminateProject - if(_votingType == VotingType.TerminateProject) { - // check that latest voting is of types: ReleaseTap, ReleaseTapDecreasedQuorum, ChangeRoadmap, ChangeRoadmapDecreasedQuorum - require(latestVoting.votingType == VotingType.ReleaseTap || latestVoting.votingType == VotingType.ReleaseTapDecreasedQuorum || latestVoting.votingType == VotingType.ChangeRoadmap || latestVoting.votingType == VotingType.ChangeRoadmapDecreasedQuorum); - // check that latest voting result is decline - require(votingResult == VotingResult.Decline); - // create a new voting - _createVoting(_tapIndex, minQuorumRate, now, now + 2 weeks, VotingType.TerminateProject); + proj.taps[t].votings[v].voted.push(msg.sender); + } + + function getInvestorAmount(address _a) public view returns(uint amount) { + for(uint i = 0; i < proj.investors.length; i++) { + if(proj.investors[i].addr == _a) { + amount = proj.investors[i].invested; + } } + } - // if investor wants to create voting of type TerminateProjectDecreasedQuorum - if(_votingType == VotingType.TerminateProjectDecreasedQuorum) { - // check that latest voting is of type TerminateProject or TerminateProjectDecreasedQuorum - require(latestVoting.votingType == VotingType.TerminateProject || latestVoting.votingType == VotingType.TerminateProjectDecreasedQuorum); - // check that latest voting result has not reached quorum or has no decision - require((votingResult == VotingResult.QuorumNotReached) || (votingResult == VotingResult.NoDecision)); - // create a new voting - _createVoting(_tapIndex, 50, now, now + 2 weeks, VotingType.TerminateProjectDecreasedQuorum); + function isInvestor(address _a) public view returns(bool isInv) { + for(uint i = 0; i < proj.investors.length; i++) { + if(proj.investors[i].addr == _a) { + isInv = true; + } } } - - /** - * @dev Voting by investor - * @param _votingIndex voting index - * @param _isYes positive/negative decision - */ - function vote(uint _votingIndex, bool _isYes) external onlyInvestor validVotingIndex(_votingIndex) { - // validation - require(now >= votings[_votingIndex].createdAt); - // require(now < votings[_votingIndex].finishAt); - require(votings[_votingIndex].votedByAmount[msg.sender]==0); - require(!isProjectTerminated()); - // vote - uint256 senderBalance = projectToken.balanceOf(msg.sender); - votings[_votingIndex].votedByAmount[msg.sender] = senderBalance; - votings[_votingIndex].voters.push(msg.sender); - if(_isYes) { - votings[_votingIndex].tokenAmountToAccept = votings[_votingIndex].tokenAmountToAccept.add(senderBalance); - } else { - votings[_votingIndex].tokenAmountToReject = votings[_votingIndex].tokenAmountToReject.add(senderBalance); + + function isVoted(address[] memory _voted, address _a) public view returns(bool isVoted) { + for(uint i = 0; i < _voted.length; i++) { + if(_voted[i] == _a) { + isVoted = true; + } } } - /** - * Evercity member / owner methods - */ - - /** - * @dev Creates a new voting by owner. Owner can create votings only of type ReleaseTapDecreasedQuorum - * @param _tapIndex tap index - * @param _votingType voting type - */ - function createVotingByOwner(uint _tapIndex, VotingType _votingType) external onlyOwner validTapIndex(_tapIndex) { - // validation - require( - _votingType == VotingType.ReleaseTapDecreasedQuorum || - _votingType == VotingType.ReleaseTap - ); - uint latestVotingIndex = tapVotings[_tapIndex][tapVotingsCount[_tapIndex].sub(1)]; - Voting memory latestVoting = votings[latestVotingIndex]; - // check that latest voting is finished - require(now >= latestVoting.finishAt); - // check that latest voting is of type ReleaseTap or ReleaseTapDecreasedQuorum - require(latestVoting.votingType == VotingType.ReleaseTap || latestVoting.votingType == VotingType.ReleaseTapDecreasedQuorum); - // check that latest voting result is quorum not reached + /* + Общая диаграмма состояния + NOQ, NOC, SUC, DEC – соответственно QuorumNotReached, ConsensusNotReached, Success, Decline + VOT, VOTDQ, VOT_RM – голосование, голосование с пониженным кворумом, голосование за принятие roadmap + PREP, RM – подготовка к голосованию, подготовка к голосованию за roadmap + >>> – переход на следующий tap + • – terminate + + | NOQ---• |15| | NOQ---• |20| | NOQ---• |24| + | |16| |17| | |21| | + | NOC------RM--VOT_RM----* | NOC----------VOTDQ--*| NOC---• |25| + |02| | SUC->>> |18| | SUC->>> |22| | SUC->>> |26| + | NOQ------VOTDQ----*| DEC---• |19| | DEC---• |23| | DEC---• |27| + |00| |01| | + INV--PREP--VOT-*| SUC->>> |03| + | DEC---• |04| + | | NOC---• |07| |08| | NOQ---• |11| + | NOC--RM--VOT_RM---*| NOQ--------------VOTDQ-----*| NOC---• |12| + |05| |06| | SUC->>> |09| | SUC->>> |13| + | DEC---• |10| | DEC---• |14| + */ + + // check that roadmap changed + function getTapsInfo() public view returns(uint, TapStage[] memory, uint) { + require(proj.startedAt > 0); + + uint votD = proj.votingDuration; + uint addD = proj.additionalDuration; + uint tapD; + uint rmD = proj.changeRoadmapDuration; + TapStage[] memory tapStages = new TapStage[](proj.taps.length); + uint tapStart = proj.startedAt; - // create a new voting - if(_votingType == VotingType.ReleaseTapDecreasedQuorum) { - require(getVotingResult(latestVotingIndex) == VotingResult.QuorumNotReached); - _createVoting(_tapIndex, 50, now, now + 7 days, VotingType.ReleaseTapDecreasedQuorum); - } else { - require(getVotingResult(latestVotingIndex) == VotingResult.QuorumNotReached); - _createVoting(_tapIndex, 50, now, now + 7 days, VotingType.ReleaseTap); + for(uint t = 0; t < proj.taps.length; t++) { + tapD = proj.taps[t].duration; + + if(at(tapStart, tapD-votD)) { + tapStages[t] = TapStage.Preparing; + return (t, tapStages, 0); + } else if(at(tapStart+tapD-votD, votD)) { + tapStages[t] = TapStage.Voting; + return (t, tapStages, 0); + } else if(now > tapStart+tapD) { + if(VotingResult.Decline == votingState(t, 0, false)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 0); + } else if(VotingResult.Success == votingState(t, 0, false)) { + tapStages[t] = TapStage.Success; + tapStart += tapD; + } else if(VotingResult.QuorumNotReached == votingState(t, 0, false)) { + if(at(tapStart+tapD, addD)) { + tapStages[t] = TapStage.VotingDQ; + return (t, tapStages, 1); + } else if(now > tapStart+tapD+addD) { + if(VotingResult.QuorumNotReached == votingState(t, 1, true)) { + tapStages[t] = TapStage.Terminated; + } else if(VotingResult.Decline == votingState(t, 1, true)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 1); + } else if(VotingResult.Success == votingState(t, 1, true)) { + tapStages[t] = TapStage.Success; + tapStart += (tapD+addD); + } else if(VotingResult.ConsensusNotReached == votingState(t, 1, true)) { + if(at(tapStart+tapD+addD, rmD)) { + tapStages[t] = TapStage.RoadmapPreparing; + return (t, tapStages, 2); + } else if(at(tapStart+tapD+addD+rmD, votD)) { + tapStages[t] = TapStage.RoadmapVoting; + return (t, tapStages, 2); + } else if(now > tapStart+tapD+addD+rmD+votD) { + if(VotingResult.Success == votingState(t, 2, false)) { + tapStages[t] = TapStage.Success; + tapStart += (tapD+addD+rmD+votD); + } else if(VotingResult.ConsensusNotReached == votingState(t, 2, false)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 2); + } else if(VotingResult.Decline == votingState(t, 2, false)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 2); + } else if(VotingResult.QuorumNotReached == votingState(t, 2, false)) { + if(at(tapStart+tapD+addD+rmD+votD, addD)) { + tapStages[t] = TapStage.RoadmapVotingDQ; + return (t, tapStages, 3); + } else if(now > tapStart+tapD+addD+rmD+votD+addD) { + if(VotingResult.Success == votingState(t, 3, true)) { + tapStages[t] = TapStage.Success; + tapStart += (tapD+addD+rmD+votD+addD); + } else if(VotingResult.ConsensusNotReached == votingState(t, 3, true)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 3); + } else if(VotingResult.Decline == votingState(t, 3, true)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 3); + } else if(VotingResult.QuorumNotReached == votingState(t, 3, true)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 3); + } + } + } + } + } + } + } else if(VotingResult.ConsensusNotReached == votingState(t, 0, false)) { + if(at(tapStart+tapD, rmD)) { + tapStages[t] = TapStage.RoadmapPreparing; + return (t, tapStages, 1); + } else if(at(tapStart+tapD+rmD, votD)) { + tapStages[t] = TapStage.RoadmapVoting; + return (t, tapStages, 1); + } else if(now > tapStart+tapD+rmD+votD) { + if(VotingResult.Decline == votingState(t, 1, false)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 1); + } else if(VotingResult.Success == votingState(t, 1, false)) { + tapStages[t] = TapStage.Success; + tapStart += tapD+rmD+votD; + } else if(VotingResult.ConsensusNotReached == votingState(t, 1, false)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 1); + } else if(VotingResult.QuorumNotReached == votingState(t, 1, false)) { + if(at(tapStart+tapD+rmD+votD, addD)) { + tapStages[t] = TapStage.RoadmapVotingDQ; + return (t, tapStages, 2); + } else if(now > tapStart+tapD+rmD+votD+addD) { + if(VotingResult.Decline == votingState(t, 2, true)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 2); + } else if(VotingResult.Success == votingState(t, 2, true)) { + tapStages[t] = TapStage.Success; + tapStart += tapD+rmD+votD+addD; + } else if(VotingResult.ConsensusNotReached == votingState(t, 2, true)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 2); + } else if(VotingResult.QuorumNotReached == votingState(t, 2, true)) { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 2); + } + } + } + } + } + } } } - /** - * @dev Withdraws DAI tokens in case project is terminated - */ - function withdrawFunding() external onlyOwner { - // validation - require(isProjectTerminated()); - // calculate amount of DAI tokens to withdraw - uint amountToWithdraw = 0; - for(uint i = 0; i < tapsCount; i++) { - if(!tapPayments[i].isWithdrawn) { - amountToWithdraw = amountToWithdraw.add(tapAmounts[i]); - } + /*function proposeNewRoadmap(uint[] _tapFunds, uint[] _tapDurations) external { + require(msg.sender == proj.owner); + require(nonZero(bytes32(_tapFunds.length)); + (uint t, TapStage[] tapStages, uint v) = getTapsInfo(); + require(tapStages[t] == TapStage.RoadmapPreparing); + require(_tapFunds.length == _tapDurations.length); + for(uint i = 0; i < _tapFunds.length; i++) { + Tap memory tap; + tap.funds = _tapFunds[i]; + tap.duration = _tapDurations[i]; + proj.proposedTaps.push(tap); } - // transfer DAI tokens to returnAddress - daiToken.transfer(returnAddress, amountToWithdraw); + // GOTO invest stage, if need more money + // GOTO withdraw proficit + // FIX: success taps shouldn't be replaced + }*/ + + function at(uint _from, uint _long) public view returns(bool) { + return ((now >= _from) && (now < _from + _long)); + } + + function isQuorumReached(Voting memory _v, uint _quorumPercent) internal view returns(bool) { + return (_v.pro.add(_v.versus).mul(100) >= tapAmountsSum().mul(_quorumPercent)); + } + + function isConsensusReached(Voting memory _v, uint _consensusPercent) internal view returns(bool) { + return (_v.pro.mul(100 - _consensusPercent) >= _v.versus.mul(_consensusPercent)); } - /** - * Project owner methods - */ - - /** - * @dev Withdraws tap payment by project owner - * @param _tapIndex tap index - */ - function withdrawTapPayment(uint _tapIndex) external validTapIndex(_tapIndex) { - // validation - require(msg.sender == projectOwner); - // require(isTapWithdrawAcceptedByInvestors(_tapIndex)); TODO: FIX - require(!tapPayments[_tapIndex].isWithdrawn); - // create tap payment - TapPayment memory tapPayment; - tapPayment.amount = tapAmounts[_tapIndex]; - tapPayment.createdAt = now; - tapPayment.isWithdrawn = true; - tapPayments[_tapIndex] = tapPayment; - // transfer DAI tokens for selected tap to project owner - daiToken.transfer(projectOwner, tapAmounts[_tapIndex]); + function isDeclined(Voting memory _v, uint _declinePercent) internal view returns(bool) { + return (_v.versus.mul(100 - _declinePercent) >= _v.pro.mul(_declinePercent)); } - /** - * Internal methods - */ - - /** - * @dev Creates a new voting - * @param _tapIndex tap index - * @param _quorumRate quorum rate - * @param _createdAt when voting was created timestamp - * @param _finishAt when voting should be finished timestamp - * @param _votingType voting type - */ - function _createVoting(uint _tapIndex, uint _quorumRate, uint _createdAt, uint _finishAt, VotingType _votingType) internal validTapIndex(_tapIndex) { - // validation - require(_quorumRate > 0); - require(_createdAt > 0); - require(_finishAt > 0); - // create a new voting - Voting memory voting; - voting.tapIndex = _tapIndex; - voting.quorumRate = _quorumRate; - voting.createdAt = _createdAt; - voting.finishAt = _finishAt; - voting.votingType = _votingType; - votings[votingsCount] = voting; - // update contract properties - tapVotings[_tapIndex][tapVotingsCount[_tapIndex]] = votingsCount; - tapVotingsCount[_tapIndex] = tapVotingsCount[_tapIndex].add(1); - votingsCount = votingsCount.add(1); + function votingState(uint _t, uint _v, bool _isQuorumDecreased) returns(VotingResult) { + uint quorumPercent; + if(_isQuorumDecreased) { + quorumPercent = proj.quorumDecresedPercent; + } else { + quorumPercent = proj.quorumPercent; + } + Voting memory v = proj.taps[_t].votings[_v]; + if(isQuorumReached(v, quorumPercent)) { + if(isConsensusReached(v, proj.consensusPercent)) { + return VotingResult.Success; + } else if(isDeclined(v, proj.declinePercent)) { + return VotingResult.Decline; + } else { + return VotingResult.ConsensusNotReached; + } + } else { + return VotingResult.QuorumNotReached; + } } -} +} \ No newline at end of file diff --git a/contracts/Daico/DaicoTestable.sol b/contracts/Daico/DaicoTestable.sol deleted file mode 100644 index 6c578c0..0000000 --- a/contracts/Daico/DaicoTestable.sol +++ /dev/null @@ -1,35 +0,0 @@ -pragma solidity ^0.4.24; - -import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; - -import "./Daico.sol"; - -contract DaicoTestable is Daico { - - constructor( - address _daiTokenAddress, - address _projectTokenAddress, - address _projectOwnerAddress, - address _returnAddress, - uint _tapsCount, - uint[] _tapAmounts, - uint[] _tapTimestampsFinishAt, - uint _minQuorumRate, - uint _minVoteRate - ) public Daico( - _daiTokenAddress, - _projectTokenAddress, - _projectOwnerAddress, - _returnAddress, - _tapsCount, - _tapAmounts, - _tapTimestampsFinishAt, - _minQuorumRate, - _minVoteRate - ) {} - - function createVoting(uint _tapIndex, uint _quorumRate, uint _createdAt, uint _finishAt, VotingType _votingType) external { - _createVoting(_tapIndex, _quorumRate, _createdAt, _finishAt, _votingType); - } - -} diff --git a/contracts/Daico/IDaico.sol b/contracts/Daico/IDaico.sol index 09dcd9a..5bf89ce 100644 --- a/contracts/Daico/IDaico.sol +++ b/contracts/Daico/IDaico.sol @@ -1,240 +1,5 @@ pragma solidity ^0.4.24; -import "zeppelin-solidity/contracts/ownership/Ownable.sol"; -import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; - -/** - * @title IDaico - * How it works: - * 1. Evercity member deploys current contract for some project. - * 2. Contract creates initial votings of type ReleaseTap for each tap. - * 3. Evercity member transfers DAI tokens to DAICO contract address. - * - * ================== - * Common scenarios: - * ================== - * # Successful voting for tap release - * 1. Token holders votes via 'vote()'. - * 2. Quorum reached with positive decision. - * 3. Project owner withdraws DAI tokens for accepted tap via 'withdrawTapPayment()'. - - * # Quorum not reached for 'ReleaseTap' voting - * 1. Token holders votes via 'vote()'. - * 2. Quorum NOT reached. - * 3. Evercity member creates a new voting of type 'ReleaseTapDecreasedQuorum' via 'createVotingByOwner()'. - * 4. Quorum reached with positive decision. - * 5. Project owner withdraws DAI tokens for accepted tap via 'withdrawTapPayment()'. - * - * # Quorum reached but minVoteRate with positive decisions is not reached - * 1. Token holders votes via 'vote()'. - * 2. Quorum reached but minVoteRate with positive decisions is not reached - * 3. One of the investors creates a new voting of type 'ChangeRoadmap' via 'createVotingByInvestor()'. - * 4. Quorum reached with positive decision. - * 5. Project owner withdraws DAI tokens for accepted tap via 'withdrawTapPayment()'. - * - * # Voting strongly against tap release - * 1. Token holders votes via 'vote()'. - * 2. Quorum reached and more than minVoteRate token holders voted against tap release. - * 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 { - - ERC20 public daiToken; - ERC20 public projectToken; - - address public projectOwner; - - uint public minQuorumRate; - uint public minVoteRate; - uint public tapsCount; - uint public tokenHoldersCount; - uint[] public tapAmounts; - uint[] public tapTimestampsFinishAt; - - enum VotingType { ReleaseTap, ReleaseTapDecreasedQuorum, ChangeRoadmap, TerminateProject } - enum VotingResult { Accept, Decline, QuorumNotReached, NoDecision } - - mapping(uint => mapping(uint => uint)) public tapVotings; - mapping(uint => uint) public tapVotingsCount; - - mapping(uint => Voting) public votings; - uint public votingsCount; - - mapping(uint => TapPayment) public tapPayments; - uint public tapPaymentsCount; - - struct TapPayment { - uint amount; - uint createdAt; - bool isWithdrawn; - } - - struct Voting { - uint tapIndex; - uint yesVotesCount; - uint noVotesCount; - uint quorumRate; - uint createdAt; - uint finishAt; - VotingType votingType; - mapping(address => bool) voted; - } - - /** - * @dev Contract constructor - * @param _daiTokenAddress address of the DAI token contract, project gets payments in DAI tokens - * @param _projectTokenAddress project token address, investors hold this token - * @param _projectOwnerAddress project owner address who can receive tap payments - * @param _tapsCount how many times project should get payments, NOTICE: we can get taps count from _tapAmounts.length but contract deployer can force so that _tapsCount != _tapAmounts.length - * @param _tapAmounts array of DAI token amounts in wei that describes how many tokens project gets per single stage - * @param _tapTimestampsFinishAt array of deadline timestamps, project should get payment before each deadline timestamp - * @param _minQuorumRate min quorum rate, 100 == 100% - * @param _minVoteRate min vote rate for proposal to be accepted/declined, 100 == 100% - * @param _tokenHoldersCount amount of token holders - */ - constructor( - address _daiTokenAddress, - address _projectTokenAddress, - address _projectOwnerAddress, - uint _tapsCount, - uint[] _tapAmounts, - uint[] _tapTimestampsFinishAt, - uint _minQuorumRate, - uint _minVoteRate, - uint _tokenHoldersCount - ) public {} - - /** - * Public methods - */ - - /** - * @dev Returns voting result for voting. - * There are 4 voting results: - * - Accept: proposal accepted, majority of investors said 'yes' - * - Decline: proposal declined, majority of investors said 'no' - * - QuorumNotReached: not enough investors voted - * - NoDecision: no consensus among investors, ex: 50% of 'yes' vs 50% of 'no' votes - * @param _votingIndex voting index - * @return voting result - */ - 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/TerminateProjectDecreasedQuorum with Accept result. - * When project is terminated contract owner(evercity member) can withdraw DAI tokens via 'withdrawFunding()'. - * @return is project terminated - */ - function isProjectTerminated() public view returns(bool); - - /** - * @dev Checks whether tap withdraw is accepted by investors for project owner - * @param _tapIndex tap index - * @return whether withdraw is accepted - */ - function isTapWithdrawAcceptedByInvestors(uint _tapIndex) public view returns(bool); - - /** - * Investor methods - */ - - /** - * @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 - */ - function createVotingByInvestor(uint _tapIndex, VotingType _votingType) external; - - /** - * @dev Vote method for token holder - * Preconditions: - * - Is token holder(has at least 1 project token) - * - 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 - */ - function vote(uint _votingIndex, bool _isYes) external; - - /** - * Owner / evercity member methods - */ - - /** - * @dev Creates a new voting by owner. Owner can create votings only of type ReleaseTapDecreasedQuorum - * @param _tapIndex tap index - * @param _votingType voting type - */ - function createVotingByOwner(uint _tapIndex, VotingType _votingType) external; - - /** - * @dev Withdraws all left DAI tokens to owner address - * Preconditions: - * - contract is terminated in case of successful TerminateProject voting - */ - function withdrawFunding() external; - - /** - * Project owner methods - */ - - /** - * @dev Withdraws tap payment by project owner - * Preconditions: - * - Valid tap index - * - Tap is not yet withdrawn - * - Tap withdrawal is accepted by investors - * @param _tapIndex tap index - * Result: DAI tokens for current tap are transfered to project owner address - */ - function withdrawTapPayment(uint _tapIndex) external; - - /** - * Internal methods - */ - - /** - * @dev Creates a new voting for tap - * @param _tapIndex tap index - * @param _quorumRate quorum rate - * @param _createdAt when voting was created timestamp - * @param _finishAt when voting should be finished timestamp - * @param _votingType voting type - */ - function _createVoting(uint _tapIndex, uint _quorumRate, uint _createdAt, uint _finishAt, VotingType _votingType) internal; +contract IDaico { } diff --git a/test/Daico.functional.tests.js b/test/Daico.functional.tests.js index 3a039a5..a459fbe 100644 --- a/test/Daico.functional.tests.js +++ b/test/Daico.functional.tests.js @@ -1,6 +1,6 @@ const moment = require("moment"); -const Daico = artifacts.require("DaicoTestable"); +const Daico = artifacts.require("Daico"); const MintableToken = artifacts.require("MintableToken"); const { increaseTime } = require("./utils/helpers"); @@ -44,10 +44,12 @@ contract("Daico functional tests", (accounts) => { // 1.1. evercityMember деплоит daiToken daiToken = await MintableToken.new({from: evercityMember1}); - // 1.2. У двух evercityMember появляются daiToken - await daiToken.mint(evercityMember1, 100, {from: evercityMember1}); - await daiToken.mint(evercityMember2, 150, {from: evercityMember1}); - + // 1.2. У ду инвесторов появляются daiToken + await daiToken.mint(inverstor1, 200, {from: projectOwner}); + await daiToken.mint(inverstor2, 501, {from: projectOwner}); + await daiToken.mint(inverstor3, 150, {from: projectOwner}); + await daiToken.mint(inverstor4, 149, {from: projectOwner}); + // 1.3. projectOwner деплоит projectToken projectToken = await MintableToken.new({from: projectOwner}); @@ -62,11 +64,13 @@ contract("Daico functional tests", (accounts) => { // узнает daiToken, returnFunds // деплоит Daico контракт // evercityMember1 и evercityMember2 проверяют все значения (например, что returnFunds тот самый, иначе projectOwner может смошенничать) - timestampsFinishAt = [ - moment.unix(web3.eth.getBlock("latest").timestamp).add(1, 'week').unix(), - moment.unix(web3.eth.getBlock("latest").timestamp).add(1, 'week').unix(), - moment.unix(web3.eth.getBlock("latest").timestamp).add(1, 'week').unix() - ]; + + tapDurations = [ + 7 * days, + 7 * days, + 7 * days + ]; + daico = await Daico.new(daiToken.address, projectToken.address, projectOwner, returnFunds, 3, [50, 100, 100], timestampsFinishAt, minVoteRate, minQuorumRate, {from: projectOwner}); // 1.6. evercityMember1 и evercityMember2 переводят токены на Daico адрес, который им дал projectOwner @@ -77,8 +81,8 @@ contract("Daico functional tests", (accounts) => { it("1. Стандартный сценарий: все идет хорошо", async() => { // 1.7. Первая стадия голосования наступает автоматически, создавать голосование не требуется // minVoteRate == minQuorumRate == 70%, то есть голосов inverstor1 (200/1000) и inverstor2 (501/1000) достаточно, чтобы перейти на следующий stage - await daico.vote(0, true, {from: inverstor1}).should.be.fulfilled; - await daico.vote(0, true, {from: inverstor2}).should.be.fulfilled; + // await daico.vote(0, true, {from: inverstor1}).should.be.fulfilled; + // await daico.vote(0, true, {from: inverstor2}).should.be.fulfilled; // 1.8. Проходит неделя и projectOwner снимает часть средств. assert.equal(await daiToken.balanceOf(projectOwner), 0); @@ -88,22 +92,20 @@ contract("Daico functional tests", (accounts) => { // 1.9. Проходит неделя, projectOwner создает новое голосование и инвесторы голосуют по новой. await increaseTime(7 * days); await daico.createVotingByOwner(1, VOTING_TYPE_RELEASE_TAP, {from: projectOwner}); - + await daico.vote(1, true, {from: inverstor1}).should.be.fulfilled; await daico.vote(1, true, {from: inverstor2}).should.be.fulfilled; - await daico.vote(1, true, {from: inverstor3}).should.be.fulfilled; - await daico.vote(1, true, {from: inverstor4}).should.be.fulfilled; await daico.withdrawTapPayment(1, {from: projectOwner}).should.be.fulfilled; // 1.10. И Снова await increaseTime(7 * days); await daico.createVotingByOwner(2, VOTING_TYPE_RELEASE_TAP, {from: projectOwner}); + await daico.vote(2, true, {from: inverstor1}).should.be.fulfilled; await daico.vote(2, true, {from: inverstor2}).should.be.fulfilled; - await daico.vote(2, true, {from: inverstor3}).should.be.fulfilled; - await daico.vote(2, true, {from: inverstor4}).should.be.fulfilled; await daico.withdrawTapPayment(2, {from: projectOwner}).should.be.fulfilled; // Проверяем баланс – projectOwner должен был получить все daiToken, которые проинвестировали - assert.equal(await daiToken.balanceOf(projectOwner), 250); + assert.equal(await daiToken.balanceOf(projectOwner), 280); + }); }); }); diff --git a/test/Daico.tests.js b/test/Daico.tests.js index 13a8ea4..8decfe7 100644 --- a/test/Daico.tests.js +++ b/test/Daico.tests.js @@ -1,7 +1,7 @@ const moment = require("moment"); const { increaseTime } = require("./utils/helpers"); -const Daico = artifacts.require("DaicoTestable"); +const Daico = artifacts.require("Daico"); const MintableToken = artifacts.require("MintableToken"); contract("Daico unit tests", (accounts) => { From cd3b250a98fe2630ce3fe6684effee8304f79ce0 Mon Sep 17 00:00:00 2001 From: enkogu Date: Tue, 29 Jan 2019 05:39:30 +0700 Subject: [PATCH 3/6] 4 functional tests: changeRoadmap works --- contracts/Daico/Daico.sol | 380 +++++++++++++++++++++------------ test/Daico.functional.tests.js | 304 ++++++++++++++++++-------- 2 files changed, 462 insertions(+), 222 deletions(-) diff --git a/contracts/Daico/Daico.sol b/contracts/Daico/Daico.sol index 72186e3..e2eaa65 100644 --- a/contracts/Daico/Daico.sol +++ b/contracts/Daico/Daico.sol @@ -7,8 +7,12 @@ import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; contract Daico { using SafeMath for uint; + + event InvestEvent(uint amount, address _sender, uint total, uint tapSum); + enum TapStage { Preparing, + Investing, Voting, VotingDQ, RoadmapPreparing, @@ -19,6 +23,7 @@ contract Daico { } enum VotingResult { + NotExist, QuorumNotReached, ConsensusNotReached, Success, @@ -28,7 +33,6 @@ contract Daico { Project public proj; struct Project { - bool isActive; address owner; MintableToken token; ERC20 daiToken; @@ -39,12 +43,20 @@ contract Daico { uint additionalDuration; uint changeRoadmapDuration; uint quorumPercent; - uint quorumDecresedPercent; + // uint quorumDecresedPercent; uint declinePercent; uint consensusPercent; - Tap[] taps; - Tap[] proposedTaps; - Investor[] investors; + bool rewrited; + uint roadmapsCount; + mapping(uint=>uint) tapToRId; // tapId -> roadmapId + mapping(uint=>Roadmap) roadmaps; // roadmapId -> roadmap + } + + struct Roadmap { + uint tapsCount; + uint investorsCount; + mapping(uint=>Tap) taps; + mapping(uint=>Investor) investors; } struct Investor { @@ -65,10 +77,6 @@ contract Daico { address[] voted; } - function nonZero(bytes32 _target) public view returns(bool) { - return (_target == bytes32(0)); - } - // owner создает Daico контракт, куда закладываются следующие параметры: // 1. daiToken – токен Evercity // 2. returnAddress - куда вернуть деньги в случае фейла проекта @@ -77,7 +85,6 @@ contract Daico { // В конструкторе деплоится projectToken, которые будут получать investors в обмен на daiToken constructor(address _owner, address _daiToken, address _returnAddress, uint[] memory _tapFunds, uint[] memory _tapDurations) public { require(_tapFunds.length == _tapDurations.length); - // require(nonZero(bytes32(_owner)) && nonZero(bytes32(_daiToken)) && nonZero(bytes32(_returnAddress)) && nonZero(bytes32(_tapFunds.length))); MintableToken projectToken = new MintableToken(); proj.token = projectToken; proj.daiToken = ERC20(_daiToken); @@ -88,69 +95,117 @@ contract Daico { proj.additionalDuration = 7 days; proj.changeRoadmapDuration = 21 days; proj.quorumPercent = 70; - proj.quorumDecresedPercent = 50; + // proj.quorumDecresedPercent = 50; proj.declinePercent = 80; proj.consensusPercent = 70; - require(_tapFunds.length == _tapDurations.length); + proj.roadmaps[proj.roadmapsCount].tapsCount = _tapFunds.length; for(uint i = 0; i < _tapFunds.length; i++) { - Tap memory tap; - tap.funds = _tapFunds[i]; - tap.duration = _tapDurations[i]; - proj.taps.push(tap); + require(_tapDurations[i] > 7); + proj.tapToRId[i] = 0; // just for clearness; + proj.roadmaps[proj.roadmapsCount].taps[i] = Tap(_tapFunds[i], _tapDurations[i]*(1 days), false); } + proj.roadmapsCount += 1; } - Investor[] public investors; - - // Функция инициации контракта. - // Имеется общее количество daiToken, которое должно поступить на контракт перед началом процесса – Sum(tapFunds) - // Любой может купить любое количество оставшихся projectToken, предварительно сделав approve на то же количество daiToken - // Как только все projectToken проданы, контракт автоматически переходит в первый stage, и owner может снять daiToken за первый tap. - // Если lifetime закончился, а projectToken проданы не все, то можно вызвать returnTokens, которая вернет токены обратно инвесторам. - // 1. Для примера – tap.durations == 1 month, все голосования проходят успешно, тогда Timeline такой: - // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(2TAP: 1 month - 1 week: nothing)---(2TAP: 1 week: voting for a next tap)---... - // 2. Не набран кворум – сразу стартует повторное голосование (7 дней) с пониженным кворумом >50%. - // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(1TAP: 1 additional week: voting for a next tap)---... - // 3. Кворум набран, но пороговый % проголосовавших «за» не пройден. В результате сразу инициируется голосование по пересмотру роадмапа и доработке проекта через месяц. - // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(1TAP: 3 additional weeks: change roadmap)---(1TAP: 1 additional week: voting)---... + function getCurrentTap() internal view returns(uint) { + (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); + return t; + } + function getMaximalTapsLength() internal view returns(uint maximal) { + for(uint i = 0; i < proj.roadmapsCount; i++) { + if(proj.roadmaps[i].tapsCount > maximal) { + maximal = proj.roadmaps[i].tapsCount; + } + } + } + + // // Функция инициации контракта. + // // Имеется общее количество daiToken, которое должно поступить на контракт перед началом процесса – Sum(tapFunds) + // // Любой может купить любое количество оставшихся projectToken, предварительно сделав approve на то же количество daiToken + // // Как только все projectToken проданы, контракт автоматически переходит в первый stage, и owner может снять daiToken за первый tap. + // // Если lifetime закончился, а projectToken проданы не все, то можно вызвать returnTokens, которая вернет токены обратно инвесторам. + // // 1. Для примера – tap.durations == 1 month, все голосования проходят успешно, тогда Timeline такой: + // // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(2TAP: 1 month - 1 week: nothing)---(2TAP: 1 week: voting for a next tap)---... + // // 2. Не набран кворум – сразу стартует повторное голосование (7 дней) с пониженным кворумом >50%. + // // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(1TAP: 1 additional week: voting for a next tap)---... + // // 3. Кворум набран, но пороговый % проголосовавших «за» не пройден. В результате сразу инициируется голосование по пересмотру роадмапа и доработке проекта через месяц. + // // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(1TAP: 3 additional weeks: change roadmap)---(1TAP: 1 additional week: voting)---... function invest(uint _amount) public { - require(!proj.isActive); - require(nonZero(bytes32(_amount))); - require(totalInvestitions() + _amount <= tapAmountsSum()); + (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); + require(tapStages[t] == TapStage.Investing); + require(_amount > 0); + + if(proj.rewrited) { + for(uint i = 0; i < proj.roadmaps[proj.roadmapsCount - 1].tapsCount; i++) { + if(i > t) { + proj.tapToRId[i] = proj.roadmapsCount - 1; + } + } + t += 1; + } + + require(totalInvestitions(t) + _amount <= tapAmountsSum(t)); proj.daiToken.transferFrom(msg.sender, address(this), _amount); proj.token.mint(msg.sender, _amount); - proj.investors.push(Investor(msg.sender, _amount)); - if(totalInvestitions() + _amount == tapAmountsSum()) { - proj.isActive = true; + if(getInvestorId(msg.sender) == proj.roadmaps[proj.tapToRId[getCurrentTap()]].investorsCount) { // not investor + uint invCount = proj.roadmaps[proj.tapToRId[t]].investorsCount; + proj.roadmaps[proj.tapToRId[t]].investors[invCount] = Investor(msg.sender, _amount); + proj.roadmaps[proj.tapToRId[t]].investorsCount += 1; + } else { + proj.roadmaps[proj.tapToRId[t]].investors[getInvestorId(msg.sender)].invested += _amount; + } + + emit InvestEvent(_amount, msg.sender, totalInvestitions(t), tapAmountsSum(t)); + if((totalInvestitions(t) >= tapAmountsSum(t)) && (proj.startedAt==0)) { proj.startedAt = now; + proj.rewrited = false; } + + } + + /*function getVotingsResultForTap(uint t) public view returns(uint[]) { + uint[] memory vr = new uint[](4); + vr[0] = proj.taps[t].votings[0].pro;//votingState(t, 0, false); + vr[1] = proj.taps[t].votings[1].pro;//votingState(t, 1, false); + vr[2] = proj.taps[t].votings[2].pro;//votingState(t, 2, false); + vr[3] = proj.taps[t].votings[3].pro;//votingState(t, 3, false); + return vr; } - function totalInvestitions() public view returns(uint sum) { - for(uint i = 0; i < proj.investors.length; i++) { - sum += proj.investors[i].invested; + + function getTaps() public view returns(uint[], uint[]) { + uint[] memory tapFunds = new uint[](getMaximalTapsLength()); + uint[] memory tapDurations = new uint[](getMaximalTapsLength()); + for(uint i = 0; i < getMaximalTapsLength(); i++) { + tapFunds[i] = proj.taps[i].funds; + tapDurations[i] = proj.taps[i].duration; + } + + return (tapFunds, tapDurations); + }*/ + + function totalInvestitions(uint _t) public view returns(uint sum) { + for(uint i = 0; i < proj.roadmaps[proj.tapToRId[_t]].investorsCount; i++) { + sum += proj.roadmaps[proj.tapToRId[_t]].investors[i].invested; } } - function tapAmountsSum() public view returns(uint sum) { - for(uint t = 0; t < proj.taps.length; t++) { - sum += proj.taps[t].funds; + function tapAmountsSum(uint _t) public view returns(uint sum) { + for(uint t = 0; t < proj.roadmaps[proj.tapToRId[_t]].tapsCount; t++) { + sum += proj.roadmaps[proj.tapToRId[_t]].taps[t].funds; } } function returnTokens() external { (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); - require(tapStages[t] == TapStage.Terminated); - uint remainder = 0; - for(uint i = 0; i < proj.taps.length; i++) { - if(!proj.taps[t].isWithdrawed) { - remainder += proj.taps[i].funds; - } - } + require(tapStages[t] == TapStage.Terminated); - for(i = 0; i < proj.investors.length; i++) { - proj.daiToken.transfer(proj.investors[i].addr, ((remainder * proj.investors[i].invested)/tapAmountsSum())); + uint remainder = proj.daiToken.balanceOf(address(this)); + uint part; + for(uint i = 0; i < proj.roadmaps[proj.tapToRId[getCurrentTap()]].investorsCount; i++) { + part = ((remainder * proj.roadmaps[proj.tapToRId[getCurrentTap()]].investors[i].invested)/totalInvestitions(t)); + proj.daiToken.transfer(proj.roadmaps[proj.tapToRId[getCurrentTap()]].investors[i].addr, part); } } @@ -160,45 +215,36 @@ contract Daico { (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); require(tapStages[_t] == TapStage.Success); - proj.taps[_t].isWithdrawed = true; - proj.daiToken.transfer(proj.owner, proj.taps[_t].funds); + proj.roadmaps[proj.tapToRId[t]].taps[_t].isWithdrawed = true; + proj.daiToken.transfer(proj.owner, proj.roadmaps[proj.tapToRId[t]].taps[_t].funds); } // Функция для голосования. function vote(bool _vote) external { - require(isInvestor(msg.sender)); - (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); + require(getInvestorId(msg.sender) < proj.roadmaps[proj.tapToRId[t]].investorsCount); // is investor require(tapStages[t] == TapStage.Voting || tapStages[t] == TapStage.VotingDQ || tapStages[t] == TapStage.RoadmapVoting || tapStages[t] == TapStage.RoadmapVotingDQ); - - require(!isVoted(proj.taps[t].votings[v].voted, msg.sender)); + require(!isVoted(proj.roadmaps[proj.tapToRId[t]].taps[t].votings[v].voted, msg.sender)); if(_vote) { - proj.taps[t].votings[v].pro += getInvestorAmount(msg.sender); + proj.roadmaps[proj.tapToRId[t]].taps[t].votings[v].pro += proj.roadmaps[proj.tapToRId[t]].investors[getInvestorId(msg.sender)].invested; } else { - proj.taps[t].votings[v].versus += getInvestorAmount(msg.sender); + proj.roadmaps[proj.tapToRId[t]].taps[t].votings[v].versus += proj.roadmaps[proj.tapToRId[t]].investors[getInvestorId(msg.sender)].invested; } - proj.taps[t].votings[v].voted.push(msg.sender); - } - - function getInvestorAmount(address _a) public view returns(uint amount) { - for(uint i = 0; i < proj.investors.length; i++) { - if(proj.investors[i].addr == _a) { - amount = proj.investors[i].invested; - } - } + proj.roadmaps[proj.tapToRId[t]].taps[t].votings[v].voted.push(msg.sender); } - function isInvestor(address _a) public view returns(bool isInv) { - for(uint i = 0; i < proj.investors.length; i++) { - if(proj.investors[i].addr == _a) { - isInv = true; + function getInvestorId(address _a) internal view returns(uint) { + for(uint i = 0; i < proj.roadmaps[proj.tapToRId[getCurrentTap()]].investorsCount; i++) { + if(proj.roadmaps[proj.tapToRId[getCurrentTap()]].investors[i].addr == _a) { + return i; } } + return proj.roadmaps[proj.tapToRId[getCurrentTap()]].investorsCount; } function isVoted(address[] memory _voted, address _a) public view returns(bool isVoted) { @@ -209,49 +255,57 @@ contract Daico { } } - /* - Общая диаграмма состояния - NOQ, NOC, SUC, DEC – соответственно QuorumNotReached, ConsensusNotReached, Success, Decline - VOT, VOTDQ, VOT_RM – голосование, голосование с пониженным кворумом, голосование за принятие roadmap - PREP, RM – подготовка к голосованию, подготовка к голосованию за roadmap - >>> – переход на следующий tap - • – terminate - - | NOQ---• |15| | NOQ---• |20| | NOQ---• |24| - | |16| |17| | |21| | - | NOC------RM--VOT_RM----* | NOC----------VOTDQ--*| NOC---• |25| - |02| | SUC->>> |18| | SUC->>> |22| | SUC->>> |26| - | NOQ------VOTDQ----*| DEC---• |19| | DEC---• |23| | DEC---• |27| - |00| |01| | - INV--PREP--VOT-*| SUC->>> |03| - | DEC---• |04| - | | NOC---• |07| |08| | NOQ---• |11| - | NOC--RM--VOT_RM---*| NOQ--------------VOTDQ-----*| NOC---• |12| - |05| |06| | SUC->>> |09| | SUC->>> |13| - | DEC---• |10| | DEC---• |14| - */ - - // check that roadmap changed - function getTapsInfo() public view returns(uint, TapStage[] memory, uint) { - require(proj.startedAt > 0); - + // /* + // Общая диаграмма состояния + // NOQ, NOC, SUC, DEC – соответственно QuorumNotReached, ConsensusNotReached, Success, Decline + // VOT, VOTDQ, VOT_RM – голосование, голосование с пониженным кворумом, голосование за принятие roadmap + // PREP, RM – подготовка к голосованию, подготовка к голосованию за roadmap + // >>> – переход на следующий tap + // • – terminate + + // | NOQ---• |15| | NOQ---• |20| | NOQ---• |24| + // | |16| |17| | |21| | + // | NOC------RM--VOT_RM----* | NOC----------VOTDQ--*| NOC---• |25| + // |02| | SUC->>> |18| | SUC->>> |22| | SUC->>> |26| + // | NOQ------VOTDQ----*| DEC---• |19| | DEC---• |23| | DEC---• |27| + // |00| |01| | + // INV--PREP--VOT-*| SUC->>> |03| + // | DEC---• |04| + // | | NOC---• |07| |08| | NOQ---• |11| + // | NOC--RM--VOT_RM---*| NOQ--------------VOTDQ-----*| NOC---• |12| + // |05| |06| | SUC->>> |09| | SUC->>> |13| + // | DEC---• |10| | DEC---• |14| + // */ + + function getTapsInfo() public view returns(uint, TapStage[], uint) { // curren_tap, tapstages, current_voting uint votD = proj.votingDuration; uint addD = proj.additionalDuration; + uint invD = proj.investDuration; uint tapD; uint rmD = proj.changeRoadmapDuration; - TapStage[] memory tapStages = new TapStage[](proj.taps.length); - uint tapStart = proj.startedAt; - - for(uint t = 0; t < proj.taps.length; t++) { - tapD = proj.taps[t].duration; - - if(at(tapStart, tapD-votD)) { + uint max = getMaximalTapsLength(); + TapStage[] memory tapStages = new TapStage[](max); + uint tapStart = 0; + uint t; + for(t = 0; t < max; t++) { + tapD = proj.roadmaps[proj.tapToRId[t]].taps[t].duration; + if(proj.startedAt == 0) { + if(now > proj.createdAt + proj.investDuration) { + tapStages[0] = TapStage.Terminated; + return (t, tapStages, 0); + } else { + tapStages[0] = TapStage.Investing; + return (t, tapStages, 0); + } + } else if(t==0) { + tapStages[t] = TapStage.Success; + } else if(at(tapStart, tapD-votD)) { tapStages[t] = TapStage.Preparing; return (t, tapStages, 0); } else if(at(tapStart+tapD-votD, votD)) { tapStages[t] = TapStage.Voting; return (t, tapStages, 0); - } else if(now > tapStart+tapD) { + } else if(now >= proj.startedAt + tapStart+tapD) { if(VotingResult.Decline == votingState(t, 0, false)) { tapStages[t] = TapStage.Terminated; return (t, tapStages, 0); @@ -262,9 +316,10 @@ contract Daico { if(at(tapStart+tapD, addD)) { tapStages[t] = TapStage.VotingDQ; return (t, tapStages, 1); - } else if(now > tapStart+tapD+addD) { + } else if(now >= proj.startedAt + tapStart+tapD+addD) { if(VotingResult.QuorumNotReached == votingState(t, 1, true)) { tapStages[t] = TapStage.Terminated; + return (t, tapStages, 1); } else if(VotingResult.Decline == votingState(t, 1, true)) { tapStages[t] = TapStage.Terminated; return (t, tapStages, 1); @@ -278,10 +333,18 @@ contract Daico { } else if(at(tapStart+tapD+addD+rmD, votD)) { tapStages[t] = TapStage.RoadmapVoting; return (t, tapStages, 2); - } else if(now > tapStart+tapD+addD+rmD+votD) { + } else if(now >= proj.startedAt + tapStart+tapD+addD+rmD+votD) { if(VotingResult.Success == votingState(t, 2, false)) { - tapStages[t] = TapStage.Success; - tapStart += (tapD+addD+rmD+votD); + if(at(tapStart+tapD+addD+rmD+votD, invD)) { // invest stage for a new roadmap + tapStages[t] = TapStage.Investing; + return (t, tapStages, 2); + } else if((now >= proj.startedAt + tapStart+tapD+addD+rmD+votD+invD) && (totalInvestitions(t) >= tapAmountsSum(t))) { + tapStages[t] = TapStage.Success; + tapStart += (tapStart+tapD+addD+rmD+votD+invD); + } else { + tapStages[t] = TapStage.Success; + return (t, tapStages, 2); + } } else if(VotingResult.ConsensusNotReached == votingState(t, 2, false)) { tapStages[t] = TapStage.Terminated; return (t, tapStages, 2); @@ -292,10 +355,18 @@ contract Daico { if(at(tapStart+tapD+addD+rmD+votD, addD)) { tapStages[t] = TapStage.RoadmapVotingDQ; return (t, tapStages, 3); - } else if(now > tapStart+tapD+addD+rmD+votD+addD) { + } else if(now >= proj.startedAt + tapStart+tapD+addD+rmD+votD+addD) { if(VotingResult.Success == votingState(t, 3, true)) { - tapStages[t] = TapStage.Success; - tapStart += (tapD+addD+rmD+votD+addD); + if(at(tapStart+tapD+addD+rmD+votD+addD, invD)) { // invest stage for a new roadmap + tapStages[t] = TapStage.Investing; + return (t, tapStages, 3); + } else if((now >= proj.startedAt + tapStart+tapD+addD+rmD+votD+addD+invD) && (totalInvestitions(t) >= tapAmountsSum(t))) { + tapStages[t] = TapStage.Success; + tapStart += (tapStart+tapD+addD+rmD+votD+addD+invD); + } else { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 3); + } } else if(VotingResult.ConsensusNotReached == votingState(t, 3, true)) { tapStages[t] = TapStage.Terminated; return (t, tapStages, 3); @@ -318,13 +389,21 @@ contract Daico { } else if(at(tapStart+tapD+rmD, votD)) { tapStages[t] = TapStage.RoadmapVoting; return (t, tapStages, 1); - } else if(now > tapStart+tapD+rmD+votD) { + } else if(now >= proj.startedAt + tapStart+tapD+rmD+votD) { if(VotingResult.Decline == votingState(t, 1, false)) { tapStages[t] = TapStage.Terminated; return (t, tapStages, 1); } else if(VotingResult.Success == votingState(t, 1, false)) { - tapStages[t] = TapStage.Success; - tapStart += tapD+rmD+votD; + if(at(tapStart+tapD+rmD+votD, invD)) { // invest stage for a new roadmap + tapStages[t] = TapStage.Investing; + return (t, tapStages, 1); + } else if((now >= proj.startedAt + tapStart+tapD+rmD+votD+invD) && (totalInvestitions(t) >= tapAmountsSum(t))) { + tapStages[t] = TapStage.Success; + tapStart += (tapD+rmD+votD+invD); + } else { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 1); + } } else if(VotingResult.ConsensusNotReached == votingState(t, 1, false)) { tapStages[t] = TapStage.Terminated; return (t, tapStages, 1); @@ -332,13 +411,21 @@ contract Daico { if(at(tapStart+tapD+rmD+votD, addD)) { tapStages[t] = TapStage.RoadmapVotingDQ; return (t, tapStages, 2); - } else if(now > tapStart+tapD+rmD+votD+addD) { + } else if(now >= proj.startedAt + tapStart+tapD+rmD+votD+addD) { if(VotingResult.Decline == votingState(t, 2, true)) { tapStages[t] = TapStage.Terminated; return (t, tapStages, 2); } else if(VotingResult.Success == votingState(t, 2, true)) { - tapStages[t] = TapStage.Success; - tapStart += tapD+rmD+votD+addD; + if(at(tapStart+tapD+rmD+votD+addD, invD)) { // invest stage for a new roadmap + tapStages[t] = TapStage.Investing; + return (t, tapStages, 2); + } else if((now >= proj.startedAt + tapStart+tapD+rmD+votD+addD+invD) && (totalInvestitions(t) >= tapAmountsSum(t))) { + tapStages[t] = TapStage.Success; + tapStart += (tapD+addD+rmD+votD+addD+invD); + } else { + tapStages[t] = TapStage.Terminated; + return (t, tapStages, 2); + } } else if(VotingResult.ConsensusNotReached == votingState(t, 2, true)) { tapStages[t] = TapStage.Terminated; return (t, tapStages, 2); @@ -352,31 +439,48 @@ contract Daico { } } } + return (t, tapStages, 0); } - /*function proposeNewRoadmap(uint[] _tapFunds, uint[] _tapDurations) external { - require(msg.sender == proj.owner); - require(nonZero(bytes32(_tapFunds.length)); - (uint t, TapStage[] tapStages, uint v) = getTapsInfo(); + function proposeNewRoadmap(uint[] _tapFunds, uint[] _tapDurations) external { + (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); require(tapStages[t] == TapStage.RoadmapPreparing); require(_tapFunds.length == _tapDurations.length); - for(uint i = 0; i < _tapFunds.length; i++) { - Tap memory tap; - tap.funds = _tapFunds[i]; - tap.duration = _tapDurations[i]; - proj.proposedTaps.push(tap); + require(msg.sender == proj.owner); + require(_tapFunds.length >= proj.roadmaps[proj.roadmapsCount - 1].tapsCount); + require(!proj.rewrited); + + proj.roadmaps[proj.roadmapsCount].tapsCount = _tapFunds.length; + proj.roadmaps[proj.roadmapsCount].investorsCount = proj.roadmaps[proj.roadmapsCount - 1].investorsCount; + uint i; + + for(i = 0; i < _tapFunds.length; i++) { + require(_tapDurations[i] > 7); + if(i <= t) { + require(_tapDurations[i]*(1 days) == proj.roadmaps[proj.tapToRId[i]].taps[i].duration); + require(_tapFunds[i] == proj.roadmaps[proj.tapToRId[i]].taps[i].funds); + } else if(i > t) { + proj.tapToRId[i] = proj.roadmapsCount; // just for clearness; + } + + proj.roadmaps[proj.roadmapsCount].taps[i] = Tap(_tapFunds[i], _tapDurations[i]*(1 days), false); } - // GOTO invest stage, if need more money - // GOTO withdraw proficit - // FIX: success taps shouldn't be replaced - }*/ + + for(i = 0; i < proj.roadmaps[proj.roadmapsCount - 1].investorsCount; i++) { + proj.roadmaps[proj.roadmapsCount].investors[i] = proj.roadmaps[proj.roadmapsCount - 1].investors[i]; + } + + proj.roadmapsCount += 1; + proj.rewrited = true; + } function at(uint _from, uint _long) public view returns(bool) { - return ((now >= _from) && (now < _from + _long)); + bool out = ((now >= _from + proj.startedAt) && (now < proj.startedAt + _from + _long)); + return out; } - function isQuorumReached(Voting memory _v, uint _quorumPercent) internal view returns(bool) { - return (_v.pro.add(_v.versus).mul(100) >= tapAmountsSum().mul(_quorumPercent)); + function isQuorumReached(uint _t, Voting memory _v, uint _quorumPercent) internal view returns(bool) { + return (_v.pro.add(_v.versus).mul(100) >= tapAmountsSum(_t).mul(_quorumPercent)); // FIX HERE: totalInvestitions change after roadmap } function isConsensusReached(Voting memory _v, uint _consensusPercent) internal view returns(bool) { @@ -387,15 +491,15 @@ contract Daico { return (_v.versus.mul(100 - _declinePercent) >= _v.pro.mul(_declinePercent)); } - function votingState(uint _t, uint _v, bool _isQuorumDecreased) returns(VotingResult) { + function votingState(uint _t, uint _v, bool _isQuorumDecreased) public view returns(VotingResult) { uint quorumPercent; if(_isQuorumDecreased) { - quorumPercent = proj.quorumDecresedPercent; + quorumPercent = proj.quorumPercent - 20; } else { quorumPercent = proj.quorumPercent; } - Voting memory v = proj.taps[_t].votings[_v]; - if(isQuorumReached(v, quorumPercent)) { + Voting memory v = proj.roadmaps[proj.tapToRId[_t]].taps[_t].votings[_v]; + if(isQuorumReached(_t, v, quorumPercent)) { if(isConsensusReached(v, proj.consensusPercent)) { return VotingResult.Success; } else if(isDeclined(v, proj.declinePercent)) { diff --git a/test/Daico.functional.tests.js b/test/Daico.functional.tests.js index a459fbe..a84bd5b 100644 --- a/test/Daico.functional.tests.js +++ b/test/Daico.functional.tests.js @@ -7,105 +7,241 @@ const { increaseTime } = require("./utils/helpers"); contract("Daico functional tests", (accounts) => { - const evercityMember1 = accounts[0]; - const evercityMember2 = accounts[9]; + const evercity = accounts[0]; const projectOwner = accounts[1]; - const inverstor1 = accounts[2]; - const inverstor2 = accounts[3]; - const inverstor3 = accounts[4]; - const inverstor4 = accounts[5]; - const inverstor5 = accounts[6]; + const investor1 = accounts[2]; + const investor2 = accounts[3]; + const investor3 = accounts[4]; + const investor4 = accounts[5]; + const investor5 = accounts[6]; const returnFunds = accounts[7]; const other = accounts[8]; - const VOTING_TYPE_RELEASE_TAP = 0; - const VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM = 1; - const VOTING_TYPE_CHANGE_ROADMAP = 2; - const VOTING_TYPE_CHANGE_ROADMAP_DECREASED_QUORUM = 3; - const VOTING_TYPE_TERMINATE_PROJECT = 4; - const VOTING_TYPE_TERMINATE_PROJECT_DECREASED_QUORUM = 5; - - const VOTING_RESULT_ACCEPT = 0; - const VOTING_RESULT_DECLINE = 1; - const VOTING_RESULT_QUORUM_NOT_REACHED = 2; - const VOTING_RESULT_NO_DECISION = 3; - - const minQuorumRate = 70; - const minVoteRate = 70; - const days = 24 * 60 * 60; - let daico; let daiToken; let projectToken; let timestampsFinishAt; + let days = 24*60*60; + + var TS = { + Preparing : 0, + Investing: 1, + Voting : 2, + VotingDQ : 3, + RoadmapPreparing : 4, + RoadmapVoting : 5, + RoadmapVotingDQ : 6, + Success : 7, + Terminated : 8 + } + + async function getTaps() { + var data = await daico.getTaps(); + var out = []; + for(var i = 0; i < data[0].length; i++) { + out.push([new web3.BigNumber(data[0][i]).toNumber(), new web3.BigNumber(data[1][i]).toNumber()]) + } + return out; + } + + async function getProjectInfo() { + var data = await daico.proj() + return { + owner: data[0], + token: data[1], + daiToken: data[2], + createdAt: new web3.BigNumber(data[3]).toNumber(), + startedAt: new web3.BigNumber(data[4]).toNumber(), + investDuration: new web3.BigNumber(data[5]).toNumber(), + votingDuration: new web3.BigNumber(data[6]).toNumber(), + additionalDuration: new web3.BigNumber(data[7]).toNumber(), + changeRoadmapDuration: new web3.BigNumber(data[8]).toNumber(), + quorumPercent: new web3.BigNumber(data[9]).toNumber(), + quorumDecresedPercent: new web3.BigNumber(data[10]).toNumber(), + declinePercent: new web3.BigNumber(data[11]).toNumber(), + consensusPercent: new web3.BigNumber(data[12]).toNumber() + } + } + + async function getTapsInfo() { + var data = await daico.getTapsInfo() + data[1] = data[1].map((t)=> new web3.BigNumber(t).toNumber()) + // data[3] = data[3].map((t)=> new web3.BigNumber(t).toNumber()) + return { + currentTap: new web3.BigNumber(data[0]).toNumber(), + tapsStages: data[1], + currentVoting: new web3.BigNumber(data[2]).toNumber() + // tapOuts: data[3] + + } + } + + function isArrayEquals(arr1, arr2) { + for(var i = 0; i < arr1.length; i++) { + if(arr1[i] != arr2[i]) { + console.log('Unequal:', arr1, arr2); + return false; + } + } + return true; + } describe("Different scenarios", () => { - beforeEach(async() => { + beforeEach(async() => { // 1.1. evercityMember деплоит daiToken - daiToken = await MintableToken.new({from: evercityMember1}); - - // 1.2. У ду инвесторов появляются daiToken - await daiToken.mint(inverstor1, 200, {from: projectOwner}); - await daiToken.mint(inverstor2, 501, {from: projectOwner}); - await daiToken.mint(inverstor3, 150, {from: projectOwner}); - await daiToken.mint(inverstor4, 149, {from: projectOwner}); - - // 1.3. projectOwner деплоит projectToken - projectToken = await MintableToken.new({from: projectOwner}); - - // 1.4. projectOwner продает эти токены инвесторам (вне приложения, возможно за фиат) - await projectToken.mint(inverstor1, 200, {from: projectOwner}); - await projectToken.mint(inverstor2, 501, {from: projectOwner}); - await projectToken.mint(inverstor3, 150, {from: projectOwner}); - await projectToken.mint(inverstor4, 149, {from: projectOwner}); - - // 1.5. projectOwner: - // согласует значения minVoteRate, minQuorumRate (или ставит произвольные), - // узнает daiToken, returnFunds - // деплоит Daico контракт - // evercityMember1 и evercityMember2 проверяют все значения (например, что returnFunds тот самый, иначе projectOwner может смошенничать) - - tapDurations = [ - 7 * days, - 7 * days, - 7 * days - ]; + evercityToken = await MintableToken.new({from: evercity}); + + // 1.2. У инвесторов появляются daiToken + await evercityToken.mint(investor1, 100, {from: evercity}); + await evercityToken.mint(investor2, 100, {from: evercity}); + await evercityToken.mint(investor3, 150, {from: evercity}); + + var owner = projectOwner; + var daiToken = evercityToken.address; + var returnAddress = evercity; + var tapFunds = [100, 100, 100]; + var tapDurations = [30, 30, 30]; + daico = await Daico.new(owner, daiToken, returnAddress, tapFunds, tapDurations, {from:projectOwner}); + + await evercityToken.approve(daico.address, 100, {from:investor1}); + await evercityToken.approve(daico.address, 100, {from:investor2}); + await evercityToken.approve(daico.address, 100, {from:investor3}); + + await daico.invest(100, {from:investor1}); + await daico.invest(50, {from:investor2}); + await daico.invest(50, {from:investor2}); + await daico.invest(100, {from:investor3}); + await daico.invest(50, {from:investor3}).should.be.rejectedWith('revert'); + }); - daico = await Daico.new(daiToken.address, projectToken.address, projectOwner, returnFunds, 3, [50, 100, 100], timestampsFinishAt, minVoteRate, minQuorumRate, {from: projectOwner}); - - // 1.6. evercityMember1 и evercityMember2 переводят токены на Daico адрес, который им дал projectOwner - await daiToken.transfer(daico.address, 100, {from: evercityMember1}); - await daiToken.transfer(daico.address, 150, {from: evercityMember2}); + it(`1. Сценарий: все голосования происходят вовремя и без задержек, все голоса за`, async() => { + await daico.withdrawFundsFromTap(0, {from:projectOwner}); + await increaseTime(23*days); + + // Голосование началось + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await daico.vote(true, {from: investor3}); + await increaseTime(7*days); // голосование кончилось + + await daico.withdrawFundsFromTap(1, {from:projectOwner}); + + await increaseTime(23*days); // Голосование началось + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await daico.vote(true, {from: investor3}); + await increaseTime(7*days); // голосование кончилось + + await daico.withdrawFundsFromTap(2, {from:projectOwner}); }); - it("1. Стандартный сценарий: все идет хорошо", async() => { - // 1.7. Первая стадия голосования наступает автоматически, создавать голосование не требуется - // minVoteRate == minQuorumRate == 70%, то есть голосов inverstor1 (200/1000) и inverstor2 (501/1000) достаточно, чтобы перейти на следующий stage - // await daico.vote(0, true, {from: inverstor1}).should.be.fulfilled; - // await daico.vote(0, true, {from: inverstor2}).should.be.fulfilled; - - // 1.8. Проходит неделя и projectOwner снимает часть средств. - assert.equal(await daiToken.balanceOf(projectOwner), 0); - await daico.withdrawTapPayment(0, {from: projectOwner}).should.be.fulfilled; - assert.equal(new web3.BigNumber(await daiToken.balanceOf(projectOwner)).toNumber(), 50); - - // 1.9. Проходит неделя, projectOwner создает новое голосование и инвесторы голосуют по новой. - await increaseTime(7 * days); - await daico.createVotingByOwner(1, VOTING_TYPE_RELEASE_TAP, {from: projectOwner}); - await daico.vote(1, true, {from: inverstor1}).should.be.fulfilled; - await daico.vote(1, true, {from: inverstor2}).should.be.fulfilled; - await daico.withdrawTapPayment(1, {from: projectOwner}).should.be.fulfilled; - - // 1.10. И Снова - await increaseTime(7 * days); - await daico.createVotingByOwner(2, VOTING_TYPE_RELEASE_TAP, {from: projectOwner}); - await daico.vote(2, true, {from: inverstor1}).should.be.fulfilled; - await daico.vote(2, true, {from: inverstor2}).should.be.fulfilled; - await daico.withdrawTapPayment(2, {from: projectOwner}).should.be.fulfilled; - - // Проверяем баланс – projectOwner должен был получить все daiToken, которые проинвестировали - assert.equal(await daiToken.balanceOf(projectOwner), 280); + it(`2. Сценарий: задержки при голосовании. в первом голосует 66/70%, во втором (пониженный кворум) – 33/50%. + Проект закрывается, средства возвращаются инвесторам`, async() => { + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Preparing, TS.Preparing])); + await increaseTime(23*days); + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Voting, TS.Preparing])); + + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await increaseTime(7*days); + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.VotingDQ, TS.Preparing])); + await daico.vote(true, {from: investor1}); + await increaseTime(7*days); + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Terminated, TS.Preparing])); + }); + it(`3. Сценарий: задержки при голосовании. в первом голосует 66/70%, во втором (пониженный кворум) – 66/50%. + Далее аналогичная ситуация`, async() => { + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Preparing, TS.Preparing])); + await increaseTime(23*days); + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Voting, TS.Preparing])); + + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await increaseTime(7*days); + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.VotingDQ, TS.Preparing])); + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await increaseTime(7*days); + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Success, TS.Preparing])); + await increaseTime(23*days); + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await increaseTime(7*days); + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Success, TS.VotingDQ])); + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await increaseTime(7*days); + assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Success, TS.Success])); + }); + + it(`4. Сценарий: отсутствия консенсуса. во втором голосовании один против, + заменяется roadmap (вместо последнего периода [100], [30] предлагается два периода [50, 150], [50, 50]), + его утверждают, привлекаются новые инвестиции`, async() => { + // Есть некоторые особенности в реализации proposeNewRoadmap: + // 1. Стадия дополнительных инвестиций длится ровно неделю, даже если все инвестиции уже собраны. + // 2. Новый roadmap должен содержать и предыдущие taps, притом пройденные taps и текущий в новом roadmap должны быть равны таковым в старом roadmap + // 3. Новый roadmap должен требовать больше денег, чем предыдущий + // 4. Должно быть больше или столько же stage + await daico.withdrawFundsFromTap(0, {from:projectOwner}); + await increaseTime(23*days); // Голосование началось + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await daico.vote(false, {from: investor3}); + await increaseTime(7*days); + + await daico.proposeNewRoadmap([100, 100, 100, 150], [30, 30, 50, 50], {from: projectOwner}); + await increaseTime(21*days); + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.RoadmapVoting, TS.Preparing, TS.Preparing].toString()) + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await daico.vote(true, {from: investor3}); + await increaseTime(7*days); + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Investing, TS.Preparing, TS.Preparing].toString()) + + await evercityToken.mint(investor1, 50, {from: evercity}); + await evercityToken.mint(investor2, 50, {from: evercity}); + await evercityToken.mint(investor3, 50, {from: evercity}); + + await evercityToken.approve(daico.address, 50, {from:investor1}); + await evercityToken.approve(daico.address, 50, {from:investor2}); + await evercityToken.approve(daico.address, 50, {from:investor3}); + + await daico.invest(50, {from:investor1}); + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Investing, TS.Preparing, TS.Preparing].toString()) + + await daico.invest(50, {from:investor2}); + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Investing, TS.Preparing, TS.Preparing].toString()) + await daico.invest(50, {from:investor3}); + await increaseTime(7*days); + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Preparing, TS.Preparing].toString()) + + await increaseTime(43*days); // Голосование началось + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Voting, TS.Preparing].toString()) + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await daico.vote(true, {from: investor3}); + await increaseTime(7*days); + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Success, TS.Preparing].toString()) + await daico.withdrawFundsFromTap(2, {from:projectOwner}); + await increaseTime(43*days); // Голосование началось + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Success, TS.Voting].toString()) + await daico.vote(true, {from: investor1}); + await daico.vote(true, {from: investor2}); + await daico.vote(true, {from: investor3}); + await increaseTime(7*days); + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Success, TS.Success].toString()) + await daico.withdrawFundsFromTap(3, {from:projectOwner}); }); }); }); + + + + + + + + + + From 6cc1a1d64ecd6eea898c20a236353d869c726a51 Mon Sep 17 00:00:00 2001 From: enkogu Date: Tue, 29 Jan 2019 05:41:56 +0700 Subject: [PATCH 4/6] old unit tests removed --- test/Daico.tests.js | 566 -------------------------------------------- 1 file changed, 566 deletions(-) delete mode 100644 test/Daico.tests.js diff --git a/test/Daico.tests.js b/test/Daico.tests.js deleted file mode 100644 index 8decfe7..0000000 --- a/test/Daico.tests.js +++ /dev/null @@ -1,566 +0,0 @@ -const moment = require("moment"); -const { increaseTime } = require("./utils/helpers"); - -const Daico = artifacts.require("Daico"); -const MintableToken = artifacts.require("MintableToken"); - -contract("Daico unit tests", (accounts) => { - const evercityMemberAddress = accounts[0]; - const projectOwnerAddress = accounts[1]; - const inverstorAddress = accounts[2]; - const inverstorAddress2 = accounts[3]; - const inverstorAddress3 = accounts[4]; - const inverstorAddress4 = accounts[5]; - const inverstorAddress5 = accounts[6]; - const returnAddress = accounts[7]; - const otherAddress = accounts[8]; - - const VOTING_TYPE_RELEASE_TAP = 0; - const VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM = 1; - const VOTING_TYPE_CHANGE_ROADMAP = 2; - const VOTING_TYPE_CHANGE_ROADMAP_DECREASED_QUORUM = 3; - const VOTING_TYPE_TERMINATE_PROJECT = 4; - const VOTING_TYPE_TERMINATE_PROJECT_DECREASED_QUORUM = 5; - - const VOTING_RESULT_ACCEPT = 0; - const VOTING_RESULT_DECLINE = 1; - const VOTING_RESULT_QUORUM_NOT_REACHED = 2; - const VOTING_RESULT_NO_DECISION = 3; - - const minQuorumRate = 70; - const minVoteRate = 70; - - 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, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate); - await daiToken.transfer(daico.address, 3, {from: evercityMemberAddress}); - }); - - describe("constructor()", () => { - it("should revert if DAI token address is 0x00", async() => { - await Daico.new(0x00, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); - }); - - it("should revert if project token address is 0x00", async() => { - await Daico.new(daiToken.address, 0x00, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); - }); - - it("should revert if project owner address is 0x00", async() => { - await Daico.new(daiToken.address, projectToken.address, 0x00, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); - }); - - it("should revert if taps count is 0", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 0, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); - }); - - it("should revert if tap amounts array length not equal to taps count", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [], timestampsFinishAt, minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); - }); - - it("should revert if tap timestamps finish at array length not equal to taps count", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], [], minVoteRate, minQuorumRate).should.be.rejectedWith("revert"); - }); - - it("should revert if min quorum rate is 0", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, 0, minVoteRate).should.be.rejectedWith("revert"); - }); - - it("should revert if min vote rate is 0", async() => { - await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], timestampsFinishAt, 0, minQuorumRate).should.be.rejectedWith("revert"); - }); - - it("should set contract properties", async() => { - const daicoNew = await Daico.new(daiToken.address, projectToken.address, projectOwnerAddress, returnAddress, 2, [1, 2], [3,4], 3, 4).should.be.fulfilled; - assert.equal(await daicoNew.daiToken(), daiToken.address); - assert.equal(await daicoNew.projectToken(), projectToken.address); - assert.equal(await daicoNew.projectOwner(), projectOwnerAddress); - assert.equal(await daicoNew.tapsCount(), 2); - assert.equal(await daicoNew.tapAmounts(0), 1); - assert.equal(await daicoNew.tapAmounts(1), 2); - assert.equal(await daicoNew.tapTimestampsFinishAt(0), 3); - assert.equal(await daicoNew.tapTimestampsFinishAt(1), 4); - assert.equal(await daicoNew.minQuorumRate(), 3); - assert.equal(await daicoNew.minVoteRate(), 4); - - }); - - it("should create initial votings of type ReleaseTap", async() => { - assert.equal(await daico.votingsCount(), 2); - const voting = await daico.votings(0); - assert.equal(voting[5].sub(voting[4]), 7 * 24 * 60 * 60); - assert.equal(voting[6], VOTING_TYPE_RELEASE_TAP); - }); - }); - - describe("createVoting()", () => { - it("should revert if quorum rate is 0", async() => { - await daico.createVoting(0, 0, 1, 1, VOTING_TYPE_RELEASE_TAP).should.be.rejectedWith("revert"); - }); - - it("should revert if created at is 0", async() => { - await daico.createVoting(0, 1, 0, 1, VOTING_TYPE_RELEASE_TAP).should.be.rejectedWith("revert"); - }); - - it("should revert if finish at is 0", async() => { - await daico.createVoting(0, 1, 1, 0, VOTING_TYPE_RELEASE_TAP).should.be.rejectedWith("revert"); - }); - - it("should create a new voting (daico.createVoting)", async() => { - await daico.createVoting(0, 1, 2, 3, VOTING_TYPE_RELEASE_TAP).should.be.fulfilled; - const votingsCount = await daico.votingsCount(); - const voting = await daico.votings(votingsCount.sub(1)); - assert.equal(voting[0], 0); - assert.equal(voting[3], 1); - assert.equal(voting[4], 2); - assert.equal(voting[5], 3); - assert.equal(voting[6], VOTING_TYPE_RELEASE_TAP); - }); - - it("should update contract properties", async() => { - const votingsCountBefore = (await daico.votingsCount()).toNumber(); - const tapVotingsCountBefore = (await daico.tapVotingsCount(0)).toNumber(); - - await daico.createVoting(0, 1, 2, 3, VOTING_TYPE_RELEASE_TAP).should.be.fulfilled; - - const votingsCountAfter = (await daico.votingsCount()).toNumber(); - const tapVotingsCountAfter = (await daico.tapVotingsCount(0)).toNumber(); - - assert.equal(await daico.tapVotings(0, tapVotingsCountAfter - 1), votingsCountAfter - 1); - assert.equal(tapVotingsCountAfter - 1, tapVotingsCountBefore); - assert.equal(votingsCountAfter - 1, votingsCountBefore); - }); - }); - - describe("createVotingByInvestor()", () => { - it("should revert if voting type is not: ChangeRoadmap, ChangeRoadmapDecreasedQuorum, TerminateProject, TerminateProjectDecreasedQuorum", async() => { - await daico.createVotingByInvestor(0, VOTING_TYPE_RELEASE_TAP, {from: inverstorAddress}).should.be.rejectedWith("revert"); - }); - - 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()", () => { - it("should revert if voting type is not release tap decreased quorum", async() => { - await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP, {from: evercityMemberAddress}).should.be.rejectedWith("revert"); - }); - - it("should revert if latest voting for particular tap is not finished", async() => { - await daico.createVotingByOwner(0, VOTING_TYPE_RELEASE_TAP_DECREASED_QUORUM, {from: evercityMemberAddress}).should.be.rejectedWith("revert"); - }); - - 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 createVotingByOwner", async() => { - await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; - await daico.vote(0, true, {from: inverstorAddress2}).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()", () => { - it("should return quorum not reached", async() => { - await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; - await daico.vote(0, true, {from: inverstorAddress2}).should.be.fulfilled; - assert.equal(new web3.BigNumber(await daico.getVotingResult(0)).toNumber(), VOTING_RESULT_QUORUM_NOT_REACHED); - }); - - it("should return accept", 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(new web3.BigNumber(await daico.getVotingResult(0)).toNumber(), VOTING_RESULT_ACCEPT); - }); - - it("should return 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; - assert.equal(await daico.getVotingResult(0), VOTING_RESULT_DECLINE); - }); - - it("should return no decision", 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, true, {from: inverstorAddress3}).should.be.fulfilled; - await daico.vote(0, true, {from: inverstorAddress4}).should.be.fulfilled; - assert.equal(await daico.getVotingResult(0), VOTING_RESULT_NO_DECISION); - }); - }); - - describe("isInvestorVoted()", () => { - it("should revert if investor address is 0x00", async() => { - await daico.isInvestorVoted(0, 0x00).should.be.rejectedWith("revert"); - }); - - it("should return false if investor has not yet voted", async() => { - assert.equal(await daico.isInvestorVoted(0, inverstorAddress), false); - }); - - it("should return true if investor has already voted", async() => { - await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; - assert.equal(await daico.isInvestorVoted(0, inverstorAddress), true); - }); - }); - - 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 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; - 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); - }); - - it("should return true if investors have accepted tap release", 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 daico.isTapWithdrawAcceptedByInvestors(0), true); - }); - }); - - describe("vote()", () => { - it("should revert if it is too early to vote", async() => { - await daico.vote(1, 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, returnAddress, 2, [1, 2], timestampsFinishAt, minVoteRate, minQuorumRate); - await increaseTime(7 * 24 * 60 * 60); - await daicoNew.vote(0, true, {from: inverstorAddress}).should.be.rejectedWith("revert"); - }); - - 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 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, 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; - await daico.vote(0, true, {from: inverstorAddress}).should.be.rejectedWith("revert"); - }); - - it("should mark investor as already voted", async() => { - assert.equal(await daico.isInvestorVoted(0, inverstorAddress), false); - await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; - assert.equal(await daico.isInvestorVoted(0, inverstorAddress), true); - }); - - it("should update yes votes count", async() => { - assert.equal((await daico.votings(0))[1], 0); - await daico.vote(0, true, {from: inverstorAddress}).should.be.fulfilled; - assert.equal((await daico.votings(0))[1], 1); - }); - - it("should update no votes count", async() => { - assert.equal((await daico.votings(0))[2], 0); - await daico.vote(0, false, {from: inverstorAddress}).should.be.fulfilled; - assert.equal((await daico.votings(0))[2], 1); - }); - }); - - 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 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; - // withdraw DAI tokens - assert.equal(await daiToken.balanceOf(returnAddress), 0); - await daico.withdrawFunding({from: evercityMemberAddress}).should.be.fulfilled; - assert.equal(await daiToken.balanceOf(returnAddress), 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); - }); - }); - - 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; - }); - }); - -}); From a15ecfb9162b3ddeb566693438dc5495be681e05 Mon Sep 17 00:00:00 2001 From: enkogu Date: Thu, 7 Feb 2019 21:49:37 +0700 Subject: [PATCH 5/6] STOContract, style fixes --- contracts/Daico/Daico.sol | 670 ++++++++++++++------------------ contracts/Daico/IDaico.sol | 3 + contracts/Daico/STOContract.sol | 31 ++ package-lock.json | 275 +++++++++---- package.json | 2 +- scripts/test.sh | 4 +- test/Daico.functional.tests.js | 95 +++-- test/utils/helpers.js | 36 +- truffle.js | 2 +- 9 files changed, 600 insertions(+), 518 deletions(-) create mode 100644 contracts/Daico/STOContract.sol diff --git a/contracts/Daico/Daico.sol b/contracts/Daico/Daico.sol index e2eaa65..e83af7c 100644 --- a/contracts/Daico/Daico.sol +++ b/contracts/Daico/Daico.sol @@ -2,13 +2,13 @@ pragma solidity ^0.4.24; import "zeppelin-solidity/contracts/math/SafeMath.sol"; import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; -import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; +import "./IDaico.sol"; - -contract Daico { +contract Daico is IDaico { using SafeMath for uint; - event InvestEvent(uint amount, address _sender, uint total, uint tapSum); + event InvestEvent(uint _amount, address _sender, uint _total, uint _tapSum, uint _startedAt); + event Vote(uint _amount, address _sender, bool _vote); enum TapStage { Preparing, @@ -22,35 +22,34 @@ contract Daico { Terminated } - enum VotingResult { - NotExist, - QuorumNotReached, - ConsensusNotReached, + // Voting result + enum VR { + NoResult, + NoCons, Success, Decline } - Project public proj; - - struct Project { - address owner; - MintableToken token; - ERC20 daiToken; - uint createdAt; - uint startedAt; - uint investDuration; - uint votingDuration; - uint additionalDuration; - uint changeRoadmapDuration; - uint quorumPercent; - // uint quorumDecresedPercent; - uint declinePercent; - uint consensusPercent; - bool rewrited; - uint roadmapsCount; - mapping(uint=>uint) tapToRId; // tapId -> roadmapId - mapping(uint=>Roadmap) roadmaps; // roadmapId -> roadmap - } + uint votesD = 7 days; + uint addVotesD = 7 days; + uint investD = 7 days; + uint roadmapD = 21 days; + uint infinity = 99999 days; + + uint quorumPercent = 70; + uint declinePercent = 80; + uint consensusPercent = 70; + + address owner; + address STOContractAddress; + ERC20 daiToken; + uint createdAt; + uint startedAt; + bool newRoadmapProposed; + uint roadmapsCount; + mapping(uint=>uint) tapToRId; // tapId -> roadmapId + mapping(uint=>Roadmap) roadmaps; // roadmapId -> roadmap + struct Roadmap { uint tapsCount; @@ -77,438 +76,335 @@ contract Daico { address[] voted; } - // owner создает Daico контракт, куда закладываются следующие параметры: - // 1. daiToken – токен Evercity - // 2. returnAddress - куда вернуть деньги в случае фейла проекта - // 3. tapFunds – массив количества выплат на каждый tap - // 4. tapDurations – массив длительностей каждого tap - // В конструкторе деплоится projectToken, которые будут получать investors в обмен на daiToken - constructor(address _owner, address _daiToken, address _returnAddress, uint[] memory _tapFunds, uint[] memory _tapDurations) public { + constructor( + address _owner, + address _daiToken, + address _STOContractAddress, + address _returnAddress, + uint[] memory _tapFunds, + uint[] memory _tapDurations) public + { require(_tapFunds.length == _tapDurations.length); - MintableToken projectToken = new MintableToken(); - proj.token = projectToken; - proj.daiToken = ERC20(_daiToken); - proj.owner = _owner; - proj.createdAt = now; - proj.investDuration = 7 days; - proj.votingDuration = 7 days; - proj.additionalDuration = 7 days; - proj.changeRoadmapDuration = 21 days; - proj.quorumPercent = 70; - // proj.quorumDecresedPercent = 50; - proj.declinePercent = 80; - proj.consensusPercent = 70; - - proj.roadmaps[proj.roadmapsCount].tapsCount = _tapFunds.length; - for(uint i = 0; i < _tapFunds.length; i++) { - require(_tapDurations[i] > 7); - proj.tapToRId[i] = 0; // just for clearness; - proj.roadmaps[proj.roadmapsCount].taps[i] = Tap(_tapFunds[i], _tapDurations[i]*(1 days), false); + STOContractAddress = _STOContractAddress; + daiToken = ERC20(_daiToken); + owner = _owner; + createdAt = now; + + roadmaps[roadmapsCount].tapsCount = _tapFunds.length; + Tap memory tap; + for(uint tapFundsNum = 0; tapFundsNum < _tapFunds.length; tapFundsNum++) { + require(_tapDurations[tapFundsNum] > 7); + tapToRId[tapFundsNum] = 0; // just for clearness; + + tap.funds = _tapFunds[tapFundsNum]; + tap.duration = _tapDurations[tapFundsNum]*(1 days); + tap.isWithdrawed = false; + roadmaps[roadmapsCount].taps[tapFundsNum] = tap; } - proj.roadmapsCount += 1; + roadmapsCount += 1; } - function getCurrentTap() internal view returns(uint) { - (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); - return t; + function getCurrentTapNum() internal view returns(uint) { + (uint curTapNum, TapStage[] memory tapStages, uint v) = getTapsInfo(); + return curTapNum; } function getMaximalTapsLength() internal view returns(uint maximal) { - for(uint i = 0; i < proj.roadmapsCount; i++) { - if(proj.roadmaps[i].tapsCount > maximal) { - maximal = proj.roadmaps[i].tapsCount; + for(uint rmNum = 0; rmNum < roadmapsCount; rmNum++) { + if(roadmaps[rmNum].tapsCount > maximal) { + maximal = roadmaps[rmNum].tapsCount; } } } - // // Функция инициации контракта. - // // Имеется общее количество daiToken, которое должно поступить на контракт перед началом процесса – Sum(tapFunds) - // // Любой может купить любое количество оставшихся projectToken, предварительно сделав approve на то же количество daiToken - // // Как только все projectToken проданы, контракт автоматически переходит в первый stage, и owner может снять daiToken за первый tap. - // // Если lifetime закончился, а projectToken проданы не все, то можно вызвать returnTokens, которая вернет токены обратно инвесторам. - // // 1. Для примера – tap.durations == 1 month, все голосования проходят успешно, тогда Timeline такой: - // // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(2TAP: 1 month - 1 week: nothing)---(2TAP: 1 week: voting for a next tap)---... - // // 2. Не набран кворум – сразу стартует повторное голосование (7 дней) с пониженным кворумом >50%. - // // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(1TAP: 1 additional week: voting for a next tap)---... - // // 3. Кворум набран, но пороговый % проголосовавших «за» не пройден. В результате сразу инициируется голосование по пересмотру роадмапа и доработке проекта через месяц. - // // invest ----> active |---(1TAP: 1 month - 1 week: nothing)---(1TAP: 1 week: voting for a next tap)---(1TAP: 3 additional weeks: change roadmap)---(1TAP: 1 additional week: voting)---... - function invest(uint _amount) public { - (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); - require(tapStages[t] == TapStage.Investing); + function replaceRoadmapToProposedOne() internal { + uint curTapNum = getCurrentTapNum(); + for(uint tapNum = 0; tapNum < roadmaps[roadmapsCount - 1].tapsCount; tapNum++) { + if(tapNum > curTapNum) tapToRId[tapNum] = roadmapsCount - 1; + } + } + + function addInvestor(uint _amount, address _investorAddress) public { + require(STOContractAddress == msg.sender); + (uint curTapNum, TapStage[] memory tapStages, uint votNum) = getTapsInfo(); + require(tapStages[curTapNum] == TapStage.Investing); require(_amount > 0); + - if(proj.rewrited) { - for(uint i = 0; i < proj.roadmaps[proj.roadmapsCount - 1].tapsCount; i++) { - if(i > t) { - proj.tapToRId[i] = proj.roadmapsCount - 1; - } - } - t += 1; - } + if(newRoadmapProposed) replaceRoadmapToProposedOne(); curTapNum += 1; + + uint rmNum = tapToRId[curTapNum]; + uint invId = getInvestorId(_investorAddress); - require(totalInvestitions(t) + _amount <= tapAmountsSum(t)); - proj.daiToken.transferFrom(msg.sender, address(this), _amount); - proj.token.mint(msg.sender, _amount); - if(getInvestorId(msg.sender) == proj.roadmaps[proj.tapToRId[getCurrentTap()]].investorsCount) { // not investor - uint invCount = proj.roadmaps[proj.tapToRId[t]].investorsCount; - proj.roadmaps[proj.tapToRId[t]].investors[invCount] = Investor(msg.sender, _amount); - proj.roadmaps[proj.tapToRId[t]].investorsCount += 1; + require(amountOfAllInvestments(curTapNum) + _amount <= tapAmountsSum(curTapNum)); + daiToken.transferFrom(STOContractAddress, address(this), _amount); + + bool notInvestor = (invId == roadmaps[rmNum].investorsCount); + if(notInvestor) { + uint invCount = roadmaps[rmNum].investorsCount; + roadmaps[rmNum].investors[invCount] = Investor(_investorAddress, _amount); + roadmaps[rmNum].investorsCount += 1; } else { - proj.roadmaps[proj.tapToRId[t]].investors[getInvestorId(msg.sender)].invested += _amount; + roadmaps[rmNum].investors[invId].invested += _amount; } - emit InvestEvent(_amount, msg.sender, totalInvestitions(t), tapAmountsSum(t)); - if((totalInvestitions(t) >= tapAmountsSum(t)) && (proj.startedAt==0)) { - proj.startedAt = now; - proj.rewrited = false; + + if(areAllFundsCollected(curTapNum) && (startedAt==0)) { + startedAt = now; + newRoadmapProposed = false; } + emit InvestEvent(_amount, _investorAddress, amountOfAllInvestments(curTapNum), tapAmountsSum(curTapNum), startedAt); } - /*function getVotingsResultForTap(uint t) public view returns(uint[]) { - uint[] memory vr = new uint[](4); - vr[0] = proj.taps[t].votings[0].pro;//votingState(t, 0, false); - vr[1] = proj.taps[t].votings[1].pro;//votingState(t, 1, false); - vr[2] = proj.taps[t].votings[2].pro;//votingState(t, 2, false); - vr[3] = proj.taps[t].votings[3].pro;//votingState(t, 3, false); - return vr; + function areAllFundsCollected(uint _tapNum) internal view returns(bool) { + return amountOfAllInvestments(_tapNum) >= tapAmountsSum(_tapNum); } - function getTaps() public view returns(uint[], uint[]) { - uint[] memory tapFunds = new uint[](getMaximalTapsLength()); - uint[] memory tapDurations = new uint[](getMaximalTapsLength()); - for(uint i = 0; i < getMaximalTapsLength(); i++) { - tapFunds[i] = proj.taps[i].funds; - tapDurations[i] = proj.taps[i].duration; - } - - return (tapFunds, tapDurations); - }*/ - - function totalInvestitions(uint _t) public view returns(uint sum) { - for(uint i = 0; i < proj.roadmaps[proj.tapToRId[_t]].investorsCount; i++) { - sum += proj.roadmaps[proj.tapToRId[_t]].investors[i].invested; + function amountOfAllInvestments(uint _tapNum) public view returns(uint sum) { + uint rmNum = tapToRId[_tapNum]; + uint invCount = roadmaps[rmNum].investorsCount; + for(uint invNum = 0; invNum < invCount; invNum++) { + sum += roadmaps[rmNum].investors[invNum].invested; } } - function tapAmountsSum(uint _t) public view returns(uint sum) { - for(uint t = 0; t < proj.roadmaps[proj.tapToRId[_t]].tapsCount; t++) { - sum += proj.roadmaps[proj.tapToRId[_t]].taps[t].funds; + function tapAmountsSum(uint _tapNum) public view returns(uint sum) { + uint rmNum = tapToRId[_tapNum]; + uint tapsCount = roadmaps[rmNum].tapsCount; + for(uint tapNum = 0; tapNum < tapsCount; tapNum++) { + sum += roadmaps[rmNum].taps[tapNum].funds; } } function returnTokens() external { - (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); - require(tapStages[t] == TapStage.Terminated); + (uint curTapNum, TapStage[] memory tapStages, uint votNum) = getTapsInfo(); + uint rmNum = tapToRId[curTapNum]; + require(tapStages[curTapNum] == TapStage.Terminated); - uint remainder = proj.daiToken.balanceOf(address(this)); + uint remainder = daiToken.balanceOf(address(this)); uint part; - for(uint i = 0; i < proj.roadmaps[proj.tapToRId[getCurrentTap()]].investorsCount; i++) { - part = ((remainder * proj.roadmaps[proj.tapToRId[getCurrentTap()]].investors[i].invested)/totalInvestitions(t)); - proj.daiToken.transfer(proj.roadmaps[proj.tapToRId[getCurrentTap()]].investors[i].addr, part); + Investor memory investor; + + for(uint invNum = 0; invNum >> – переход на следующий tap - // • – terminate - - // | NOQ---• |15| | NOQ---• |20| | NOQ---• |24| - // | |16| |17| | |21| | - // | NOC------RM--VOT_RM----* | NOC----------VOTDQ--*| NOC---• |25| - // |02| | SUC->>> |18| | SUC->>> |22| | SUC->>> |26| - // | NOQ------VOTDQ----*| DEC---• |19| | DEC---• |23| | DEC---• |27| - // |00| |01| | - // INV--PREP--VOT-*| SUC->>> |03| - // | DEC---• |04| - // | | NOC---• |07| |08| | NOQ---• |11| - // | NOC--RM--VOT_RM---*| NOQ--------------VOTDQ-----*| NOC---• |12| - // |05| |06| | SUC->>> |09| | SUC->>> |13| - // | DEC---• |10| | DEC---• |14| - // */ + function getTap(uint _tapNum) internal view returns(Tap) { + uint rmNum = tapToRId[_tapNum]; + return roadmaps[rmNum].taps[_tapNum]; + } function getTapsInfo() public view returns(uint, TapStage[], uint) { // curren_tap, tapstages, current_voting - uint votD = proj.votingDuration; - uint addD = proj.additionalDuration; - uint invD = proj.investDuration; - uint tapD; - uint rmD = proj.changeRoadmapDuration; uint max = getMaximalTapsLength(); TapStage[] memory tapStages = new TapStage[](max); - uint tapStart = 0; - uint t; - for(t = 0; t < max; t++) { - tapD = proj.roadmaps[proj.tapToRId[t]].taps[t].duration; - if(proj.startedAt == 0) { - if(now > proj.createdAt + proj.investDuration) { - tapStages[0] = TapStage.Terminated; - return (t, tapStages, 0); - } else { - tapStages[0] = TapStage.Investing; - return (t, tapStages, 0); - } - } else if(t==0) { - tapStages[t] = TapStage.Success; - } else if(at(tapStart, tapD-votD)) { - tapStages[t] = TapStage.Preparing; - return (t, tapStages, 0); - } else if(at(tapStart+tapD-votD, votD)) { - tapStages[t] = TapStage.Voting; - return (t, tapStages, 0); - } else if(now >= proj.startedAt + tapStart+tapD) { - if(VotingResult.Decline == votingState(t, 0, false)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 0); - } else if(VotingResult.Success == votingState(t, 0, false)) { - tapStages[t] = TapStage.Success; - tapStart += tapD; - } else if(VotingResult.QuorumNotReached == votingState(t, 0, false)) { - if(at(tapStart+tapD, addD)) { - tapStages[t] = TapStage.VotingDQ; - return (t, tapStages, 1); - } else if(now >= proj.startedAt + tapStart+tapD+addD) { - if(VotingResult.QuorumNotReached == votingState(t, 1, true)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 1); - } else if(VotingResult.Decline == votingState(t, 1, true)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 1); - } else if(VotingResult.Success == votingState(t, 1, true)) { - tapStages[t] = TapStage.Success; - tapStart += (tapD+addD); - } else if(VotingResult.ConsensusNotReached == votingState(t, 1, true)) { - if(at(tapStart+tapD+addD, rmD)) { - tapStages[t] = TapStage.RoadmapPreparing; - return (t, tapStages, 2); - } else if(at(tapStart+tapD+addD+rmD, votD)) { - tapStages[t] = TapStage.RoadmapVoting; - return (t, tapStages, 2); - } else if(now >= proj.startedAt + tapStart+tapD+addD+rmD+votD) { - if(VotingResult.Success == votingState(t, 2, false)) { - if(at(tapStart+tapD+addD+rmD+votD, invD)) { // invest stage for a new roadmap - tapStages[t] = TapStage.Investing; - return (t, tapStages, 2); - } else if((now >= proj.startedAt + tapStart+tapD+addD+rmD+votD+invD) && (totalInvestitions(t) >= tapAmountsSum(t))) { - tapStages[t] = TapStage.Success; - tapStart += (tapStart+tapD+addD+rmD+votD+invD); - } else { - tapStages[t] = TapStage.Success; - return (t, tapStages, 2); - } - } else if(VotingResult.ConsensusNotReached == votingState(t, 2, false)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 2); - } else if(VotingResult.Decline == votingState(t, 2, false)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 2); - } else if(VotingResult.QuorumNotReached == votingState(t, 2, false)) { - if(at(tapStart+tapD+addD+rmD+votD, addD)) { - tapStages[t] = TapStage.RoadmapVotingDQ; - return (t, tapStages, 3); - } else if(now >= proj.startedAt + tapStart+tapD+addD+rmD+votD+addD) { - if(VotingResult.Success == votingState(t, 3, true)) { - if(at(tapStart+tapD+addD+rmD+votD+addD, invD)) { // invest stage for a new roadmap - tapStages[t] = TapStage.Investing; - return (t, tapStages, 3); - } else if((now >= proj.startedAt + tapStart+tapD+addD+rmD+votD+addD+invD) && (totalInvestitions(t) >= tapAmountsSum(t))) { - tapStages[t] = TapStage.Success; - tapStart += (tapStart+tapD+addD+rmD+votD+addD+invD); - } else { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 3); - } - } else if(VotingResult.ConsensusNotReached == votingState(t, 3, true)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 3); - } else if(VotingResult.Decline == votingState(t, 3, true)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 3); - } else if(VotingResult.QuorumNotReached == votingState(t, 3, true)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 3); - } - } - } - } - } - } - } else if(VotingResult.ConsensusNotReached == votingState(t, 0, false)) { - if(at(tapStart+tapD, rmD)) { - tapStages[t] = TapStage.RoadmapPreparing; - return (t, tapStages, 1); - } else if(at(tapStart+tapD+rmD, votD)) { - tapStages[t] = TapStage.RoadmapVoting; - return (t, tapStages, 1); - } else if(now >= proj.startedAt + tapStart+tapD+rmD+votD) { - if(VotingResult.Decline == votingState(t, 1, false)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 1); - } else if(VotingResult.Success == votingState(t, 1, false)) { - if(at(tapStart+tapD+rmD+votD, invD)) { // invest stage for a new roadmap - tapStages[t] = TapStage.Investing; - return (t, tapStages, 1); - } else if((now >= proj.startedAt + tapStart+tapD+rmD+votD+invD) && (totalInvestitions(t) >= tapAmountsSum(t))) { - tapStages[t] = TapStage.Success; - tapStart += (tapD+rmD+votD+invD); - } else { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 1); - } - } else if(VotingResult.ConsensusNotReached == votingState(t, 1, false)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 1); - } else if(VotingResult.QuorumNotReached == votingState(t, 1, false)) { - if(at(tapStart+tapD+rmD+votD, addD)) { - tapStages[t] = TapStage.RoadmapVotingDQ; - return (t, tapStages, 2); - } else if(now >= proj.startedAt + tapStart+tapD+rmD+votD+addD) { - if(VotingResult.Decline == votingState(t, 2, true)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 2); - } else if(VotingResult.Success == votingState(t, 2, true)) { - if(at(tapStart+tapD+rmD+votD+addD, invD)) { // invest stage for a new roadmap - tapStages[t] = TapStage.Investing; - return (t, tapStages, 2); - } else if((now >= proj.startedAt + tapStart+tapD+rmD+votD+addD+invD) && (totalInvestitions(t) >= tapAmountsSum(t))) { - tapStages[t] = TapStage.Success; - tapStart += (tapD+addD+rmD+votD+addD+invD); - } else { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 2); - } - } else if(VotingResult.ConsensusNotReached == votingState(t, 2, true)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 2); - } else if(VotingResult.QuorumNotReached == votingState(t, 2, true)) { - tapStages[t] = TapStage.Terminated; - return (t, tapStages, 2); - } - } - } - } - } - } + uint start = 0; + uint tapD; + uint votNum = 0; + + for(uint tapNum = 0; tapNum < max; tapNum++) { + tapD = getTap(tapNum).duration; + (votNum, tapStages[tapNum], start) = getTapStage(tapNum, tapD, start); + if((tapStages[tapNum]!=TapStage.Success)) return (tapNum, tapStages, votNum); } - return (t, tapStages, 0); + + return (tapNum, tapStages, votNum); + } + + // votingnum, tapstage, NewstartTime + function getTapStage(uint _tapNum, uint _tapD, uint _start) public view returns(uint, TapStage, uint) { + bool invC = areAllFundsCollected(_tapNum); + uint RmV = _start + _tapD + roadmapD + votesD; + uint addVRmV = _start + _tapD + addVotesD + roadmapD + votesD; + + if((startedAt == 0) && (now < createdAt + investD)) return (0, TapStage.Investing, 0); + if((startedAt == 0) && (now >= createdAt + investD)) return (0, TapStage.Terminated, 0); + if((_tapNum==0)) return (0, TapStage.Success, 0); + + // _tapNum _start time duration voting1 voting2 voting3 voting4 + //---------------------------------------------------------------------------------------------------------- + if(thisCase(_tapNum, _start, _tapD-votesD,VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Preparing, 0); + if(thisCase(_tapNum, _start+_tapD-votesD, votesD, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Voting, 0); + if(thisCase(_tapNum, investD, infinity, VR.Decline, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, investD, infinity, VR.Success, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Success, _start + _tapD); + if(thisCase(_tapNum, _start+_tapD, addVotesD, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (1, TapStage.VotingDQ, 0); + if(thisCase(_tapNum, _start+_tapD, infinity, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, _start+_tapD, infinity, VR.NoResult, VR.Decline, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, _start+_tapD, infinity, VR.NoResult, VR.Success, VR.NoResult, VR.NoResult)) return (0, TapStage.Success, _start + _tapD + addVotesD); + if(thisCase(_tapNum, _start+_tapD+addVotesD, roadmapD, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (0, TapStage.RoadmapPreparing, 0); + if(thisCase(_tapNum, addVRmV-votesD, votesD, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (2, TapStage.RoadmapVoting, 0); + if(thisCase(_tapNum, addVRmV, investD, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult)) return (0, TapStage.Investing, 0); + if(thisCase(_tapNum, addVRmV, infinity, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult) && invC) return (0, TapStage.Success, addVRmV + investD); + if(thisCase(_tapNum, addVRmV, infinity, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, _start+_tapD+addVotesD, infinity, VR.NoResult, VR.NoCons, VR.NoCons, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, _start+_tapD+addVotesD, infinity, VR.NoResult, VR.NoCons, VR.Decline, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, addVRmV, addVotesD, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (3, TapStage.RoadmapVotingDQ, 0); + if(thisCase(_tapNum, addVRmV+addVotesD, investD, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success )) return (0, TapStage.Investing, 0); + if(thisCase(_tapNum, addVRmV+investD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult )) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, addVRmV+investD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoCons )) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, addVRmV+investD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.Decline )) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, addVRmV+addVotesD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success ) && invC) return (0, TapStage.Success, addVRmV + addVotesD + investD); + if(thisCase(_tapNum, addVRmV+addVotesD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success ) && !invC) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, _start+_tapD, roadmapD, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.RoadmapPreparing, 0); + if(thisCase(_tapNum, _start+_tapD+roadmapD, votesD, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (1, TapStage.RoadmapVoting, 0); + if(thisCase(_tapNum, addVRmV+addVotesD+investD, infinity, VR.NoCons, VR.Decline, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, addVRmV+addVotesD+investD, infinity, VR.NoCons, VR.NoCons, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, RmV, investD, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult)) return (0, TapStage.Investing, 0); + if(thisCase(_tapNum, RmV, infinity, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult) && invC) return (0, TapStage.Success, RmV + investD); + if(thisCase(_tapNum, RmV, infinity, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, RmV, addVotesD, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (2, TapStage.RoadmapVotingDQ, 0); + if(thisCase(_tapNum, RmV+addVotesD, investD, VR.NoCons, VR.NoResult, VR.Success, VR.NoResult)) return (0, TapStage.Investing, 0); + if(thisCase(_tapNum, RmV+investD, infinity, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, RmV+investD, infinity, VR.NoCons, VR.NoResult, VR.Decline, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, RmV+investD, infinity, VR.NoCons, VR.NoResult, VR.NoCons, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(thisCase(_tapNum, RmV+addVotesD, infinity, VR.NoCons, VR.NoCons, VR.Success, VR.NoResult) && invC) return (0, TapStage.Success, addVRmV + addVotesD + investD); + if(thisCase(_tapNum, RmV+addVotesD, infinity, VR.NoCons, VR.NoCons, VR.Success, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); + //---------------------------------------------------------------------------------------------------------- + return (0, TapStage.Preparing, 0); + } + + function thisCase(uint _tapNum, uint _from, uint _duration, + VR _votingRes1, + VR _votingRes2, + VR _votingRes3, + VR _votingRes4) public view returns(bool) + { + if(!at(_from, _duration)) return false; + + bool withLessQuorum = false; + if(_votingRes1 != votingState(_tapNum, 0, withLessQuorum)) return false; + withLessQuorum = (_votingRes1 == VR.NoResult); + + if(_votingRes2 != votingState(_tapNum, 1, withLessQuorum)) return false; + withLessQuorum = (_votingRes2 == VR.NoResult); + + if(_votingRes3 != votingState(_tapNum, 2, withLessQuorum)) return false; + withLessQuorum = (_votingRes3 == VR.NoResult); + + if(_votingRes4 != votingState(_tapNum, 3, withLessQuorum)) return false; + + return true; } function proposeNewRoadmap(uint[] _tapFunds, uint[] _tapDurations) external { - (uint t, TapStage[] memory tapStages, uint v) = getTapsInfo(); - require(tapStages[t] == TapStage.RoadmapPreparing); + (uint curTapNum, TapStage[] memory tapStages, uint votNum) = getTapsInfo(); + uint rmNum; + require(tapStages[curTapNum] == TapStage.RoadmapPreparing); require(_tapFunds.length == _tapDurations.length); - require(msg.sender == proj.owner); - require(_tapFunds.length >= proj.roadmaps[proj.roadmapsCount - 1].tapsCount); - require(!proj.rewrited); - - proj.roadmaps[proj.roadmapsCount].tapsCount = _tapFunds.length; - proj.roadmaps[proj.roadmapsCount].investorsCount = proj.roadmaps[proj.roadmapsCount - 1].investorsCount; - uint i; - - for(i = 0; i < _tapFunds.length; i++) { - require(_tapDurations[i] > 7); - if(i <= t) { - require(_tapDurations[i]*(1 days) == proj.roadmaps[proj.tapToRId[i]].taps[i].duration); - require(_tapFunds[i] == proj.roadmaps[proj.tapToRId[i]].taps[i].funds); - } else if(i > t) { - proj.tapToRId[i] = proj.roadmapsCount; // just for clearness; + require(msg.sender == owner); + require(_tapFunds.length >= roadmaps[roadmapsCount - 1].tapsCount); + require(!newRoadmapProposed); + + roadmaps[roadmapsCount].tapsCount = _tapFunds.length; + roadmaps[roadmapsCount].investorsCount = roadmaps[roadmapsCount - 1].investorsCount; + + for(uint tapFundsNum = 0; tapFundsNum < _tapFunds.length; tapFundsNum++) { + rmNum = tapToRId[tapFundsNum]; + require(_tapDurations[tapFundsNum] > 7); + if(tapFundsNum <= curTapNum) { + require(_tapDurations[tapFundsNum]*(1 days) == roadmaps[rmNum].taps[tapFundsNum].duration); + require(_tapFunds[tapFundsNum] == roadmaps[rmNum].taps[tapFundsNum].funds); + } else if(tapFundsNum > curTapNum) { + tapToRId[tapFundsNum] = roadmapsCount; // just for clearness; } - proj.roadmaps[proj.roadmapsCount].taps[i] = Tap(_tapFunds[i], _tapDurations[i]*(1 days), false); + Tap memory tap; + tap.funds = _tapFunds[tapFundsNum]; + tap.duration = _tapDurations[tapFundsNum]*(1 days); + tap.isWithdrawed = false; + roadmaps[roadmapsCount].taps[tapFundsNum] = tap; } - for(i = 0; i < proj.roadmaps[proj.roadmapsCount - 1].investorsCount; i++) { - proj.roadmaps[proj.roadmapsCount].investors[i] = proj.roadmaps[proj.roadmapsCount - 1].investors[i]; + uint invNum; + for(invNum = 0; invNum < roadmaps[roadmapsCount - 1].investorsCount; invNum++) { + roadmaps[roadmapsCount].investors[invNum] = roadmaps[roadmapsCount - 1].investors[invNum]; } - proj.roadmapsCount += 1; - proj.rewrited = true; + roadmapsCount += 1; + newRoadmapProposed = true; } function at(uint _from, uint _long) public view returns(bool) { - bool out = ((now >= _from + proj.startedAt) && (now < proj.startedAt + _from + _long)); - return out; + return ((now >= _from + startedAt) && (now < startedAt + _from + _long)); } - function isQuorumReached(uint _t, Voting memory _v, uint _quorumPercent) internal view returns(bool) { - return (_v.pro.add(_v.versus).mul(100) >= tapAmountsSum(_t).mul(_quorumPercent)); // FIX HERE: totalInvestitions change after roadmap + function isQuorumReached(uint _tapNum, Voting memory _voting, uint _quorumPercent) internal view returns(bool) { + return (_voting.pro.add(_voting.versus).mul(100) >= tapAmountsSum(_tapNum).mul(_quorumPercent)); } - function isConsensusReached(Voting memory _v, uint _consensusPercent) internal view returns(bool) { - return (_v.pro.mul(100 - _consensusPercent) >= _v.versus.mul(_consensusPercent)); + function isConsensusReached(Voting memory _voting, uint _consensusPercent) internal view returns(bool) { + return (_voting.pro.mul(100 - _consensusPercent) >= _voting.versus.mul(_consensusPercent)); } - function isDeclined(Voting memory _v, uint _declinePercent) internal view returns(bool) { - return (_v.versus.mul(100 - _declinePercent) >= _v.pro.mul(_declinePercent)); + function isDeclined(Voting memory _voting, uint _declinePercent) internal view returns(bool) { + return (_voting.versus.mul(100 - _declinePercent) >= _voting.pro.mul(_declinePercent)); } - function votingState(uint _t, uint _v, bool _isQuorumDecreased) public view returns(VotingResult) { - uint quorumPercent; - if(_isQuorumDecreased) { - quorumPercent = proj.quorumPercent - 20; - } else { - quorumPercent = proj.quorumPercent; - } - Voting memory v = proj.roadmaps[proj.tapToRId[_t]].taps[_t].votings[_v]; - if(isQuorumReached(_t, v, quorumPercent)) { - if(isConsensusReached(v, proj.consensusPercent)) { - return VotingResult.Success; - } else if(isDeclined(v, proj.declinePercent)) { - return VotingResult.Decline; - } else { - return VotingResult.ConsensusNotReached; - } - } else { - return VotingResult.QuorumNotReached; - } + function getVoting(uint _tapNum, uint _votNum) internal view returns(Voting) { + uint rmNum = tapToRId[_tapNum]; + return roadmaps[rmNum].taps[_tapNum].votings[_votNum]; + } + + function votingState(uint _tapNum, uint _votNum, bool _isQuorumDecreased) public view returns(VR) { + uint _quorumPercent = quorumPercent; + if(_isQuorumDecreased) _quorumPercent = quorumPercent - 20; + + Voting memory voting = getVoting(_tapNum, _votNum); + + if(!isQuorumReached(_tapNum, voting, _quorumPercent)) return VR.NoResult; + if(isConsensusReached(voting, consensusPercent)) return VR.Success; + if(isDeclined(voting, declinePercent)) return VR.Decline; + return VR.NoCons; } } \ No newline at end of file diff --git a/contracts/Daico/IDaico.sol b/contracts/Daico/IDaico.sol index 5bf89ce..c7717cd 100644 --- a/contracts/Daico/IDaico.sol +++ b/contracts/Daico/IDaico.sol @@ -2,4 +2,7 @@ pragma solidity ^0.4.24; contract IDaico { + function addInvestor(uint _amount, address _investorAddress) public; + function vote(bool _vote) external; + function proposeNewRoadmap(uint[] _tapFunds, uint[] _tapDurations) external; } diff --git a/contracts/Daico/STOContract.sol b/contracts/Daico/STOContract.sol new file mode 100644 index 0000000..8ee0dc0 --- /dev/null +++ b/contracts/Daico/STOContract.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.4.24; + +import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; +import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; +import "./IDaico.sol"; + + +contract STOContract is MintableToken { + address public ervercityTokenAddress; + address public daicoAddress; + + constructor(address _ervercityTokenAddress) public { + ervercityTokenAddress = _ervercityTokenAddress; + } + + function setDaicoAddress(address _daicoAddress) public onlyOwner { + daicoAddress = _daicoAddress; + } + + function invest(uint _amount) public { + ERC20(ervercityTokenAddress).transferFrom(msg.sender, address(this), _amount); + ERC20(ervercityTokenAddress).approve(daicoAddress, _amount); + + totalSupply_ = totalSupply_.add(_amount); + balances[msg.sender] = balances[msg.sender].add(_amount); + emit Mint(msg.sender, _amount); + emit Transfer(address(0), msg.sender, _amount); + + IDaico(daicoAddress).addInvestor(_amount, msg.sender); + } +} diff --git a/package-lock.json b/package-lock.json index b26fa73..361a512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1259,10 +1259,6 @@ "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" }, - "bignumber.js": { - "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", - "from": "bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" - }, "binaryextensions": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.1.tgz", @@ -4695,6 +4691,28 @@ } } }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, "loader-utils": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", @@ -5655,6 +5673,16 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", @@ -5833,6 +5861,44 @@ } } }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -6587,6 +6653,29 @@ "solidity-parser-sc": "0.4.11", "tree-kill": "^1.2.0", "web3": "^0.18.4" + }, + "dependencies": { + "bignumber.js": { + "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "from": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" + }, + "utf8": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", + "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=" + }, + "web3": { + "version": "0.18.4", + "resolved": "https://registry.npmjs.org/web3/-/web3-0.18.4.tgz", + "integrity": "sha1-gewXhBRUkfLqqJVbMcBgSeB8Xn0=", + "requires": { + "bignumber.js": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "crypto-js": "^3.1.4", + "utf8": "^2.1.1", + "xhr2": "*", + "xmlhttprequest": "*" + } + } } }, "solidity-parser-sc": { @@ -7119,58 +7208,25 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, "truffle": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.13.tgz", - "integrity": "sha1-vydYaYi0/4RWPt+/MrR5QUCKdq0=", + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.15.tgz", + "integrity": "sha512-6gaNn9ZjvNjdalJMF/qEDUDoRdieKfdYcyFVpVxd+ogAta5kgJxI3XlEmS79Ih0vBhb00tKa9rBweVJ5892sYg==", "requires": { "mocha": "^4.1.0", "original-require": "1.0.1", - "solc": "0.4.24" + "solc": "0.4.25" }, "dependencies": { - "commander": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==" - }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==" - }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==" - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" - }, - "mocha": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", - "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.11.0", - "debug": "3.1.0", - "diff": "3.3.1", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.3", - "he": "1.1.1", - "mkdirp": "0.5.1", - "supports-color": "4.4.0" - } - }, - "supports-color": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "solc": { + "version": "0.4.25", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.25.tgz", + "integrity": "sha512-jU1YygRVy6zatgXrLY2rRm7HW1d7a8CkkEgNJwvH2VLpWhMFsMdWcJn6kUqZwcSz/Vm+w89dy7Z/aB5p6AFTrg==", "requires": { - "has-flag": "^2.0.0" + "fs-extra": "^0.30.0", + "memorystream": "^0.3.1", + "require-from-string": "^1.1.0", + "semver": "^5.3.0", + "yargs": "^4.7.1" } } } @@ -7184,6 +7240,29 @@ "ethereumjs-wallet": "^0.6.0", "web3": "^0.18.2", "web3-provider-engine": "^14.0.5" + }, + "dependencies": { + "bignumber.js": { + "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "from": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" + }, + "utf8": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", + "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=" + }, + "web3": { + "version": "0.18.4", + "resolved": "https://registry.npmjs.org/web3/-/web3-0.18.4.tgz", + "integrity": "sha1-gewXhBRUkfLqqJVbMcBgSeB8Xn0=", + "requires": { + "bignumber.js": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", + "crypto-js": "^3.1.4", + "utf8": "^2.1.1", + "xhr2": "*", + "xmlhttprequest": "*" + } + } } }, "tslib": { @@ -7442,25 +7521,6 @@ } } }, - "web3": { - "version": "0.18.4", - "resolved": "https://registry.npmjs.org/web3/-/web3-0.18.4.tgz", - "integrity": "sha1-gewXhBRUkfLqqJVbMcBgSeB8Xn0=", - "requires": { - "bignumber.js": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", - "crypto-js": "^3.1.4", - "utf8": "^2.1.1", - "xhr2": "*", - "xmlhttprequest": "*" - }, - "dependencies": { - "utf8": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", - "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=" - } - } - }, "web3-provider-engine": { "version": "14.0.6", "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-14.0.6.tgz", @@ -7872,6 +7932,83 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, + "yargs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "requires": { + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "lodash.assign": "^4.0.3", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.1", + "which-module": "^1.0.0", + "window-size": "^0.2.0", + "y18n": "^3.2.1", + "yargs-parser": "^2.4.1" + }, + "dependencies": { + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=" + } + } + }, + "yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "requires": { + "camelcase": "^3.0.0", + "lodash.assign": "^4.0.6" + }, + "dependencies": { + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + } + } + }, "yeoman-environment": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.3.0.tgz", diff --git a/package.json b/package.json index 3f49c55..1cba4a6 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "growl": "^1.10.0", "moment": "^2.22.2", "solidity-coverage": "^0.5.5", - "truffle": "^4.1.8", + "truffle": "^4.1.15", "truffle-hdwallet-provider": "0.0.5", "utf8": "^3.0.0", "zeppelin-solidity": "^1.9.0" diff --git a/scripts/test.sh b/scripts/test.sh index aa17c0f..e2bbbfe 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -11,14 +11,14 @@ cleanup() { } ganachecli_running() { - nc -z localhost 8555 + nc -z localhost 8545 } if ganachecli_running; then echo "Using existing ganache-cli instance" else echo "Starting ganache-cli" - ./node_modules/ganache-cli/build/cli.node.js --gasLimit 0xfffffffffff --defaultBalanceEther 200 --port 8555\ + ./node_modules/ganache-cli/build/cli.node.js --gasLimit 0xfffffffffff --defaultBalanceEther 200 --port 8545\ > /dev/null & ganachecli_pid=$! fi diff --git a/test/Daico.functional.tests.js b/test/Daico.functional.tests.js index a84bd5b..aff6618 100644 --- a/test/Daico.functional.tests.js +++ b/test/Daico.functional.tests.js @@ -2,6 +2,7 @@ const moment = require("moment"); const Daico = artifacts.require("Daico"); const MintableToken = artifacts.require("MintableToken"); +const STOContract = artifacts.require("STOContract"); const { increaseTime } = require("./utils/helpers"); @@ -17,6 +18,7 @@ contract("Daico functional tests", (accounts) => { const returnFunds = accounts[7]; const other = accounts[8]; + let stoContract; let daico; let daiToken; let projectToken; @@ -86,47 +88,78 @@ contract("Daico functional tests", (accounts) => { return true; } + // // 1.2. У инвесторов появляются daiToken + // await evercityToken.mint(investor1, 100, {from: evercity}); + // await evercityToken.mint(investor2, 100, {from: evercity}); + // await evercityToken.mint(investor3, 150, {from: evercity}); + + + // constructor(address _ervercityTokenAddress, address _daicoAddress) public { + // daicoAddress = _daicoAddress; + // ervercityTokenAddress = _ervercityTokenAddress; + // } + + // function invest(uint _amount, address _investorAddress) public { + // ERC20(ervercityTokenAddress).transferFrom(msg,sender, address(this), _amount); + // ERC20(ervercityTokenAddress).approve(daicoAddress, _amount); + // mint(msg.sender, _amount); + + // IDaico(daicoAddress).addInvestor(_amount, _investorAddress); + // } + + describe("Different scenarios", () => { - beforeEach(async() => { + beforeEach(async() => { // 1.1. evercityMember деплоит daiToken evercityToken = await MintableToken.new({from: evercity}); - // 1.2. У инвесторов появляются daiToken - await evercityToken.mint(investor1, 100, {from: evercity}); - await evercityToken.mint(investor2, 100, {from: evercity}); - await evercityToken.mint(investor3, 150, {from: evercity}); + stoContract = await STOContract.new(evercityToken.address, {from: projectOwner}); - var owner = projectOwner; - var daiToken = evercityToken.address; var returnAddress = evercity; var tapFunds = [100, 100, 100]; var tapDurations = [30, 30, 30]; - daico = await Daico.new(owner, daiToken, returnAddress, tapFunds, tapDurations, {from:projectOwner}); - - await evercityToken.approve(daico.address, 100, {from:investor1}); - await evercityToken.approve(daico.address, 100, {from:investor2}); - await evercityToken.approve(daico.address, 100, {from:investor3}); - - await daico.invest(100, {from:investor1}); - await daico.invest(50, {from:investor2}); - await daico.invest(50, {from:investor2}); - await daico.invest(100, {from:investor3}); - await daico.invest(50, {from:investor3}).should.be.rejectedWith('revert'); + daico = await Daico.new( + projectOwner, + evercityToken.address, + stoContract.address, + returnAddress, + tapFunds, + tapDurations, {from:projectOwner}); + + await stoContract.setDaicoAddress(daico.address, {from:projectOwner}); + + await evercityToken.mint(investor1, 100, {from:evercity}); + await evercityToken.mint(investor2, 100, {from:evercity}); + await evercityToken.mint(investor3, 100, {from:evercity}); + + await evercityToken.approve(stoContract.address, 100, {from:investor1}); + await evercityToken.approve(stoContract.address, 100, {from:investor2}); + await evercityToken.approve(stoContract.address, 100, {from:investor3}); + + // console.log('======= before all:\n', await getTapsInfo(),'\n') + await stoContract.invest(100, {from:investor1}); + await stoContract.invest(50, {from:investor2}); + await stoContract.invest(50, {from:investor2}); + await stoContract.invest(100, {from:investor3}); + console.log('======= after investing:\n', await getTapsInfo(),'\n') + await stoContract.invest(50, {from:investor3}).should.be.rejectedWith('revert'); }); it(`1. Сценарий: все голосования происходят вовремя и без задержек, все голоса за`, async() => { await daico.withdrawFundsFromTap(0, {from:projectOwner}); - await increaseTime(23*days); - // Голосование началось + await increaseTime(23*days); + console.log('======= before 1 voting:\n', await getTapsInfo(),'\n') await daico.vote(true, {from: investor1}); await daico.vote(true, {from: investor2}); await daico.vote(true, {from: investor3}); await increaseTime(7*days); // голосование кончилось + console.log('======= after 1 voting:\n', await getTapsInfo(),'\n') await daico.withdrawFundsFromTap(1, {from:projectOwner}); await increaseTime(23*days); // Голосование началось + await daico.vote(true, {from: investor1}); await daico.vote(true, {from: investor2}); await daico.vote(true, {from: investor3}); @@ -203,16 +236,16 @@ contract("Daico functional tests", (accounts) => { await evercityToken.mint(investor2, 50, {from: evercity}); await evercityToken.mint(investor3, 50, {from: evercity}); - await evercityToken.approve(daico.address, 50, {from:investor1}); - await evercityToken.approve(daico.address, 50, {from:investor2}); - await evercityToken.approve(daico.address, 50, {from:investor3}); + await evercityToken.approve(stoContract.address, 50, {from:investor1}); + await evercityToken.approve(stoContract.address, 50, {from:investor2}); + await evercityToken.approve(stoContract.address, 50, {from:investor3}); - await daico.invest(50, {from:investor1}); + await stoContract.invest(50, {from:investor1}); assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Investing, TS.Preparing, TS.Preparing].toString()) - await daico.invest(50, {from:investor2}); + await stoContract.invest(50, {from:investor2}); assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Investing, TS.Preparing, TS.Preparing].toString()) - await daico.invest(50, {from:investor3}); + await stoContract.invest(50, {from:investor3}); await increaseTime(7*days); assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Preparing, TS.Preparing].toString()) @@ -235,13 +268,3 @@ contract("Daico functional tests", (accounts) => { }); }); }); - - - - - - - - - - diff --git a/test/utils/helpers.js b/test/utils/helpers.js index 461b37a..64899ee 100644 --- a/test/utils/helpers.js +++ b/test/utils/helpers.js @@ -7,30 +7,22 @@ const utf8 = require('utf8'); /** * Increases latest block time by duration in seconds */ -const increaseTime = function increaseTime (duration) { - - const id = Date.now(); - - return new Promise((resolve, reject) => { - web3.currentProvider.sendAsync({ - jsonrpc: '2.0', - method: 'evm_increaseTime', - params: [duration], - id: id, - }, err1 => { - if (err1) return reject(err1); - - web3.currentProvider.sendAsync({ - jsonrpc: '2.0', - method: 'evm_mine', - id: id + 1, - }, (err2, res) => { - return err2 ? reject(err2) : resolve(res); - }); - }); - }); +async function increaseTime (duration) { + var id = Date.now(); + await web3.currentProvider.sendAsync({ + jsonrpc: '2.0', + method: 'evm_increaseTime', + params: [duration], + id: id + }, function (err) { if (err) console.log('err:', err); }); + await web3.currentProvider.sendAsync({ + jsonrpc: '2.0', + method: 'evm_mine', + id: id + 1 + }, function (err) { if (err) console.log('err:', err); }); } + function uintToBytes32(n) { n = Number(n).toString(16); while (n.length < 64) { diff --git a/truffle.js b/truffle.js index 658c81e..f998f0c 100644 --- a/truffle.js +++ b/truffle.js @@ -9,7 +9,7 @@ module.exports = { networks: { development: { host: "localhost", - port: 8555, + port: 8545, network_id: "*", gas: 100000000 }, From 57bd53d96b116cfee1f9ab8b3e5dcc90b04465f6 Mon Sep 17 00:00:00 2001 From: enkogu Date: Mon, 11 Feb 2019 15:24:51 +0700 Subject: [PATCH 6/6] Daico: comments and small refactoring; tests: small refactoring + additional checks added --- contracts/Daico/Daico.sol | 282 ++++++++++++++++++++++----------- test/Daico.functional.tests.js | 50 ++---- 2 files changed, 209 insertions(+), 123 deletions(-) diff --git a/contracts/Daico/Daico.sol b/contracts/Daico/Daico.sol index e83af7c..06284d3 100644 --- a/contracts/Daico/Daico.sol +++ b/contracts/Daico/Daico.sol @@ -74,8 +74,16 @@ contract Daico is IDaico { uint pro; uint versus; address[] voted; - } + } + /** + * @param _owner owner address + * @param _daiToken evercity token address + * @param _STOContractAddress address of the project token + * @param _returnAddress address to return tokens if project fails + * @param _tapFunds array of tap amount to invest + * @param _tapDurations array of tap durations + */ constructor( address _owner, address _daiToken, @@ -104,37 +112,32 @@ contract Daico is IDaico { roadmapsCount += 1; } - function getCurrentTapNum() internal view returns(uint) { + /** + * @dev this function will replace current roadmap to the proposed one if it was accepted + this function will be called at the first investment operation for a new roadmap + */ + function _replaceRoadmapToProposedOne() internal { (uint curTapNum, TapStage[] memory tapStages, uint v) = getTapsInfo(); - return curTapNum; - } - - function getMaximalTapsLength() internal view returns(uint maximal) { - for(uint rmNum = 0; rmNum < roadmapsCount; rmNum++) { - if(roadmaps[rmNum].tapsCount > maximal) { - maximal = roadmaps[rmNum].tapsCount; - } - } - } - - function replaceRoadmapToProposedOne() internal { - uint curTapNum = getCurrentTapNum(); for(uint tapNum = 0; tapNum < roadmaps[roadmapsCount - 1].tapsCount; tapNum++) { if(tapNum > curTapNum) tapToRId[tapNum] = roadmapsCount - 1; } } + /** + * @dev interface for the STOContract to add an investor + * @param _amount – token amount, that investor going to invest + * @param _investorAddress – address of an investor + */ function addInvestor(uint _amount, address _investorAddress) public { require(STOContractAddress == msg.sender); (uint curTapNum, TapStage[] memory tapStages, uint votNum) = getTapsInfo(); require(tapStages[curTapNum] == TapStage.Investing); require(_amount > 0); - - if(newRoadmapProposed) replaceRoadmapToProposedOne(); curTapNum += 1; + if(newRoadmapProposed) _replaceRoadmapToProposedOne(); curTapNum += 1; uint rmNum = tapToRId[curTapNum]; - uint invId = getInvestorId(_investorAddress); + uint invId = _getInvestorId(_investorAddress); require(amountOfAllInvestments(curTapNum) + _amount <= tapAmountsSum(curTapNum)); daiToken.transferFrom(STOContractAddress, address(this), _amount); @@ -157,10 +160,19 @@ contract Daico is IDaico { emit InvestEvent(_amount, _investorAddress, amountOfAllInvestments(curTapNum), tapAmountsSum(curTapNum), startedAt); } + /** + * @param _tapNum – number of a tap + * @return are all funds for a this _tapNum collected or not + */ function areAllFundsCollected(uint _tapNum) internal view returns(bool) { return amountOfAllInvestments(_tapNum) >= tapAmountsSum(_tapNum); } + /** + * @dev sum can vary for a different _tapNum in cases when roadmap have been changed + * @param _tapNum – number of a tap + * @return sum – sum of all gotten investments for a specific tap + */ function amountOfAllInvestments(uint _tapNum) public view returns(uint sum) { uint rmNum = tapToRId[_tapNum]; uint invCount = roadmaps[rmNum].investorsCount; @@ -169,6 +181,11 @@ contract Daico is IDaico { } } + /** + * @dev sum of all tap amounts; output can vary for a different _tapNum in cases when roadmap have been changed + * @param _tapNum – number of the tap (to get an appropriate roadmap) + * @return sum – sum of tap amounts + */ function tapAmountsSum(uint _tapNum) public view returns(uint sum) { uint rmNum = tapToRId[_tapNum]; uint tapsCount = roadmaps[rmNum].tapsCount; @@ -177,6 +194,10 @@ contract Daico is IDaico { } } + /** + * @dev withdrawal interface for the investors; works in a cases when project fails + * @dev returns all tokens to the investors that was not spent yet + */ function returnTokens() external { (uint curTapNum, TapStage[] memory tapStages, uint votNum) = getTapsInfo(); uint rmNum = tapToRId[curTapNum]; @@ -193,29 +214,37 @@ contract Daico is IDaico { } } - // Функция для снятия средств owner'ом. + /** + * @dev withdrawal interface for the project owner + * @param _tapNum – number of tap to withdraw from + */ function withdrawFundsFromTap(uint _tapNum) external { require(msg.sender == owner); (uint curTapNum, TapStage[] memory tapStages, uint votNum) = getTapsInfo(); + uint rmNum = tapToRId[_tapNum]; + require(tapStages[_tapNum] == TapStage.Success); - - roadmaps[tapToRId[curTapNum]].taps[_tapNum].isWithdrawed = true; - daiToken.transfer(owner, getTap(_tapNum).funds); + roadmaps[rmNum].taps[_tapNum].isWithdrawed = true; + + daiToken.transfer(owner, roadmaps[rmNum].taps[_tapNum].funds); } - // Функция для голосования. + /** + * @dev voting interface for investors + * @param _vote – pro or versus + */ function vote(bool _vote) external { (uint curTapNum, TapStage[] memory tapStages, uint votNum) = getTapsInfo(); uint rmNum = tapToRId[curTapNum]; - uint invId = getInvestorId(msg.sender); + uint invId = _getInvestorId(msg.sender); require(invId < roadmaps[rmNum].investorsCount); // is investor require(tapStages[curTapNum] == TapStage.Voting || tapStages[curTapNum] == TapStage.VotingDQ || tapStages[curTapNum] == TapStage.RoadmapVoting || tapStages[curTapNum] == TapStage.RoadmapVotingDQ); - require(!isVoted(getVoting(curTapNum, votNum).voted, msg.sender)); + require(!isVoted(curTapNum, votNum, msg.sender)); Investor memory investor = roadmaps[rmNum].investors[invId]; @@ -226,35 +255,65 @@ contract Daico is IDaico { emit Vote(investor.invested, msg.sender, _vote); } - function getInvestorId(address _address) internal view returns(uint) { - uint currTapNum = getCurrentTapNum(); - uint rmNum = tapToRId[currTapNum]; + /** + * @param _targetAddress – address of the investor (presumably) + * @return investor id + */ + function _getInvestorId(address _targetAddress) internal view returns(uint) { + (uint curTapNum, TapStage[] memory tapStages, uint v) = getTapsInfo(); + uint rmNum = tapToRId[curTapNum]; for(uint invNum = 0; invNum < roadmaps[rmNum].investorsCount; invNum++) { - if(roadmaps[rmNum].investors[invNum].addr == _address) return invNum; + if(roadmaps[rmNum].investors[invNum].addr == _targetAddress) return invNum; } return roadmaps[rmNum].investorsCount; } - function isVoted(address[] memory _voted, address _address) public view returns(bool isVoted) { - for(uint voterNum = 0; voterNum < _voted.length; voterNum++) { - if(_voted[voterNum] == _address) isVoted = true; + /** + * @dev + * @param _tapNum – number of the tap + * @param _votNum – number of the voting + * @param _voterAddress – address of the voter (presumably) + * @return is voted or not + */ + function isVoted(uint _tapNum, uint _votNum, address _voterAddress) public view returns(bool isVoted) { + uint rmNum = tapToRId[_tapNum]; + Voting memory voting = roadmaps[rmNum].taps[_tapNum].votings[_votNum]; + + for(uint voterNum = 0; voterNum < voting.voted.length; voterNum++) { + if(voting.voted[voterNum] == _voterAddress) isVoted = true; } } - function getTap(uint _tapNum) internal view returns(Tap) { + /** + * @dev – returns tap + * @param _tapNum – number of the tap to return + * @return Tap + */ + function _getTap(uint _tapNum) internal view returns(Tap) { uint rmNum = tapToRId[_tapNum]; return roadmaps[rmNum].taps[_tapNum]; } + /** + * @return current_tap – number of the current tap + * @return tapStages array – array of a states for all tap stages + * @return current voting num – number of the current voting + */ function getTapsInfo() public view returns(uint, TapStage[], uint) { // curren_tap, tapstages, current_voting - uint max = getMaximalTapsLength(); - TapStage[] memory tapStages = new TapStage[](max); + uint maximalTapsAmount = 0; + for(uint rmNum = 0; rmNum < roadmapsCount; rmNum++) { + if(roadmaps[rmNum].tapsCount > maximalTapsAmount) { + maximalTapsAmount = roadmaps[rmNum].tapsCount; + } + } + + TapStage[] memory tapStages = new TapStage[](maximalTapsAmount); uint start = 0; uint tapD; uint votNum = 0; - for(uint tapNum = 0; tapNum < max; tapNum++) { - tapD = getTap(tapNum).duration; + for(uint tapNum = 0; tapNum < maximalTapsAmount; tapNum++) { + tapD = _getTap(tapNum).duration; (votNum, tapStages[tapNum], start) = getTapStage(tapNum, tapD, start); if((tapStages[tapNum]!=TapStage.Success)) return (tapNum, tapStages, votNum); } @@ -262,7 +321,14 @@ contract Daico is IDaico { return (tapNum, tapStages, votNum); } - // votingnum, tapstage, NewstartTime + /** + * @param _tapNum – number of the tap + * @param _tapD – tap duration + * @param _start – start time of the current tap + * @return current voting num – number of the current voting + * @return tapStage – state of the current tap + * @return NewstartTime – start time of the next tap + */ function getTapStage(uint _tapNum, uint _tapD, uint _start) public view returns(uint, TapStage, uint) { bool invC = areAllFundsCollected(_tapNum); uint RmV = _start + _tapD + roadmapD + votesD; @@ -274,54 +340,61 @@ contract Daico is IDaico { // _tapNum _start time duration voting1 voting2 voting3 voting4 //---------------------------------------------------------------------------------------------------------- - if(thisCase(_tapNum, _start, _tapD-votesD,VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Preparing, 0); - if(thisCase(_tapNum, _start+_tapD-votesD, votesD, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Voting, 0); - if(thisCase(_tapNum, investD, infinity, VR.Decline, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, investD, infinity, VR.Success, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Success, _start + _tapD); - if(thisCase(_tapNum, _start+_tapD, addVotesD, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (1, TapStage.VotingDQ, 0); - if(thisCase(_tapNum, _start+_tapD, infinity, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, _start+_tapD, infinity, VR.NoResult, VR.Decline, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, _start+_tapD, infinity, VR.NoResult, VR.Success, VR.NoResult, VR.NoResult)) return (0, TapStage.Success, _start + _tapD + addVotesD); - if(thisCase(_tapNum, _start+_tapD+addVotesD, roadmapD, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (0, TapStage.RoadmapPreparing, 0); - if(thisCase(_tapNum, addVRmV-votesD, votesD, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (2, TapStage.RoadmapVoting, 0); - if(thisCase(_tapNum, addVRmV, investD, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult)) return (0, TapStage.Investing, 0); - if(thisCase(_tapNum, addVRmV, infinity, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult) && invC) return (0, TapStage.Success, addVRmV + investD); - if(thisCase(_tapNum, addVRmV, infinity, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, _start+_tapD+addVotesD, infinity, VR.NoResult, VR.NoCons, VR.NoCons, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, _start+_tapD+addVotesD, infinity, VR.NoResult, VR.NoCons, VR.Decline, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, addVRmV, addVotesD, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (3, TapStage.RoadmapVotingDQ, 0); - if(thisCase(_tapNum, addVRmV+addVotesD, investD, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success )) return (0, TapStage.Investing, 0); - if(thisCase(_tapNum, addVRmV+investD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult )) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, addVRmV+investD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoCons )) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, addVRmV+investD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.Decline )) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, addVRmV+addVotesD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success ) && invC) return (0, TapStage.Success, addVRmV + addVotesD + investD); - if(thisCase(_tapNum, addVRmV+addVotesD, infinity, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success ) && !invC) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, _start+_tapD, roadmapD, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.RoadmapPreparing, 0); - if(thisCase(_tapNum, _start+_tapD+roadmapD, votesD, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (1, TapStage.RoadmapVoting, 0); - if(thisCase(_tapNum, addVRmV+addVotesD+investD, infinity, VR.NoCons, VR.Decline, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, addVRmV+addVotesD+investD, infinity, VR.NoCons, VR.NoCons, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, RmV, investD, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult)) return (0, TapStage.Investing, 0); - if(thisCase(_tapNum, RmV, infinity, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult) && invC) return (0, TapStage.Success, RmV + investD); - if(thisCase(_tapNum, RmV, infinity, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, RmV, addVotesD, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (2, TapStage.RoadmapVotingDQ, 0); - if(thisCase(_tapNum, RmV+addVotesD, investD, VR.NoCons, VR.NoResult, VR.Success, VR.NoResult)) return (0, TapStage.Investing, 0); - if(thisCase(_tapNum, RmV+investD, infinity, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, RmV+investD, infinity, VR.NoCons, VR.NoResult, VR.Decline, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, RmV+investD, infinity, VR.NoCons, VR.NoResult, VR.NoCons, VR.NoResult)) return (0, TapStage.Terminated, 0); - if(thisCase(_tapNum, RmV+addVotesD, infinity, VR.NoCons, VR.NoCons, VR.Success, VR.NoResult) && invC) return (0, TapStage.Success, addVRmV + addVotesD + investD); - if(thisCase(_tapNum, RmV+addVotesD, infinity, VR.NoCons, VR.NoCons, VR.Success, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); + if(at(_start, _tapD-votesD) &&thisCase(_tapNum, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Preparing, 0); + if(at(_start+_tapD-votesD, votesD) && thisCase(_tapNum, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Voting, 0); + if(at(investD, infinity) && thisCase(_tapNum, VR.Decline, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(investD, infinity) && thisCase(_tapNum, VR.Success, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Success, _start + _tapD); + if(at(_start+_tapD, addVotesD)&& thisCase(_tapNum, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (1, TapStage.VotingDQ, 0); + if(at(_start+_tapD, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(_start+_tapD, infinity) && thisCase(_tapNum, VR.NoResult, VR.Decline, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(_start+_tapD, infinity) && thisCase(_tapNum, VR.NoResult, VR.Success, VR.NoResult, VR.NoResult)) return (0, TapStage.Success, _start + _tapD + addVotesD); + if(at(_start+_tapD+addVotesD, roadmapD) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (0, TapStage.RoadmapPreparing, 0); + if(at(addVRmV-votesD, votesD) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (2, TapStage.RoadmapVoting, 0); + if(at(addVRmV, investD) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult)) return (0, TapStage.Investing, 0); + if(at(addVRmV, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult) && invC) return (0, TapStage.Success, addVRmV + investD); + if(at(addVRmV, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.Success, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); + if(at(_start+_tapD+addVotesD, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoCons, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(_start+_tapD+addVotesD, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.Decline, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(addVRmV, addVotesD)&& thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult)) return (3, TapStage.RoadmapVotingDQ, 0); + if(at(addVRmV+addVotesD, investD) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success )) return (0, TapStage.Investing, 0); + if(at(addVRmV+investD, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoResult )) return (0, TapStage.Terminated, 0); + if(at(addVRmV+investD, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.NoCons )) return (0, TapStage.Terminated, 0); + if(at(addVRmV+investD, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.Decline )) return (0, TapStage.Terminated, 0); + if(at(addVRmV+addVotesD, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success ) && invC) return (0, TapStage.Success, addVRmV + addVotesD + investD); + if(at(addVRmV+addVotesD, infinity) && thisCase(_tapNum, VR.NoResult, VR.NoCons, VR.NoResult, VR.Success ) && !invC) return (0, TapStage.Terminated, 0); + if(at(_start+_tapD, roadmapD) && thisCase(_tapNum, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.RoadmapPreparing, 0); + if(at(_start+_tapD+roadmapD, votesD) && thisCase(_tapNum, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (1, TapStage.RoadmapVoting, 0); + if(at(addVRmV+addVotesD+investD, infinity) && thisCase(_tapNum, VR.NoCons, VR.Decline, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(addVRmV+addVotesD+investD, infinity) && thisCase(_tapNum, VR.NoCons, VR.NoCons, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(RmV, investD) && thisCase(_tapNum, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult)) return (0, TapStage.Investing, 0); + if(at(RmV, infinity) && thisCase(_tapNum, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult) && invC) return (0, TapStage.Success, RmV + investD); + if(at(RmV, infinity) && thisCase(_tapNum, VR.NoCons, VR.Success, VR.NoResult, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); + if(at(RmV, addVotesD)&& thisCase(_tapNum, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (2, TapStage.RoadmapVotingDQ, 0); + if(at(RmV+addVotesD, investD) && thisCase(_tapNum, VR.NoCons, VR.NoResult, VR.Success, VR.NoResult)) return (0, TapStage.Investing, 0); + if(at(RmV+investD, infinity) && thisCase(_tapNum, VR.NoCons, VR.NoResult, VR.NoResult, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(RmV+investD, infinity) && thisCase(_tapNum, VR.NoCons, VR.NoResult, VR.Decline, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(RmV+investD, infinity) && thisCase(_tapNum, VR.NoCons, VR.NoResult, VR.NoCons, VR.NoResult)) return (0, TapStage.Terminated, 0); + if(at(RmV+addVotesD, infinity) && thisCase(_tapNum, VR.NoCons, VR.NoCons, VR.Success, VR.NoResult) && invC) return (0, TapStage.Success, addVRmV + addVotesD + investD); + if(at(RmV+addVotesD, infinity) && thisCase(_tapNum, VR.NoCons, VR.NoCons, VR.Success, VR.NoResult) && !invC) return (0, TapStage.Terminated, 0); //---------------------------------------------------------------------------------------------------------- return (0, TapStage.Preparing, 0); } - function thisCase(uint _tapNum, uint _from, uint _duration, + /** + * @dev check that all voting results are the same + * @param _tapNum – number of the tap + * @param _votingRes1 – voting result for a 1 voting in the current tap + * @param _votingRes2 – voting result for a 2 voting in the current tap + * @param _votingRes3 – voting result for a 3 voting in the current tap + * @param _votingRes4 – voting result for a 4 voting in the current tap + * @return are all voting results the same + */ + function thisCase(uint _tapNum, VR _votingRes1, VR _votingRes2, VR _votingRes3, VR _votingRes4) public view returns(bool) { - if(!at(_from, _duration)) return false; - bool withLessQuorum = false; if(_votingRes1 != votingState(_tapNum, 0, withLessQuorum)) return false; withLessQuorum = (_votingRes1 == VR.NoResult); @@ -337,6 +410,11 @@ contract Daico is IDaico { return true; } + /** + * @dev project owner can propose new roadmap in the case case if consensus wasn't reached + * @param _tapFunds – array of amounts to invest for an each tap + * @return _tapDurations – array of durations for an each tap + */ function proposeNewRoadmap(uint[] _tapFunds, uint[] _tapDurations) external { (uint curTapNum, TapStage[] memory tapStages, uint votNum) = getTapsInfo(); uint rmNum; @@ -375,36 +453,60 @@ contract Daico is IDaico { newRoadmapProposed = true; } + /** + * @param _from – start time of this time interval + * @param _long – duration of this time interval + * @return is current moment in this time interval + */ function at(uint _from, uint _long) public view returns(bool) { return ((now >= _from + startedAt) && (now < startedAt + _from + _long)); } - function isQuorumReached(uint _tapNum, Voting memory _voting, uint _quorumPercent) internal view returns(bool) { + /** + * @param _tapNum – number of the tap + * @param _voting – current voting + * @param _quorumPercent – quorum percent + * @return is quorum reached for a given voting with given quorum percent + */ + function _isQuorumReached(uint _tapNum, Voting memory _voting, uint _quorumPercent) internal view returns(bool) { return (_voting.pro.add(_voting.versus).mul(100) >= tapAmountsSum(_tapNum).mul(_quorumPercent)); } - function isConsensusReached(Voting memory _voting, uint _consensusPercent) internal view returns(bool) { + /** + * @param _voting – current voting + * @param _consensusPercent – consensus percent + * @return is consensus reached for a given voting with given consensus percent + */ + function _isConsensusReached(Voting memory _voting, uint _consensusPercent) internal view returns(bool) { return (_voting.pro.mul(100 - _consensusPercent) >= _voting.versus.mul(_consensusPercent)); } - function isDeclined(Voting memory _voting, uint _declinePercent) internal view returns(bool) { + /** + * @param _voting – current voting + * @param _declinePercent – decline percent + * @return is this voting declined with given decline percent + */ + function _isDeclined(Voting memory _voting, uint _declinePercent) internal view returns(bool) { return (_voting.versus.mul(100 - _declinePercent) >= _voting.pro.mul(_declinePercent)); } - function getVoting(uint _tapNum, uint _votNum) internal view returns(Voting) { - uint rmNum = tapToRId[_tapNum]; - return roadmaps[rmNum].taps[_tapNum].votings[_votNum]; - } - + /** + * @dev + * @param _tapNum – number of the tap + * @param _votNum – number of the voting + * @param _isQuorumDecreased – is quorum decreased or not + * @return voting result vor this voting + */ function votingState(uint _tapNum, uint _votNum, bool _isQuorumDecreased) public view returns(VR) { uint _quorumPercent = quorumPercent; if(_isQuorumDecreased) _quorumPercent = quorumPercent - 20; + + uint rmNum = tapToRId[_tapNum]; + Voting memory voting = roadmaps[rmNum].taps[_tapNum].votings[_votNum]; - Voting memory voting = getVoting(_tapNum, _votNum); - - if(!isQuorumReached(_tapNum, voting, _quorumPercent)) return VR.NoResult; - if(isConsensusReached(voting, consensusPercent)) return VR.Success; - if(isDeclined(voting, declinePercent)) return VR.Decline; + if(!_isQuorumReached(_tapNum, voting, _quorumPercent)) return VR.NoResult; + if(_isConsensusReached(voting, consensusPercent)) return VR.Success; + if(_isDeclined(voting, declinePercent)) return VR.Decline; return VR.NoCons; } } \ No newline at end of file diff --git a/test/Daico.functional.tests.js b/test/Daico.functional.tests.js index aff6618..228c2b3 100644 --- a/test/Daico.functional.tests.js +++ b/test/Daico.functional.tests.js @@ -68,12 +68,10 @@ contract("Daico functional tests", (accounts) => { async function getTapsInfo() { var data = await daico.getTapsInfo() data[1] = data[1].map((t)=> new web3.BigNumber(t).toNumber()) - // data[3] = data[3].map((t)=> new web3.BigNumber(t).toNumber()) return { currentTap: new web3.BigNumber(data[0]).toNumber(), tapsStages: data[1], currentVoting: new web3.BigNumber(data[2]).toNumber() - // tapOuts: data[3] } } @@ -81,36 +79,14 @@ contract("Daico functional tests", (accounts) => { function isArrayEquals(arr1, arr2) { for(var i = 0; i < arr1.length; i++) { if(arr1[i] != arr2[i]) { - console.log('Unequal:', arr1, arr2); return false; } } return true; } - // // 1.2. У инвесторов появляются daiToken - // await evercityToken.mint(investor1, 100, {from: evercity}); - // await evercityToken.mint(investor2, 100, {from: evercity}); - // await evercityToken.mint(investor3, 150, {from: evercity}); - - - // constructor(address _ervercityTokenAddress, address _daicoAddress) public { - // daicoAddress = _daicoAddress; - // ervercityTokenAddress = _ervercityTokenAddress; - // } - - // function invest(uint _amount, address _investorAddress) public { - // ERC20(ervercityTokenAddress).transferFrom(msg,sender, address(this), _amount); - // ERC20(ervercityTokenAddress).approve(daicoAddress, _amount); - // mint(msg.sender, _amount); - - // IDaico(daicoAddress).addInvestor(_amount, _investorAddress); - // } - - describe("Different scenarios", () => { beforeEach(async() => { - // 1.1. evercityMember деплоит daiToken evercityToken = await MintableToken.new({from: evercity}); stoContract = await STOContract.new(evercityToken.address, {from: projectOwner}); @@ -136,28 +112,24 @@ contract("Daico functional tests", (accounts) => { await evercityToken.approve(stoContract.address, 100, {from:investor2}); await evercityToken.approve(stoContract.address, 100, {from:investor3}); - // console.log('======= before all:\n', await getTapsInfo(),'\n') await stoContract.invest(100, {from:investor1}); await stoContract.invest(50, {from:investor2}); await stoContract.invest(50, {from:investor2}); await stoContract.invest(100, {from:investor3}); - console.log('======= after investing:\n', await getTapsInfo(),'\n') await stoContract.invest(50, {from:investor3}).should.be.rejectedWith('revert'); }); it(`1. Сценарий: все голосования происходят вовремя и без задержек, все голоса за`, async() => { await daico.withdrawFundsFromTap(0, {from:projectOwner}); - + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 100); await increaseTime(23*days); - console.log('======= before 1 voting:\n', await getTapsInfo(),'\n') await daico.vote(true, {from: investor1}); await daico.vote(true, {from: investor2}); await daico.vote(true, {from: investor3}); await increaseTime(7*days); // голосование кончилось - console.log('======= after 1 voting:\n', await getTapsInfo(),'\n') await daico.withdrawFundsFromTap(1, {from:projectOwner}); - + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 200); await increaseTime(23*days); // Голосование началось await daico.vote(true, {from: investor1}); @@ -166,6 +138,7 @@ contract("Daico functional tests", (accounts) => { await increaseTime(7*days); // голосование кончилось await daico.withdrawFundsFromTap(2, {from:projectOwner}); + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 300); }); it(`2. Сценарий: задержки при голосовании. в первом голосует 66/70%, во втором (пониженный кворум) – 33/50%. @@ -181,6 +154,7 @@ contract("Daico functional tests", (accounts) => { await daico.vote(true, {from: investor1}); await increaseTime(7*days); assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Terminated, TS.Preparing])); + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 0); }); it(`3. Сценарий: задержки при голосовании. в первом голосует 66/70%, во втором (пониженный кворум) – 66/50%. @@ -206,9 +180,13 @@ contract("Daico functional tests", (accounts) => { await daico.vote(true, {from: investor2}); await increaseTime(7*days); assert.isTrue(isArrayEquals((await getTapsInfo()).tapsStages, [TS.Success, TS.Success, TS.Success])); + await daico.withdrawFundsFromTap(0, {from:projectOwner}); + await daico.withdrawFundsFromTap(1, {from:projectOwner}); + await daico.withdrawFundsFromTap(2, {from:projectOwner}); + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 300); }); - it(`4. Сценарий: отсутствия консенсуса. во втором голосовании один против, + it(`4. Сценарий: отсутствие консенсуса. во втором голосовании один против, заменяется roadmap (вместо последнего периода [100], [30] предлагается два периода [50, 150], [50, 50]), его утверждают, привлекаются новые инвестиции`, async() => { // Есть некоторые особенности в реализации proposeNewRoadmap: @@ -217,6 +195,7 @@ contract("Daico functional tests", (accounts) => { // 3. Новый roadmap должен требовать больше денег, чем предыдущий // 4. Должно быть больше или столько же stage await daico.withdrawFundsFromTap(0, {from:projectOwner}); + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 100); await increaseTime(23*days); // Голосование началось await daico.vote(true, {from: investor1}); await daico.vote(true, {from: investor2}); @@ -248,15 +227,19 @@ contract("Daico functional tests", (accounts) => { await stoContract.invest(50, {from:investor3}); await increaseTime(7*days); assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Preparing, TS.Preparing].toString()) - + await daico.withdrawFundsFromTap(1, {from:projectOwner}); + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 200); + await increaseTime(43*days); // Голосование началось assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Voting, TS.Preparing].toString()) await daico.vote(true, {from: investor1}); await daico.vote(true, {from: investor2}); await daico.vote(true, {from: investor3}); await increaseTime(7*days); - assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Success, TS.Preparing].toString()) await daico.withdrawFundsFromTap(2, {from:projectOwner}); + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 300); + + assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Success, TS.Preparing].toString()) await increaseTime(43*days); // Голосование началось assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Success, TS.Voting].toString()) await daico.vote(true, {from: investor1}); @@ -265,6 +248,7 @@ contract("Daico functional tests", (accounts) => { await increaseTime(7*days); assert.equal((await getTapsInfo()).tapsStages.toString(), [TS.Success, TS.Success, TS.Success, TS.Success].toString()) await daico.withdrawFundsFromTap(3, {from:projectOwner}); + assert.equal(new web3.BigNumber(await evercityToken.balanceOf(projectOwner)).toNumber(), 450); }); }); });