diff --git a/contracts/Governance.sol b/contracts/Governance.sol index 6cc399aeee..351cbfdabc 100644 --- a/contracts/Governance.sol +++ b/contracts/Governance.sol @@ -38,8 +38,6 @@ contract Governance is IGovernance, Iupgradable { uint propStatus; uint finalVerdict; uint category; - // uint totalVoteValue; - // uint majVoteValue; uint commonIncentive; uint dateUpd; address owner; @@ -47,10 +45,8 @@ contract Governance is IGovernance, Iupgradable { struct ProposalVote { address voter; - // uint64 solutionChosen; uint proposalId; uint dateAdd; - // uint voteValue; } struct VoteTally { @@ -65,14 +61,13 @@ contract Governance is IGovernance, Iupgradable { uint lastUpd; } - // ProposalStruct[] internal allProposal; ProposalVote[] internal allVotes; DelegateVote[] public allDelegation; mapping(uint => ProposalData) internal allProposalData; mapping(uint => bytes[]) internal allProposalSolutions; mapping(address => uint[]) internal allVotesByMember; - mapping(uint => mapping(address => bool)) public rewardClaimed; //voteid->member->reward claimed + mapping(uint => mapping(address => bool)) public rewardClaimed; mapping (address => mapping(uint => uint)) public memberProposalVote; mapping (address => uint) public followerDelegation; mapping (address => uint) internal followerCount; @@ -80,7 +75,6 @@ contract Governance is IGovernance, Iupgradable { mapping (uint => VoteTally) public proposalVoteTally; mapping (address => bool) public isOpenForDelegation; mapping (address => uint) public lastRewardClaimed; - bool internal constructorCheck; uint public tokenHoldingTime; @@ -94,33 +88,82 @@ contract Governance is IGovernance, Iupgradable { MemberRoles internal memberRole; ProposalCategory internal proposalCategory; TokenController internal tokenInstance; - MemberRoles internal mr; + mapping(uint => uint) public proposalActionStatus; + mapping(uint => uint) internal proposalExecutionTime; + mapping(uint => mapping(address => bool)) public proposalRejectedByAB; + mapping(uint => uint) internal actionRejectedCount; + + bool internal actionParamsInitialised; + uint internal actionWaitingTime; + uint constant internal AB_MAJ_TO_REJECT_ACTION = 3; + + enum ActionStatus { + Pending, + Accepted, + Rejected, + Executed, + NoAction + } + + /** + * @dev Called whenever an action execution is failed. + */ + event ActionFailed ( + uint256 proposalId + ); + + /** + * @dev Called whenever an AB member rejects the action execution. + */ + event ActionRejected ( + uint256 indexed proposalId, + address rejectedBy + ); + + /** + * @dev Checks if msg.sender is proposal owner + */ modifier onlyProposalOwner(uint _proposalId) { - require(msg.sender == allProposalData[_proposalId].owner, "Not authorized"); + require(msg.sender == allProposalData[_proposalId].owner, "Not allowed"); _; } + /** + * @dev Checks if proposal is opened for voting + */ modifier voteNotStarted(uint _proposalId) { require(allProposalData[_proposalId].propStatus < uint(ProposalStatus.VotingStarted)); _; } + /** + * @dev Checks if msg.sender is allowed to create proposal under given category + */ modifier isAllowed(uint _categoryId) { - require(allowedToCreateProposal(_categoryId), "Not authorized"); + require(allowedToCreateProposal(_categoryId), "Not allowed"); _; } + /** + * @dev Checks if msg.sender is allowed categorize proposal under given category + */ modifier isAllowedToCategorize() { - require(memberRole.checkRole(msg.sender, roleIdAllowedToCatgorize), "Not authorized"); + require(memberRole.checkRole(msg.sender, roleIdAllowedToCatgorize), "Not allowed"); _; } + /** + * @dev Checks if msg.sender had any pending rewards to be claimed + */ modifier checkPendingRewards { - require(getPendingReward(msg.sender) == 0, "Claim pending rewards"); + require(getPendingReward(msg.sender) == 0, "Claim reward"); _; } + /** + * @dev Event emitted whenever a proposal is categorized + */ event ProposalCategorized( uint indexed proposalId, address indexed categorizedBy, @@ -128,16 +171,18 @@ contract Governance is IGovernance, Iupgradable { ); /** - * @dev to remove delegation of an address. + * @dev Removes delegation of an address. * @param _add address to undelegate. */ function removeDelegation(address _add) external onlyInternal { _unDelegate(_add); } - /// @dev Creates a new proposal - /// @param _proposalDescHash Proposal description hash through IPFS having Short and long description of proposal - /// @param _categoryId This id tells under which the proposal is categorized i.e. Proposal's Objective + /** + * @dev Creates a new proposal + * @param _proposalDescHash Proposal description hash through IPFS having Short and long description of proposal + * @param _categoryId This id tells under which the proposal is categorized i.e. Proposal's Objective + */ function createProposal( string calldata _proposalTitle, string calldata _proposalSD, @@ -151,9 +196,11 @@ contract Governance is IGovernance, Iupgradable { _createProposal(_proposalTitle, _proposalSD, _proposalDescHash, _categoryId); } - /// @dev Edits the details of an existing proposal - /// @param _proposalId Proposal id that details needs to be updated - /// @param _proposalDescHash Proposal description hash having long and short description of proposal. + /** + * @dev Edits the details of an existing proposal + * @param _proposalId Proposal id that details needs to be updated + * @param _proposalDescHash Proposal description hash having long and short description of proposal. + */ function updateProposal( uint _proposalId, string calldata _proposalTitle, @@ -164,7 +211,7 @@ contract Governance is IGovernance, Iupgradable { { require( allProposalSolutions[_proposalId].length < 2, - "Solution submitted" + "Not allowed" ); allProposalData[_proposalId].propStatus = uint(ProposalStatus.Draft); allProposalData[_proposalId].category = 0; @@ -179,7 +226,9 @@ contract Governance is IGovernance, Iupgradable { ); } - /// @dev Categorizes proposal to proceed further. Categories shows the proposal objective. + /** + * @dev Categorizes proposal to proceed further. Categories shows the proposal objective. + */ function categorizeProposal( uint _proposalId, uint _categoryId, @@ -191,19 +240,25 @@ contract Governance is IGovernance, Iupgradable { _categorizeProposal(_proposalId, _categoryId, _incentive); } - /// @dev Initiates add solution - //To implement the governance interface + /** + * @dev Initiates add solution + * To implement the governance interface + */ function addSolution(uint, string calldata, bytes calldata) external { } - /// @dev Opens proposal for voting - //To implement the governance interface + /** + * @dev Opens proposal for voting + * To implement the governance interface + */ function openProposalForVoting(uint) external { } - /// @dev Submit proposal with solution - /// @param _proposalId Proposal id - /// @param _solutionHash Solution hash contains parameters, values and description needed according to proposal + /** + * @dev Submit proposal with solution + * @param _proposalId Proposal id + * @param _solutionHash Solution hash contains parameters, values and description needed according to proposal + */ function submitProposalWithSolution( uint _proposalId, string calldata _solutionHash, @@ -212,13 +267,18 @@ contract Governance is IGovernance, Iupgradable { external onlyProposalOwner(_proposalId) { + + require(allProposalData[_proposalId].propStatus == uint(ProposalStatus.AwaitingSolution)); + _proposalSubmission(_proposalId, _solutionHash, _action); } - /// @dev Creates a new proposal with solution - /// @param _proposalDescHash Proposal description hash through IPFS having Short and long description of proposal - /// @param _categoryId This id tells under which the proposal is categorized i.e. Proposal's Objective - /// @param _solutionHash Solution hash contains parameters, values and description needed according to proposal + /** + * @dev Creates a new proposal with solution + * @param _proposalDescHash Proposal description hash through IPFS having Short and long description of proposal + * @param _categoryId This id tells under which the proposal is categorized i.e. Proposal's Objective + * @param _solutionHash Solution hash contains parameters, values and description needed according to proposal + */ function createProposalwithSolution( string calldata _proposalTitle, string calldata _proposalSD, @@ -230,9 +290,12 @@ contract Governance is IGovernance, Iupgradable { external isAllowed(_categoryId) { + uint proposalId = totalProposals; _createProposal(_proposalTitle, _proposalSD, _proposalDescHash, _categoryId); + + require(_categoryId > 0); _proposalSubmission( proposalId, @@ -242,24 +305,23 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev used to submit vote on a proposal. + * @dev Submit a vote on the proposal. * @param _proposalId to vote upon. * @param _solutionChosen is the chosen vote. */ function submitVote(uint _proposalId, uint _solutionChosen) external { - // require(addressProposalVote[msg.sender][_proposalId] == 0, "Already voted"); - + require(allProposalData[_proposalId].propStatus == uint(Governance.ProposalStatus.VotingStarted), "Not allowed"); - require(_solutionChosen <= allProposalSolutions[_proposalId].length, "Solution doesn't exist"); + require(_solutionChosen < allProposalSolutions[_proposalId].length); _submitVote(_proposalId, _solutionChosen); } /** - * @dev used to close a proposal. + * @dev Closes the proposal. * @param _proposalId of proposal to be closed. */ function closeProposal(uint _proposalId) external { @@ -271,10 +333,10 @@ contract Governance is IGovernance, Iupgradable { allProposalData[_proposalId].propStatus < uint(ProposalStatus.VotingStarted)) { _updateProposalStatus(_proposalId, uint(ProposalStatus.Denied)); } else { - require(canCloseProposal(_proposalId) == 1, "Cannot close"); + require(canCloseProposal(_proposalId) == 1); (, _memberRole, , , , , ) = proposalCategory.category(allProposalData[_proposalId].category); if (_memberRole == uint(MemberRoles.Role.AdvisoryBoard)) { - _closeABVote(_proposalId, category, _memberRole); + _closeAdvisoryBoardVote(_proposalId, category); } else { _closeMemberVote(_proposalId, category); } @@ -283,7 +345,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev to claim reward on a list of proposal. + * @dev Claims reward for member. * @param _memberAddress to claim reward of. * @param _maxRecords maximum number of records to claim reward for. _proposals list of proposals of which reward will be claimed. @@ -300,9 +362,10 @@ contract Governance is IGovernance, Iupgradable { require(msg.sender == ms.getLatestAddress("CR")); uint delegationId = followerDelegation[_memberAddress]; - if (delegationId > 0 && allDelegation[delegationId].leader != address(0)) { - leader = allDelegation[delegationId].leader; - lastUpd = allDelegation[delegationId].lastUpd; + DelegateVote memory delegationData = allDelegation[delegationId]; + if (delegationId > 0 && delegationData.leader != address(0)) { + leader = delegationData.leader; + lastUpd = delegationData.lastUpd; } else leader = _memberAddress; @@ -311,27 +374,30 @@ contract Governance is IGovernance, Iupgradable { uint lastClaimed = totalVotes; uint j; uint i; - for (i = lastRewardClaimed[_memberAddress];i < totalVotes && j < _maxRecords; i++) { + for (i = lastRewardClaimed[_memberAddress]; i < totalVotes && j < _maxRecords; i++) { voteId = allVotesByMember[leader][i]; proposalId = allVotes[voteId].proposalId; if (proposalVoteTally[proposalId].voters > 0 && (allVotes[voteId].dateAdd > ( - lastUpd + tokenHoldingTime) || leader == _memberAddress)) { + lastUpd.add(tokenHoldingTime)) || leader == _memberAddress)) { if (allProposalData[proposalId].propStatus > uint(ProposalStatus.VotingStarted)) { if (!rewardClaimed[voteId][_memberAddress]) { - pendingDAppReward += allProposalData[proposalId].commonIncentive / - proposalVoteTally[proposalId].voters; + pendingDAppReward = pendingDAppReward.add( + allProposalData[proposalId].commonIncentive.div( + proposalVoteTally[proposalId].voters + ) + ); rewardClaimed[voteId][_memberAddress] = true; j++; } } else { - if(lastClaimed == totalVotes) { + if (lastClaimed == totalVotes) { lastClaimed = i; } } } } - if(lastClaimed == totalVotes) { + if (lastClaimed == totalVotes) { lastRewardClaimed[_memberAddress] = i; } else { lastRewardClaimed[_memberAddress] = lastClaimed; @@ -346,7 +412,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev Used to set delegation acceptance status of individual user + * @dev Sets delegation acceptance status of individual user * @param _status delegation acceptance status */ function setDelegationStatus(bool _status) external isMemberAndcheckPause checkPendingRewards { @@ -354,21 +420,20 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev to delegate vote to an address. + * @dev Delegates vote to an address. * @param _add is the address to delegate vote to. */ function delegateVote(address _add) external isMemberAndcheckPause checkPendingRewards { - //Ensure that NXMaster has initialized. + require(ms.masterInitialized()); - //Check if given address is not a follower require(allDelegation[followerDelegation[_add]].leader == address(0)); if (followerDelegation[msg.sender] > 0) { - require(SafeMath.add(allDelegation[followerDelegation[msg.sender]].lastUpd, tokenHoldingTime) < now); + require((allDelegation[followerDelegation[msg.sender]].lastUpd).add(tokenHoldingTime) < now); } - require(!alreadyDelegated(msg.sender), "Already a leader"); + require(!alreadyDelegated(msg.sender)); require(!memberRole.checkRole(msg.sender, uint(MemberRoles.Role.Owner))); require(!memberRole.checkRole(msg.sender, uint(MemberRoles.Role.AdvisoryBoard))); @@ -376,13 +441,10 @@ contract Governance is IGovernance, Iupgradable { require(followerCount[_add] < maxFollowers); if (allVotesByMember[msg.sender].length > 0) { - uint memberLastVoteId = SafeMath.sub(allVotesByMember[msg.sender].length, 1); - require(SafeMath.add(allVotes[allVotesByMember[msg.sender][memberLastVoteId]].dateAdd, tokenHoldingTime) + require((allVotes[allVotesByMember[msg.sender][allVotesByMember[msg.sender].length - 1]].dateAdd).add(tokenHoldingTime) < now); } - // require(getPendingReward(msg.sender) == 0); - require(ms.isMember(_add)); require(isOpenForDelegation[_add]); @@ -401,6 +463,47 @@ contract Governance is IGovernance, Iupgradable { _unDelegate(msg.sender); } + /** + * @dev Triggers action of accepted proposal after waiting time is finished + */ + function triggerAction(uint _proposalId) external { + require(proposalActionStatus[_proposalId] == uint(ActionStatus.Accepted) && proposalExecutionTime[_proposalId] <= now, "Cannot trigger"); + _triggerAction(_proposalId, allProposalData[_proposalId].category); + } + + /** + * @dev Provides option to Advisory board member to reject proposal action execution within actionWaitingTime, if found suspicious + */ + function rejectAction(uint _proposalId) external { + require(memberRole.checkRole(msg.sender, uint(MemberRoles.Role.AdvisoryBoard)) && proposalExecutionTime[_proposalId] > now); + + require(proposalActionStatus[_proposalId] == uint(ActionStatus.Accepted)); + + require(!proposalRejectedByAB[_proposalId][msg.sender]); + + require( + keccak256(proposalCategory.categoryActionHashes(allProposalData[_proposalId].category)) + != keccak256(abi.encodeWithSignature("swapABMember(address,address)")) + ); + + proposalRejectedByAB[_proposalId][msg.sender] = true; + actionRejectedCount[_proposalId]++; + emit ActionRejected(_proposalId, msg.sender); + if (actionRejectedCount[_proposalId] == AB_MAJ_TO_REJECT_ACTION) { + proposalActionStatus[_proposalId] = uint(ActionStatus.Rejected); + } + } + + /** + * @dev Sets intial actionWaitingTime value + * To be called after governance implementation has been updated + */ + function setInitialActionParameters() external onlyOwner { + require(!actionParamsInitialised); + actionParamsInitialised = true; + actionWaitingTime = 24 * 1 hours; + } + /** * @dev Gets Uint Parameters of a code * @param code whose details we want @@ -423,15 +526,15 @@ contract Governance is IGovernance, Iupgradable { val = maxDraftTime / (1 days); - } else if (code == "MAXAB") { - - val = mr.maxABCount(); - } else if (code == "EPTIME") { + val = ms.pauseTime() / (1 days); - } + } else if (code == "ACWT") { + val = actionWaitingTime / (1 hours); + + } } /** @@ -492,7 +595,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev get length of propsal + * @dev Gets length of propsal * @return length of propsal */ function getProposalLength() external view returns(uint) { @@ -500,7 +603,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev get followers of an address + * @dev Get followers of an address * @return get followers of an address */ function getFollowers(address _add) external view returns(uint[] memory) { @@ -508,7 +611,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev get pending reward of a member + * @dev Gets pending rewards of a member * @param _memberAddress in concern * @return amount of pending reward */ @@ -518,22 +621,27 @@ contract Governance is IGovernance, Iupgradable { uint delegationId = followerDelegation[_memberAddress]; address leader; uint lastUpd; - if (delegationId > 0 && allDelegation[delegationId].leader != address(0)) { - leader = allDelegation[delegationId].leader; - lastUpd = allDelegation[delegationId].lastUpd; + DelegateVote memory delegationData = allDelegation[delegationId]; + + if (delegationId > 0 && delegationData.leader != address(0)) { + leader = delegationData.leader; + lastUpd = delegationData.lastUpd; } else leader = _memberAddress; uint proposalId; - for (uint i = lastRewardClaimed[_memberAddress];i < allVotesByMember[leader].length; i++) { + for (uint i = lastRewardClaimed[_memberAddress]; i < allVotesByMember[leader].length; i++) { if (allVotes[allVotesByMember[leader][i]].dateAdd > ( - lastUpd + tokenHoldingTime) || leader == _memberAddress) { + lastUpd.add(tokenHoldingTime)) || leader == _memberAddress) { if (!rewardClaimed[allVotesByMember[leader][i]][_memberAddress]) { proposalId = allVotes[allVotesByMember[leader][i]].proposalId; if (proposalVoteTally[proposalId].voters > 0 && allProposalData[proposalId].propStatus > uint(ProposalStatus.VotingStarted)) { - pendingDAppReward += allProposalData[proposalId].commonIncentive / - proposalVoteTally[proposalId].voters; + pendingDAppReward = pendingDAppReward.add( + allProposalData[proposalId].commonIncentive.div( + proposalVoteTally[proposalId].voters + ) + ); } } } @@ -560,30 +668,33 @@ contract Governance is IGovernance, Iupgradable { maxDraftTime = val * 1 days; - } else if (code == "MAXAB") { - - mr.changeMaxABCount(val); - } else if (code == "EPTIME") { + ms.updatePauseTime(val * 1 days); + } else if (code == "ACWT") { + + actionWaitingTime = val * 1 hours; + } else { + revert("Invalid code"); + } } - /// @dev updates all dependency addresses to latest ones from Master + /** + * @dev Updates all dependency addresses to latest ones from Master + */ function changeDependentContractAddress() public { - if (!constructorCheck) { - _initiateGovernance(); - } tokenInstance = TokenController(ms.dAppLocker()); memberRole = MemberRoles(ms.getLatestAddress("MR")); proposalCategory = ProposalCategory(ms.getLatestAddress("PC")); - mr = MemberRoles(ms.getLatestAddress("MR")); } - /// @dev checks if the msg.sender is allowed to create a proposal under certain category + /** + * @dev Checks if msg.sender is allowed to create a proposal under given category + */ function allowedToCreateProposal(uint category) public view returns(bool check) { if (category == 0) return true; @@ -596,7 +707,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev to know if an address is already delegated + * @dev Checks if an address is already delegated * @param _add in concern * @return bool value if the address is delegated or not */ @@ -608,19 +719,25 @@ contract Governance is IGovernance, Iupgradable { } } - /// @dev pause a proposal - //To implement govblocks interface + /** + * @dev Pauses a proposal + * To implement govblocks interface + */ function pauseProposal(uint) public { } - /// @dev resume a proposal - //To implement govblocks interface + /** + * @dev Resumes a proposal + * To implement govblocks interface + */ function resumeProposal(uint) public { } - /// @dev Checks If the proposal voting time is up and it's ready to close - /// i.e. Closevalue is 1 if proposal is ready to be closed, 2 if already closed, 0 otherwise! - /// @param _proposalId Proposal id to which closing value is being checked + /** + * @dev Checks If the proposal voting time is up and it's ready to close + * i.e. Closevalue is 1 if proposal is ready to be closed, 2 if already closed, 0 otherwise! + * @param _proposalId Proposal id to which closing value is being checked + */ function canCloseProposal(uint _proposalId) public view @@ -639,10 +756,9 @@ contract Governance is IGovernance, Iupgradable { ) { uint numberOfMembers = memberRole.numberOfMembers(_roleId); if (_roleId == uint(MemberRoles.Role.AdvisoryBoard)) { - uint totalABVoted = proposalVoteTally[_proposalId].abVoteValue[1] + - proposalVoteTally[_proposalId].abVoteValue[0]; if (proposalVoteTally[_proposalId].abVoteValue[1].mul(100).div(numberOfMembers) >= majority - || totalABVoted == numberOfMembers || dateUpdate.add(_closingTime) <= now) { + || proposalVoteTally[_proposalId].abVoteValue[1].add(proposalVoteTally[_proposalId].abVoteValue[0]) == numberOfMembers + || dateUpdate.add(_closingTime) <= now) { return 1; } @@ -659,15 +775,15 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev get roleId of member allowed to categorize the proposal - * @return roleId + * @dev Gets Id of member role allowed to categorize the proposal + * @return roleId allowed to categorize the proposal */ function allowedToCatgorize() public view returns(uint roleId) { return roleIdAllowedToCatgorize; } /** - * @dev get vote tally data + * @dev Gets vote tally data * @param _proposalId in concern * @param _solution of a proposal id * @return member vote value @@ -680,7 +796,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev to create a proposal + * @dev Internal call to create proposal * @param _proposalTitle of proposal * @param _proposalSD is short description of proposal * @param _proposalDescHash IPFS hash value of propsal @@ -700,8 +816,6 @@ contract Governance is IGovernance, Iupgradable { allProposalData[_proposalId].dateUpd = now; allProposalSolutions[_proposalId].push(""); totalProposals++; - if (_categoryId > 0) - _categorizeProposal(_proposalId, _categoryId, 0); emit Proposal( msg.sender, @@ -711,11 +825,13 @@ contract Governance is IGovernance, Iupgradable { _proposalSD, _proposalDescHash ); - // emit ProposalCreated(_proposalId, _categoryId, address(ms), _proposalDescHash); + + if (_categoryId > 0) + _categorizeProposal(_proposalId, _categoryId, 0); } /** - * @dev to categorize a proposal + * @dev Internal call to categorize a proposal * @param _proposalId of proposal * @param _categoryId of proposal * @param _incentive is commonIncentive @@ -739,7 +855,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev add solution to a proposalId + * @dev Internal call to add solution to a proposal * @param _proposalId in concern * @param _action on that solution * @param _solutionHash string value @@ -751,7 +867,9 @@ contract Governance is IGovernance, Iupgradable { emit Solution(_proposalId, msg.sender, allProposalSolutions[_proposalId].length - 1, _solutionHash, now); } - /// @dev When creating or submitting proposal with solution, This function open the proposal for voting + /** + * @dev Internal call to add solution and open proposal for voting + */ function _proposalSubmission( uint _proposalId, string memory _solutionHash, @@ -760,19 +878,26 @@ contract Governance is IGovernance, Iupgradable { internal { + uint _categoryId = allProposalData[_proposalId].category; + if (proposalCategory.categoryActionHashes(_categoryId).length == 0) { + require(keccak256(_action) == keccak256("")); + proposalActionStatus[_proposalId] = uint(ActionStatus.NoAction); + } + _addSolution( _proposalId, _action, _solutionHash ); - _openProposalForVoting( - _proposalId - ); + _updateProposalStatus(_proposalId, uint(ProposalStatus.VotingStarted)); + (, , , , , uint closingTime, ) = proposalCategory.category(_categoryId); + emit CloseProposalOnTime(_proposalId, closingTime.add(now)); + } /** - * @dev for submitting vote on a proposal + * @dev Internal call to submit vote * @param _proposalId of proposal in concern * @param _solution for that proposal */ @@ -786,7 +911,7 @@ contract Governance is IGovernance, Iupgradable { require(allProposalData[_proposalId].dateUpd.add(closingTime) > now, "Closed"); - require(memberProposalVote[msg.sender][_proposalId] == 0, "Voted"); + require(memberProposalVote[msg.sender][_proposalId] == 0, "Not allowed"); require((delegationId == 0) || (delegationId > 0 && allDelegation[delegationId].leader == address(0) && _checkLastUpd(allDelegation[delegationId].lastUpd))); @@ -796,13 +921,12 @@ contract Governance is IGovernance, Iupgradable { allVotesByMember[msg.sender].push(totalVotes); memberProposalVote[msg.sender][_proposalId] = totalVotes; - // addressProposalVote[msg.sender][_proposalId] = totalVotes; allVotes.push(ProposalVote(msg.sender, _proposalId, now)); emit Vote(msg.sender, _proposalId, totalVotes, now, _solution); if (mrSequence == uint(MemberRoles.Role.Owner)) { if (_solution == 1) - _callIfMajReach(_proposalId, uint(ProposalStatus.Accepted), allProposalData[_proposalId].category, 1); + _callIfMajReached(_proposalId, uint(ProposalStatus.Accepted), allProposalData[_proposalId].category, 1, MemberRoles.Role.Owner); else _updateProposalStatus(_proposalId, uint(ProposalStatus.Rejected)); @@ -811,10 +935,9 @@ contract Governance is IGovernance, Iupgradable { _setVoteTally(_proposalId, _solution, mrSequence); if (mrSequence == uint(MemberRoles.Role.AdvisoryBoard)) { - uint totalABVoted = proposalVoteTally[_proposalId].abVoteValue[1] + - proposalVoteTally[_proposalId].abVoteValue[0]; if (proposalVoteTally[_proposalId].abVoteValue[1].mul(100).div(numberOfMembers) - >= majority || totalABVoted == numberOfMembers) { + >= majority + || (proposalVoteTally[_proposalId].abVoteValue[1].add(proposalVoteTally[_proposalId].abVoteValue[0])) == numberOfMembers) { emit VoteCast(_proposalId); } } else { @@ -826,55 +949,55 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev to set vote tally of a proposal + * @dev Internal call to set vote tally of a proposal * @param _proposalId of proposal in concern * @param _solution of proposal in concern * @param mrSequence number of members for a role */ function _setVoteTally(uint _proposalId, uint _solution, uint mrSequence) internal { - uint category = allProposalData[_proposalId].category; - uint voteWeight; - uint voteWeightAB; - uint voters = 1; - uint isSpecialResolution = proposalCategory.isSpecialResolution(category); - uint tokenBalance = tokenInstance.totalBalanceOf(msg.sender); - uint totalSupply = tokenInstance.totalSupply(); - if (isSpecialResolution == 1) { - voteWeight = tokenBalance + 10**18; - } else { - voteWeight = (_minOf(tokenBalance, maxVoteWeigthPer.mul(totalSupply).div(100))) + 10**18; + uint categoryABReq; + uint isSpecialResolution; + (, categoryABReq, isSpecialResolution) = proposalCategory.categoryExtendedData(allProposalData[_proposalId].category); + if (memberRole.checkRole(msg.sender, uint(MemberRoles.Role.AdvisoryBoard)) && (categoryABReq > 0) || + mrSequence == uint(MemberRoles.Role.AdvisoryBoard)) { + proposalVoteTally[_proposalId].abVoteValue[_solution]++; } - if (memberRole.checkRole(msg.sender, 1) && (proposalCategory.categoryABReq(category) > 0) || - mrSequence == uint(MemberRoles.Role.AdvisoryBoard)) - voteWeightAB = 1; - uint delegationId; tokenInstance.lockForMemberVote(msg.sender, tokenHoldingTime); - for (uint i = 0; i < leaderDelegation[msg.sender].length; i++) { - delegationId = leaderDelegation[msg.sender][i]; - if (allDelegation[delegationId].leader == msg.sender && - _checkLastUpd(allDelegation[delegationId].lastUpd)) { - if (memberRole.checkRole(allDelegation[delegationId].follower, mrSequence)) { - tokenBalance = tokenInstance.totalBalanceOf(allDelegation[delegationId].follower); - tokenInstance.lockForMemberVote(allDelegation[delegationId].follower, tokenHoldingTime); - voters++; - if (isSpecialResolution == 1) { - voteWeight += tokenBalance + 10**18; - } else { - voteWeight += (_minOf(tokenBalance, maxVoteWeigthPer.mul(totalSupply).div(100))) + 10**18; + if (mrSequence != uint(MemberRoles.Role.AdvisoryBoard)) { + uint voteWeight; + uint voters = 1; + uint tokenBalance = tokenInstance.totalBalanceOf(msg.sender); + uint totalSupply = tokenInstance.totalSupply(); + if (isSpecialResolution == 1) { + voteWeight = tokenBalance.add(10**18); + } else { + voteWeight = (_minOf(tokenBalance, maxVoteWeigthPer.mul(totalSupply).div(100))).add(10**18); + } + DelegateVote memory delegationData; + for (uint i = 0; i < leaderDelegation[msg.sender].length; i++) { + delegationData = allDelegation[leaderDelegation[msg.sender][i]]; + if (delegationData.leader == msg.sender && + _checkLastUpd(delegationData.lastUpd)) { + if (memberRole.checkRole(delegationData.follower, mrSequence)) { + tokenBalance = tokenInstance.totalBalanceOf(delegationData.follower); + tokenInstance.lockForMemberVote(delegationData.follower, tokenHoldingTime); + voters++; + if (isSpecialResolution == 1) { + voteWeight = voteWeight.add(tokenBalance.add(10**18)); + } else { + voteWeight = voteWeight.add((_minOf(tokenBalance, maxVoteWeigthPer.mul(totalSupply).div(100))).add(10**18)); + } } } } + proposalVoteTally[_proposalId].memberVoteValue[_solution] = proposalVoteTally[_proposalId].memberVoteValue[_solution].add(voteWeight); + proposalVoteTally[_proposalId].voters = proposalVoteTally[_proposalId].voters + voters; } - if (mrSequence == uint(MemberRoles.Role.Member) || mrSequence == uint(MemberRoles.Role.Owner)) { - proposalVoteTally[_proposalId].memberVoteValue[_solution] += voteWeight; - proposalVoteTally[_proposalId].voters += voters; - } - proposalVoteTally[_proposalId].abVoteValue[_solution] += voteWeightAB; } /** - * @dev get minimum of two numbers + * @dev Gets minimum of two numbers * @param a one of the two numbers * @param b one of the two numbers * @return minimum number out of the two @@ -886,7 +1009,7 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev check the time since last update has exceeded token holding time or not + * @dev Check the time since last update has exceeded token holding time or not * @param _lastUpd is last update time * @return the bool which tells if the time since last update has exceeded token holding time or not */ @@ -894,101 +1017,136 @@ contract Governance is IGovernance, Iupgradable { return (now - _lastUpd) > tokenHoldingTime; } - /// @dev Checks if the vote count against any solution passes the threshold value or not. + /** + * @dev Checks if the vote count against any solution passes the threshold value or not. + */ function _checkForThreshold(uint _proposalId, uint _category) internal view returns(bool check) { uint categoryQuorumPerc; - (, , , categoryQuorumPerc, , , ) = proposalCategory.category(_category); - uint totalTokenVoted = proposalVoteTally[_proposalId].memberVoteValue[0] - +proposalVoteTally[_proposalId].memberVoteValue[1]; - check = totalTokenVoted.mul(100).div(tokenInstance.totalSupply() + - memberRole.numberOfMembers(uint(MemberRoles.Role.Member))) >= categoryQuorumPerc; + uint roleAuthorized; + (, roleAuthorized, , categoryQuorumPerc, , , ) = proposalCategory.category(_category); + check = ((proposalVoteTally[_proposalId].memberVoteValue[0] + .add(proposalVoteTally[_proposalId].memberVoteValue[1])) + .mul(100)) + .div( + tokenInstance.totalSupply().add( + memberRole.numberOfMembers(roleAuthorized).mul(10 ** 18) + ) + ) >= categoryQuorumPerc; } /** - * @dev this function is called when vote majority is reached + * @dev Called when vote majority is reached * @param _proposalId of proposal in concern * @param _status of proposal in concern * @param category of proposal in concern * @param max vote value of proposal in concern */ - function _callIfMajReach (uint _proposalId, uint _status, uint category, uint max) internal { - bytes2 contractName; - address actionAddress; + function _callIfMajReached(uint _proposalId, uint _status, uint category, uint max, MemberRoles.Role role) internal { + allProposalData[_proposalId].finalVerdict = max; - (, actionAddress, contractName, ) = proposalCategory.categoryAction(category); _updateProposalStatus(_proposalId, _status); + emit ProposalAccepted(_proposalId); + if (proposalActionStatus[_proposalId] != uint(ActionStatus.NoAction)) { + if (role == MemberRoles.Role.AdvisoryBoard) { + _triggerAction(_proposalId, category); + } else { + proposalActionStatus[_proposalId] = uint(ActionStatus.Accepted); + proposalExecutionTime[_proposalId] = actionWaitingTime.add(now); + } + } + } + + /** + * @dev Internal function to trigger action of accepted proposal + */ + function _triggerAction(uint _proposalId, uint _categoryId) internal { + proposalActionStatus[_proposalId] = uint(ActionStatus.Executed); + bytes2 contractName; + address actionAddress; + bytes memory _functionHash; + (, actionAddress, contractName, , _functionHash) = proposalCategory.categoryActionDetails(_categoryId); if (contractName == "MS") { actionAddress = address(ms); } else if (contractName != "EX") { actionAddress = ms.getLatestAddress(contractName); } - (bool actionStatus, ) = actionAddress.call(allProposalSolutions[_proposalId][max]);//solhint-disable-line - if (actionStatus) { //solhint-disable-line + (bool actionStatus, ) = actionAddress.call(abi.encodePacked(_functionHash, allProposalSolutions[_proposalId][1])); + if (actionStatus) { emit ActionSuccess(_proposalId); + } else { + proposalActionStatus[_proposalId] = uint(ActionStatus.Accepted); + emit ActionFailed(_proposalId); } - emit ProposalAccepted(_proposalId); } /** - * @dev to update proposal status + * @dev Internal call to update proposal status * @param _proposalId of proposal in concern * @param _status of proposal to set */ function _updateProposalStatus(uint _proposalId, uint _status) internal { + if (_status == uint(ProposalStatus.Rejected) || _status == uint(ProposalStatus.Denied)) { + proposalActionStatus[_proposalId] = uint(ActionStatus.NoAction); + } allProposalData[_proposalId].dateUpd = now; allProposalData[_proposalId].propStatus = _status; } /** - * @dev to undelegate a follower + * @dev Internal call to undelegate a follower * @param _follower is address of follower to undelegate */ function _unDelegate(address _follower) internal { uint followerId = followerDelegation[_follower]; if (followerId > 0) { - followerCount[allDelegation[followerId].leader]--; + followerCount[allDelegation[followerId].leader] = followerCount[allDelegation[followerId].leader].sub(1); allDelegation[followerId].leader = address(0); allDelegation[followerId].lastUpd = now; - lastRewardClaimed[msg.sender] = allVotesByMember[msg.sender].length; + lastRewardClaimed[_follower] = allVotesByMember[_follower].length; } } /** - * @dev to close member voting + * @dev Internal call to close member voting * @param _proposalId of proposal in concern * @param category of proposal in concern */ function _closeMemberVote(uint _proposalId, uint category) internal { - uint totalVoteValue; - uint majorityVote; - if (proposalCategory.isSpecialResolution(category) == 1) { + uint isSpecialResolution; + uint abMaj; + (, abMaj, isSpecialResolution) = proposalCategory.categoryExtendedData(category); + if (isSpecialResolution == 1) { uint acceptedVotePerc = proposalVoteTally[_proposalId].memberVoteValue[1].mul(100) - .div(tokenInstance.totalSupply() - + (memberRole.numberOfMembers(uint(MemberRoles.Role.Member))) * 10**18); + .div( + tokenInstance.totalSupply().add( + memberRole.numberOfMembers(uint(MemberRoles.Role.Member)).mul(10**18) + )); if (acceptedVotePerc >= specialResolutionMajPerc) { - _callIfMajReach(_proposalId, uint(ProposalStatus.Accepted), category, 1); + _callIfMajReached(_proposalId, uint(ProposalStatus.Accepted), category, 1, MemberRoles.Role.Member); } else { _updateProposalStatus(_proposalId, uint(ProposalStatus.Denied)); } } else { if (_checkForThreshold(_proposalId, category)) { - totalVoteValue = proposalVoteTally[_proposalId].memberVoteValue[0] + - proposalVoteTally[_proposalId].memberVoteValue[1]; + uint majorityVote; (, , majorityVote, , , , ) = proposalCategory.category(category); - if (SafeMath.div(SafeMath.mul(proposalVoteTally[_proposalId].memberVoteValue[1], 100), totalVoteValue) - >= majorityVote) { - _callIfMajReach(_proposalId, uint(ProposalStatus.Accepted), category, 1); - } else { - _updateProposalStatus(_proposalId, uint(ProposalStatus.Rejected)); - } + if ( + ((proposalVoteTally[_proposalId].memberVoteValue[1].mul(100)) + .div(proposalVoteTally[_proposalId].memberVoteValue[0] + .add(proposalVoteTally[_proposalId].memberVoteValue[1]) + )) + >= majorityVote + ) { + _callIfMajReached(_proposalId, uint(ProposalStatus.Accepted), category, 1, MemberRoles.Role.Member); + } else { + _updateProposalStatus(_proposalId, uint(ProposalStatus.Rejected)); + } } else { - uint abMaj = proposalCategory.categoryABReq(category); - // uint abMem = memberRole.numberOfMembers(uint(MemberRoles.Role.AdvisoryBoard)); if (abMaj > 0 && proposalVoteTally[_proposalId].abVoteValue[1].mul(100) .div(memberRole.numberOfMembers(uint(MemberRoles.Role.AdvisoryBoard))) >= abMaj) { - _callIfMajReach(_proposalId, uint(ProposalStatus.Accepted), category, 1); + _callIfMajReached(_proposalId, uint(ProposalStatus.Accepted), category, 1, MemberRoles.Role.Member); } else { _updateProposalStatus(_proposalId, uint(ProposalStatus.Denied)); } @@ -1001,52 +1159,21 @@ contract Governance is IGovernance, Iupgradable { } /** - * @dev to close advisory board voting + * @dev Internal call to close advisory board voting * @param _proposalId of proposal in concern * @param category of proposal in concern - * @param _roleId of group of members involved in voting */ - function _closeABVote(uint _proposalId, uint category, uint _roleId) internal { + function _closeAdvisoryBoardVote(uint _proposalId, uint category) internal { uint _majorityVote; - // uint abMem = memberRole.numberOfMembers(_roleId); + MemberRoles.Role _roleId = MemberRoles.Role.AdvisoryBoard; (, , _majorityVote, , , , ) = proposalCategory.category(category); if (proposalVoteTally[_proposalId].abVoteValue[1].mul(100) - .div(memberRole.numberOfMembers(_roleId)) >= _majorityVote) { - _callIfMajReach(_proposalId, uint(ProposalStatus.Accepted), category, 1); + .div(memberRole.numberOfMembers(uint(_roleId))) >= _majorityVote) { + _callIfMajReached(_proposalId, uint(ProposalStatus.Accepted), category, 1, _roleId); } else { _updateProposalStatus(_proposalId, uint(ProposalStatus.Denied)); } } - /** - * @dev to open proposal for voting - * @param _proposalId of proposal in concern - */ - function _openProposalForVoting(uint _proposalId) internal { - - require(allProposalData[_proposalId].category != 0, "Proposal not categorized"); - _updateProposalStatus(_proposalId, uint(ProposalStatus.VotingStarted)); - uint closingTime; - (, , , , , closingTime, ) = proposalCategory.category(allProposalData[_proposalId].category); - emit CloseProposalOnTime(_proposalId, SafeMath.add(closingTime, now)); - } - - /** - * @dev to initiate the governance process - */ - function _initiateGovernance() internal { - allVotes.push(ProposalVote(address(0), 0, 0)); - totalProposals = 1; - // allProposal.push(ProposalStruct(address(0), now)); - allDelegation.push(DelegateVote(address(0), address(0), now)); - tokenHoldingTime = 1 * 7 days; - maxDraftTime = 2 * 7 days; - maxVoteWeigthPer = 5; - maxFollowers = 40; - constructorCheck = true; - roleIdAllowedToCatgorize = uint(MemberRoles.Role.AdvisoryBoard); - specialResolutionMajPerc = 75; - } - -} \ No newline at end of file +} diff --git a/contracts/ProposalCategory.sol b/contracts/ProposalCategory.sol index 520ea0f3f1..2ae161646b 100644 --- a/contracts/ProposalCategory.sol +++ b/contracts/ProposalCategory.sol @@ -41,18 +41,31 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { mapping (uint => CategoryAction) internal categoryActionData; mapping (uint => uint) public categoryABReq; mapping (uint => uint) public isSpecialResolution; + mapping (uint => bytes) public categoryActionHashes; - /// @dev Adds new category - /// @param _name Category name - /// @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. - /// @param _majorityVotePerc Majority Vote threshold for Each voting layer - /// @param _quorumPerc minimum threshold percentage required in voting to calculate result - /// @param _allowedToCreateProposal Member roles allowed to create the proposal - /// @param _closingTime Vote closing time for Each voting layer - /// @param _actionHash hash of details containing the action that has to be performed after proposal is accepted - /// @param _contractAddress address of contract to call after proposal is accepted - /// @param _contractName name of contract to be called after proposal is accepted - /// @param _incentives rewards to distributed after proposal is accepted + bool public categoryActionHashUpdated; + + /** + * @dev Restricts calls to deprecated functions + */ + modifier deprecated() { + revert("Function deprecated"); + _; + } + + /** + * @dev Adds new category (Discontinued, moved functionality to newCategory) + * @param _name Category name + * @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. + * @param _majorityVotePerc Majority Vote threshold for Each voting layer + * @param _quorumPerc minimum threshold percentage required in voting to calculate result + * @param _allowedToCreateProposal Member roles allowed to create the proposal + * @param _closingTime Vote closing time for Each voting layer + * @param _actionHash hash of details containing the action that has to be performed after proposal is accepted + * @param _contractAddress address of contract to call after proposal is accepted + * @param _contractName name of contract to be called after proposal is accepted + * @param _incentives rewards to distributed after proposal is accepted + */ function addCategory( string calldata _name, uint _memberRoleToVote, @@ -66,28 +79,65 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { uint[] calldata _incentives ) external - onlyAuthorizedToGovern + deprecated { - _addCategory( - _name, - _memberRoleToVote, - _majorityVotePerc, - _quorumPerc, - _allowedToCreateProposal, - _closingTime, - _actionHash, - _contractAddress, - _contractName, - _incentives - ); } - /// @dev Gets Total number of categories added till now + /** + * @dev Initiates Default settings for Proposal Category contract (Adding default categories) + */ + function proposalCategoryInitiate() external deprecated { //solhint-disable-line + } + + /** + * @dev Initiates Default action function hashes for existing categories + * To be called after the contract has been upgraded by governance + */ + function updateCategoryActionHashes() external onlyOwner { + + require(!categoryActionHashUpdated, "Category action hashes already updated"); + categoryActionHashUpdated = true; + categoryActionHashes[1] = abi.encodeWithSignature("addRole(bytes32,string,address)"); + categoryActionHashes[2] = abi.encodeWithSignature("updateRole(address,uint256,bool)"); + categoryActionHashes[3] = abi.encodeWithSignature("newCategory(string,uint256,uint256,uint256,uint256[],uint256,string,address,bytes2,uint256[],string)");//solhint-disable-line + categoryActionHashes[4] = abi.encodeWithSignature("editCategory(uint256,string,uint256,uint256,uint256,uint256[],uint256,string,address,bytes2,uint256[],string)");//solhint-disable-line + categoryActionHashes[5] = abi.encodeWithSignature("upgradeContractImplementation(bytes2,address)"); + categoryActionHashes[6] = abi.encodeWithSignature("startEmergencyPause()"); + categoryActionHashes[7] = abi.encodeWithSignature("addEmergencyPause(bool,bytes4)"); + categoryActionHashes[8] = abi.encodeWithSignature("burnCAToken(uint256,uint256,address)"); + categoryActionHashes[9] = abi.encodeWithSignature("setUserClaimVotePausedOn(address)"); + categoryActionHashes[12] = abi.encodeWithSignature("transferEther(uint256,address)"); + categoryActionHashes[13] = abi.encodeWithSignature("addInvestmentAssetCurrency(bytes4,address,bool,uint64,uint64,uint8)");//solhint-disable-line + categoryActionHashes[14] = abi.encodeWithSignature("changeInvestmentAssetHoldingPerc(bytes4,uint64,uint64)"); + categoryActionHashes[15] = abi.encodeWithSignature("changeInvestmentAssetStatus(bytes4,bool)"); + categoryActionHashes[16] = abi.encodeWithSignature("swapABMember(address,address)"); + categoryActionHashes[17] = abi.encodeWithSignature("addCurrencyAssetCurrency(bytes4,address,uint256)"); + categoryActionHashes[20] = abi.encodeWithSignature("updateUintParameters(bytes8,uint256)"); + categoryActionHashes[21] = abi.encodeWithSignature("updateUintParameters(bytes8,uint256)"); + categoryActionHashes[22] = abi.encodeWithSignature("updateUintParameters(bytes8,uint256)"); + categoryActionHashes[23] = abi.encodeWithSignature("updateUintParameters(bytes8,uint256)"); + categoryActionHashes[24] = abi.encodeWithSignature("updateUintParameters(bytes8,uint256)"); + categoryActionHashes[25] = abi.encodeWithSignature("updateUintParameters(bytes8,uint256)"); + categoryActionHashes[26] = abi.encodeWithSignature("updateUintParameters(bytes8,uint256)"); + categoryActionHashes[27] = abi.encodeWithSignature("updateAddressParameters(bytes8,address)"); + categoryActionHashes[28] = abi.encodeWithSignature("updateOwnerParameters(bytes8,address)"); + categoryActionHashes[29] = abi.encodeWithSignature("upgradeContract(bytes2,address)"); + categoryActionHashes[30] = abi.encodeWithSignature("changeCurrencyAssetAddress(bytes4,address)"); + categoryActionHashes[31] = abi.encodeWithSignature("changeCurrencyAssetBaseMin(bytes4,uint256)"); + categoryActionHashes[32] = abi.encodeWithSignature("changeInvestmentAssetAddressAndDecimal(bytes4,address,uint8)");//solhint-disable-line + categoryActionHashes[33] = abi.encodeWithSignature("externalLiquidityTrade()"); + } + + /** + * @dev Gets Total number of categories added till now + */ function totalCategories() external view returns(uint) { return allCategory.length; } - /// @dev gets category details + /** + * @dev Gets category details + */ function category(uint _categoryId) external view returns(uint, uint, uint, uint, uint[] memory, uint, uint) { return( _categoryId, @@ -101,7 +151,21 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { } /** - * @dev to get the category acion details of a category id + * @dev Gets category ab required and isSpecialResolution + * @return the category id + * @return if AB voting is required + * @return is category a special resolution + */ + function categoryExtendedData(uint _categoryId) external view returns(uint, uint, uint) { + return( + _categoryId, + categoryABReq[_categoryId], + isSpecialResolution[_categoryId] + ); + } + + /** + * @dev Gets the category acion details * @param _categoryId is the category id in concern * @return the category id * @return the contract address @@ -109,6 +173,7 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { * @return the default incentive */ function categoryAction(uint _categoryId) external view returns(uint, address, bytes2, uint) { + return( _categoryId, categoryActionData[_categoryId].contractAddress, @@ -117,93 +182,97 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { ); } - /// @dev Initiates Default settings for Proposal Category contract (Adding default categories) - function proposalCategoryInitiate() external { //solhint-disable-line - require(!constructorCheck); - _addInitialCategories("Uncategorized", "", "MR", 60, 15, 1, 0); //0 - _addInitialCategories("Add new member role", "QmQFnBep7AyMYU3LJDuHSpTYatnw65XjHzzirrghtZoR8U", - "MR", 60, 15, 1, 0); - _addInitialCategories("Update member role", "QmXMzSViLBJ22P9oj51Zz7isKTRnXWPHZcQ5hzGvvWD3UV", - "MR", 60, 15, 1, 0); - _addInitialCategories("Add new category", "QmUq9Rb6rWFHZXjVtyzh7AWGDeyVFtDHKiP5fJpgnuinQ7", "PC", - 60, 15, 1, 0); - _addInitialCategories("Edit category", "QmQmvfBiCLfe5jPdq69iRBRRdnSHSroJQ4SG8DhtkXcLfQ", //4 - "PC", 60, 15, 1, 0); - _addInitialCategories( - "Upgrade a contract Implementation", - "Qme4hGas6RuDYk9LKE2XkK9E46LNeCBUzY12DdT5uQstvh", - "MS", - 50, - 15, - 2, - 80 + /** + * @dev Gets the category acion details of a category id + * @param _categoryId is the category id in concern + * @return the category id + * @return the contract address + * @return the contract name + * @return the default incentive + * @return action function hash + */ + function categoryActionDetails(uint _categoryId) external view returns(uint, address, bytes2, uint, bytes memory) { + return( + _categoryId, + categoryActionData[_categoryId].contractAddress, + categoryActionData[_categoryId].contractName, + categoryActionData[_categoryId].defaultIncentive, + categoryActionHashes[_categoryId] ); - - // --------------------------------------------------------------------------------------------- // - _addInitialCategories("Implement Emergency Pause", "QmZSaEsvTCpy357ZSrPYKqby1iaksBwPdKCGWzW1HpgSpe", - "MS", 0, 15, 1, 0); - _addInitialCategories("Extend or Switch Off Emergency Pause", "Qmao6dD8amq4kxsAheWn5gQX22ABucFFGRvnRuY1VqtEKy", - "MS", 50, 15, 2, 0); - _addInitialCategories("Burn Claims Assessor Bond", "QmezNJUF2BM5Nv9EMnsEKUmuqjvdySzvQFvhEdvFJbau3k", //8 - "TF", 80, 15, 1, 0); - _addInitialCategories("Pause Claim Assessor Voting for 3 days", - "QmRBXh9NGoGV7U7tTurKPhL4bzvDc9n23QZYidELpBPVdg", "CD", 60, 15, 1, 0); - _addInitialCategories("Changes to Capital Model", "", "EX", 50, 15, 2, 60); - _addInitialCategories("Changes to Pricing Model", "", "EX", 50, 15, 2, 60); - _addInitialCategories("Withdraw funds to Pay for Support Services", - "QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM", "P1", 50, 15, 2, 80); - // _addInitialCategories("Change to Authorities", "", "EX", 80, 15, 2); //no func yet - _addInitialCategories("Add Investment Asset", "Qmd66GdYtn1BYmZTB1op1Fbfkq6uywMpow5LRmG2Twbzjb", //13 - "PD", 50, 15, 2, 60); - _addInitialCategories("Edit Investment Asset min and max holding percentages", - "QmXwyffmk7rYGHE7p4g3oroJkmyEYAn6EffhZu2MCNcJGA", - "PD", 50, 15, 2, 60); - _addInitialCategories("Update Investment Asset Status", "QmZkxcC82WFRvnBahLT3eQ95ZSGMxuAyCYqxvR3tSyhFmB", - "PD", 50, 15, 2, 60); - _addInitialCategories("Change AB Member", "QmUBjPDdSiG3pRMqkVzZA2WaqiksT7ixNd3gPQwngGmF9x", - "MR", 50, 15, 2, 0); - _addInitialCategories("Add Currency Asset", "QmYtpNuTdProressqZwEmN7cFtyyJvhFBrqr6xnxQGWrPm", //17 - "PD", 50, 15, 2, 0); - _addInitialCategories("Any other Item", "", "EX", 50, 15, 2, 80); - _addInitialCategories("Special Resolution", "", "EX", 75, 0, 2, 0); - _addInitialCategories("Update Token Parameters", "QmbfJTXyLTDsq41U4dukHbagcXef8bRfyMdMqcqLd9aKNM", - "TD", 50, 15, 2, 60); - _addInitialCategories("Update Risk Assessment Parameters", "QmUHvBShLpDwPWAsWcZvbUJfVGyXYscybi5ASmF6ectxSo", - //21 - "TD", 50, 15, 2, 60); - _addInitialCategories("Update Governance Parameters", "QmdFDVEaZnJxXncFczTW6EvrcgR3jBfuPWftR7PfkPfqqT", - "GV", 50, 15, 2, 60); - _addInitialCategories("Update Quotation Parameters", "QmTtSbBp2Cxaz8HzB4TingUozr9AW91siCfMjjyzf8qqAb", - "QD", 50, 15, 2, 60); - _addInitialCategories("Update Claims Assessment Parameters", "QmPo6HPydwXEeoVdwBpwGeZasFnmFwZoTsQ93Bg5pFtQg6", - "CD", 50, 15, 2, 60); - _addInitialCategories("Update Investment Module Parameters", "QmYSUJBJD9hUevydfdF34rGFG7bBQhMrxh2ga9XfeAkdEM", - //25 - "PD", 50, 15, 2, 60); - _addInitialCategories("Update Capital Model Parameters", "QmaQH6AdvBdgrW4xdzcMHa7gNyYSGa2fz7gBuuic2hLkZQ", - "PD", 50, 15, 2, 60); - _addInitialCategories("Update Address Parameters", "QmPfXySkeDFbdMvZyD35y1hiB4g6ZXLSEHfS7JjS6e1VKL", - "MS", 50, 15, 2, 60); - _addInitialCategories("Update Owner Parameters", "QmTEmDA1ECmGPfh5x3co1GmjXQCp3zisUP6rnLQjWmW8nu", //28 - "MS", 50, 15, 3, 0); - _addInitialCategories("Release new smart contract code", "QmSStfVwXF1TzDPCseVtMydgdF1xmzqhMtfpUg9Btx7tUp", - "MS", 50, 15, 2, 80); - _addInitialCategories("Edit Currency Asset Address", "QmahwCzxmUX1QEjgczmA2NF4Nxtx839eRLCXbBFeFCm3cF", - "PD", 50, 15, 3, 60); - _addInitialCategories("Edit Currency Asset baseMin", "QmeFSwZ21d7XabxVc7eiNKbtfEXUuD8qQXkeHZ5To1vo4t", - "PD", 50, 15, 2, 60); - _addInitialCategories("Edit Investment Asset Address and decimal", - "QmRpztKqva2ud5xz9CQeb562bRQt2VEBPnjaWEPwN8q3vf", - "PD", 50, 15, 3, 60); - constructorCheck = true; } + /** + * @dev Updates dependant contract addresses + */ function changeDependentContractAddress() public { mr = MemberRoles(ms.getLatestAddress("MR")); } /** - * @dev to change the master address + * @dev Adds new category + * @param _name Category name + * @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. + * @param _majorityVotePerc Majority Vote threshold for Each voting layer + * @param _quorumPerc minimum threshold percentage required in voting to calculate result + * @param _allowedToCreateProposal Member roles allowed to create the proposal + * @param _closingTime Vote closing time for Each voting layer + * @param _actionHash hash of details containing the action that has to be performed after proposal is accepted + * @param _contractAddress address of contract to call after proposal is accepted + * @param _contractName name of contract to be called after proposal is accepted + * @param _incentives rewards to distributed after proposal is accepted + * @param _functionHash function signature to be executed + */ + function newCategory( + string memory _name, + uint _memberRoleToVote, + uint _majorityVotePerc, + uint _quorumPerc, + uint[] memory _allowedToCreateProposal, + uint _closingTime, + string memory _actionHash, + address _contractAddress, + bytes2 _contractName, + uint[] memory _incentives, + string memory _functionHash + ) + public + onlyAuthorizedToGovern + { + + require(_quorumPerc <= 100 && _majorityVotePerc <= 100, "Invalid percentage"); + + require((_contractName == "EX" && _contractAddress == address(0)) || bytes(_functionHash).length > 0); + + require(_incentives[3] <= 1, "Invalid special resolution flag"); + + //If category is special resolution role authorized should be member + if (_incentives[3] == 1) { + require(_memberRoleToVote == uint(MemberRoles.Role.Member)); + _majorityVotePerc = 0; + _quorumPerc = 0; + } + + _addCategory( + _name, + _memberRoleToVote, + _majorityVotePerc, + _quorumPerc, + _allowedToCreateProposal, + _closingTime, + _actionHash, + _contractAddress, + _contractName, + _incentives + ); + + + if (bytes(_functionHash).length > 0 && abi.encodeWithSignature(_functionHash).length == 4) { + categoryActionHashes[allCategory.length - 1] = abi.encodeWithSignature(_functionHash); + } + } + + /** + * @dev Changes the master address and update it's instance * @param _masterAddress is the new master address */ function changeMasterAddress(address _masterAddress) public { @@ -215,18 +284,20 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { } - /// @dev Updates category details - /// @param _categoryId Category id that needs to be updated - /// @param _name Category name - /// @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. - /// @param _allowedToCreateProposal Member roles allowed to create the proposal - /// @param _majorityVotePerc Majority Vote threshold for Each voting layer - /// @param _quorumPerc minimum threshold percentage required in voting to calculate result - /// @param _closingTime Vote closing time for Each voting layer - /// @param _actionHash hash of details containing the action that has to be performed after proposal is accepted - /// @param _contractAddress address of contract to call after proposal is accepted - /// @param _contractName name of contract to be called after proposal is accepted - /// @param _incentives rewards to distributed after proposal is accepted + /** + * @dev Updates category details (Discontinued, moved functionality to editCategory) + * @param _categoryId Category id that needs to be updated + * @param _name Category name + * @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. + * @param _allowedToCreateProposal Member roles allowed to create the proposal + * @param _majorityVotePerc Majority Vote threshold for Each voting layer + * @param _quorumPerc minimum threshold percentage required in voting to calculate result + * @param _closingTime Vote closing time for Each voting layer + * @param _actionHash hash of details containing the action that has to be performed after proposal is accepted + * @param _contractAddress address of contract to call after proposal is accepted + * @param _contractName name of contract to be called after proposal is accepted + * @param _incentives rewards to distributed after proposal is accepted + */ function updateCategory( uint _categoryId, string memory _name, @@ -239,11 +310,63 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { address _contractAddress, bytes2 _contractName, uint[] memory _incentives + ) + public + deprecated + { + } + + /** + * @dev Updates category details + * @param _categoryId Category id that needs to be updated + * @param _name Category name + * @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. + * @param _allowedToCreateProposal Member roles allowed to create the proposal + * @param _majorityVotePerc Majority Vote threshold for Each voting layer + * @param _quorumPerc minimum threshold percentage required in voting to calculate result + * @param _closingTime Vote closing time for Each voting layer + * @param _actionHash hash of details containing the action that has to be performed after proposal is accepted + * @param _contractAddress address of contract to call after proposal is accepted + * @param _contractName name of contract to be called after proposal is accepted + * @param _incentives rewards to distributed after proposal is accepted + * @param _functionHash function signature to be executed + */ + function editCategory( + uint _categoryId, + string memory _name, + uint _memberRoleToVote, + uint _majorityVotePerc, + uint _quorumPerc, + uint[] memory _allowedToCreateProposal, + uint _closingTime, + string memory _actionHash, + address _contractAddress, + bytes2 _contractName, + uint[] memory _incentives, + string memory _functionHash ) public onlyAuthorizedToGovern { - require(_verifyMemberRoles(_memberRoleToVote, _allowedToCreateProposal) == 0, "Invalid Role"); + require(_verifyMemberRoles(_memberRoleToVote, _allowedToCreateProposal) == 1, "Invalid Role"); + + require(_quorumPerc <= 100 && _majorityVotePerc <= 100, "Invalid percentage"); + + require((_contractName == "EX" && _contractAddress == address(0)) || bytes(_functionHash).length > 0); + + require(_incentives[3] <= 1, "Invalid special resolution flag"); + + //If category is special resolution role authorized should be member + if (_incentives[3] == 1) { + require(_memberRoleToVote == uint(MemberRoles.Role.Member)); + _majorityVotePerc = 0; + _quorumPerc = 0; + } + + delete categoryActionHashes[_categoryId]; + if (bytes(_functionHash).length > 0 && abi.encodeWithSignature(_functionHash).length == 4) { + categoryActionHashes[_categoryId] = abi.encodeWithSignature(_functionHash); + } allCategory[_categoryId].memberRoleToVote = _memberRoleToVote; allCategory[_categoryId].majorityVotePerc = _majorityVotePerc; allCategory[_categoryId].closingTime = _closingTime; @@ -254,20 +377,23 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { categoryActionData[_categoryId].contractName = _contractName; categoryActionData[_categoryId].contractAddress = _contractAddress; categoryABReq[_categoryId] = _incentives[2]; + isSpecialResolution[_categoryId] = _incentives[3]; emit Category(_categoryId, _name, _actionHash); } - /// @dev Adds new category - /// @param _name Category name - /// @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. - /// @param _majorityVotePerc Majority Vote threshold for Each voting layer - /// @param _quorumPerc minimum threshold percentage required in voting to calculate result - /// @param _allowedToCreateProposal Member roles allowed to create the proposal - /// @param _closingTime Vote closing time for Each voting layer - /// @param _actionHash hash of details containing the action that has to be performed after proposal is accepted - /// @param _contractAddress address of contract to call after proposal is accepted - /// @param _contractName name of contract to be called after proposal is accepted - /// @param _incentives rewards to distributed after proposal is accepted + /** + * @dev Internal call to add new category + * @param _name Category name + * @param _memberRoleToVote Voting Layer sequence in which the voting has to be performed. + * @param _majorityVotePerc Majority Vote threshold for Each voting layer + * @param _quorumPerc minimum threshold percentage required in voting to calculate result + * @param _allowedToCreateProposal Member roles allowed to create the proposal + * @param _closingTime Vote closing time for Each voting layer + * @param _actionHash hash of details containing the action that has to be performed after proposal is accepted + * @param _contractAddress address of contract to call after proposal is accepted + * @param _contractName name of contract to be called after proposal is accepted + * @param _incentives rewards to distributed after proposal is accepted + */ function _addCategory( string memory _name, uint _memberRoleToVote, @@ -282,7 +408,7 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { ) internal { - require(_verifyMemberRoles(_memberRoleToVote, _allowedToCreateProposal) == 0, "Invalid Role"); + require(_verifyMemberRoles(_memberRoleToVote, _allowedToCreateProposal) == 1, "Invalid Role"); allCategory.push( CategoryStruct( _memberRoleToVote, @@ -300,69 +426,21 @@ contract ProposalCategory is Governed, IProposalCategory, Iupgradable { emit Category(categoryId, _name, _actionHash); } + /** + * @dev Internal call to check if given roles are valid or not + */ function _verifyMemberRoles(uint _memberRoleToVote, uint[] memory _allowedToCreateProposal) internal view returns(uint) { uint totalRoles = mr.totalRoles(); if (_memberRoleToVote >= totalRoles) { - return 1; + return 0; } for (uint i = 0; i < _allowedToCreateProposal.length; i++) { if (_allowedToCreateProposal[i] >= totalRoles) { - return 1; + return 0; } } - return 0; - } - - /** - * @dev to add the initial categories - * @param _name is category name - * @param _actionHash hash of category action - * @param _contractName is the name of contract - * @param _majorityVotePerc percentage of majority vote - * @param _quorumPerc is the quorom percentage - * @param _memberRoleToVote is the member role the category can vote on - * @param _categoryABReq is majority percentage required by advisory board - */ - function _addInitialCategories( - string memory _name, - string memory _actionHash, - bytes2 _contractName, - uint _majorityVotePerc, - uint _quorumPerc, - uint _memberRoleToVote, - uint _categoryABReq - ) - internal - { - uint[] memory allowedToCreateProposal = new uint[](1); - uint[] memory stakeIncentive = new uint[](4); - if (_memberRoleToVote == 3) { - allowedToCreateProposal[0] = 3; - } else { - allowedToCreateProposal[0] = 2; - } - stakeIncentive[0] = 0; - stakeIncentive[1] = 0; - stakeIncentive[2] = _categoryABReq; - if (_quorumPerc == 0) {//For special resolutions - stakeIncentive[3] = 1; - } else { - stakeIncentive[3] = 0; - } - _addCategory( - _name, - _memberRoleToVote, - _majorityVotePerc, - _quorumPerc, - allowedToCreateProposal, - 604800, - _actionHash, - address(0), - _contractName, - stakeIncentive - ); + return 1; } - -} \ No newline at end of file +} diff --git a/contracts/mocks/GovernanceMock.sol b/contracts/mocks/GovernanceMock.sol new file mode 100644 index 0000000000..0783512086 --- /dev/null +++ b/contracts/mocks/GovernanceMock.sol @@ -0,0 +1,35 @@ +// /* Copyright (C) 2017 GovBlocks.io + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ */ + +pragma solidity 0.5.7; + +import "../Governance.sol"; + + +contract GovernanceMock is Governance { + function _initiateGovernance() external { + allVotes.push(ProposalVote(address(0), 0, 0)); + totalProposals = 1; + // allProposal.push(ProposalStruct(address(0), now)); + allDelegation.push(DelegateVote(address(0), address(0), now)); + tokenHoldingTime = 1 * 7 days; + maxDraftTime = 2 * 7 days; + maxVoteWeigthPer = 5; + maxFollowers = 40; + roleIdAllowedToCatgorize = uint(MemberRoles.Role.AdvisoryBoard); + specialResolutionMajPerc = 75; + } + +} \ No newline at end of file diff --git a/contracts/mocks/ProposalCategoryMock.sol b/contracts/mocks/ProposalCategoryMock.sol new file mode 100644 index 0000000000..77050ce943 --- /dev/null +++ b/contracts/mocks/ProposalCategoryMock.sol @@ -0,0 +1,138 @@ +/* Copyright (C) 2017 GovBlocks.io + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ */ +pragma solidity 0.5.7; + +import "../ProposalCategory.sol"; + + +contract ProposalCategoryMock is ProposalCategory { + function proposalCategoryInitiate() public { + require(!constructorCheck); + constructorCheck = true; + _addInitialCategories("Uncategorized", "", "MR", 60, 15, 1, 0); //0 + _addInitialCategories("Add new member role", "QmQFnBep7AyMYU3LJDuHSpTYatnw65XjHzzirrghtZoR8U", + "MR", 60, 15, 1, 0); + _addInitialCategories("Update member role", "QmXMzSViLBJ22P9oj51Zz7isKTRnXWPHZcQ5hzGvvWD3UV", + "MR", 60, 15, 1, 0); + _addInitialCategories("Add new category", "QmUq9Rb6rWFHZXjVtyzh7AWGDeyVFtDHKiP5fJpgnuinQ7", "PC", + 60, 15, 1, 0); + _addInitialCategories("Edit category", "QmQmvfBiCLfe5jPdq69iRBRRdnSHSroJQ4SG8DhtkXcLfQ", //4 + "PC", 60, 15, 1, 0); + _addInitialCategories( + "Upgrade a contract Implementation", + "Qme4hGas6RuDYk9LKE2XkK9E46LNeCBUzY12DdT5uQstvh", + "MS", + 50, + 15, + 2, + 80 + ); + + // --------------------------------------------------------------------------------------------- // + _addInitialCategories("Implement Emergency Pause", "QmZSaEsvTCpy357ZSrPYKqby1iaksBwPdKCGWzW1HpgSpe", + "MS", 0, 15, 1, 0); + _addInitialCategories("Extend or Switch Off Emergency Pause", "Qmao6dD8amq4kxsAheWn5gQX22ABucFFGRvnRuY1VqtEKy", + "MS", 50, 15, 2, 0); + _addInitialCategories("Burn Claims Assessor Bond", "QmezNJUF2BM5Nv9EMnsEKUmuqjvdySzvQFvhEdvFJbau3k", //8 + "TF", 80, 15, 1, 0); + _addInitialCategories("Pause Claim Assessor Voting for 3 days", + "QmRBXh9NGoGV7U7tTurKPhL4bzvDc9n23QZYidELpBPVdg", "CD", 60, 15, 1, 0); + _addInitialCategories("Changes to Capital Model", "", "EX", 50, 15, 2, 60); + _addInitialCategories("Changes to Pricing Model", "", "EX", 50, 15, 2, 60); + _addInitialCategories("Withdraw funds to Pay for Support Services", + "QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM", "P1", 50, 15, 2, 80); + // _addInitialCategories("Change to Authorities", "", "EX", 80, 15, 2); //no func yet + _addInitialCategories("Add Investment Asset", "Qmd66GdYtn1BYmZTB1op1Fbfkq6uywMpow5LRmG2Twbzjb", //13 + "PD", 50, 15, 2, 60); + _addInitialCategories("Edit Investment Asset min and max holding percentages", + "QmXwyffmk7rYGHE7p4g3oroJkmyEYAn6EffhZu2MCNcJGA", + "PD", 50, 15, 2, 60); + _addInitialCategories("Update Investment Asset Status", "QmZkxcC82WFRvnBahLT3eQ95ZSGMxuAyCYqxvR3tSyhFmB", + "PD", 50, 15, 2, 60); + _addInitialCategories("Change AB Member", "QmUBjPDdSiG3pRMqkVzZA2WaqiksT7ixNd3gPQwngGmF9x", + "MR", 50, 15, 2, 0); + _addInitialCategories("Add Currency Asset", "QmYtpNuTdProressqZwEmN7cFtyyJvhFBrqr6xnxQGWrPm", //17 + "PD", 50, 15, 2, 0); + _addInitialCategories("Any other Item", "", "EX", 50, 15, 2, 80); + _addInitialCategories("Special Resolution", "", "EX", 75, 0, 2, 0); + _addInitialCategories("Update Token Parameters", "QmbfJTXyLTDsq41U4dukHbagcXef8bRfyMdMqcqLd9aKNM", + "TD", 50, 15, 2, 60); + _addInitialCategories("Update Risk Assessment Parameters", "QmUHvBShLpDwPWAsWcZvbUJfVGyXYscybi5ASmF6ectxSo", + //21 + "TD", 50, 15, 2, 60); + _addInitialCategories("Update Governance Parameters", "QmdFDVEaZnJxXncFczTW6EvrcgR3jBfuPWftR7PfkPfqqT", + "GV", 50, 15, 2, 60); + _addInitialCategories("Update Quotation Parameters", "QmTtSbBp2Cxaz8HzB4TingUozr9AW91siCfMjjyzf8qqAb", + "QD", 50, 15, 2, 60); + _addInitialCategories("Update Claims Assessment Parameters", "QmPo6HPydwXEeoVdwBpwGeZasFnmFwZoTsQ93Bg5pFtQg6", + "CD", 50, 15, 2, 60); + _addInitialCategories("Update Investment Module Parameters", "QmYSUJBJD9hUevydfdF34rGFG7bBQhMrxh2ga9XfeAkdEM", + //25 + "PD", 50, 15, 2, 60); + _addInitialCategories("Update Capital Model Parameters", "QmaQH6AdvBdgrW4xdzcMHa7gNyYSGa2fz7gBuuic2hLkZQ", + "PD", 50, 15, 2, 60); + _addInitialCategories("Update Address Parameters", "QmPfXySkeDFbdMvZyD35y1hiB4g6ZXLSEHfS7JjS6e1VKL", + "MS", 50, 15, 2, 60); + _addInitialCategories("Update Owner Parameters", "QmTEmDA1ECmGPfh5x3co1GmjXQCp3zisUP6rnLQjWmW8nu", //28 + "MS", 50, 15, 3, 0); + _addInitialCategories("Release new smart contract code", "QmSStfVwXF1TzDPCseVtMydgdF1xmzqhMtfpUg9Btx7tUp", + "MS", 50, 15, 2, 80); + _addInitialCategories("Edit Currency Asset Address", "QmahwCzxmUX1QEjgczmA2NF4Nxtx839eRLCXbBFeFCm3cF", + "PD", 50, 15, 3, 60); + _addInitialCategories("Edit Currency Asset baseMin", "QmeFSwZ21d7XabxVc7eiNKbtfEXUuD8qQXkeHZ5To1vo4t", + "PD", 50, 15, 2, 60); + _addInitialCategories("Edit Investment Asset Address and decimal", + "QmRpztKqva2ud5xz9CQeb562bRQt2VEBPnjaWEPwN8q3vf", + "PD", 50, 15, 3, 60); + } + + function _addInitialCategories( + string memory _name, + string memory _actionHash, + bytes2 _contractName, + uint _majorityVotePerc, + uint _quorumPerc, + uint _memberRoleToVote, + uint _categoryABReq + ) + internal + { + uint[] memory allowedToCreateProposal = new uint[](1); + uint[] memory stakeIncentive = new uint[](4); + if (_memberRoleToVote == 3) { + allowedToCreateProposal[0] = 3; + } else { + allowedToCreateProposal[0] = 2; + } + stakeIncentive[0] = 0; + stakeIncentive[1] = 0; + stakeIncentive[2] = _categoryABReq; + if (_quorumPerc == 0) {//For special resolutions + stakeIncentive[3] = 1; + } else { + stakeIncentive[3] = 0; + } + _addCategory( + _name, + _memberRoleToVote, + _majorityVotePerc, + _quorumPerc, + allowedToCreateProposal, + 604800, + _actionHash, + address(0), + _contractName, + stakeIncentive + ); + } + +} \ No newline at end of file diff --git a/migrations/3_deploy_nexusmutual.js b/migrations/3_deploy_nexusmutual.js index d3dda9d79f..9d9186328e 100644 --- a/migrations/3_deploy_nexusmutual.js +++ b/migrations/3_deploy_nexusmutual.js @@ -12,8 +12,9 @@ const Pool2 = artifacts.require('Pool2'); const PoolData = artifacts.require('PoolDataMock'); const Quotation = artifacts.require('Quotation'); const QuotationDataMock = artifacts.require('QuotationDataMock'); +const GovernanceMock = artifacts.require('GovernanceMock'); const Governance = artifacts.require('Governance'); -const ProposalCategory = artifacts.require('ProposalCategory'); +const ProposalCategory = artifacts.require('ProposalCategoryMock'); const MemberRoles = artifacts.require('MemberRoles'); const FactoryMock = artifacts.require('FactoryMock'); const DSValue = artifacts.require('NXMDSValueMock'); @@ -41,6 +42,7 @@ module.exports = function(deployer, network, accounts) { await deployer.deploy(Quotation); await deployer.deploy(QuotationDataMock, QE, founderAddress); await deployer.deploy(Governance); + await deployer.deploy(GovernanceMock); await deployer.deploy(ProposalCategory); await deployer.deploy(MemberRoles); await deployer.deploy(NXMaster, tk.address); diff --git a/migrations/4_initialize_nexusmutual.js b/migrations/4_initialize_nexusmutual.js index b6630bf3bc..d7fd39e698 100644 --- a/migrations/4_initialize_nexusmutual.js +++ b/migrations/4_initialize_nexusmutual.js @@ -15,8 +15,9 @@ const PoolData = artifacts.require('PoolDataMock'); const Quotation = artifacts.require('Quotation'); const QuotationDataMock = artifacts.require('QuotationDataMock'); const MemberRoles = artifacts.require('MemberRoles'); +const GovernanceMock = artifacts.require('GovernanceMock'); const Governance = artifacts.require('Governance'); -const ProposalCategory = artifacts.require('ProposalCategory'); +const ProposalCategory = artifacts.require('ProposalCategoryMock'); const FactoryMock = artifacts.require('FactoryMock'); const QE = '0x51042c4d8936a7764d18370a6a0762b860bb8e07'; @@ -42,7 +43,7 @@ module.exports = function(deployer, network, accounts) { const cd = await ClaimsData.deployed(); const mcr = await MCR.deployed(); const dsv = await DSValue.deployed(); - const gov = await Governance.deployed(); + const gov = await GovernanceMock.deployed(); let propCat = await ProposalCategory.deployed(); const mr = await MemberRoles.deployed(); const factory = await FactoryMock.deployed(); @@ -69,12 +70,16 @@ module.exports = function(deployer, network, accounts) { await nxms.addNewVersion(addr); let pcAddress = await nxms.getLatestAddress('0x5043'); pc = await ProposalCategory.at(pcAddress); + let gvAddress = await nxms.getLatestAddress('0x4756'); + let gv = await GovernanceMock.at(gvAddress); + gv._initiateGovernance(); await pc.proposalCategoryInitiate(); + await pc.updateCategoryActionHashes(); const dai = await DAI.deployed(); // await qd.changeCurrencyAssetAddress('0x444149', dai.address); // await qd.changeInvestmentAssetAddress('0x444149', dai.address); - await pl1.sendEther({ from: Owner, value: POOL_ETHER }); - await pl2.sendEther({ from: Owner, value: POOL_ETHER }); // + await pl1.sendEther({from: Owner, value: POOL_ETHER}); + await pl2.sendEther({from: Owner, value: POOL_ETHER}); // await mcr.addMCRData( 13000, '100000000000000000000', diff --git a/test/01_NXMaster.test.js b/test/01_NXMaster.test.js index 54b8aa9408..cc40ade5c7 100644 --- a/test/01_NXMaster.test.js +++ b/test/01_NXMaster.test.js @@ -22,11 +22,13 @@ const FactoryMock = artifacts.require('FactoryMock'); const QE = '0xb24919181daead6635e613576ca11c5aa5a4e133'; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; -const Exchange_0x = web3.eth.accounts[17]; +var Exchange_0x; -const { ether, toHex, toWei } = require('./utils/ethTools'); -const { assertRevert } = require('./utils/assertRevert'); +const {ether, toHex, toWei} = require('./utils/ethTools'); +const {assertRevert} = require('./utils/assertRevert'); const gvProp = require('./utils/gvProposal.js').gvProposal; +const setTriggerActionTime = require('./utils/gvProposal.js') + .setTriggerActionTime; const encode = require('./utils/encoder.js').encode; const getValue = require('./utils/getMCRPerThreshold.js').getValue; @@ -35,6 +37,8 @@ require('chai') .use(require('chai-bignumber')(BigNumber)) .should(); +let accounts = []; + let nxms; let nxmtk; let tf; @@ -70,9 +74,22 @@ contract('NXMaster', function([ govVoter4 ]) { // const fee = ether(0.002); + accounts = [ + owner, + newOwner, + member, + nonMember, + anotherAccount, + govVoter1, + govVoter2, + govVoter3, + govVoter4 + ]; + Exchange_0x = accounts[17]; + const fee = toWei(0.002); const poolEther = ether(2); - const founderAddress = web3.eth.accounts[19]; + const founderAddress = accounts[19]; const INITIAL_SUPPLY = ether(1500000); const pauseTime = new web3.utils.BN(2419200); @@ -176,7 +193,7 @@ contract('NXMaster', function([ await nxms.getLatestAddress(toHex('MR')) ); let oldGv = await Governance.at(await nxms.getLatestAddress(toHex('GV'))); - await pl1.sendEther({ from: owner, value: poolEther }); + await pl1.sendEther({from: owner, value: poolEther}); let actionHash = encode( 'updateOwnerParameters(bytes8,address)', 'OWNER', @@ -256,13 +273,13 @@ contract('NXMaster', function([ describe('when called by unauthorised source', function() { it('1.9 should not be able to add a new version', async function() { - await assertRevert(nxms.addNewVersion(addr, { from: anotherAccount })); + await assertRevert(nxms.addNewVersion(addr, {from: anotherAccount})); }); it('1.10 should not be able to change master address', async function() { newMaster = await NXMaster.new(nxmtk.address); await assertRevert( - nxms.changeMasterAddress(newMaster.address, { from: anotherAccount }) + nxms.changeMasterAddress(newMaster.address, {from: anotherAccount}) ); }); }); @@ -285,7 +302,7 @@ contract('NXMaster', function([ isInternal.should.equal(false); }); it('1.16 should return true if member', async function() { - await memberRoles.payJoiningFee(member, { from: member, value: fee }); + await memberRoles.payJoiningFee(member, {from: member, value: fee}); await memberRoles.kycVerdict(member, true); const isMember = await nxms.isMember(member); isMember.should.equal(true); @@ -321,7 +338,7 @@ contract('NXMaster', function([ // const updatePauseTime = pauseTime.addn(new web3.utils.BN(60)); const updatePauseTime = pauseTime.toNumber() + 60; await assertRevert( - nxms.updatePauseTime(updatePauseTime, { from: newOwner }) + nxms.updatePauseTime(updatePauseTime, {from: newOwner}) ); let pauseTime1 = await nxms.getPauseTime(); updatePauseTime.should.be.not.equal(pauseTime1.toNumber()); diff --git a/test/12_Pool.test.js b/test/12_Pool.test.js index 55f0eb1a56..97660ae86d 100644 --- a/test/12_Pool.test.js +++ b/test/12_Pool.test.js @@ -16,11 +16,11 @@ const MKR = artifacts.require('MockMKR'); const Governance = artifacts.require('Governance'); const FactoryMock = artifacts.require('FactoryMock'); -const { advanceBlock } = require('./utils/advanceToBlock'); -const { assertRevert } = require('./utils/assertRevert'); -const { ether, toHex, toWei } = require('./utils/ethTools'); -const { increaseTimeTo, duration } = require('./utils/increaseTime'); -const { latestTime } = require('./utils/latestTime'); +const {advanceBlock} = require('./utils/advanceToBlock'); +const {assertRevert} = require('./utils/assertRevert'); +const {ether, toHex, toWei} = require('./utils/ethTools'); +const {increaseTimeTo, duration} = require('./utils/increaseTime'); +const {latestTime} = require('./utils/latestTime'); const encode = require('./utils/encoder.js').encode; const gvProp = require('./utils/gvProposal.js').gvProposal; const getQuoteValues = require('./utils/getQuote.js').getQuoteValues; @@ -115,25 +115,25 @@ contract('Pool', function([ // await mr.payJoiningFee(owner, { from: owner, value: fee }); // await mr.kycVerdict(owner, true); - await mr.payJoiningFee(member1, { from: member1, value: fee }); + await mr.payJoiningFee(member1, {from: member1, value: fee}); await mr.kycVerdict(member1, true); - await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member1 }); + await tk.approve(tc.address, UNLIMITED_ALLOWANCE, {from: member1}); await tk.transfer(member1, tokens); - await tf.addStake(smartConAdd, stakeTokens, { from: member1 }); + await tf.addStake(smartConAdd, stakeTokens, {from: member1}); - await mr.payJoiningFee(member2, { from: member2, value: fee }); + await mr.payJoiningFee(member2, {from: member2, value: fee}); await mr.kycVerdict(member2, true); - await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member2 }); + await tk.approve(tc.address, UNLIMITED_ALLOWANCE, {from: member2}); await tk.transfer(member2, tokens); - await mr.payJoiningFee(member3, { from: member3, value: fee }); + await mr.payJoiningFee(member3, {from: member3, value: fee}); await mr.kycVerdict(member3, true); - await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member3 }); + await tk.approve(tc.address, UNLIMITED_ALLOWANCE, {from: member3}); await tk.transfer(member3, tokens); - await mr.payJoiningFee(member4, { from: member4, value: fee }); + await mr.payJoiningFee(member4, {from: member4, value: fee}); await mr.kycVerdict(member4, true); - await tk.approve(tc.address, UNLIMITED_ALLOWANCE, { from: member4 }); + await tk.approve(tc.address, UNLIMITED_ALLOWANCE, {from: member4}); await tk.transfer(member4, tokens); }); @@ -147,7 +147,7 @@ contract('Pool', function([ }); it('12.13 should not be able to change master address', async function() { await assertRevert( - pd.changeMasterAddress(pd.address, { from: notOwner }) + pd.changeMasterAddress(pd.address, {from: notOwner}) ); }); }); @@ -180,7 +180,7 @@ contract('Pool', function([ it('12.17 should not be able to change UniswapFactoryAddress directly', async function() { await assertRevert( - p2.changeUniswapFactoryAddress(pd.address, { from: notOwner }) + p2.changeUniswapFactoryAddress(pd.address, {from: notOwner}) ); }); @@ -213,7 +213,7 @@ contract('Pool', function([ await p1.upgradeInvestmentPool(cad.address); await tf.transferCurrencyAsset(toHex('DAI'), owner, toWei(5)); await tf.transferCurrencyAsset(toHex('ETH'), owner, toWei(5)); - await p1.sendEther({ from: owner, value: toWei(20) }); + await p1.sendEther({from: owner, value: toWei(20)}); await cad.transfer(p1.address, toWei(20)); await p2.saveIADetails( @@ -273,7 +273,7 @@ contract('Pool', function([ await pd.getInvestmentAssetAddress(toHex('DAI')) ); emock = await exchangeMock.at(exchangeDAI); - await emock.sendEther({ from: notOwner, value: toWei(2000) }); + await emock.sendEther({from: notOwner, value: toWei(2000)}); await cad.transfer(emock.address, toWei(200000)); let time = await latestTime(); await increaseTimeTo( @@ -495,7 +495,7 @@ contract('Pool', function([ }); it('12.38 ELT(DAI->DAI)', async function() { - await p2.sendEther({ from: owner, value: toWei(3) }); + await p2.sendEther({from: owner, value: toWei(3)}); await p2.saveIADetails( ['0x455448', '0x444149'], [100, 1000], @@ -682,7 +682,7 @@ contract('Pool', function([ }); it('12.41 ILT DAI to ETH', async function() { - await p2.sendEther({ from: owner, value: toWei(5) }); + await p2.sendEther({from: owner, value: toWei(5)}); await tf.transferCurrencyAsset(toHex('DAI'), owner, toWei(5)); let ICABalE; let ICABalD; @@ -753,9 +753,9 @@ contract('Pool', function([ it('12.42 ELT(ETH->ETH)', async function() { let ICABalE2 = await web3.eth.getBalance(p2.address); let ICABalD2 = await cad.balanceOf(p2.address); - await p1.sendEther({ from: owner, value: toWei(5) }); + await p1.sendEther({from: owner, value: toWei(5)}); await p1.upgradeInvestmentPool(cad.address); - await p2.sendEther({ from: owner, value: CABalE2 / 1 - toWei(5) }); + await p2.sendEther({from: owner, value: CABalE2 / 1 - toWei(5)}); await cad.transfer(p2.address, CABalD2); let ICABalE; let ICABalD; @@ -812,7 +812,7 @@ contract('Pool', function([ }); it('12.43 ILT ETH to DAI', async function() { - await cad.transfer(p2.address, toWei(50), { from: owner }); + await cad.transfer(p2.address, toWei(50), {from: owner}); await tf.transferCurrencyAsset(toHex('ETH'), owner, toWei(5)); let ICABalE; let ICABalD; @@ -879,7 +879,7 @@ contract('Pool', function([ it('12.44 RBT DAI to ETH amount > price slippage', async function() { await emock.removeEther(toWei(2087.96)); - await cad.transfer(p2.address, toWei(50), { from: owner }); + await cad.transfer(p2.address, toWei(50), {from: owner}); let ICABalE; let ICABalD; let ICABalE2; @@ -921,9 +921,9 @@ contract('Pool', function([ let ICABalD; let ICABalE2; let ICABalD2; - await p1.sendEther({ from: owner, value: toWei(5) }); + await p1.sendEther({from: owner, value: toWei(5)}); await p1.upgradeInvestmentPool(cad.address); - await p2.sendEther({ from: owner, value: CABalE2 }); + await p2.sendEther({from: owner, value: CABalE2}); await cad.transfer(p2.address, (CABalD2 / 1 - toWei(50)).toString()); await p2.saveIADetails( ['0x455448', '0x444149'], @@ -979,7 +979,7 @@ contract('Pool', function([ let ICABalD; let ICABalE2; let ICABalD2; - await p1.sendEther({ from: owner, value: toWei(10) }); + await p1.sendEther({from: owner, value: toWei(10)}); await p2.saveIADetails( ['0x455448', '0x444149'], [100, 1000], @@ -1021,12 +1021,12 @@ contract('Pool', function([ ICABalE2 = await web3.eth.getBalance(p2.address); ICABalD = await cad.balanceOf(p1.address); ICABalD2 = await cad.balanceOf(p2.address); - await emock.sendEther({ from: owner, value: 17400000000000000 }); + await emock.sendEther({from: owner, value: 17400000000000000}); await p1.upgradeInvestmentPool(cad.address); - await p2.sendEther({ from: owner, value: CABalE2 / 1 - toWei(5) }); + await p2.sendEther({from: owner, value: CABalE2 / 1 - toWei(5)}); await cad.transfer(p2.address, CABalD2); await tf.transferCurrencyAsset(toHex('ETH'), owner, toWei(10)); - await cad.transfer(p1.address, toWei(10), { from: owner }); + await cad.transfer(p1.address, toWei(10), {from: owner}); await p2.saveIADetails( ['0x455448', '0x444149'], [100, 1000], @@ -1068,7 +1068,7 @@ contract('Pool', function([ ICABalD2 = await cad.balanceOf(p2.address); await tf.transferCurrencyAsset(toHex('ETH'), owner, toWei(3)); await tf.transferCurrencyAsset(toHex('DAI'), owner, toWei(5)); - await cad.transfer(p2.address, toWei(5), { from: owner }); + await cad.transfer(p2.address, toWei(5), {from: owner}); await p2.saveIADetails( ['0x455448', '0x444149'], [100, 1000], @@ -1111,7 +1111,7 @@ contract('Pool', function([ }); it('12.49 ILT(DAI->ETH) amount > price slippage', async function() { await emock.removeEther(toWei(1.52)); - await p2.sendEther({ from: owner, value: toWei(5) }); + await p2.sendEther({from: owner, value: toWei(5)}); await p1.sendEther({ from: owner, value: ( @@ -1167,10 +1167,10 @@ contract('Pool', function([ .should.be.equal(((ICABalD2 / toWei(1)) * 1).toFixed(0).toString()); }); it('12.50 ILT(ETH->DAI) IA dont have enough amount', async function() { - await emock.sendEther({ from: owner, value: toWei(50000) }); + await emock.sendEther({from: owner, value: toWei(50000)}); await p1.upgradeInvestmentPool(cad.address); - await p2.sendEther({ from: owner, value: CABalE2 / 1 - toWei(5) }); + await p2.sendEther({from: owner, value: CABalE2 / 1 - toWei(5)}); await cad.transfer(p2.address, CABalD2); await pd.changeCurrencyAssetBaseMin(toHex('ETH'), toWei(11)); @@ -1315,7 +1315,7 @@ contract('Pool', function([ .should.be.equal((ICABalD2 / toWei(1)).toFixed(0).toString()); }); it('12.53 Initial ILT(DAI->ETH) but at time of call back ILT(DAI->DAI)', async function() { - await p2.sendEther({ from: owner, value: toWei(5) }); + await p2.sendEther({from: owner, value: toWei(5)}); await p2.saveIADetails( ['0x455448', '0x444149'], [100, 1000], @@ -1330,9 +1330,9 @@ contract('Pool', function([ let p2ETH = await web3.eth.getBalance(p2.address); let p2DAI = await cad.balanceOf(p2.address); await p1.upgradeInvestmentPool(cad.address); - await p2.sendEther({ from: owner, value: p2ETH / 1 - toWei(5) * 1 }); + await p2.sendEther({from: owner, value: p2ETH / 1 - toWei(5) * 1}); await cad.transfer(p2.address, p2DAI); - await cad.transfer(p2.address, toWei(30), { from: owner }); + await cad.transfer(p2.address, toWei(30), {from: owner}); await p2.saveIADetails( ['0x455448', '0x444149'], [100, 1000], @@ -1405,7 +1405,7 @@ contract('Pool', function([ vrsdata[0], vrsdata[1], vrsdata[2], - { from: member1 } + {from: member1} ); coverID = await qd.getAllCoversOfUser(member1); @@ -1453,13 +1453,14 @@ contract('Pool', function([ await tk.transfer(member2, toWei(75000)); await tk.transfer(member3, toWei(75000)); await tk.transfer(member4, toWei(75000)); - await gv.submitVote(pId, 1, { from: member1 }); - await gv.submitVote(pId, 1, { from: member2 }); - await gv.submitVote(pId, 1, { from: member3 }); - await gv.submitVote(pId, 1, { from: member4 }); + await gv.submitVote(pId, 1, {from: member1}); + await gv.submitVote(pId, 1, {from: member2}); + await gv.submitVote(pId, 1, {from: member3}); + await gv.submitVote(pId, 1, {from: member4}); let time = await latestTime(); await increaseTimeTo(time + 604800); await gv.closeProposal(pId); + await gv.triggerAction(pId); let newAssetAdd = await pd.getInvestmentAssetAddress(toHex('MKR')); newAssetAdd.should.be.equal(mkr.address); await p2.saveIADetails( @@ -1473,7 +1474,7 @@ contract('Pool', function([ }); it('12.57 ELT(DAI->MKR)', async function() { await pd.changeCurrencyAssetBaseMin('0x444149', toWei(15)); - await p2.sendEther({ from: owner, value: toWei(5) }); + await p2.sendEther({from: owner, value: toWei(5)}); await p2.saveIADetails( ['0x455448', '0x444149', '0x4d4b52'], [100, 1000, 500], @@ -1618,10 +1619,10 @@ contract('Pool', function([ it('12.59 ILT(DAI->MKR) IA dont have enough amount', async function() { let emockM = await fac.getExchange(mkr.address); emock = await exchangeMock.at(emockM); - await emock.sendEther({ from: owner, value: toWei(1300) }); + await emock.sendEther({from: owner, value: toWei(1300)}); let emockD = await fac.getExchange(cad.address); let emockDAI = await exchangeMock.at(emockD); - await emockDAI.sendEther({ from: owner, value: toWei(1300) }); + await emockDAI.sendEther({from: owner, value: toWei(1300)}); await pd.changeCurrencyAssetBaseMin('0x444149', toWei(66)); await p1.upgradeInvestmentPool(cad.address); await cad.transfer(p2.address, CABalD2); @@ -1685,8 +1686,8 @@ contract('Pool', function([ ); emockDAI = await exchangeMock.at(emockD); emockDAI.removeEther(await web3.eth.getBalance(emockDAI.address)); - await emockDAI.sendEther({ from: owner, value: toWei(80) }); - await emock.sendEther({ from: owner, value: toWei(75) }); + await emockDAI.sendEther({from: owner, value: toWei(80)}); + await emock.sendEther({from: owner, value: toWei(75)}); await tf.transferCurrencyAsset(toHex('DAI'), owner, toWei(12.5)); await mkr.transfer(p2.address, toWei(50)); await p2.saveIADetails( @@ -1747,11 +1748,11 @@ contract('Pool', function([ it('12.61 ELT(DAI->MKR) amount > price slippage', async function() { await pd.changeCurrencyAssetBaseMin('0x444149', toWei(6)); await p1.upgradeInvestmentPool(cad.address); - await p2.sendEther({ from: owner, value: CABalE2 }); + await p2.sendEther({from: owner, value: CABalE2}); await cad.transfer(p2.address, CABalD2); await mkr.transfer(p2.address, (CABalM / 1 - toWei(30)).toString()); - await p2.sendEther({ from: owner, value: toWei(10) }); - await emock.sendEther({ from: owner, value: toWei(3) }); + await p2.sendEther({from: owner, value: toWei(10)}); + await emock.sendEther({from: owner, value: toWei(3)}); await p2.saveIADetails( ['0x455448', '0x444149', '0x4d4b52'], [100, 1000, 500], @@ -1934,24 +1935,25 @@ contract('Pool', function([ from: member1 } ); - await gv.categorizeProposal(pId, 14, 0); + await gv.categorizeProposal(pId, 15, 0); let actionHash = encode( 'changeInvestmentAssetStatus(bytes4,bool)', - '0x444149', + toHex('DAI'), false ); await gv.submitProposalWithSolution(pId, 'Inactive IA', actionHash, { from: member1 }); - await gv.submitVote(pId, 1, { from: member1 }); - await gv.submitVote(pId, 1, { from: member2 }); - await gv.submitVote(pId, 1, { from: member3 }); - await gv.submitVote(pId, 1, { from: member4 }); + await gv.submitVote(pId, 1, {from: member1}); + await gv.submitVote(pId, 1, {from: member2}); + await gv.submitVote(pId, 1, {from: member3}); + await gv.submitVote(pId, 1, {from: member4}); let time = await latestTime(); await increaseTimeTo(time + 604800); await gv.closeProposal(pId); + await gv.triggerAction(pId); (await pd.getInvestmentAssetStatus(toHex('DAI'))).should.be.equal(false); - await p1.sendEther({ from: owner, value: toWei(2) }); + await p1.sendEther({from: owner, value: toWei(2)}); await p2.saveIADetails( ['0x444149', '0x455448'], [100, 15517], @@ -1974,7 +1976,7 @@ contract('Pool', function([ from: member1 } ); - await gv.categorizeProposal(pId, 13, 0); + await gv.categorizeProposal(pId, 14, 0); let actionHash = encode( 'changeInvestmentAssetHoldingPerc(bytes4,uint64,uint64)', '0x444149', @@ -1989,13 +1991,14 @@ contract('Pool', function([ from: member1 } ); - await gv.submitVote(pId, 1, { from: member1 }); - await gv.submitVote(pId, 1, { from: member2 }); - await gv.submitVote(pId, 1, { from: member3 }); - await gv.submitVote(pId, 1, { from: member4 }); + await gv.submitVote(pId, 1, {from: member1}); + await gv.submitVote(pId, 1, {from: member2}); + await gv.submitVote(pId, 1, {from: member3}); + await gv.submitVote(pId, 1, {from: member4}); let time = await latestTime(); await increaseTimeTo(time + 604800); await gv.closeProposal(pId); + await gv.triggerAction(pId); let initialPerc = await pd.getInvestmentAssetHoldingPerc(toHex('DAI')); (initialPerc[0] / 1).should.be.equal(100); (initialPerc[1] / 1).should.be.equal(1000); @@ -2027,13 +2030,14 @@ contract('Pool', function([ await gv.submitProposalWithSolution(pId, 'add CA', actionHash, { from: member1 }); - await gv.submitVote(pId, 1, { from: member1 }); - await gv.submitVote(pId, 1, { from: member2 }); - await gv.submitVote(pId, 1, { from: member3 }); - await gv.submitVote(pId, 1, { from: member4 }); + await gv.submitVote(pId, 1, {from: member1}); + await gv.submitVote(pId, 1, {from: member2}); + await gv.submitVote(pId, 1, {from: member3}); + await gv.submitVote(pId, 1, {from: member4}); let time = await latestTime(); await increaseTimeTo(time + 604800); await gv.closeProposal(pId); + await gv.triggerAction(pId); let varbase = await pd.getCurrencyAssetVarBase(toHex('MKR')); (varbase[1] / 1).should.be.equal(toWei(10) * 1); (varbase[2] / 1).should.be.equal(0); diff --git a/test/14_ProposalCategory.test.js b/test/14_ProposalCategory.test.js index 4664e30204..6df9fd323d 100644 --- a/test/14_ProposalCategory.test.js +++ b/test/14_ProposalCategory.test.js @@ -3,14 +3,18 @@ const Governance = artifacts.require('Governance'); const ProposalCategory = artifacts.require('ProposalCategory'); const NXMaster = artifacts.require('NXMaster'); const TokenFunctions = artifacts.require('TokenFunctionMock'); +const PoolData = artifacts.require('PoolDataMock'); const assertRevert = require('./utils/assertRevert').assertRevert; -const { toHex, toWei } = require('./utils/ethTools'); +const {toHex, toWei} = require('./utils/ethTools'); +const gvProposal = require('./utils/gvProposal.js').gvProposal; +const {encode, encode1} = require('./utils/encoder.js'); +const increaseTime = require('./utils/increaseTime.js').increaseTime; let pc; let gv; let tf; let mr; +let pd; let nullAddress = '0x0000000000000000000000000000000000000000'; -const encode = require('./utils/encoder.js').encode; contract('Proposal Category', function([owner, other]) { before(async function() { @@ -22,6 +26,7 @@ contract('Proposal Category', function([owner, other]) { tf = await TokenFunctions.deployed(); address = await nxms.getLatestAddress(toHex('MR')); mr = await MemberRoles.at(address); + pd = await PoolData.deployed(); }); it('14.1 Should be initialized', async function() { @@ -36,7 +41,7 @@ contract('Proposal Category', function([owner, other]) { }); it('14.2 should not allow unauthorized to change master address', async function() { - await assertRevert(pc.changeMasterAddress(nxms.address, { from: other })); + await assertRevert(pc.changeMasterAddress(nxms.address, {from: other})); }); it('14.3 Should not add a proposal category if member roles are invalid', async function() { @@ -210,4 +215,572 @@ contract('Proposal Category', function([owner, other]) { let cat2 = await pc.category(c1); assert.notEqual(cat1[1], cat2[1], 'category not updated'); }); + + it('Add new category with no action hash and contract address as master, category should not be created', async function() { + //externalLiquidityTrade + let actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'external Liquidity Trade', + 2, + 75, + 75, + [2], + 604800, + 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', + pd.address, + toHex('PD'), + [0, 0, 0, 1], + 'externalLiquidityTrade()' + ] + ); + let categoryLengthOld = (await pc.totalCategories()).toNumber(); + pId = (await gv.getProposalLength()).toNumber(); + await gvProposal(3, actionHash, mr, gv, 1); + let categoryLengthNew = (await pc.totalCategories()).toNumber(); + assert.equal(categoryLengthNew, categoryLengthOld + 1); + await increaseTime(604800); + + actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'Test', + 1, + 60, + 15, + [2], + 604800, + '', + nullAddress, + toHex('MS'), + [0, 0, 0, 0], + '' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(3, actionHash, mr, gv, 1); + assert.equal((await gv.proposalActionStatus(pId)).toNumber(), 1); + categoryLengthNew = (await pc.totalCategories()).toNumber(); + assert.equal(categoryLengthNew, categoryLengthOld); + }); + + it('Edit category with no action hash and contract address as master, category should not be updated', async function() { + actionHash = encode1( + [ + 'uint256', + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 2, + 'Test', + 1, + 65, + 15, + [2], + 604800, + '', + nullAddress, + toHex('MS'), + [0, 0, 0, 0], + '' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(4, actionHash, mr, gv, 1); + let category = await pc.category(2); + assert.equal((await gv.proposalActionStatus(pId)).toNumber(), 1); + assert.notEqual(category[2].toNumber(), 65, 'Category updated'); + }); + + it('Edit category with invalid member roles, category should not be updated', async function() { + actionHash = encode1( + [ + 'uint256', + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 2, + 'Test', + 6, + 54, + 15, + [5], + 604800, + '', + nullAddress, + toHex('EX'), + [0, 0, 0, 0], + '' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(4, actionHash, mr, gv, 1); + let category = await pc.category(2); + assert.equal((await gv.proposalActionStatus(pId)).toNumber(), 1); + assert.notEqual(category[2].toNumber(), 65, 'Category updated'); + }); + + it('Edit category with with special resolution and AB vote, category should not be updated', async function() { + actionHash = encode1( + [ + 'uint256', + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 2, + 'Test', + 1, + 54, + 15, + [2], + 604800, + '', + nullAddress, + toHex('EX'), + [0, 0, 0, 1], + '' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(4, actionHash, mr, gv, 1); + let category = await pc.category(2); + assert.equal((await gv.proposalActionStatus(pId)).toNumber(), 1); + assert.notEqual(category[2].toNumber(), 65, 'Category updated'); + }); + + it('Edit category with with invalid special resolution flag, category should not be updated', async function() { + actionHash = encode1( + [ + 'uint256', + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 2, + 'Test', + 2, + 54, + 15, + [2], + 604800, + '', + nullAddress, + toHex('EX'), + [0, 0, 0, 4], + '' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(4, actionHash, mr, gv, 1); + let category = await pc.category(2); + assert.equal((await gv.proposalActionStatus(pId)).toNumber(), 1); + assert.notEqual(category[2].toNumber(), 65, 'Category updated'); + }); + + it('Add category with valid action data and invalid votepercent, category should not be added', async function() { + let categoryId = await pc.totalCategories(); + actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'New external Liquidity Trade', + 2, + 124, + 75, + [2], + 604800, + 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', + pd.address, + toHex('PD'), + [0, 0, 0, 0], + 'externalLiquidityTrade()' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(3, actionHash, mr, gv, 1); + let proposalActionStatus = await gv.proposalActionStatus(pId); + assert.equal(proposalActionStatus.toNumber(), 1, 'Action executed'); + }); + + it('Add category with valid action data and invalid member roles, category should not be added', async function() { + let categoryId = await pc.totalCategories(); + actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'New external Liquidity Trade', + 1, + 75, + 75, + [9], + 604800, + 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', + pd.address, + toHex('PD'), + [0, 0, 0, 0], + 'externalLiquidityTrade()' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(3, actionHash, mr, gv, 1); + let proposalActionStatus = await gv.proposalActionStatus(pId); + assert.equal(proposalActionStatus.toNumber(), 1, 'Action executed'); + }); + + it('Add category with special resolution and AB vote, category should not be added', async function() { + let categoryId = await pc.totalCategories(); + actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'New external Liquidity Trade', + 1, + 75, + 75, + [2], + 604800, + 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', + pd.address, + toHex('PD'), + [0, 0, 0, 1], + 'externalLiquidityTrade()' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(3, actionHash, mr, gv, 1); + let proposalActionStatus = await gv.proposalActionStatus(pId); + assert.equal(proposalActionStatus.toNumber(), 1, 'Action executed'); + }); + + it('Add category with invalid special resolution flag , category should not be added', async function() { + let categoryId = await pc.totalCategories(); + actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'New external Liquidity Trade', + 2, + 75, + 75, + [2], + 604800, + 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', + pd.address, + toHex('PD'), + [0, 0, 0, 4], + 'externalLiquidityTrade()' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(3, actionHash, mr, gv, 1); + let proposalActionStatus = await gv.proposalActionStatus(pId); + assert.equal(proposalActionStatus.toNumber(), 1, 'Action executed'); + }); + + it('Edit category with valid action data and invalid votepercent, category should be not updated', async function() { + let categoryId = 33; + actionHash = encode1( + [ + 'uint256', + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + categoryId, + 'external Liquidity Trade', + 2, + 124, + 75, + [2], + 604800, + 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', + pd.address, + toHex('PD'), + [0, 0, 0, 1], + 'externalLiquidityTrade()' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(4, actionHash, mr, gv, 1); + let category = await pc.category(categoryId); + assert.notEqual(category[2].toNumber(), 124, 'Category updated'); + }); + + it('Edit category with valid action hash and contract name, category should be updated', async function() { + let categoryId = 33; + actionHash = encode1( + [ + 'uint256', + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + categoryId, + 'external Liquidity Trade', + 2, + 68, + 75, + [2], + 604800, + 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', + gv.address, + toHex('EX'), + [0, 0, 0, 1], + 'changeDependentContractAddress()' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(4, actionHash, mr, gv, 1); + let category = await pc.category(categoryId); + let proposalActionStatus = await gv.proposalActionStatus(pId); + assert.equal(proposalActionStatus.toNumber(), 3, 'Action not executed'); + }); + + it('Create proposal in category with external address', async function() { + let categoryId = 33; + pId = (await gv.getProposalLength()).toNumber(); + await gvProposal(categoryId, actionHash, mr, gv, 2); + assert.equal((await gv.proposalActionStatus(pId)).toNumber(), 3); + }); + + it('Edit category with no hash and no contract name, category should be updated', async function() { + let categoryId = 33; + actionHash = encode1( + [ + 'uint256', + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + categoryId, + 'external Liquidity Trade', + 2, + 67, + 75, + [2], + 604800, + '', + nullAddress, + toHex('EX'), + [0, 0, 0, 1], + '' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(4, actionHash, mr, gv, 1); + let category = await pc.category(categoryId); + let proposalActionStatus = await gv.proposalActionStatus(pId); + assert.equal(proposalActionStatus.toNumber(), 3, 'Action not executed'); + }); + + it('Create proposal in category with no action, submit action, should revert', async function() { + let categoryId = 33; + actionHash = encode( + 'upgradeContractImplementation(bytes2,address)', + toHex('GV'), + gv.address + ); + pId = (await gv.getProposalLength()).toNumber(); + await gv.createProposal('Proposal2', 'Proposal2', 'Proposal2', 0); + await gv.categorizeProposal(pId, categoryId, 0); + await assertRevert( + gv.submitProposalWithSolution(pId, 'Upgrade', actionHash) + ); + await gv.submitProposalWithSolution(pId, 'Upgrade', '0x'); + let members = await mr.members(2); + let iteration = 0; + for (iteration = 0; iteration < members[1].length; iteration++) + await gv.submitVote(pId, 1, { + from: members[1][iteration] + }); + + await increaseTime(604800); + await gv.closeProposal(pId); + await assertRevert(gv.rejectAction(pId)); + await increaseTime(86500); + await assertRevert(gv.triggerAction(pId)); + }); + + it('Add category with no action hash and valid data, category should be added', async function() { + let categoryId = await pc.totalCategories(); + actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'Test', + 2, + 75, + 75, + [2], + 604800, + '', + nullAddress, + toHex('EX'), + [0, 0, 0, 0], + '' + ] + ); + pId = (await gv.getProposalLength()).toNumber(); + categoryLengthOld = (await pc.totalCategories()).toNumber(); + await gvProposal(3, actionHash, mr, gv, 1); + let proposalActionStatus = await gv.proposalActionStatus(pId); + assert.equal(proposalActionStatus.toNumber(), 3, 'Action not executed'); + }); }); diff --git a/test/15_BasicGovernance.test.js b/test/15_BasicGovernance.test.js index 435c0d790c..907ac95c21 100644 --- a/test/15_BasicGovernance.test.js +++ b/test/15_BasicGovernance.test.js @@ -1,21 +1,27 @@ const Governance = artifacts.require('Governance'); +// const GovernanceNew = artifacts.require('GovernanceNew'); const MemberRoles = artifacts.require('MemberRoles'); const ProposalCategory = artifacts.require('ProposalCategory'); +// const ProposalCategoryNew = artifacts.require('ProposalCategoryNew'); +const OwnedUpgradeabilityProxy = artifacts.require('OwnedUpgradeabilityProxy'); const TokenController = artifacts.require('TokenController'); const NXMaster = artifacts.require('NXMaster'); const ClaimsReward = artifacts.require('ClaimsReward'); const NXMToken = artifacts.require('NXMToken'); const TokenData = artifacts.require('TokenDataMock'); +const PoolData = artifacts.require('PoolDataMock'); const assertRevert = require('./utils/assertRevert.js').assertRevert; const increaseTime = require('./utils/increaseTime.js').increaseTime; -const encode = require('./utils/encoder.js').encode; -const { toHex, toWei } = require('./utils/ethTools.js'); +const {encode, encode1} = require('./utils/encoder.js'); +const {toHex, toWei} = require('./utils/ethTools.js'); const expectEvent = require('./utils/expectEvent'); const gvProp = require('./utils/gvProposal.js').gvProposal; const Web3 = require('web3'); -const gvProposalWithIncentive = require('./utils/gvProposal.js') - .gvProposalWithIncentive; -const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); +const { + gvProposalWithIncentive, + gvProposalWithoutTrigger, + setTriggerActionTime +} = require('./utils/gvProposal.js'); const AdvisoryBoard = '0x41420000'; let maxAllowance = '115792089237316195423570985008687907853269984665640564039457584007913129639935'; @@ -30,6 +36,8 @@ let mr; let nxmToken; let tc; let td; +let pd; +let accounts = []; contract( 'Governance', @@ -48,6 +56,20 @@ contract( notMember ]) => { before(async function() { + accounts = [ + ab1, + ab2, + ab3, + ab4, + mem1, + mem2, + mem3, + mem4, + mem5, + mem6, + mem7, + notMember + ]; nxms = await NXMaster.deployed(); cr = await ClaimsReward.deployed(); nxmToken = await NXMToken.deployed(); @@ -59,33 +81,42 @@ contract( mr = await MemberRoles.at(address); tc = await TokenController.deployed(); td = await TokenData.deployed(); + pd = await PoolData.deployed(); + await nxmToken.approve(tc.address, maxAllowance); + //To cover functions in govblocks interface, which are not implemented by NexusMutual await gv.addSolution(0, '', '0x'); await gv.openProposalForVoting(0); await gv.pauseProposal(0); await gv.resumeProposal(0); - // - // await mr.payJoiningFee(ab1, { value: 2000000000000000 }); - // await mr.kycVerdict(ab1, true); + + actionHash = encode1(['bytes8', 'uint256'], [toHex('ACWT'), 0]); + pId = (await gv.getProposalLength()).toNumber(); + await gvProp(22, actionHash, mr, gv, 2); + await increaseTime(86500); + await assertRevert(gv.rejectAction(pId)); + await gv.setInitialActionParameters(); + await assertRevert(gv.setInitialActionParameters()); + await setTriggerActionTime(mr, gv); }); - it('15.1 Should be able to change tokenHoldingTime manually', async function() { + it('Should be able to change tokenHoldingTime manually', async function() { await assertRevert(gv.updateUintParameters(toHex('GOVHOLD'), 3000)); }); - it('15.2 Only Advisory Board members are authorized to categorize proposal', async function() { + it('Only Advisory Board members are authorized to categorize proposal', async function() { let allowedToCategorize = await gv.allowedToCatgorize(); assert.equal(allowedToCategorize.toNumber(), 1); }); - it('15.3 Should not allow unauthorized to change master address', async function() { + it('Should not allow unauthorized to change master address', async function() { await assertRevert( - gv.changeMasterAddress(nxms.address, { from: notMember }) + gv.changeMasterAddress(nxms.address, {from: notMember}) ); await gv.changeDependentContractAddress(); }); - it('15.4 Should not allow unauthorized to create proposal', async function() { + it('Should not allow unauthorized to create proposal', async function() { await assertRevert( gv.createProposal('Proposal', 'Description', 'Hash', 0, { from: notMember @@ -99,7 +130,20 @@ contract( 9, '', '0x', - { from: notMember } + {from: notMember} + ) + ); + }); + + it('Should not allow to create proposal with solution with category id zero', async function() { + await assertRevert( + gv.createProposalwithSolution( + 'Add new member', + 'Add new member', + 'hash', + 0, + '', + '0x' ) ); }); @@ -108,7 +152,7 @@ contract( await assertRevert(mr.addInitialABMembers([ab2, ab3, ab4])); }); - it('15.5 Should create a proposal', async function() { + it('Should create a proposal', async function() { let propLength = await gv.getProposalLength(); proposalId = propLength.toNumber(); await gv.createProposal('Proposal1', 'Proposal1', 'Proposal1', 0); //Pid 1 @@ -120,25 +164,25 @@ contract( ); }); - it('15.6 Should not allow unauthorized person to categorize proposal', async function() { + it('Should not allow unauthorized person to categorize proposal', async function() { await assertRevert( - gv.categorizeProposal(proposalId, 1, 0, { from: notMember }) + gv.categorizeProposal(proposalId, 1, 0, {from: notMember}) ); }); - it('15.7 Should not categorize under invalid category', async function() { + it('Should not categorize under invalid category', async function() { await assertRevert(gv.categorizeProposal(proposalId, 0, 0)); await assertRevert(gv.categorizeProposal(proposalId, 35, 0)); }); - it('15.8 Should categorize proposal', async function() { + it('Should categorize proposal', async function() { await gv.categorizeProposal(proposalId, 1, 0); let proposalData = await gv.proposal(proposalId); assert.equal(proposalData[1].toNumber(), 1, 'Proposal not categorized'); }); - it('15.9 Should update proposal details', async function() { - let { logs } = await gv.updateProposal( + it('Should update proposal details', async function() { + let {logs} = await gv.updateProposal( proposalId, 'Addnewmember', 'AddnewmemberSD', @@ -146,18 +190,18 @@ contract( ); }); - it('15.10 Should reset proposal category', async function() { + it('Should reset proposal category', async function() { var proposalDataUpdated = await gv.proposal(proposalId); assert.equal(proposalDataUpdated[1].toNumber(), 0, 'Category not reset'); }); - it('15.11 Should not open proposal for voting before categorizing', async () => { + it('Should not open proposal for voting before categorizing', async () => { await assertRevert( gv.submitProposalWithSolution(proposalId, 'Addnewmember', '0x4d52') ); }); - it('15.12 Should allow only owner to open proposal for voting', async () => { + it('Should allow only owner to open proposal for voting', async () => { await gv.categorizeProposal(proposalId, 9, toWei(1)); await gv.proposal(proposalId); await pc.category(9); @@ -167,7 +211,7 @@ contract( proposalId, 'Addnewmember', '0xffa3992900000000000000000000000000000000000000000000000000000000000000004344000000000000000000000000000000000000000000000000000000000000', - { from: notMember } + {from: notMember} ) ); await gv.submitProposalWithSolution( @@ -178,7 +222,17 @@ contract( assert.equal((await gv.canCloseProposal(proposalId)).toNumber(), 0); }); - it('15.13 Should not update proposal if solution exists', async function() { + it('Should allow open proposal for voting only once', async () => { + await assertRevert( + gv.submitProposalWithSolution( + proposalId, + 'Addnewmember', + '0xffa3992900000000000000000000000000000000000000000000000000000000000000004344000000000000000000000000000000000000000000000000000000000000' + ) + ); + }); + + it('Should not update proposal if solution exists', async function() { await assertRevert(gv.categorizeProposal(proposalId, 2, toWei(1))); await assertRevert( gv.updateProposal( @@ -190,45 +244,57 @@ contract( ); }); - it('15.14 Should not allow voting for non existent solution', async () => { + it('Should not allow voting for non existent solution', async () => { await assertRevert(gv.submitVote(proposalId, 5)); }); - it('15.15 Should not allow unauthorized people to vote', async () => { - await assertRevert(gv.submitVote(proposalId, 1, { from: notMember })); + it('Should not allow unauthorized people to vote', async () => { + await assertRevert(gv.submitVote(proposalId, 1, {from: notMember})); }); - it('15.16 Should submit vote to valid solution', async function() { + it('Should submit vote to valid solution', async function() { await gv.submitVote(proposalId, 1); await gv.proposalDetails(proposalId); await assertRevert(gv.submitVote(proposalId, 1)); }); - // it('15.17 Should not claim reward for an open proposal', async function() { + // it('Should not claim reward for an open proposal', async function() { // await assertRevert(cr.claimAllPendingReward(20)); // }); - it('15.18 Should close proposal', async function() { + it('Should not trigger action before closing proposal', async function() { + await assertRevert(gv.triggerAction(proposalId)); + }); + + it('Should close proposal', async function() { let canClose = await gv.canCloseProposal(proposalId); assert.equal(canClose.toNumber(), 1); await gv.closeProposal(proposalId); }); - it('15.19 Should not close already closed proposal', async function() { + it('Should execute action for AB proposals', async function() { + assert.equal(await gv.proposalActionStatus(proposalId), 3); + }); + + it('Should not reject action after trigger action', async function() { + await assertRevert(gv.rejectAction(proposalId)); + }); + + it('Should not close already closed proposal', async function() { let canClose = await gv.canCloseProposal(proposalId); assert.equal(canClose.toNumber(), 2); await assertRevert(gv.closeProposal(proposalId)); }); - it('15.20 Should get rewards', async function() { + it('Should get rewards', async function() { let pendingRewards = await gv.getPendingReward(ab1); }); - it('15.21 Should claim reward only through claimRewards contract', async function() { + it('Should claim reward only through claimRewards contract', async function() { await assertRevert(gv.claimReward(ab1, 20)); }); - it('15.22 Should claim rewards', async function() { + it('Should claim rewards', async function() { await nxms.isMember(ab1); await nxmToken.balanceOf(cr.address); await cr.claimAllPendingReward(20); @@ -238,19 +304,20 @@ contract( lastClaimed = await gv.lastRewardClaimed(ab1); }); - // it('15.23 Should not claim reward twice for same proposal', async function() { + // it('Should not claim reward twice for same proposal', async function() { // await assertRevert(cr.claimAllPendingReward(20)); // }); it('Should claim rewards for multiple number of proposals', async function() { let action = 'updateUintParameters(bytes8,uint)'; - let code = 'MAXFOL'; + let code = toHex('MAXFOL'); let proposedValue = 50; let lastClaimed = await gv.lastRewardClaimed(ab1); - let actionHash = encode(action, code, proposedValue); + let actionHash = encode1(['bytes8', 'uint256'], [code, proposedValue]); pId = await gv.getProposalLength(); lastClaimed = await gv.lastRewardClaimed(ab1); for (let i = 0; i < 3; i++) { + let propId = (await gv.getProposalLength()).toNumber(); await gvProposalWithIncentive(22, actionHash, mr, gv, 2, 10); } let members = await mr.members(2); @@ -265,10 +332,11 @@ contract( it('Claim rewards for proposals which are not in sequence', async function() { pId = await gv.getProposalLength(); let action = 'updateUintParameters(bytes8,uint)'; - let code = 'MAXFOL'; + let code = toHex('MAXFOL'); let proposedValue = 50; - let actionHash = encode(action, code, proposedValue); + let actionHash = encode1(['bytes8', 'uint256'], [code, proposedValue]); for (let i = 0; i < 3; i++) { + let propId = (await gv.getProposalLength()).toNumber(); await gvProposalWithIncentive(22, actionHash, mr, gv, 2, 10); } let p = await gv.getProposalLength(); @@ -283,6 +351,7 @@ contract( }); } for (let i = 0; i < 3; i++) { + let propId = (await gv.getProposalLength()).toNumber(); await gvProposalWithIncentive(22, actionHash, mr, gv, 2, 10); } for (iteration = 0; iteration < members[1].length; iteration++) { @@ -292,6 +361,8 @@ contract( let lastClaimed = await gv.lastRewardClaimed(ab1); assert.equal(lastClaimed.toNumber(), p.toNumber() - 1); await gv.closeProposal(p); + await gv.triggerAction(p); + await gv.getPendingReward(ab1); for (iteration = 0; iteration < members[1].length; iteration++) { await cr.claimAllPendingReward(20); } @@ -300,16 +371,64 @@ contract( assert.equal(lastClaimed.toNumber(), p1.toNumber() - 1); }); + it('Claim rewards for proposals which are not in sequence - 2', async function() { + pId = await gv.getProposalLength(); + let action = 'updateUintParameters(bytes8,uint)'; + let code = toHex('MAXFOL'); + let proposedValue = 50; + let actionHash = encode1(['bytes8', 'uint256'], [code, proposedValue]); + for (let i = 0; i < 3; i++) { + let propId = (await gv.getProposalLength()).toNumber(); + await gvProposalWithIncentive(22, actionHash, mr, gv, 2, 10); + } + let p = await gv.getProposalLength(); + await gv.createProposal('proposal', 'proposal', 'proposal', 0); + await gv.categorizeProposal(p, 22, 10); + await gv.submitProposalWithSolution(p, 'proposal', actionHash); + let members = await mr.members(2); + let iteration = 0; + for (iteration = 0; iteration < members[1].length; iteration++) { + await gv.submitVote(p, 1, { + from: members[1][iteration] + }); + } + for (let i = 0; i < 3; i++) { + let propId = (await gv.getProposalLength()).toNumber(); + await gvProposalWithIncentive(22, actionHash, mr, gv, 2, 10); + } + let p2 = await gv.getProposalLength(); + await gv.createProposal('proposal', 'proposal', 'proposal', 0); + await gv.categorizeProposal(p2, 22, 10); + await gv.submitProposalWithSolution(p2, 'proposal', actionHash); + members = await mr.members(2); + iteration = 0; + for (iteration = 0; iteration < members[1].length; iteration++) { + await gv.submitVote(p2, 1, { + from: members[1][iteration] + }); + } + for (iteration = 0; iteration < members[1].length; iteration++) { + await cr.claimAllPendingReward(20); + } + await gv.closeProposal(p); + await gv.closeProposal(p2); + await gv.triggerAction(p); + await gv.triggerAction(p2); + await gv.getPendingReward(ab1); + for (iteration = 0; iteration < members[1].length; iteration++) { + await cr.claimAllPendingReward(20); + } + + lastClaimed = await gv.lastRewardClaimed(ab1); + }); + it('Claim Rewards for maximum of 20 proposals', async function() { - await gv.setDelegationStatus(true, { from: ab1 }); - let actionHash = encode( - 'updateUintParameters(bytes8,uint)', - 'MAXFOL', - 50 - ); + await gv.setDelegationStatus(true, {from: ab1}); + let actionHash = encode1(['bytes8', 'uint'], [toHex('MAXFOL'), 50]); let p1 = await gv.getProposalLength(); let lastClaimed = await gv.lastRewardClaimed(ab1); for (let i = 0; i < 7; i++) { + let propId = (await gv.getProposalLength()).toNumber(); await gvProposalWithIncentive(22, actionHash, mr, gv, 2, 10); } await cr.claimAllPendingReward(5); @@ -321,11 +440,7 @@ contract( }); it('Claim Reward for followers', async function() { - let actionHash = encode( - 'updateUintParameters(bytes8,uint)', - 'MAXFOL', - 50 - ); + let actionHash = encode1(['bytes8', 'uint'], [toHex('MAXFOL'), 50]); await mr.payJoiningFee(mem1, { value: '2000000000000000', from: mem1 @@ -333,7 +448,7 @@ contract( await mr.kycVerdict(mem1, true, { from: ab1 }); - await gv.delegateVote(ab1, { from: mem1 }); + await gv.delegateVote(ab1, {from: mem1}); await increaseTime(604805); let lastClaimedAb1 = await gv.lastRewardClaimed(ab1); let lastClaimedMem1 = await gv.lastRewardClaimed(mem1); @@ -341,18 +456,19 @@ contract( //last claimed of member will be total number of votes of ab1 untill his delegation assert.equal(lastClaimedMem1.toNumber(), lastClaimedAb1.toNumber() + 2); for (let i = 0; i < 7; i++) { + let propId = (await gv.getProposalLength()).toNumber(); await gvProposalWithIncentive(22, actionHash, mr, gv, 1, 10); } - await cr.claimAllPendingReward(5, { from: mem1 }); + await cr.claimAllPendingReward(5, {from: mem1}); let lastClaimedMem2 = await gv.lastRewardClaimed(mem1); assert.equal(lastClaimedMem1.toNumber() + 5, lastClaimedMem2); }); it('Last reward claimed should be updated when follower undelegates', async function() { - await cr.claimAllPendingReward(20, { from: ab1 }); - await cr.claimAllPendingReward(20, { from: mem1 }); + await cr.claimAllPendingReward(20, {from: ab1}); + await cr.claimAllPendingReward(20, {from: mem1}); // await gv.setDelegationStatus(false, { from: ab1 }); - await gv.unDelegate({ from: mem1 }); + await gv.unDelegate({from: mem1}); await increaseTime(604900); let lastRewardClaimed = await gv.lastRewardClaimed(mem1); //Till now Member 1 hasn't voted on his own, so his vote count will be 0 @@ -367,15 +483,16 @@ contract( 50 ); for (let i = 0; i < 6; i++) { + let propId = (await gv.getProposalLength()).toNumber(); await gvProposalWithIncentive(22, actionHash, mr, gv, 1, 10); } - await cr.claimAllPendingReward(4, { from: ab1 }); + await cr.claimAllPendingReward(4, {from: ab1}); let lastProposal = (await gv.getProposalLength()).toNumber() - 1; let lastVoteId = await gv.memberProposalVote(ab1, lastProposal); let lastClaimedAb1 = await gv.lastRewardClaimed(ab1); //Two proposals are pending to claim reward assert.equal(lastClaimedAb1.toNumber(), lastVoteId.toNumber() - 2); - await gv.delegateVote(ab1, { from: mem1 }); + await gv.delegateVote(ab1, {from: mem1}); await gvProposalWithIncentive(22, actionHash, mr, gv, 1, 10); //32 Member doesn't get rewards for this proposal await increaseTime(604805); let p1 = await gv.getProposalLength(); @@ -385,11 +502,11 @@ contract( await gv.createProposal('proposal', 'proposal', 'proposal', 0); //34 await gv.categorizeProposal(p, 22, 10); await gv.submitProposalWithSolution(p, 'proposal', actionHash); - await gv.submitVote(p, 1, { from: ab1 }); + await gv.submitVote(p, 1, {from: ab1}); let p2 = await gv.getProposalLength(); await gvProposalWithIncentive(22, actionHash, mr, gv, 1, 10); //35 let p2Rewards = await gv.proposal(p2.toNumber()); - await cr.claimAllPendingReward(5, { from: mem1 }); + await cr.claimAllPendingReward(5, {from: mem1}); // lastClaimedAb1 = await gv.lastRewardClaimed(ab1); let lastClaimedMem1 = await gv.lastRewardClaimed(mem1); assert.equal(lastClaimedMem1.toNumber(), p.toNumber() - 1); @@ -400,16 +517,17 @@ contract( p2Rewards[4].toNumber() / 2; assert.equal(mem1Balance1, expectedBalance); await gv.closeProposal(p); - await cr.claimAllPendingReward(5, { from: mem1 }); + await gv.triggerAction(p); + await cr.claimAllPendingReward(5, {from: mem1}); lastClaimedMem1 = await gv.lastRewardClaimed(mem1); let pRewards = await gv.proposal(p.toNumber()); let mem1Balance2 = await nxmToken.balanceOf(mem1); expectedBalance = mem1Balance1.toNumber() + pRewards[4].toNumber() / 2; assert.equal(mem1Balance2.toNumber(), expectedBalance); - await cr.claimAllPendingReward(20, { from: ab1 }); - await cr.claimAllPendingReward(20, { from: mem1 }); - await gv.setDelegationStatus(false, { from: ab1 }); - await gv.unDelegate({ from: mem1 }); + await cr.claimAllPendingReward(20, {from: ab1}); + await cr.claimAllPendingReward(20, {from: mem1}); + await gv.setDelegationStatus(false, {from: ab1}); + await gv.unDelegate({from: mem1}); }); it('Proposal should be closed if not categorized for more than 14 days', async function() { @@ -417,6 +535,7 @@ contract( await gv.createProposal('Proposal', 'Proposal', 'Proposal', 0); await increaseTime(604810 * 2); await gv.closeProposal(pId); + await assertRevert(gv.triggerAction(pId)); }); it('Proposal should be closed if not submitted to vote for more than 14 days', async function() { @@ -425,91 +544,341 @@ contract( await gv.categorizeProposal(pId, 22, 10); await increaseTime(604810 * 2); await gv.closeProposal(pId); + await assertRevert(gv.triggerAction(pId)); }); - describe('Delegation cases', function() { - it('15.24 Initialising Members', async function() { - await increaseTime(604900); - await assertRevert(mr.changeMaxABCount(4, { from: ab2 })); - await mr.payJoiningFee(ab2, { - value: '2000000000000000', - from: ab2 - }); - await mr.kycVerdict(ab2, true, { - from: ab1 - }); - await mr.payJoiningFee(ab3, { - value: '2000000000000000', - from: ab3 - }); - await mr.kycVerdict(ab3, true, { - from: ab1 - }); - await mr.payJoiningFee(ab4, { - value: '2000000000000000', - from: ab4 - }); - await mr.kycVerdict(ab4, true, { - from: ab1 - }); - await mr.addInitialABMembers([ab2, ab3, ab4]); - for (let i = 5; i < 11; i++) { - await mr.payJoiningFee(web3.eth.accounts[i], { + it('Initialising AB Members', async function() { + await increaseTime(604900); + await assertRevert(mr.changeMaxABCount(4, {from: ab2})); + await mr.payJoiningFee(ab2, { + value: '2000000000000000', + from: ab2 + }); + await mr.kycVerdict(ab2, true, { + from: ab1 + }); + await mr.payJoiningFee(ab3, { + value: '2000000000000000', + from: ab3 + }); + await mr.kycVerdict(ab3, true, { + from: ab1 + }); + await mr.payJoiningFee(ab4, { + value: '2000000000000000', + from: ab4 + }); + await mr.kycVerdict(ab4, true, { + from: ab1 + }); + await mr.addInitialABMembers([ab2, ab3, ab4]); + for (let i = 1; i < 11; i++) { + if (i >= 5) { + await mr.payJoiningFee(accounts[i], { value: '2000000000000000', - from: web3.eth.accounts[i] + from: accounts[i] }); - await mr.kycVerdict(web3.eth.accounts[i], true, { - from: web3.eth.accounts[0] + await mr.kycVerdict(accounts[i], true, { + from: accounts[0] }); } + await nxmToken.transfer(accounts[i], toWei(60000)); + } + await increaseTime(604800); + }); + + it('Create a proposal, pass, then check no action executed. Try to execute action after x timeperiod and it should work', async function() { + await increaseTime(604800); + balance = await web3.eth.getBalance(notMember); + pId = (await gv.getProposalLength()) / 1; + let IAStatusETH = await pd.getInvestmentAssetStatus(toHex('ETH')); + let actionHash = encode1( + ['bytes4', 'bool'], + [toHex('ETH'), !IAStatusETH] + ); + await gvProposalWithoutTrigger(15, actionHash, mr, gv, 2); + let IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.equal(IAStatusETH, IAStatusETHNew, 'Action executed'); + let proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + await increaseTime(86500); + await gv.triggerAction(pId); + IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.notEqual(IAStatusETH, IAStatusETHNew, 'Action not executed'); + }); + + it('Increase proposal action waiting time', async function() { + let actionHash = encode1(['bytes8', 'uint256'], [toHex('ACWT'), 24]); + let p = await gv.getProposalLength(); + await gv.createProposal('proposal', 'proposal', 'proposal', 0); + await gv.categorizeProposal(p, 22, 0); + await gv.submitProposalWithSolution(p, 'proposal', actionHash); + let members = await mr.members(2); + let iteration = 0; + for (iteration = 0; iteration < members[1].length; iteration++) + await gv.submitVote(p, 1, { + from: members[1][iteration] + }); + await gv.closeProposal(p); + let proposal = await gv.proposal(p); + assert.equal(proposal[2].toNumber(), 3); + await gv.triggerAction(p); + }); + + it('Create a proposal, pass, then check no action executed. Reject by 1 AB. Try to execute action after x timeperiod and it should work', async function() { + await increaseTime(604800); + let IAStatusETH = await pd.getInvestmentAssetStatus(toHex('ETH')); + pId = (await gv.getProposalLength()) / 1; + let actionHash = encode1( + ['bytes4', 'bool'], + [toHex('ETH'), !IAStatusETH] + ); + await gvProposalWithoutTrigger(15, actionHash, mr, gv, 2); + let IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.equal(IAStatusETH, IAStatusETHNew, 'Action executed'); + let proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + await gv.rejectAction(pId); + await increaseTime(86500); + await gv.triggerAction(pId); + IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.notEqual(IAStatusETH, IAStatusETHNew, 'Action not executed'); + }); + + it('Create a proposal, pass, then check no action executed. Reject by 3 AB. Try to execute action after x timeperiod and it should not work', async function() { + await increaseTime(604800); + let IAStatusETH = await pd.getInvestmentAssetStatus(toHex('ETH')); + pId = (await gv.getProposalLength()) / 1; + let actionHash = encode1( + ['bytes4', 'bool'], + [toHex('ETH'), !IAStatusETH] + ); + await gvProposalWithoutTrigger(15, actionHash, mr, gv, 2); + let IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.equal(IAStatusETH, IAStatusETHNew, 'Action executed'); + let proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + await gv.rejectAction(pId); + await gv.rejectAction(pId, {from: ab3}); + await gv.rejectAction(pId, {from: ab2}); + await increaseTime(86500); + await assertRevert(gv.triggerAction(pId)); + IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.equal(IAStatusETH, IAStatusETHNew, 'Action executed'); + }); + + it('Create a proposal, pass, then check no action executed. Reject by same AB member twice. Should revert', async function() { + await increaseTime(604800); + let IAStatusETH = await pd.getInvestmentAssetStatus(toHex('ETH')); + pId = (await gv.getProposalLength()) / 1; + let actionHash = encode1( + ['bytes4', 'bool'], + [toHex('ETH'), !IAStatusETH] + ); + await gvProposalWithoutTrigger(15, actionHash, mr, gv, 2); + let IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.equal(IAStatusETH, IAStatusETHNew, 'Action executed'); + let proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + await gv.rejectAction(pId); + await assertRevert(gv.rejectAction(pId)); + await gv.rejectAction(pId, {from: ab3}); + await gv.rejectAction(pId, {from: ab4}); + await increaseTime(86500); + await assertRevert(gv.rejectAction(pId)); + await assertRevert(gv.triggerAction(pId)); + IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.equal(IAStatusETH, IAStatusETHNew, 'Action executed'); + }); + + it('Create a proposal, pass, then check no action executed. Reject by 1/3 AB after time elapsed it s should revert. Try to execute action after x timeperiod and it should work', async function() { + await increaseTime(604800); + let IAStatusETH = await pd.getInvestmentAssetStatus(toHex('ETH')); + pId = (await gv.getProposalLength()) / 1; + let actionHash = encode1( + ['bytes4', 'bool'], + [toHex('ETH'), !IAStatusETH] + ); + await gvProposalWithoutTrigger(15, actionHash, mr, gv, 2); + let proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + let IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.equal(IAStatusETH, IAStatusETHNew, 'Action executed'); + await gv.rejectAction(pId); + await increaseTime(86500); + await assertRevert(gv.rejectAction(pId, {from: ab3})); + await gv.triggerAction(pId); + IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.notEqual(IAStatusETH, IAStatusETHNew, 'Action not executed'); + }); + + it('Create a proposal to swap AB, pass, then check no action executed. Reject by AB to fail. Try to execute action after x timeperiod and it should work', async function() { + await increaseTime(604800); + pId = (await gv.getProposalLength()).toNumber(); + let actionHash = encode1(['address', 'address'], [mem1, ab2]); + + await gv.createProposalwithSolution( + 'Proposal9', + 'Proposal9', + 'Proposal9', + 16, + 'Swap AB Member', + actionHash, + {from: mem1} + ); + + await gv.submitVote(pId, 1, {from: ab1}); + await gv.submitVote(pId, 1, {from: ab2}); + await gv.submitVote(pId, 1, {from: ab3}); + await gv.submitVote(pId, 1, {from: ab4}); + await gv.submitVote(pId, 1, {from: mem1}); + await gv.submitVote(pId, 1, {from: mem2}); + await gv.submitVote(pId, 1, {from: mem3}); + await gv.submitVote(pId, 1, {from: mem4}); + await gv.submitVote(pId, 1, {from: mem6}); + await gv.submitVote(pId, 1, {from: mem7}); + + await increaseTime(604800); + await gv.closeProposal(pId); + await assertRevert(gv.rejectAction(pId)); + let proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + await increaseTime(86500); + await gv.triggerAction(pId); + let roleCheck = await mr.checkRole(ab2, 1); + assert.equal(roleCheck, false); + let roleCheck1 = await mr.checkRole(mem1, 1); + assert.equal(roleCheck1, true); + + // Revert swap + await increaseTime(604800); + pId = (await gv.getProposalLength()).toNumber(); + actionHash = encode1(['address', 'address'], [ab2, mem1]); + + await gv.createProposalwithSolution( + 'Proposal9', + 'Proposal9', + 'Proposal9', + 16, + 'Swap AB Member', + actionHash, + {from: ab2} + ); + + await gv.submitVote(pId, 1, {from: ab1}); + // await gv.submitVote(pId, 1, {from: ab2}); + await gv.submitVote(pId, 1, {from: ab3}); + await gv.submitVote(pId, 1, {from: ab4}); + await gv.submitVote(pId, 1, {from: mem1}); + await gv.submitVote(pId, 1, {from: mem2}); + await gv.submitVote(pId, 1, {from: mem3}); + await gv.submitVote(pId, 1, {from: mem4}); + await gv.submitVote(pId, 1, {from: mem6}); + await gv.submitVote(pId, 1, {from: mem7}); + + await increaseTime(604800); + await gv.closeProposal(pId); + await assertRevert(gv.rejectAction(pId)); + proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + await increaseTime(86500); + await gv.triggerAction(pId); + roleCheck = await mr.checkRole(mem1, 1); + assert.equal(roleCheck, false); + roleCheck1 = await mr.checkRole(ab2, 1); + assert.equal(roleCheck1, true); + }); + + it('Create a proposal, pass, then check no action executed. Reject by non-AB, It should revert. Try to execute action after x timeperiod and it should work', async function() { + await increaseTime(604800); + let IAStatusETH = await pd.getInvestmentAssetStatus(toHex('ETH')); + pId = (await gv.getProposalLength()) / 1; + let actionHash = encode1( + ['bytes4', 'bool'], + [toHex('ETH'), !IAStatusETH] + ); + await gvProposalWithoutTrigger(15, actionHash, mr, gv, 2); + let IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.equal(IAStatusETH, IAStatusETHNew, 'Action executed'); + let proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + await assertRevert(gv.rejectAction(pId, {from: mem5})); + await increaseTime(86500); + await gv.triggerAction(pId); + IAStatusETHNew = await pd.getInvestmentAssetStatus(toHex('ETH')); + assert.notEqual(IAStatusETH, IAStatusETHNew, 'Action not executed'); + }); + + it('Create a proposal with AB voting, pass, then action should be executed automatically. Reject should revert', async function() { + await increaseTime(604800); + pId = (await gv.getProposalLength()) / 1; + let actionHash = '0x00'; + await gvProposalWithoutTrigger(6, actionHash, mr, gv, 1); + let isPause = await nxms.isPause(); + assert.equal(isPause, true, 'Action executed'); + let proposal = await gv.proposal(pId); + assert.equal(proposal[2].toNumber(), 3); + await assertRevert(gv.rejectAction(pId, {from: mem5})); + + await increaseTime(86500); + pId = (await gv.getProposalLength()) / 1; + actionHash = encode1(['bool', 'bytes4'], [false, toHex('AB')]); + await gvProposalWithoutTrigger(7, actionHash, mr, gv, 2); + await increaseTime(86500); + await gv.triggerAction(pId); + isPause = await nxms.isPause(); + assert.equal(isPause, false, 'Action not executed'); + }); + + describe('Delegation cases', function() { + it('Fllower cannot delegate vote if Leader is not open for delegation', async function() { + await increaseTime(604800); + await assertRevert(gv.delegateVote(ab1, {from: mem1})); }); - it('15.25 Fllower cannot delegate vote if Leader is not open for delegation', async function() { - await assertRevert(gv.delegateVote(ab1, { from: mem1 })); - }); - it('15.26 AB member cannot delegate vote to AB', async function() { - await gv.setDelegationStatus(true, { from: ab1 }); - await assertRevert(gv.delegateVote(ab1, { from: ab2 })); + it('AB member cannot delegate vote to AB', async function() { + await gv.setDelegationStatus(true, {from: ab1}); + await assertRevert(gv.delegateVote(ab1, {from: ab2})); }); - it('15.27 Owner cannot delegate vote', async function() { - await gv.setDelegationStatus(true, { from: ab3 }); - await assertRevert(gv.delegateVote(ab3, { from: ab1 })); + it('Owner cannot delegate vote', async function() { + await gv.setDelegationStatus(true, {from: ab3}); + await assertRevert(gv.delegateVote(ab3, {from: ab1})); }); - it('15.28 AB member cannot delegate vote to Member', async function() { - await gv.setDelegationStatus(true, { from: mem1 }); - await assertRevert(gv.delegateVote(mem1, { from: ab4 })); + it('AB member cannot delegate vote to Member', async function() { + await gv.setDelegationStatus(true, {from: mem1}); + await assertRevert(gv.delegateVote(mem1, {from: ab4})); }); - it('15.29 AB member cannot delegate vote to Non-Member', async function() { - await assertRevert(gv.delegateVote(notMember, { from: ab4 })); + it('AB member cannot delegate vote to Non-Member', async function() { + await assertRevert(gv.delegateVote(notMember, {from: ab4})); }); - it('15.30 Non-Member cannot delegate vote', async function() { - await assertRevert(gv.delegateVote(ab1, { from: notMember })); + it('Non-Member cannot delegate vote', async function() { + await assertRevert(gv.delegateVote(ab1, {from: notMember})); }); - it('15.31 AB member cannot delegate vote to AB who is follower', async function() { - await gv.setDelegationStatus(true, { from: ab2 }); - await assertRevert(gv.delegateVote(ab2, { from: ab4 })); + it('AB member cannot delegate vote to AB who is follower', async function() { + await gv.setDelegationStatus(true, {from: ab2}); + await assertRevert(gv.delegateVote(ab2, {from: ab4})); }); - it('15.32 Member can delegate vote to AB who is not a follower', async function() { - await gv.delegateVote(ab1, { from: mem1 }); + it('Member can delegate vote to AB who is not a follower', async function() { + await gv.delegateVote(ab1, {from: mem1}); let alreadyDelegated = await gv.alreadyDelegated(ab1); assert.equal(alreadyDelegated, true); }); - it('15.34 Member can delegate vote to Member who is not follower', async function() { - await gv.setDelegationStatus(true, { from: mem3 }); - await gv.delegateVote(mem3, { from: mem5 }); + it('Member can delegate vote to Member who is not follower', async function() { + await gv.setDelegationStatus(true, {from: mem3}); + await gv.delegateVote(mem3, {from: mem5}); let followers = await gv.getFollowers(mem3); let delegationData = await gv.allDelegation(followers[0].toNumber()); assert.equal(delegationData[0], mem5); }); - it('15.35 Leader cannot delegate vote', async function() { - await assertRevert(gv.delegateVote(ab3, { from: mem3 })); + it('Leader cannot delegate vote', async function() { + await assertRevert(gv.delegateVote(ab3, {from: mem3})); }); - it('15.36 Member cannot delegate vote to Non-Member', async function() { - await assertRevert(gv.delegateVote(notMember, { from: mem2 })); + it('Member cannot delegate vote to Non-Member', async function() { + await assertRevert(gv.delegateVote(notMember, {from: mem2})); }); - it('15.37 Member cannot delegate vote to member who is follower', async function() { - await assertRevert(gv.delegateVote(mem5, { from: mem2 })); + it('Member cannot delegate vote to member who is follower', async function() { + await assertRevert(gv.delegateVote(mem5, {from: mem2})); }); - it('15.38 Create a proposal', async function() { + it('Create a proposal', async function() { pId = (await gv.getProposalLength()).toNumber(); await gv.createProposal('Proposal1', 'Proposal1', 'Proposal1', 0); //Pid 2 await gv.categorizeProposal(pId, 13, toWei(130)); @@ -519,38 +888,38 @@ contract( '0x' ); }); - it('15.39 Ab cannot vote twice on a same proposal and cannot transfer nxm to others', async function() { - await gv.submitVote(pId, 1, { from: ab3 }); + it('Ab cannot vote twice on a same proposal and cannot transfer nxm to others', async function() { + await gv.submitVote(pId, 1, {from: ab3}); await assertRevert(nxmToken.transferFrom(ab3, ab2, toWei(1))); - await assertRevert(gv.submitVote(pId, 1, { from: ab3 })); + await assertRevert(gv.submitVote(pId, 1, {from: ab3})); }); - it('15.40 Member cannot vote twice on a same proposal', async function() { - await gv.submitVote(pId, 1, { from: mem4 }); - await assertRevert(gv.submitVote(pId, 1, { from: mem4 })); + it('Member cannot vote twice on a same proposal', async function() { + await gv.submitVote(pId, 1, {from: mem4}); + await assertRevert(gv.submitVote(pId, 1, {from: mem4})); }); - it('15.41 Member cannot assign proxy if voted within 7 days', async function() { - await assertRevert(gv.delegateVote(ab1, { from: mem4 })); + it('Member cannot assign proxy if voted within 7 days', async function() { + await assertRevert(gv.delegateVote(ab1, {from: mem4})); }); - it('15.42 Follower cannot vote on a proposal', async function() { - await assertRevert(gv.submitVote(pId, 1, { from: mem5 })); + it('Follower cannot vote on a proposal', async function() { + await assertRevert(gv.submitVote(pId, 1, {from: mem5})); }); - it('15.43 Member can assign proxy if voted more than 7 days earlier', async function() { + it('Member can assign proxy if voted more than 7 days earlier', async function() { await increaseTime(604850); - await gv.delegateVote(ab1, { from: mem4 }); + await gv.delegateVote(ab1, {from: mem4}); }); - it('15.44 Follower can undelegate vote if not voted since 7 days', async function() { + it('Follower can undelegate vote if not voted since 7 days', async function() { await increaseTime(604800); - await gv.unDelegate({ from: mem5 }); + await gv.unDelegate({from: mem5}); await gv.alreadyDelegated(mem3); await increaseTime(259200); }); - it('15.45 Leader can change delegation status if there are no followers', async function() { - await gv.setDelegationStatus(false, { from: mem5 }); + it('Leader can change delegation status if there are no followers', async function() { + await gv.setDelegationStatus(false, {from: mem5}); }); - it('15.46 Follower cannot assign new proxy if revoked proxy within 7 days', async function() { - await assertRevert(gv.delegateVote(ab1, { from: mem5 })); + it('Follower cannot assign new proxy if revoked proxy within 7 days', async function() { + await assertRevert(gv.delegateVote(ab1, {from: mem5})); }); - it('15.47 Undelegated Follower cannot vote within 7 days since undelegation', async function() { + it('Undelegated Follower cannot vote within 7 days since undelegation', async function() { pId = (await gv.getProposalLength()).toNumber(); await gv.createProposal('Proposal2', 'Proposal2', 'Proposal2', 0); //Pid 3 await gv.categorizeProposal(pId, 13, toWei(130)); @@ -559,67 +928,66 @@ contract( 'changes to pricing model', '0x' ); - await assertRevert(gv.submitVote(pId, 1, { from: mem5 })); + await assertRevert(gv.submitVote(pId, 1, {from: mem5})); await increaseTime(432000); //7 days will be completed since revoking proxy - await gv.delegateVote(ab1, { from: mem7 }); + await gv.delegateVote(ab1, {from: mem7}); }); - it('15.48 Undelegated Follower can vote after 7 days', async function() { + it('Undelegated Follower can vote after 7 days', async function() { let lockedTime = await nxmToken.isLockedForMV(mem2); - await gv.submitVote(pId, 1, { from: ab1 }); - await gv.submitVote(pId, 1, { from: ab3 }); - await gv.submitVote(pId, 1, { from: mem2 }); - await gv.submitVote(pId, 1, { from: mem3 }); - await gv.submitVote(pId, 1, { from: mem5 }); + await gv.submitVote(pId, 1, {from: ab1}); + await gv.submitVote(pId, 1, {from: ab3}); + await gv.submitVote(pId, 1, {from: mem2}); + await gv.submitVote(pId, 1, {from: mem3}); + await gv.submitVote(pId, 1, {from: mem5}); }); - it('15.49 Tokens should be locked for 7 days after voting', async function() { + it('Tokens should be locked for 7 days after voting', async function() { let lockedTime = await nxmToken.isLockedForMV(mem2); assert.isAbove(lockedTime.toNumber(), Date.now() / 1000); }); - it('15.50 should not withdraw membership if he have pending rewads to claim', async function() { + it('should not withdraw membership if he have pending rewads to claim', async function() { await increaseTime(604810); await gv.closeProposal(pId); - await assertRevert(mr.withdrawMembership({ from: mem5 })); + await assertRevert(gv.triggerAction(pId)); + await assertRevert(mr.withdrawMembership({from: mem5})); }); - it('15.51 Follower cannot undelegate if there are rewards pending to be claimed', async function() { - await assertRevert(gv.unDelegate({ from: mem5 })); - await cr.claimAllPendingReward(20, { from: mem5 }); + it('Follower cannot undelegate if there are rewards pending to be claimed', async function() { + await assertRevert(gv.unDelegate({from: mem5})); + await cr.claimAllPendingReward(20, {from: mem5}); }); - it('15.52 Follower should not get reward if delegated within 7days', async function() { + it('Follower should not get reward if delegated within 7days', async function() { let pendingReward = await gv.getPendingReward(mem7); assert.equal(pendingReward.toNumber(), 0); }); - it('15.53 Follower can assign new proxy if revoked proxy more than 7 days earlier', async function() { + it('Follower can assign new proxy if revoked proxy more than 7 days earlier', async function() { await increaseTime(604810); - await gv.delegateVote(ab1, { from: mem5 }); + await gv.delegateVote(ab1, {from: mem5}); }); - it('15.54 Should not get rewards if not participated in voting', async function() { + it('Should not get rewards if not participated in voting', async function() { let pendingReward = await gv.getPendingReward(mem6); assert.equal(pendingReward.toNumber(), 0); }); - it('15.55 Should not add followers more than followers limit', async function() { + it('Should not add followers more than followers limit', async function() { await increaseTime(604810); pId = (await gv.getProposalLength()).toNumber(); await gv.createProposal('Proposal2', 'Proposal2', 'Proposal2', 0); //Pid 3 await gv.categorizeProposal(pId, 22, 0); - let actionHash = encode( - 'updateUintParameters(bytes8,uint)', - 'MAXFOL', - 2 - ); + let actionHash = encode1(['bytes8', 'uint'], [toHex('MAXFOL'), 2]); await gv.submitProposalWithSolution( pId, 'update max followers limit', actionHash ); - await gv.submitVote(pId, 1, { from: ab1 }); - await gv.submitVote(pId, 1, { from: ab2 }); - await gv.submitVote(pId, 1, { from: ab3 }); - await gv.submitVote(pId, 1, { from: mem2 }); - await gv.submitVote(pId, 1, { from: mem3 }); - await gv.submitVote(pId, 1, { from: mem6 }); + await gv.submitVote(pId, 1, {from: ab1}); + await gv.submitVote(pId, 1, {from: ab2}); + await gv.submitVote(pId, 1, {from: ab3}); + await gv.submitVote(pId, 1, {from: mem2}); + await gv.submitVote(pId, 1, {from: mem3}); + await gv.submitVote(pId, 1, {from: mem6}); await increaseTime(604810); await gv.closeProposal(pId); - await assertRevert(gv.delegateVote(ab1, { from: mem6 })); + await increaseTime(86500); + await gv.triggerAction(pId); + await assertRevert(gv.delegateVote(ab1, {from: mem6})); }); }); } diff --git a/test/17_GovernanceCases.test.js b/test/17_GovernanceCases.test.js index f48bf04dab..0b622edc14 100644 --- a/test/17_GovernanceCases.test.js +++ b/test/17_GovernanceCases.test.js @@ -1,5 +1,6 @@ const Governance = artifacts.require('Governance'); const ProposalCategory = artifacts.require('ProposalCategory'); +const OwnedUpgradeabilityProxy = artifacts.require('OwnedUpgradeabilityProxy'); const MemberRoles = artifacts.require('MemberRoles'); const NXMaster = artifacts.require('NXMaster'); const PoolData = artifacts.require('PoolDataMock'); @@ -11,12 +12,11 @@ const {toWei, toHex} = require('./utils/ethTools.js'); const gvProposal = require('./utils/gvProposal.js').gvProposal; const assertRevert = require('./utils/assertRevert.js').assertRevert; const increaseTime = require('./utils/increaseTime.js').increaseTime; -const encode = require('./utils/encoder.js').encode; +const {encode, encode1} = require('./utils/encoder.js'); const AdvisoryBoard = '0x41420000'; const TokenFunctions = artifacts.require('TokenFunctionMock'); const Web3 = require('web3'); -const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); - +const web3_instance = new Web3(); let tf; let gv; let cr; @@ -33,6 +33,7 @@ let IAstatus; let status; let voters; let accounts = []; +let nullAddress = '0x0000000000000000000000000000000000000000'; let maxAllowance = '115792089237316195423570985008687907853269984665640564039457584007913129639935'; @@ -286,10 +287,9 @@ contract( await gv.categorizeProposal(pId, 15, toWei(130)); }); it('17.12 Should open for voting', async function() { - let actionHash = encode( - 'changeInvestmentAssetStatus(bytes4,bool)', - 'ETH', - !IAstatus + let actionHash = encode1( + ['bytes4', 'bool'], + [toHex('ETH'), !IAstatus] ); await gv.submitProposalWithSolution( pId, @@ -316,6 +316,8 @@ contract( assert.equal(proposal[2].toNumber(), 3); }); it('17.16 Should execute defined automatic action', async function() { + await increaseTime(86500); + await gv.triggerAction(pId); let iaStatusLatest = await pd.getInvestmentAssetStatus( '0x455448' ); @@ -358,10 +360,9 @@ contract( await gv.categorizeProposal(pId, 12, toWei(130)); }); it('17.21 Should open for voting', async function() { - let actionHash = encode( - 'changeInvestmentAssetStat(bytes4,bool)', //invalid function declared instead of original one changeInvestmentAssetStatus - 'ETH', - false + let actionHash = encode1( + ['bool'], //invalid params , Expected Bytes8, bool + [false] ); await gv.submitProposalWithSolution( pId, @@ -388,9 +389,14 @@ contract( assert.equal(proposal[2].toNumber(), 3); }); it('17.25 Should not execute defined automatic action', async function() { + await increaseTime(86500); let iaStatusLatest = await pd.getInvestmentAssetStatus( '0x455448' ); + assert.equal( + (await gv.proposalActionStatus(pId)).toNumber(), + 1 + ); assert.equal(iaStatusLatest, IAstatus, 'Action executed'); }); it('17.26 Should get rewards', async function() { @@ -422,10 +428,9 @@ contract( await gv.categorizeProposal(pId, 12, toWei(130)); }); it('17.30 Should open for voting', async function() { - let actionHash = encode( - 'transferEther(uint,address)', - '10000000000000000', - notMember + let actionHash = encode1( + ['uint', 'address'], + ['10000000000000000', notMember] ); await gv.submitProposalWithSolution( pId, @@ -450,6 +455,8 @@ contract( it('17.33 Proposal should be rejected', async function() { let proposal = await gv.proposal(pId); assert.equal(proposal[2].toNumber(), 4, 'Incorrect result'); + await increaseTime(86500); + await assertRevert(gv.triggerAction(pId)); // let proposalsStatus = await gv.getStatusOfProposals(); // assert.equal(proposalsStatus[5].toNumber(), 1); }); @@ -528,7 +535,7 @@ contract( it('17.44 Should create proposal', async function() { await increaseTime(604800); status = await pd.getInvestmentAssetDetails( - web3.toHex('DAI') + web3_instance.toHex('DAI') ); pId = (await gv.getProposalLength()).toNumber(); await gv.createProposal( @@ -542,10 +549,9 @@ contract( await gv.categorizeProposal(pId, 15, toWei(140)); }); it('17.46 Should open for voting', async function() { - let actionHash = encode( - 'changeInvestmentAssetStatus(bytes4,bool)', - web3.toHex('DAI'), - false + let actionHash = encode1( + ['bytes4', 'bool'], + [web3_instance.toHex('DAI'), false] ); await gv.submitProposalWithSolution( pId, @@ -569,8 +575,10 @@ contract( assert.equal(proposal[2].toNumber(), 3); }); it('17.50 Should execute defined automatic action', async function() { + await increaseTime(86500); + await gv.triggerAction(pId); let status1 = await pd.getInvestmentAssetDetails( - web3.toHex('DAI') + web3_instance.toHex('DAI') ); assert.notEqual(status[2], status1[2], 'Action not executed'); }); @@ -702,11 +710,9 @@ contract( await gv.categorizeProposal(pId, 8, toWei(140)); }); it('17.69 Should open for voting', async function() { - let actionHash = encode( - 'burnCAToken(uint,uint,address)', - 0, - (500 * 1e18).toString(), - mem9 + let actionHash = encode1( + ['uint', 'uint', 'address'], + [0, (500 * 1e18).toString(), mem9] ); await gv.submitProposalWithSolution( pId, @@ -731,6 +737,7 @@ contract( assert.equal(proposal[2].toNumber(), 3); }); it('17.73 Should execute defined automatic action', async function() { + await increaseTime(86500); console.log( 'Locked token balance after burning the tokens-' + (await tc.tokensLocked(mem9, CLA)) @@ -764,11 +771,9 @@ contract( await gv.categorizeProposal(pId, 8, toWei(140)); }); it('17.77 Should open for voting', async function() { - let actionHash = encode( - 'burnCAToken(uint,uint,address)', - 0, - (500 * 1e18).toString(), - mem9 + let actionHash = encode1( + ['uint', 'uint', 'address'], + [0, (500 * 1e18).toString(), mem9] ); await gv.submitProposalWithSolution( pId, @@ -790,6 +795,8 @@ contract( assert.equal(proposal[2].toNumber(), 6); }); it('17.81 Should not execute defined automatic action', async function() { + await increaseTime(86500); + await assertRevert(gv.triggerAction(pId)); console.log( 'Locked token balance after burning the tokens-' + (await tc.tokensLocked(mem9, CLA)) @@ -819,7 +826,7 @@ contract( it('17.83 Should create proposal with solution', async function() { await increaseTime(604800); pId = (await gv.getProposalLength()).toNumber(); - let actionHash = encode('swapABMember(address,address)', mem9, ab5); + let actionHash = encode1(['address', 'address'], [mem9, ab5]); let isAllowed = await gv.allowedToCreateProposal(17, { from: mem1 }); @@ -850,6 +857,8 @@ contract( assert.equal(proposal[2].toNumber(), 3); }); it('17.87 Should execute defined automatic action', async function() { + await increaseTime(86500); + await gv.triggerAction(pId); let roleCheck = await mr.checkRole(ab5, 1); assert.equal(roleCheck, false); let roleCheck1 = await mr.checkRole(mem9, 1); @@ -873,7 +882,7 @@ contract( it('17.89 Should create proposal with solution', async function() { await increaseTime(604800); pId = (await gv.getProposalLength()).toNumber(); - let actionHash = encode('swapABMember(address,address)', mem9, ab5); + let actionHash = encode1(['address', 'address'], [mem9, ab5]); let isAllowed = await gv.allowedToCreateProposal(17, { from: mem1 }); @@ -902,6 +911,8 @@ contract( assert.equal(proposal[2].toNumber(), 6); }); it('17.93 Should not execute defined automatic action', async function() { + await increaseTime(86500); + await assertRevert(gv.triggerAction(pId)); let roleCheck = await mr.checkRole(ab5, 1); assert.equal(roleCheck, true); let roleCheck1 = await mr.checkRole(mem9, 1); @@ -922,7 +933,7 @@ contract( it('17.95 Should create proposal with solution', async function() { await increaseTime(604800); pId = (await gv.getProposalLength()).toNumber(); - let actionHash = encode('swapABMember(address,address)', mem9, ab5); + let actionHash = encode1(['address', 'address'], [mem9, ab5]); await gv.createProposalwithSolution( 'Proposal11', 'Proposal11', @@ -1002,19 +1013,84 @@ contract( describe('with Automatic action', function() { it('17.106 Add new category for special resolution', async function() { let pool1Address = await nxms.getLatestAddress(toHex('P1')); - let actionHash = encode( - 'addCategory(string,uint,uint,uint,uint[],uint,string,address,bytes2,uint[])', - 'Special Withdraw funds', - 2, - 75, - 75, - [2], - 604800, - 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', - pd.address, - 'PD', - [0, 0, 0, 1] + //externalLiquidityTrade + let actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'change Investment Asset Status', + 2, + 50, + 15, + [2], + 604800, + 'QmZQhJunZesYuCJkdGwejSATTR8eynUgV8372cHvnAPMaM', + pd.address, + toHex('PD'), + [0, 0, 0, 1], + 'externalLiquidityTrade()' + ] + ); + let categoryLengthOld = (await pc.totalCategories()).toNumber(); + pId = (await gv.getProposalLength()).toNumber(); + await gv.createProposalwithSolution( + 'New category', + 'proposal', + 'proposal', + 3, + '', + actionHash + ); + await gv.submitVote(pId, 1, {from: ab1}); + await gv.submitVote(pId, 1, {from: ab2}); + await gv.submitVote(pId, 1, {from: ab3}); + await gv.submitVote(pId, 1, {from: ab4}); + await gv.submitVote(pId, 1, {from: ab5}); + await gv.closeProposal(pId); + await increaseTime(86500); + let categoryLengthNew = (await pc.totalCategories()).toNumber(); + assert.equal(categoryLengthNew, categoryLengthOld + 1); + //changeInvestmentAssetStatus + actionHash = encode1( + [ + 'string', + 'uint256', + 'uint256', + 'uint256', + 'uint256[]', + 'uint256', + 'string', + 'address', + 'bytes2', + 'uint256[]', + 'string' + ], + [ + 'Test', + 2, + 75, + 0, + [2], + 604800, + 'actionHash', + nullAddress, + toHex('PD'), + [0, 0, 0, 0], + 'changeInvestmentAssetStatus(bytes4,bool)' + ] ); + categoryLengthOld = (await pc.totalCategories()).toNumber(); pId = (await gv.getProposalLength()).toNumber(); await gv.createProposalwithSolution( 'New category', @@ -1030,11 +1106,13 @@ contract( await gv.submitVote(pId, 1, {from: ab4}); await gv.submitVote(pId, 1, {from: ab5}); await gv.closeProposal(pId); - assert.equal((await pc.totalCategories()).toNumber(), 34); + await increaseTime(86500); + categoryLengthNew = (await pc.totalCategories()).toNumber(); + assert.equal(categoryLengthNew, categoryLengthOld + 1); }); it('17.107 Should create proposal', async function() { await increaseTime(604800); - IAstatus = await pd.getInvestmentAssetStatus('0x455448'); + IAstatus = await pd.getInvestmentAssetStatus(toHex('ETH')); pId = (await gv.getProposalLength()).toNumber(); await gv.createProposal( 'Proposal13', @@ -1044,17 +1122,18 @@ contract( ); }); it('17.108 Should whitelist proposal and set Incentives', async function() { - await gv.categorizeProposal(pId, 33, toWei(160)); + let categoryLength = (await pc.totalCategories()).toNumber(); + await gv.categorizeProposal(pId, categoryLength - 1, 0); }); it('17.109 Should open for voting', async function() { - let actionHash = encode( - 'changeInvestmentAssetStatus(bytes4,bool)', - 'ETH', - !IAstatus + IAstatus = await pd.getInvestmentAssetStatus(toHex('ETH')); + let actionHash = encode1( + ['bytes4', 'bool'], + [toHex('ETH'), !IAstatus] ); await gv.submitProposalWithSolution( pId, - 'Withdraw funds to Pay for Support Services', + 'Change Investment Asset Status', actionHash ); }); @@ -1076,8 +1155,10 @@ contract( await gv.closeProposal(pId); }); it('17.112 Should execute defined automatic action', async function() { + await increaseTime(86500); + await gv.triggerAction(pId); let iaStatusLatest = await pd.getInvestmentAssetStatus( - '0x455448' + toHex('ETH') ); assert.notEqual(iaStatusLatest, IAstatus, 'Action not executed'); }); @@ -1122,8 +1203,8 @@ contract( it('17.119 Should get rewards', async function() { for (let i = 0; i < 12; i++) { if ( - web3.toChecksumAddress(accounts[i]) != - web3.toChecksumAddress(mem9) + web3_instance.toChecksumAddress(accounts[i]) != + web3_instance.toChecksumAddress(mem9) ) { assert.isAbove( (await gv.getPendingReward(accounts[i])).toString() * 1, @@ -1143,10 +1224,9 @@ contract( await increaseTime(604800); balance = await web3.eth.getBalance(notMember); pId = (await gv.getProposalLength()).toNumber(); - let actionHash = encode( - 'updateOwnerParameters(bytes8,address)', - 'OWNER', - accounts[1] + let actionHash = encode1( + ['bytes8', 'address'], + [toHex('OWNER'), accounts[1]] ); await gv.createProposalwithSolution( 'Proposal14', @@ -1170,10 +1250,9 @@ contract( await increaseTime(604800); balance = await web3.eth.getBalance(notMember); pId = (await gv.getProposalLength()).toNumber(); - let actionHash = encode( - 'updateOwnerParameters(bytes8,address)', - 'OWNER', - accounts[1] + let actionHash = encode1( + ['bytes8', 'address'], + [toHex('OWNER'), accounts[1]] ); await gv.createProposalwithSolution( 'Proposal14', @@ -1188,11 +1267,13 @@ contract( await gv.submitVote(pId, 1); }); it('17.125 Should execute defined automatic action', async function() { + await increaseTime(86500); + await gv.triggerAction(pId); await increaseTime(1000); let owner = await nxms.getOwnerParameters(toHex('OWNER')); assert.equal( - web3.toChecksumAddress(owner[1]), - web3.toChecksumAddress(accounts[1]), + web3_instance.toChecksumAddress(owner[1]), + web3_instance.toChecksumAddress(accounts[1]), 'Action not executed' ); }); diff --git a/test/21_ConfigureParams.test.js b/test/21_ConfigureParams.test.js index 604f21f775..24980d3286 100644 --- a/test/21_ConfigureParams.test.js +++ b/test/21_ConfigureParams.test.js @@ -24,7 +24,7 @@ const TokenFunctions = artifacts.require('TokenFunctionMock'); const Web3 = require('web3'); const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); // Hardcoded development port // const { toWei } = require('./utils/ethTools'); -const { toHex, toWei } = require('./utils/ethTools'); +const {toHex, toWei} = require('./utils/ethTools'); let mcr; let tf; @@ -224,9 +224,6 @@ contract( it('Should update Max Draft time limit', async function() { await updateParameter(22, 2, 'MAXDRFT', gv, 'uint', '86400'); }); - it('Should update Max Advisory Board Members', async function() { - await updateParameter(22, 2, 'MAXAB', gv, 'uint', '10'); - }); it('Should update Emergency Pause Time', async function() { await updateParameter(22, 2, 'EPTIME', gv, 'uint', '86400'); }); @@ -336,7 +333,7 @@ contract( before(async function() { const c1 = await pc.totalCategories(); let actionHash = encode( - 'addCategory(string,uint,uint,uint,uint[],uint,string,address,bytes2,uint[])', + 'newCategory(string,uint256,uint256,uint256,uint256[],uint256,string,address,bytes2,uint256[],string)', 'Description', 2, 50, @@ -346,7 +343,8 @@ contract( '', mcr.address, toHex('MC'), - [0, 0, 0, 1] + [0, 0, 0, 1], + 'updateUintParameters(bytes8,uint256)' ); let p1 = await gv.getProposalLength(); await gv.createProposalwithSolution( diff --git a/test/22_UnlockFixes.test.js b/test/22_UnlockFixes.test.js index 5e6e601f17..f374af8576 100644 --- a/test/22_UnlockFixes.test.js +++ b/test/22_UnlockFixes.test.js @@ -201,7 +201,7 @@ contract('unlock-fixes', function([ let gv = await Governance.at(await nxms.getLatestAddress(toHex('GV'))); let c1 = await pc.totalCategories(); let actionHash = encode( - 'addCategory(string,uint,uint,uint,uint[],uint,string,address,bytes2,uint[])', + 'newCategory(string,uint256,uint256,uint256,uint256[],uint256,string,address,bytes2,uint256[],string)', 'Description', 1, 1, @@ -211,7 +211,8 @@ contract('unlock-fixes', function([ '', tc.address, toHex('EX'), - [0, 0, 0, 0] + [0, 0, 0, 0], + 'updateUintParameters(bytes8,uint256)' ); let p1 = await gv.getProposalLength(); await gv.createProposalwithSolution( diff --git a/test/utils/encoder.js b/test/utils/encoder.js index d4b2e73873..6b8293673c 100644 --- a/test/utils/encoder.js +++ b/test/utils/encoder.js @@ -1,23 +1,31 @@ var abi = require('ethereumjs-abi'); -const {toHex} = require('./ethTools'); +const Web3 = require('web3'); +const web3 = new Web3(); function encode(...args) { - var signature = args[0]; - var datatypes = signature - .substring(0, signature.length - 1) + if (args.length == 1) { + return '0x'; + } + let fn = args[0]; + let params = args.slice(1); + let types = fn + .slice(0, fn.length - 1) .split('(')[1] .split(','); - var params = args.slice(1); - for (let i = 0; i < datatypes.length; i++) { - if (datatypes[i].includes('byte')) { - if (!params[i].startsWith('0x')) { - params[i] = toHex(params[i]); - args[i + 1] = params[i]; - } + for (let i = 0; i < types.length; i++) { + if (types[i].includes('bytes') && !params[i].startsWith('0x')) { + params[i] = web3.toHex(params[i]); } } - var encoded = abi.simpleEncode.apply(this, args); + args = [types, params]; + let encoded = abi.rawEncode.apply(this, args); + encoded = encoded.toString('hex'); + return '0x' + encoded; +} + +function encode1(...args) { + var encoded = abi.rawEncode.apply(this, args); encoded = encoded.toString('hex'); return '0x' + encoded; } -module.exports = {encode}; +module.exports = {encode, encode1}; diff --git a/test/utils/ethTools.js b/test/utils/ethTools.js index 1c3eff90ad..33ec3e4fda 100644 --- a/test/utils/ethTools.js +++ b/test/utils/ethTools.js @@ -1,5 +1,5 @@ const Web3 = require('web3'); -const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); // Hardcoded development port +const web3 = new Web3(); // Hardcoded development port function ether(n) { return new web3.BigNumber(web3.toWei(n, 'ether')); } diff --git a/test/utils/gvProposal.js b/test/utils/gvProposal.js index 986d80ae2e..7237d7929c 100644 --- a/test/utils/gvProposal.js +++ b/test/utils/gvProposal.js @@ -1,4 +1,7 @@ const increaseTime = require('./increaseTime.js').increaseTime; +const encode1 = require('./encoder.js').encode1; +const Web3 = require('web3'); +const web3 = new Web3(); async function gvProposal(...args) { let catId = args[0]; let actionHash = args[1]; @@ -19,6 +22,7 @@ async function gvProposal(...args) { if (seq != 3) await gv.closeProposal(p); let proposal = await gv.proposal(p); assert.equal(proposal[2].toNumber(), 3); + if (seq != 1) await gv.triggerAction(p); } async function gvProposalWithIncentive(...args) { @@ -43,6 +47,56 @@ async function gvProposalWithIncentive(...args) { if (seq != 3) await gv.closeProposal(p); let proposal = await gv.proposal(p); assert.equal(proposal[2].toNumber(), 3); + await gv.triggerAction(p); } -module.exports = { gvProposalWithIncentive, gvProposal }; +async function gvProposalWithoutTrigger(...args) { + let catId = args[0]; + let actionHash = args[1]; + let mr = args[2]; + let gv = args[3]; + let seq = args[4]; + let p = await gv.getProposalLength(); + await gv.createProposal('proposal', 'proposal', 'proposal', 0); + await gv.categorizeProposal(p, catId, 0); + await gv.submitProposalWithSolution(p, 'proposal', actionHash); + let members = await mr.members(seq); + let iteration = 0; + for (iteration = 0; iteration < members[1].length; iteration++) + await gv.submitVote(p, 1, { + from: members[1][iteration] + }); + // console.log(await gv.proposalDetails(p)); + if (seq != 3) await gv.closeProposal(p); + let proposal = await gv.proposal(p); + assert.equal(proposal[2].toNumber(), 3); +} + +async function setTriggerActionTime(...args) { + let mr = args[0]; + let gv = args[1]; + let actionHash = encode1(['bytes8', 'uint256'], [web3.toHex('ACWT'), 0]); + let p = await gv.getProposalLength(); + await gv.createProposal('proposal', 'proposal', 'proposal', 0); + await gv.categorizeProposal(p, 22, 0); + await gv.submitProposalWithSolution(p, 'proposal', actionHash); + let members = await mr.members(2); + let iteration = 0; + for (iteration = 0; iteration < members[1].length; iteration++) + await gv.submitVote(p, 1, { + from: members[1][iteration] + }); + // console.log(await gv.proposalDetails(p)); + await gv.closeProposal(p); + let proposal = await gv.proposal(p); + assert.equal(proposal[2].toNumber(), 3); + await increaseTime(86401); + await gv.triggerAction(p); +} + +module.exports = { + gvProposalWithIncentive, + gvProposal, + setTriggerActionTime, + gvProposalWithoutTrigger +};