From ed9929b4563c9d420bccf4fae5322f2d0ac71ea0 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Thu, 28 Nov 2019 17:58:56 +0200 Subject: [PATCH 01/21] first version --- contracts/schemes/ContributionRewardExt.sol | 444 ++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 contracts/schemes/ContributionRewardExt.sol diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol new file mode 100644 index 00000000..e4d429e8 --- /dev/null +++ b/contracts/schemes/ContributionRewardExt.sol @@ -0,0 +1,444 @@ +pragma solidity ^0.5.11; + +import "@daostack/infra/contracts/votingMachines/IntVoteInterface.sol"; +import "@daostack/infra/contracts/votingMachines/VotingMachineCallbacksInterface.sol"; +import "../votingMachines/VotingMachineCallbacks.sol"; + + +/** + * @title A scheme for proposing and rewarding contributions to an organization + * @dev An agent can ask an organization to recognize a contribution and reward + * him with token, reputation, ether or any combination. + */ +contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterface { + using SafeMath for uint; + + event NewContributionProposal( + address indexed _avatar, + bytes32 indexed _proposalId, + address indexed _intVoteInterface, + string _descriptionHash, + int256 _reputationChange, + uint[3] _rewards, + IERC20 _externalToken, + address _beneficiary, + address _proposer + ); + + event ProposalExecuted(address indexed _avatar, bytes32 indexed _proposalId, int256 _param); + + event RedeemReputation( + address indexed _avatar, + bytes32 indexed _proposalId, + address indexed _beneficiary, + int256 _amount); + + event RedeemEther(address indexed _avatar, + bytes32 indexed _proposalId, + address indexed _beneficiary, + uint256 _amount); + + event RedeemNativeToken(address indexed _avatar, + bytes32 indexed _proposalId, + address indexed _beneficiary, + uint256 _amount); + + event RedeemExternalToken(address indexed _avatar, + bytes32 indexed _proposalId, + address indexed _beneficiary, + uint256 _amount); + + // A struct holding the data for a contribution proposal + struct ContributionProposal { + uint256 nativeTokenReward; // Reward asked in the native token of the organization. + int256 reputationChange; // Organization reputation reward requested. + uint256 ethReward; + IERC20 externalToken; + uint256 externalTokenReward; + address payable beneficiary; + uint256 executionTime; + uint256 nativeTokenRewardLeft; + uint256 reputationChangeLeft; + uint256 ethRewardLeft; + uint256 externalTokenRewardLeft; + } + + modifier onlyRedeemer() { + if (redeemer != address(0)) { + require(msg.sender == redeemer, "only redeemer allowed to redeem"); + } + _; + } + + mapping(bytes32=>ContributionProposal) public organizationProposals; + + IntVoteInterface public votingMachine; + bytes32 public voteParams; + address public contractToCall; + Avatar public avatar; + address public redeemer; + + /** + * @dev initialize + * @param _avatar the avatar to mint reputation from + * @param _votingMachine the voting machines address to + * @param _voteParams voting machine parameters. + * @param _redeemer an address which allowed to redeem the contribution. + if _redeemer is 0 this param is agnored. + */ + function initialize( + Avatar _avatar, + IntVoteInterface _votingMachine, + bytes32 _voteParams, + address _redeemer + ) + external + { + require(avatar == Avatar(0), "can be called only one time"); + require(_avatar != Avatar(0), "avatar cannot be zero"); + avatar = _avatar; + votingMachine = _votingMachine; + voteParams = _voteParams; + redeemer = _redeemer; + } + + /** + * @dev execution of proposals, can only be called by the voting machine in which the vote is held. + * @param _proposalId the ID of the voting in the voting machine + * @param _decision a parameter of the voting result, 1 yes and 2 is no. + */ + function executeProposal(bytes32 _proposalId, int256 _decision) + external + onlyVotingMachine(_proposalId) + returns(bool) { + require(organizationProposals[_proposalId].executionTime == 0); + require(organizationProposals[_proposalId].beneficiary != address(0)); + // Check if vote was successful: + if (_decision == 1) { + // solhint-disable-next-line not-rely-on-time + organizationProposals[_proposalId].executionTime = now; + } + emit ProposalExecuted(address(avatar), _proposalId, _decision); + return true; + } + + /** + * @dev Submit a proposal for a reward for a contribution: + * @param _descriptionHash A hash of the proposal's description + * @param _reputationChange - Amount of reputation change requested .Can be negative. + * @param _rewards rewards array: + * rewards[0] - Amount of tokens requested per period + * rewards[1] - Amount of ETH requested per period + * rewards[2] - Amount of external tokens requested per period + * @param _externalToken Address of external token, if reward is requested there + * @param _beneficiary Who gets the rewards. if equal to 0 the beneficiary will be msg.sender. + * @param _proposer proposer . if equal to 0 the proposer will be msg.sender. + */ + function proposeContributionReward( + string memory _descriptionHash, + int256 _reputationChange, + uint[3] memory _rewards, + IERC20 _externalToken, + address payable _beneficiary, + address _proposer + ) + public + returns(bytes32) + { + address proposer = _proposer; + if (proposer == address(0)) { + proposer = msg.sender; + } + bytes32 contributionId = votingMachine.propose(2, voteParams, proposer, address(avatar)); + address payable beneficiary = _beneficiary; + if (beneficiary == address(0)) { + beneficiary = msg.sender; + } + + ContributionProposal memory proposal = ContributionProposal({ + nativeTokenReward: _rewards[0], + reputationChange: _reputationChange, + ethReward: _rewards[1], + externalToken: _externalToken, + externalTokenReward: _rewards[2], + beneficiary: beneficiary, + executionTime: 0, + nativeTokenRewardLeft: 0, + reputationChangeLeft: 0, + ethRewardLeft: 0, + externalTokenRewardLeft: 0 + }); + organizationProposals[contributionId] = proposal; + + emit NewContributionProposal( + address(avatar), + contributionId, + address(votingMachine), + _descriptionHash, + _reputationChange, + _rewards, + _externalToken, + beneficiary, + proposer + ); + + proposalsInfo[address(votingMachine)][contributionId] = ProposalInfo({ + blockNumber:block.number, + avatar:avatar + }); + return contributionId; + } + + /** + * @dev RedeemReputation reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @return reputation the redeemed reputation. + */ + function redeemReputation(bytes32 _proposalId) public returns(int256 reputation) { + ContributionProposal memory _proposal = organizationProposals[_proposalId]; + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //set proposal reward to zero to prevent reentrancy attack. + proposal.reputationChange = 0; + + if (_proposal.beneficiary == address(this)) { + if (_proposal.reputationChange != 0) { + if (_proposal.reputationChange > 0) {//for now only mint(not burn) rep allowed from ext contract. + proposal.reputationChangeLeft = uint256(_proposal.reputationChange); + } + } + } else { + reputation = _proposal.reputationChange; + } + + if (reputation > 0) { + require( + Controller( + avatar.owner()).mintReputation(uint(reputation), _proposal.beneficiary, address(avatar))); + } else if (reputation < 0) { + require( + Controller( + avatar.owner()).burnReputation(uint(reputation*(-1)), _proposal.beneficiary, address(avatar))); + } + if (reputation != 0) { + emit RedeemReputation(address(avatar), _proposalId, _proposal.beneficiary, reputation); + } + } + + /** + * @dev redeemReputationFromExtContract redeem reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @param _beneficiary the beneficiary to mint reputation to. + * @param _reputation the reputation amount to mint + */ + function redeemReputationFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _reputation) + public + onlyRedeemer + { + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //this will ensure sum zero of reputation. + proposal.reputationChangeLeft = proposal.reputationChangeLeft.sub(_reputation); + require( + Controller( + avatar.owner()).mintReputation(_reputation, _beneficiary, address(avatar))); + if (_reputation != 0) { + emit RedeemReputation(address(avatar), _proposalId, _beneficiary, int256(_reputation)); + } + } + + /** + * @dev RedeemNativeToken reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @return amount the redeemed nativeToken. + */ + function redeemNativeToken(bytes32 _proposalId) public returns(uint256 amount) { + + ContributionProposal memory _proposal = organizationProposals[_proposalId]; + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //set proposal rewards to zero to prevent reentrancy attack. + proposal.nativeTokenReward = 0; + + if (_proposal.beneficiary == address(this)) { + if (_proposal.nativeTokenReward != 0) { + proposal.nativeTokenRewardLeft = _proposal.nativeTokenReward; + } + } + amount = _proposal.nativeTokenReward; + if (amount > 0) { + require(Controller(avatar.owner()).mintTokens(amount, _proposal.beneficiary, address(avatar))); + emit RedeemNativeToken(address(avatar), _proposalId, _proposal.beneficiary, amount); + } + } + + /** + * @dev redeemNativeTokenFromExtContract redeem reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @param _beneficiary the beneficiary to mint tokens to. + * @param _amount the tokens amount to mint + */ + function redeemNativeTokenFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _amount) + public + onlyRedeemer + { + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //this will ensure sum zero of reputation. + proposal.nativeTokenRewardLeft = proposal.nativeTokenRewardLeft.sub(_amount); + + if (_amount > 0) { + require(Controller(avatar.owner()).mintTokens(_amount, _beneficiary, address(avatar))); + emit RedeemNativeToken(address(avatar), _proposalId, _beneficiary, _amount); + } + } + + /** + * @dev RedeemEther reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @return amount ether redeemed amount + */ + function redeemEther(bytes32 _proposalId) public returns(uint256 amount) { + ContributionProposal memory _proposal = organizationProposals[_proposalId]; + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //set proposal rewards to zero to prevent reentrancy attack. + proposal.ethReward = 0; + if (_proposal.beneficiary == address(this)) { + if (_proposal.ethReward != 0) { + proposal.ethRewardLeft = _proposal.ethReward; + } + } + amount = _proposal.ethReward; + + if (amount > 0) { + require(Controller(avatar.owner()).sendEther(amount, _proposal.beneficiary, avatar)); + emit RedeemEther(address(avatar), _proposalId, _proposal.beneficiary, amount); + } + } + + /** + * @dev redeemEtherFromExtContract redeem reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @param _beneficiary the beneficiary to send eth to. + * @param _amount eth amount to send + */ + function redeemEtherFromExtContract(bytes32 _proposalId, address payable _beneficiary, uint256 _amount) + public + onlyRedeemer + { + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //this will ensure sum zero of reputation. + proposal.ethRewardLeft = proposal.ethRewardLeft.sub(_amount); + + if (_amount > 0) { + require(Controller(avatar.owner()).sendEther(_amount, _beneficiary, avatar)); + emit RedeemEther(address(avatar), _proposalId, _beneficiary, _amount); + } + } + + /** + * @dev RedeemNativeToken reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @return amount the external token redeemed amount + */ + function redeemExternalToken(bytes32 _proposalId) public returns(uint256 amount) { + ContributionProposal memory _proposal = organizationProposals[_proposalId]; + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //set proposal rewards to zero to prevent reentrancy attack. + proposal.externalTokenReward = 0; + + if (_proposal.beneficiary == address(this)) { + if (_proposal.externalTokenReward != 0) { + proposal.externalTokenRewardLeft = _proposal.externalTokenReward; + } + } + + if (proposal.externalToken != IERC20(0) && _proposal.externalTokenReward > 0) { + amount = _proposal.externalTokenReward; + if (amount > 0) { + require( + Controller( + avatar.owner()) + .externalTokenTransfer(_proposal.externalToken, _proposal.beneficiary, amount, avatar)); + emit RedeemExternalToken(address(avatar), _proposalId, _proposal.beneficiary, amount); + } + } + } + + /** + * @dev redeemExternalTokenFromExtContract redeem reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @param _beneficiary the beneficiary to send the external token to. + * @param _amount the amount of external token to send + */ + function redeemExternalTokenFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _amount) + public + onlyRedeemer { + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //this will ensure sum zero of reputation. + proposal.externalTokenRewardLeft = proposal.externalTokenRewardLeft.sub(_amount); + + if (proposal.externalToken != IERC20(0)) { + if (_amount > 0) { + require( + Controller( + avatar.owner()) + .externalTokenTransfer(proposal.externalToken, _beneficiary, _amount, avatar)); + emit RedeemExternalToken(address(avatar), _proposalId, _beneficiary, _amount); + } + } + } + + /** + * @dev redeem rewards for proposal + * @param _proposalId the ID of the voting in the voting machine + * @param _whatToRedeem whatToRedeem array: + * whatToRedeem[0] - reputation + * whatToRedeem[1] - nativeTokenReward + * whatToRedeem[2] - Ether + * whatToRedeem[3] - ExternalToken + * @return result boolean array for each redeem type. + */ + function redeem(bytes32 _proposalId, bool[4] memory _whatToRedeem) + public + returns(int256 reputationReward, uint256 nativeTokenReward, uint256 etherReward, uint256 externalTokenReward) + { + + if (_whatToRedeem[0]) { + reputationReward = redeemReputation(_proposalId); + } + + if (_whatToRedeem[1]) { + nativeTokenReward = redeemNativeToken(_proposalId); + } + + if (_whatToRedeem[2]) { + etherReward = redeemEther(_proposalId); + } + + if (_whatToRedeem[3]) { + externalTokenReward = redeemExternalToken(_proposalId); + } + } + + function getProposalEthReward(bytes32 _proposalId) public view returns (uint256) { + return organizationProposals[_proposalId].ethReward; + } + + function getProposalExternalTokenReward(bytes32 _proposalId) public view returns (uint256) { + return organizationProposals[_proposalId].externalTokenReward; + } + + function getProposalExternalToken(bytes32 _proposalId) public view returns (address) { + return address(organizationProposals[_proposalId].externalToken); + } + + function getProposalExecutionTime(bytes32 _proposalId) public view returns (uint256) { + return organizationProposals[_proposalId].executionTime; + } + +} From 60d20bcd991b899011fc5295acf030642def225b Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Thu, 28 Nov 2019 17:59:50 +0200 Subject: [PATCH 02/21] comp version --- contracts/schemes/ContributionRewardExt.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index e4d429e8..e21f5920 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.11; +pragma solidity 0.5.13; import "@daostack/infra/contracts/votingMachines/IntVoteInterface.sol"; import "@daostack/infra/contracts/votingMachines/VotingMachineCallbacksInterface.sol"; From f2d3bbfb5d33028218eb4762eeb71fe660449b41 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Sun, 1 Dec 2019 15:11:19 +0200 Subject: [PATCH 03/21] wip : competition scheme --- contracts/schemes/Competition.sol | 128 ++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 contracts/schemes/Competition.sol diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol new file mode 100644 index 00000000..55e27b4f --- /dev/null +++ b/contracts/schemes/Competition.sol @@ -0,0 +1,128 @@ +pragma solidity 0.5.13; +pragma experimental ABIEncoderV2; + +import "./ContributionRewardExt.sol"; + +contract Competition { + using SafeMath for uint256; + + uint256 constant public MAX_NUMBER_OF_WINNERS = 100; + + event NewCompetitionProposal( + bytes32 indexed _proposalId, + uint256 _numberOfWinners, + uint256[MAX_NUMBER_OF_WINNERS] _rewardSplit, + uint256 _startTime, + uint256 _votingStartTime, + uint256 _endTime, + uint256 _numberOfVotesPerVoters, + address payable _contributionReward //address of the contract to redeem from. + ); + + event NewSuggestion( + bytes32 indexed _proposalId, + uint256 indexed _suggestionId, + string indexed _descriptionHash + ); + + // A struct holding the data for a competition proposal + struct CompetitionProposal { + uint256 numberOfWinners; + uint256[MAX_NUMBER_OF_WINNERS] rewardSplit; + uint256 startTime; + uint256 votingStartTime; + uint256 endTime; + uint256 numberOfVotesPerVoters; + address payable contributionReward; //address of the contract to redeem from. + //suggestionsHeap + } + + struct Suggestion { + uint256 totalVotes; + bytes32 proposalId; + } + + //mapping from proposalID to CompetitionProposal + mapping(bytes32=>CompetitionProposal) public competitionProposals; + + //mapping from suggestionId to Suggestion + mapping(uint256=>Suggestion) public suggestions; + + mapping(address=>uint256) public votesPerVoter; + + uint256 public suggestionsCounter; + + function proposeCompetition( + string memory _descriptionHash, + ContributionRewardExt.ContributionProposal memory _contributionProposal, + CompetitionProposal memory _competitionProposal + ) + public + returns(bytes32 proposalId) + { + require(_competitionProposal.numberOfWinners <= MAX_NUMBER_OF_WINNERS, + "number of winners greater than max allowed"); + require(_competitionProposal.votingStartTime < _competitionProposal.endTime, + "voting start time greater than end time"); + require(_competitionProposal.votingStartTime >= _competitionProposal.startTime, + "voting start time smaller than start time"); + require(_competitionProposal.startTime < _competitionProposal.endTime, + "start time greater than end time"); + require(_competitionProposal.numberOfVotesPerVoters > 0, + "numberOfVotesPerVoters should be greater than 0"); + proposalId = ContributionRewardExt(_competitionProposal.contributionReward).proposeContributionReward( + _descriptionHash, + _contributionProposal.reputationChange, + [_contributionProposal.nativeTokenReward, + _contributionProposal.ethReward, + _contributionProposal.externalTokenReward], + _contributionProposal.externalToken, + _competitionProposal.contributionReward, + msg.sender); + competitionProposals[proposalId] = _competitionProposal; + + emit NewCompetitionProposal( + proposalId, + _competitionProposal.numberOfWinners, + _competitionProposal.rewardSplit, + _competitionProposal.startTime, + _competitionProposal.votingStartTime, + _reputationChange.endTime, + _reputationChange.contributionReward + ); + } + + function suggest( + bytes32 _proposalId, + string memory _descriptionHash + ) + public + returns(uint256) + { + require(competitionProposals[_proposalId].startTime <= now, "competition not started yet"); + require(competitionProposals[_proposalId].endTime > now, "competition ended"); + suggestionId = suggestionId.add(1); + suggestion[suggestionId].proposalId = _proposalId; + emit NewSuggestion(_proposalId, suggestionId, _descriptionHash); + return suggestionId; + } + + function vote(uint256 _suggestionId) + public + returns(bytes32) + { + require(competitionProposals[_proposalId].votingStartTime > now, "votingh period not started yet"); + require(competitionProposals[_proposalId].endTime < now, "competition ended"); + bytes32 proposalId = suggestions[_suggestionId]._proposalId; + require(proposalId != bytes32(0), "suggestion not exist"); + require(votes[msg.sender] < competitionProposals[proposalId].numberOfVotesPerVoters, + "exceed number of votes allowd"); + votes[msg.sender] = votes[msg.sender].add(1); + + suggestionId = suggestionId.add(1); + emit Suggestion(_proposalId, suggestionId, _descriptionHash); + return suggestionId; + } + + +} From 825a3dfec632993eefa50977be78842baa7f0ad9 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Sun, 1 Dec 2019 18:54:45 +0200 Subject: [PATCH 04/21] add redeem to competition. --- contracts/schemes/Competition.sol | 158 ++++++++++++++++---- contracts/schemes/ContributionRewardExt.sol | 8 + 2 files changed, 141 insertions(+), 25 deletions(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index 55e27b4f..e49b8dd2 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -3,6 +3,7 @@ pragma experimental ABIEncoderV2; import "./ContributionRewardExt.sol"; + contract Competition { using SafeMath for uint256; @@ -16,13 +17,22 @@ contract Competition { uint256 _votingStartTime, uint256 _endTime, uint256 _numberOfVotesPerVoters, - address payable _contributionReward //address of the contract to redeem from. + address payable _contributionReward, //address of the contract to redeem from. + uint256 snapshotBlock ); event NewSuggestion( bytes32 indexed _proposalId, uint256 indexed _suggestionId, - string indexed _descriptionHash + string indexed _descriptionHash, + address suggester + ); + + event NewVote( + bytes32 indexed _proposalId, + uint256 indexed _suggestionId, + address indexed _voter, + uint256 reputation ); // A struct holding the data for a competition proposal @@ -34,16 +44,25 @@ contract Competition { uint256 endTime; uint256 numberOfVotesPerVoters; address payable contributionReward; //address of the contract to redeem from. - //suggestionsHeap + } + + // A struct holding the data for a competition proposal + struct Proposal { + CompetitionProposal competitionProposal; + uint256 snapshotBlock; + uint256 currentMinTopSuggestiosScore; + uint256[MAX_NUMBER_OF_WINNERS] topSuggestionsOrdered; } struct Suggestion { uint256 totalVotes; bytes32 proposalId; + address suggester; + mapping(address=>uint256) votes; } - //mapping from proposalID to CompetitionProposal - mapping(bytes32=>CompetitionProposal) public competitionProposals; + //mapping from proposalID to Proposal + mapping(bytes32=>Proposal) public proposals; //mapping from suggestionId to Suggestion mapping(uint256=>Suggestion) public suggestions; @@ -70,6 +89,13 @@ contract Competition { "start time greater than end time"); require(_competitionProposal.numberOfVotesPerVoters > 0, "numberOfVotesPerVoters should be greater than 0"); + + uint256 totalRewardSplit; + for (uint256 i = 0; i < _competitionProposal.numberOfWinners; i++) { + totalRewardSplit = totalRewardSplit.add(_competitionProposal.rewardSplit[i]); + } + require(totalRewardSplit == 100, "total rewards split is not 100%"); + proposalId = ContributionRewardExt(_competitionProposal.contributionReward).proposeContributionReward( _descriptionHash, _contributionProposal.reputationChange, @@ -79,7 +105,9 @@ contract Competition { _contributionProposal.externalToken, _competitionProposal.contributionReward, msg.sender); - competitionProposals[proposalId] = _competitionProposal; + // solhint-disable-next-line not-rely-on-time + proposals[proposalId].snapshotBlock = block.number + (_competitionProposal.votingStartTime.sub(now)).div(15); + proposals[proposalId].competitionProposal = _competitionProposal; emit NewCompetitionProposal( proposalId, @@ -87,8 +115,10 @@ contract Competition { _competitionProposal.rewardSplit, _competitionProposal.startTime, _competitionProposal.votingStartTime, - _reputationChange.endTime, - _reputationChange.contributionReward + _competitionProposal.endTime, + _competitionProposal.numberOfVotesPerVoters, + _competitionProposal.contributionReward, + proposals[proposalId].snapshotBlock ); } @@ -99,30 +129,108 @@ contract Competition { public returns(uint256) { - require(competitionProposals[_proposalId].startTime <= now, "competition not started yet"); - require(competitionProposals[_proposalId].endTime > now, "competition ended"); - suggestionId = suggestionId.add(1); - suggestion[suggestionId].proposalId = _proposalId; - emit NewSuggestion(_proposalId, suggestionId, _descriptionHash); - return suggestionId; + // solhint-disable-next-line not-rely-on-time + require(proposals[_proposalId].competitionProposal.startTime <= now, "competition not started yet"); + // solhint-disable-next-line not-rely-on-time + require(proposals[_proposalId].competitionProposal.endTime > now, "competition ended"); + suggestionsCounter = suggestionsCounter.add(1); + suggestions[suggestionsCounter].proposalId = _proposalId; + suggestions[suggestionsCounter].suggester = msg.sender; + emit NewSuggestion(_proposalId, suggestionsCounter, _descriptionHash, msg.sender); + return suggestionsCounter; } function vote(uint256 _suggestionId) public - returns(bytes32) + returns(bool) { - require(competitionProposals[_proposalId].votingStartTime > now, "votingh period not started yet"); - require(competitionProposals[_proposalId].endTime < now, "competition ended"); - bytes32 proposalId = suggestions[_suggestionId]._proposalId; + bytes32 proposalId = suggestions[_suggestionId].proposalId; require(proposalId != bytes32(0), "suggestion not exist"); - require(votes[msg.sender] < competitionProposals[proposalId].numberOfVotesPerVoters, - "exceed number of votes allowd"); - votes[msg.sender] = votes[msg.sender].add(1); - - suggestionId = suggestionId.add(1); - emit Suggestion(_proposalId, suggestionId, _descriptionHash); - return suggestionId; + CompetitionProposal memory competitionProposal = proposals[proposalId].competitionProposal; + // solhint-disable-next-line not-rely-on-time + require(competitionProposal.votingStartTime > now, "votingh period not started yet"); + // solhint-disable-next-line not-rely-on-time + require(competitionProposal.endTime < now, "competition ended"); + Suggestion storage suggestion = suggestions[_suggestionId]; + require(suggestion.votes[msg.sender] == 0, "already voted on this suggestion"); + require(suggestion.votes[msg.sender] < competitionProposal.numberOfVotesPerVoters, + "exceed number of votes allowed"); + + suggestion.votes[msg.sender] = suggestion.votes[msg.sender].add(1); + Avatar avatar = ContributionRewardExt(competitionProposal.contributionReward).avatar(); + uint256 reputation = avatar.nativeReputation().balanceOfAt(msg.sender, proposals[proposalId].snapshotBlock); + suggestion.totalVotes = suggestion.totalVotes.add(reputation); + suggestion.votes[msg.sender] = reputation; + + if (suggestions[_suggestionId].totalVotes > + suggestions[proposals[proposalId]. + topSuggestionsOrdered[competitionProposal.numberOfWinners - 1]].totalVotes) { + refreshTopXOrdered(proposalId, _suggestionId); + } + + emit NewVote(proposalId, _suggestionId, msg.sender, reputation); + return true; } + function redeem(uint256 _suggestionId, address payable _beneficiary) public { + bytes32 proposalId = suggestions[_suggestionId].proposalId; + require(proposalId != bytes32(0), "suggestion not exist"); + CompetitionProposal memory competitionProposal = proposals[proposalId].competitionProposal; + // solhint-disable-next-line not-rely-on-time + require(competitionProposal.endTime > now, "competition is still on"); + uint256 amount; + //check if there is a win + for (uint256 i = 0; i < competitionProposal.numberOfWinners; i++) { + if (suggestions[proposals[proposalId].topSuggestionsOrdered[i]].suggester == _beneficiary) { + require(proposals[proposalId].topSuggestionsOrdered[i] == _suggestionId, "wrong suggestionId"); + suggestions[_suggestionId].suggester = address(0); + amount = ContributionRewardExt(competitionProposal.contributionReward) + .getProposalExternalTokenReward(proposalId).mul(competitionProposal.rewardSplit[i]).div(100); + ContributionRewardExt(competitionProposal.contributionReward).redeemExternalTokenFromExtContract( + proposalId, + _beneficiary, + amount); + + amount = uint256(ContributionRewardExt(competitionProposal.contributionReward) + .getProposalReputationReward(proposalId)).mul(competitionProposal.rewardSplit[i]).div(100); + ContributionRewardExt(competitionProposal.contributionReward).redeemReputationFromExtContract( + proposalId, + _beneficiary, + amount); + + amount = ContributionRewardExt(competitionProposal.contributionReward) + .getProposalEthReward(proposalId).mul(competitionProposal.rewardSplit[i]).div(100); + ContributionRewardExt(competitionProposal.contributionReward).redeemEtherFromExtContract( + proposalId, + _beneficiary, + amount); + + amount = ContributionRewardExt(competitionProposal.contributionReward) + .getProposalNativeTokenReward(proposalId).mul(competitionProposal.rewardSplit[i]).div(100); + ContributionRewardExt(competitionProposal.contributionReward).redeemNativeTokenFromExtContract( + proposalId, + _beneficiary, + amount); + break; + } + } + } + function refreshTopXOrdered(bytes32 _proposalId, uint256 _suggestionId) internal { + uint256[MAX_NUMBER_OF_WINNERS] storage topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; + uint i = 0; + /** get the index of the current max element **/ + for (i; i < proposals[_proposalId].competitionProposal.numberOfWinners; i++) { + if (suggestions[topSuggestionsOrdered[i]].totalVotes < + suggestions[_suggestionId].totalVotes) { + break; + } + } + /** shift the array of one position (getting rid of the last element) **/ + for (uint j = proposals[_proposalId].competitionProposal.numberOfWinners - 1; j > i; j--) { + topSuggestionsOrdered[j] = topSuggestionsOrdered[j - 1]; + } + /** update the new max element **/ + topSuggestionsOrdered[i] = _suggestionId; + } } diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index e21f5920..83c0990c 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -437,6 +437,14 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa return address(organizationProposals[_proposalId].externalToken); } + function getProposalReputationReward(bytes32 _proposalId) public view returns (int256) { + return organizationProposals[_proposalId].reputationChange; + } + + function getProposalNativeTokenReward(bytes32 _proposalId) public view returns (uint256) { + return organizationProposals[_proposalId].nativeTokenReward; + } + function getProposalExecutionTime(bytes32 _proposalId) public view returns (uint256) { return organizationProposals[_proposalId].executionTime; } From debc26f359f1ef1e365bf11ad8ca06c4a39de1d8 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Mon, 2 Dec 2019 15:22:54 +0200 Subject: [PATCH 05/21] remove experimental pragma --- contracts/schemes/Competition.sol | 121 ++--- test/contributionrewardext.js | 703 ++++++++++++++++++++++++++++++ 2 files changed, 773 insertions(+), 51 deletions(-) create mode 100644 test/contributionrewardext.js diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index e49b8dd2..6d3ae8ee 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -1,5 +1,4 @@ pragma solidity 0.5.13; -pragma experimental ABIEncoderV2; import "./ContributionRewardExt.sol"; @@ -62,7 +61,8 @@ contract Competition { } //mapping from proposalID to Proposal - mapping(bytes32=>Proposal) public proposals; + //this is private due to avoid use of pragma experimental ABIEncoderV2; + mapping(bytes32=>Proposal) private proposals; //mapping from suggestionId to Suggestion mapping(uint256=>Suggestion) public suggestions; @@ -73,51 +73,66 @@ contract Competition { function proposeCompetition( string memory _descriptionHash, - ContributionRewardExt.ContributionProposal memory _contributionProposal, - CompetitionProposal memory _competitionProposal + int256 _reputationChange, + uint[3] memory _rewards, + IERC20 _externalToken, + uint256 _numberOfWinners, + uint256[MAX_NUMBER_OF_WINNERS] memory _rewardSplit, + uint256 _startTime, + uint256 _votingStartTime, + uint256 _endTime, + uint256 _numberOfVotesPerVoters, + address payable _contributionReward //address of the contract to redeem from. ) public returns(bytes32 proposalId) { - require(_competitionProposal.numberOfWinners <= MAX_NUMBER_OF_WINNERS, + require(_numberOfWinners <= MAX_NUMBER_OF_WINNERS, "number of winners greater than max allowed"); - require(_competitionProposal.votingStartTime < _competitionProposal.endTime, + require(_votingStartTime < _endTime, "voting start time greater than end time"); - require(_competitionProposal.votingStartTime >= _competitionProposal.startTime, + require(_votingStartTime >= _startTime, "voting start time smaller than start time"); - require(_competitionProposal.startTime < _competitionProposal.endTime, + require(_startTime < _endTime, "start time greater than end time"); - require(_competitionProposal.numberOfVotesPerVoters > 0, + require(_numberOfVotesPerVoters > 0, "numberOfVotesPerVoters should be greater than 0"); uint256 totalRewardSplit; - for (uint256 i = 0; i < _competitionProposal.numberOfWinners; i++) { - totalRewardSplit = totalRewardSplit.add(_competitionProposal.rewardSplit[i]); + for (uint256 i = 0; i < _numberOfWinners; i++) { + totalRewardSplit = totalRewardSplit.add(_rewardSplit[i]); } require(totalRewardSplit == 100, "total rewards split is not 100%"); - proposalId = ContributionRewardExt(_competitionProposal.contributionReward).proposeContributionReward( + proposalId = ContributionRewardExt(_contributionReward).proposeContributionReward( _descriptionHash, - _contributionProposal.reputationChange, - [_contributionProposal.nativeTokenReward, - _contributionProposal.ethReward, - _contributionProposal.externalTokenReward], - _contributionProposal.externalToken, - _competitionProposal.contributionReward, + _reputationChange, + _rewards, + _externalToken, + _contributionReward, msg.sender); // solhint-disable-next-line not-rely-on-time - proposals[proposalId].snapshotBlock = block.number + (_competitionProposal.votingStartTime.sub(now)).div(15); - proposals[proposalId].competitionProposal = _competitionProposal; + proposals[proposalId].snapshotBlock = block.number + (_votingStartTime.sub(now)).div(15); + + proposals[proposalId].competitionProposal = CompetitionProposal({ + numberOfWinners: _numberOfWinners, + rewardSplit: _rewardSplit, + startTime: _startTime, + votingStartTime: _votingStartTime, + endTime: _endTime, + numberOfVotesPerVoters: _numberOfVotesPerVoters, + contributionReward: _contributionReward + }); emit NewCompetitionProposal( proposalId, - _competitionProposal.numberOfWinners, - _competitionProposal.rewardSplit, - _competitionProposal.startTime, - _competitionProposal.votingStartTime, - _competitionProposal.endTime, - _competitionProposal.numberOfVotesPerVoters, - _competitionProposal.contributionReward, + _numberOfWinners, + _rewardSplit, + _startTime, + _votingStartTime, + _endTime, + _numberOfVotesPerVoters, + _contributionReward, proposals[proposalId].snapshotBlock ); } @@ -161,13 +176,7 @@ contract Competition { uint256 reputation = avatar.nativeReputation().balanceOfAt(msg.sender, proposals[proposalId].snapshotBlock); suggestion.totalVotes = suggestion.totalVotes.add(reputation); suggestion.votes[msg.sender] = reputation; - - if (suggestions[_suggestionId].totalVotes > - suggestions[proposals[proposalId]. - topSuggestionsOrdered[competitionProposal.numberOfWinners - 1]].totalVotes) { - refreshTopXOrdered(proposalId, _suggestionId); - } - + refreshTopSuggestions(proposalId, _suggestionId); emit NewVote(proposalId, _suggestionId, msg.sender, reputation); return true; } @@ -182,31 +191,31 @@ contract Competition { //check if there is a win for (uint256 i = 0; i < competitionProposal.numberOfWinners; i++) { if (suggestions[proposals[proposalId].topSuggestionsOrdered[i]].suggester == _beneficiary) { - require(proposals[proposalId].topSuggestionsOrdered[i] == _suggestionId, "wrong suggestionId"); + uint256 orderIndex = getOrderedIndexOfSuggestion(proposalId, _suggestionId); suggestions[_suggestionId].suggester = address(0); amount = ContributionRewardExt(competitionProposal.contributionReward) - .getProposalExternalTokenReward(proposalId).mul(competitionProposal.rewardSplit[i]).div(100); + .getProposalExternalTokenReward(proposalId).mul(competitionProposal.rewardSplit[orderIndex]).div(100); ContributionRewardExt(competitionProposal.contributionReward).redeemExternalTokenFromExtContract( proposalId, _beneficiary, amount); amount = uint256(ContributionRewardExt(competitionProposal.contributionReward) - .getProposalReputationReward(proposalId)).mul(competitionProposal.rewardSplit[i]).div(100); + .getProposalReputationReward(proposalId)).mul(competitionProposal.rewardSplit[orderIndex]).div(100); ContributionRewardExt(competitionProposal.contributionReward).redeemReputationFromExtContract( proposalId, _beneficiary, amount); amount = ContributionRewardExt(competitionProposal.contributionReward) - .getProposalEthReward(proposalId).mul(competitionProposal.rewardSplit[i]).div(100); + .getProposalEthReward(proposalId).mul(competitionProposal.rewardSplit[orderIndex]).div(100); ContributionRewardExt(competitionProposal.contributionReward).redeemEtherFromExtContract( proposalId, _beneficiary, amount); amount = ContributionRewardExt(competitionProposal.contributionReward) - .getProposalNativeTokenReward(proposalId).mul(competitionProposal.rewardSplit[i]).div(100); + .getProposalNativeTokenReward(proposalId).mul(competitionProposal.rewardSplit[orderIndex]).div(100); ContributionRewardExt(competitionProposal.contributionReward).redeemNativeTokenFromExtContract( proposalId, _beneficiary, @@ -216,21 +225,31 @@ contract Competition { } } - function refreshTopXOrdered(bytes32 _proposalId, uint256 _suggestionId) internal { + function refreshTopSuggestions(bytes32 _proposalId, uint256 _suggestionId) internal { uint256[MAX_NUMBER_OF_WINNERS] storage topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; - uint i = 0; - /** get the index of the current max element **/ - for (i; i < proposals[_proposalId].competitionProposal.numberOfWinners; i++) { - if (suggestions[topSuggestionsOrdered[i]].totalVotes < - suggestions[_suggestionId].totalVotes) { - break; + /** get the index of the smallest element **/ + uint256 smallest = 0; + for (uint256 i; i < proposals[_proposalId].competitionProposal.numberOfWinners; i++) { + if (suggestions[topSuggestionsOrdered[i]].totalVotes > smallest) { + smallest++; } } - /** shift the array of one position (getting rid of the last element) **/ - for (uint j = proposals[_proposalId].competitionProposal.numberOfWinners - 1; j > i; j--) { - topSuggestionsOrdered[j] = topSuggestionsOrdered[j - 1]; + + if (suggestions[topSuggestionsOrdered[smallest]].totalVotes < suggestions[_suggestionId].totalVotes) { + topSuggestionsOrdered[smallest] = _suggestionId; + } + } + + function getOrderedIndexOfSuggestion(bytes32 _proposalId, uint256 _suggestionId) + internal + view + returns(uint256 index) { + uint256[MAX_NUMBER_OF_WINNERS] memory topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; + /** get how many elements are greater than a given element **/ + for (uint256 i; i < proposals[_proposalId].competitionProposal.numberOfWinners; i++) { + if (suggestions[topSuggestionsOrdered[i]].totalVotes > suggestions[_suggestionId].totalVotes) { + index++; + } } - /** update the new max element **/ - topSuggestionsOrdered[i] = _suggestionId; } } diff --git a/test/contributionrewardext.js b/test/contributionrewardext.js new file mode 100644 index 00000000..42cad90b --- /dev/null +++ b/test/contributionrewardext.js @@ -0,0 +1,703 @@ +import * as helpers from './helpers'; +const constants = require('./constants'); +const ContributionRewardExt = artifacts.require("./ContributionRewardExt.sol"); +const ERC20Mock = artifacts.require('./test/ERC20Mock.sol'); +const DaoCreator = artifacts.require("./DaoCreator.sol"); +const ControllerCreator = artifacts.require("./ControllerCreator.sol"); +const DAOTracker = artifacts.require("./DAOTracker.sol"); +const Avatar = artifacts.require("./Avatar.sol"); +const Redeemer = artifacts.require("./Redeemer.sol"); + + + +export class ContributionRewardParams { + constructor() { + } +} + +const setupContributionRewardParams = async function( + contributionReward, + accounts, + genesisProtocol, + token, + avatar, + redeemer = helpers.NULL_ADDRESS + ) { + var contributionRewardParams = new ContributionRewardParams(); + if (genesisProtocol === true) { + contributionRewardParams.votingMachine = await helpers.setupGenesisProtocol(accounts,token,avatar,helpers.NULL_ADDRESS); + await contributionReward.initialize( avatar.address, + contributionRewardParams.votingMachine.genesisProtocol.address, + contributionRewardParams.votingMachine.params, + redeemer); + } else { + contributionRewardParams.votingMachine = await helpers.setupAbsoluteVote(helpers.NULL_ADDRESS,50,contributionReward.address); + await contributionReward.initialize( + avatar.address, + contributionRewardParams.votingMachine.absoluteVote.address, + contributionRewardParams.votingMachine.params, + redeemer + ); + } + return contributionRewardParams; +}; + +const setup = async function (accounts,genesisProtocol = false,tokenAddress=0) { + var testSetup = new helpers.TestSetup(); + testSetup.standardTokenMock = await ERC20Mock.new(accounts[1],100); + testSetup.contributionRewardExt = await ContributionRewardExt.new(); + var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT}); + var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT}); + testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT}); + if (genesisProtocol) { + testSetup.reputationArray = [1000,100,0]; + } else { + testSetup.reputationArray = [2000,4000,7000]; + } + testSetup.org = await helpers.setupOrganizationWithArrays(testSetup.daoCreator,[accounts[0],accounts[1],accounts[2]],[1000,0,0],testSetup.reputationArray); + testSetup.contributionRewardExtParams= await setupContributionRewardParams( + testSetup.contributionRewardExt, + accounts,genesisProtocol, + tokenAddress, + testSetup.org.avatar); + var permissions = "0x00000000"; + await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address, + [testSetup.contributionRewardExt.address], + [helpers.NULL_HASH],[permissions],"metaData"); + return testSetup; +}; +contract('ContributionRewardExt', accounts => { + + it("initialize", async function() { + var testSetup = await setup(accounts); + assert.equal(await testSetup.contributionRewardExt.votingMachine(),testSetup.contributionRewardExtParams.votingMachine.absoluteVote.address); + }); + + // it("proposeContributionReward log", async function() { + // var testSetup = await setup(accounts); + // var periodLength = 1; + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // "description-hash", + // 10, + // [1,2,3,periodLength,5], + // testSetup.standardTokenMock.address, + // accounts[0]); + // assert.equal(tx.logs.length, 1); + // assert.equal(tx.logs[0].event, "NewContributionProposal"); + // assert.equal(await helpers.getValueFromLogs(tx, '_avatar',0), testSetup.org.avatar.address, "Wrong log: _avatar"); + // assert.equal(await helpers.getValueFromLogs(tx, '_intVoteInterface',0), testSetup.contributionRewardExtParams.votingMachine.absoluteVote.address, "Wrong log: _intVoteInterface"); + // assert.equal(await helpers.getValueFromLogs(tx, '_descriptionHash',15), "description-hash", "Wrong log: _contributionDescription"); + // assert.equal(await helpers.getValueFromLogs(tx, '_reputationChange',0), 10, "Wrong log: _reputationChange"); + // var arr = await helpers.getValueFromLogs(tx, '_rewards',0); + // assert.equal(arr[0].words[0], 1, "Wrong log: _rewards"); + // assert.equal(arr[1].words[0], 2, "Wrong log: _rewards"); + // assert.equal(arr[2].words[0], 3, "Wrong log: _rewards"); + // assert.equal(arr[3].words[0], periodLength, "Wrong log: _rewards"); + // assert.equal(arr[4].words[0], 5, "Wrong log: _rewards"); + // assert.equal(await helpers.getValueFromLogs(tx, '_externalToken',0), testSetup.standardTokenMock.address, "Wrong log: _externalToken"); + // assert.equal(await helpers.getValueFromLogs(tx, '_beneficiary',0), accounts[0], "Wrong log: _beneficiary"); + // }); + // + // it("proposeContributionReward check beneficiary==0", async() => { + // var testSetup = await setup(accounts); + // var beneficiary = helpers.NULL_ADDRESS; + // var periodLength = 1; + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // 0, + // [0,0,0,periodLength,0], + // testSetup.standardTokenMock.address, + // beneficiary + // ); + // assert.equal(await helpers.getValueFromLogs(tx, '_beneficiary'),accounts[0]); + // }); + // + // it("execute proposeContributionReward yes ", async function() { + // var testSetup = await setup(accounts); + // var periodLength = 1; + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // 0, + // [0,0,0,periodLength,0], + // testSetup.standardTokenMock.address, + // accounts[0] + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // var organizationProposal = await testSetup.contributionRewardExt.organizationsProposals(testSetup.org.avatar.address,proposalId); + // assert.notEqual(organizationProposal[8],0);//executionTime + // }); + // + // it("execute proposeContributionReward mint reputation ", async function() { + // var testSetup = await setup(accounts); + // var reputationReward = 12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [0,0,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[1] + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // await helpers.increaseTime(periodLength+1); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,false,false,false]); + // assert.equal(tx.logs.length, 1); + // assert.equal(tx.logs[0].event, "RedeemReputation"); + // assert.equal(tx.logs[0].args._amount, reputationReward); + // var rep = await testSetup.org.reputation.balanceOf(accounts[1]); + // assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); + // }); + // + // it("execute proposeContributionReward mint tokens ", async function() { + // var testSetup = await setup(accounts); + // var reputationReward = 12; + // var nativeTokenReward = 12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,0,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[1] + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // await helpers.increaseTime(periodLength+1); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,true,false,false]); + // var tokens = await testSetup.org.token.balanceOf(accounts[1]); + // assert.equal(tokens.toNumber(),nativeTokenReward); + // }); + // + // it("execute proposeContributionReward send ethers ", async function() { + // var testSetup = await setup(accounts); + // var reputationReward = 12; + // var nativeTokenReward = 12; + // var ethReward = 12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // //send some ether to the org avatar + // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // otherAvatar.address + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // await helpers.increaseTime(periodLength+1); + // await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // var eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward); + // }); + // + // it("execute proposeContributionReward send externalToken ", async function() { + // var testSetup = await setup(accounts); + // //give some tokens to organization avatar + // await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,30,{from:accounts[1]}); + // var reputationReward = 12; + // var nativeTokenReward = 12; + // var ethReward = 12; + // var externalTokenReward = 12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // //send some ether to the org avatar + // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // otherAvatar.address + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // await helpers.increaseTime(periodLength+1); + // await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,false,true]); + // var tokens = await testSetup.standardTokenMock.balanceOf(otherAvatar.address); + // assert.equal(tokens.toNumber(),externalTokenReward); + // }); + // + // it("execute proposeContributionReward proposal decision=='no' send externalToken ", async function() { + // var testSetup = await setup(accounts); + // var reputationReward = 12; + // var nativeTokenReward = 12; + // var ethReward = 12; + // var externalTokenReward = 12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // //send some ether to the org avatar + // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // otherAvatar.address + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // var organizationProposal = await testSetup.contributionRewardExt.organizationsProposals(testSetup.org.avatar.address,proposalId); + // assert.equal(organizationProposal[5],otherAvatar.address);//beneficiary + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,0,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // await helpers.increaseTime(periodLength+1); + // try { + // await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,true,true,true]); + // assert(false, 'redeem should revert because there was no positive voting'); + // } catch (ex) { + // helpers.assertVMException(ex); + // } + // }); + // + // + // it("redeem periods ether ", async function() { + // var testSetup = await setup(accounts); + // var reputationReward = 0; + // var nativeTokenReward = 0; + // var ethReward = 3; + // var periodLength = 50; + // var numberOfPeriods = 5; + // //send some ether to the org avatar + // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:12}); + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // otherAvatar.address + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // await helpers.increaseTime(periodLength+1); + // + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // + // assert.equal(tx.logs.length, 1); + // assert.equal(tx.logs[0].event, "RedeemEther"); + // assert.equal(tx.logs[0].args._amount, ethReward); + // var eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward); + // + // //now try again on the same period + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // assert.equal(tx.logs.length, 0); + // eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward); + // + // //now try again on 2nd period + // await helpers.increaseTime(periodLength+1); + // + // + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // assert.equal(tx.logs.length, 1); + // assert.equal(tx.logs[0].event, "RedeemEther"); + // assert.equal(tx.logs[0].args._amount, ethReward); + // eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward*2); + // + // //now try again on 4th period + // await helpers.increaseTime((periodLength*2)+1); + // + // + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // assert.equal(tx.logs.length, 1); + // assert.equal(tx.logs[0].event, "RedeemEther"); + // assert.equal(tx.logs[0].args._amount, ethReward*2); + // eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward*4); + // + // //now try again on 5th period - no ether on avatar should revert + // await helpers.increaseTime(periodLength+1); + // try { + // await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // assert(false, 'redeem should revert because no ether left on avatar'); + // } catch (ex) { + // helpers.assertVMException(ex); + // } + // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:ethReward}); + // + // + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // assert.equal(tx.logs.length, 1); + // assert.equal(tx.logs[0].event, "RedeemEther"); + // assert.equal(tx.logs[0].args._amount, ethReward); + // eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward*5); + // + // + // + // //cannot redeem any more.. + // await helpers.increaseTime(periodLength+1); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // assert.equal(tx.logs.length, 0); + // eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward*5); + // + // }); + // + // it("execute proposeContributionReward mint negative reputation ", async function() { + // var testSetup = await setup(accounts); + // var reputationReward = -12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [0,0,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[0] + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // await helpers.increaseTime(periodLength+1); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,false,false,false]); + // assert.equal(tx.logs.length, 1); + // assert.equal(tx.logs[0].event, "RedeemReputation"); + // assert.equal(tx.logs[0].args._amount, reputationReward); + // var rep = await testSetup.org.reputation.balanceOf(accounts[0]); + // assert.equal(rep.toNumber(),testSetup.reputationArray[0]+reputationReward); + // }); + // + // + // it("call execute should revert ", async function() { + // var testSetup = await setup(accounts); + // var reputationReward = -12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [0,0,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[0] + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // try { + // await testSetup.contributionRewardExt.executeProposal(proposalId,1); + // assert(false, 'only voting machine can call execute'); + // } catch (ex) { + // helpers.assertVMException(ex); + // } + // + // }); + // + // it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer", async function() { + // var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + // var testSetup = await setup(accounts,true,standardTokenMock.address); + // var reputationReward = 12; + // var nativeTokenReward = 12; + // var ethReward = 12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // //send some ether to the org avatar + // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // otherAvatar.address + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[0]}); + // await helpers.increaseTime(periodLength+1); + // var arcUtils = await Redeemer.new(); + // var redeemRewards = await arcUtils.redeem.call(testSetup.contributionRewardExt.address, + // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + // proposalId,testSetup.org.avatar.address, + // accounts[0]); + // assert.equal(redeemRewards[0][1],100); //redeemRewards[0] gpRewards + // assert.equal(redeemRewards[0][2],60); + // assert.equal(redeemRewards[1][0],0); //daoBountyRewards + // assert.equal(redeemRewards[1][1],0); //daoBountyRewards + // assert.equal(redeemRewards[2],false); //isExecuted + // assert.equal(redeemRewards[3],1); //winningVote + // assert.equal(redeemRewards[4],reputationReward); //crReputationReward + // assert.equal(redeemRewards[5],nativeTokenReward); //crNativeTokenReward + // assert.equal(redeemRewards[6],ethReward); //crEthReward + // assert.equal(redeemRewards[7],0); //crExternalTokenReward + // + // await arcUtils.redeem(testSetup.contributionRewardExt.address, + // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + // proposalId,testSetup.org.avatar.address, + // accounts[0]); + // + // var eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward); + // assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),reputationReward); + // assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),nativeTokenReward); + // var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); + // var reputationGainAsVoter = 0; + // var proposingRepRewardConstA=60; + // var reputationGainAsProposer = proposingRepRewardConstA; + // assert.equal(reputation, 1000+reputationGainAsVoter + reputationGainAsProposer); + // }); + // + // it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer for un excuted boosted proposal", async function() { + // var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + // var testSetup = await setup(accounts,true,standardTokenMock.address); + // var reputationReward = 12; + // var nativeTokenReward = 12; + // var ethReward = 12; + // var periodLength = 0; + // var numberOfPeriods = 1; + // //send some ether to the org avatar + // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // otherAvatar.address + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // + // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[1]}); + // await standardTokenMock.approve(testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address,1000); + // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.stake(proposalId,1,1000); + // await helpers.increaseTime(60+1); + // var arcUtils = await Redeemer.new(); + // var redeemRewards = await arcUtils.redeem.call(testSetup.contributionRewardExt.address, + // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + // proposalId,testSetup.org.avatar.address, + // accounts[0]); + // assert.equal(redeemRewards[0][1],0); //redeemRewards[0] gpRewards + // assert.equal(redeemRewards[0][2],60); + // assert.equal(redeemRewards[1][0],0); //daoBountyRewards + // assert.equal(redeemRewards[1][1],15); //daoBountyRewards + // assert.equal(redeemRewards[2],true); //isExecuted + // assert.equal(redeemRewards[3],1); //winningVote + // assert.equal(redeemRewards[4],reputationReward); //crReputationReward + // assert.equal(redeemRewards[5],nativeTokenReward); //crNativeTokenReward + // assert.equal(redeemRewards[6],ethReward); //crEthReward + // assert.equal(redeemRewards[7],0); //crExternalTokenReward + // + // await arcUtils.redeem(testSetup.contributionRewardExt.address, + // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + // proposalId,testSetup.org.avatar.address, + // accounts[0]); + // + // var eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,ethReward); + // assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),reputationReward); + // assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),nativeTokenReward); + // var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); + // var reputationGainAsVoter = 0; + // var proposingRepRewardConstA=60; + // var reputationGainAsProposer = proposingRepRewardConstA; + // assert.equal(reputation, 1000+reputationGainAsVoter + reputationGainAsProposer); + // }); + // + // it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer for negative proposal", async function() { + // var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + // var testSetup = await setup(accounts,true,standardTokenMock.address); + // var reputationReward = 12; + // var nativeTokenReward = 12; + // var ethReward = 12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // //send some ether to the org avatar + // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // otherAvatar.address + // ); + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,2,0,helpers.NULL_ADDRESS,{from:accounts[0]}); + // await helpers.increaseTime(periodLength+1); + // var arcUtils = await Redeemer.new(); + // await arcUtils.redeem(testSetup.contributionRewardExt.address, + // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + // proposalId, + // testSetup.org.avatar.address, + // accounts[0]); + // var eth = await web3.eth.getBalance(otherAvatar.address); + // assert.equal(eth,0); + // assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),0); + // assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),0); + // var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); + // //no reputation reward for proposer for negative proposal. + // //reputation reward for a single voter = 0 + // assert.equal(reputation, 1000); + // }); + // + // it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer ExpiredInQueue", async function() { + // var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + // var testSetup = await setup(accounts,true,standardTokenMock.address); + // var reputationReward = 12; + // var nativeTokenReward = 12; + // var ethReward = 12; + // var periodLength = 50; + // var numberOfPeriods = 1; + // //send some ether to the org avatar + // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // otherAvatar.address + // ); + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // + // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[1]}); + // await helpers.increaseTime(60+1); + // var arcUtils = await Redeemer.new(); + // await arcUtils.redeem(testSetup.contributionRewardExt.address, + // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + // proposalId,testSetup.org.avatar.address, + // accounts[1]); + // var proposal = await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.proposals(proposalId); + // assert.equal(proposal.state,1); //ExpiredInQueue + // var reputation = await testSetup.org.reputation.balanceOf(accounts[1]); + // //accounts[1] redeems its deposit rep. + // assert.equal(reputation.toNumber(), 100); + // }); + // + // it("execute proposeContributionReward mint reputation with period 0 ", async function() { + // var testSetup = await setup(accounts); + // var reputationReward = 12; + // var periodLength = 0; + // var numberOfPeriods = 1; + // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [0,0,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[1], + // {from:accounts[2]} + // ); + // try { + // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [0,0,0,periodLength,2], + // testSetup.standardTokenMock.address, + // accounts[1], + // {from:accounts[2]} + // ); + // assert(false, 'if periodLength==0 so numberOfPeriods must be 1'); + // } catch (ex) { + // helpers.assertVMException(ex); + // } + // + // //Vote with reputation to trigger execution + // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,false,false,false]); + // assert.equal(tx.logs.length, 1); + // assert.equal(tx.logs[0].event, "RedeemReputation"); + // assert.equal(tx.logs[0].args._amount, reputationReward); + // var rep = await testSetup.org.reputation.balanceOf(accounts[1]); + // assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); + // //try to redeem again. + // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,false,false,false]); + // assert.equal(tx.logs.length, 0); + // rep = await testSetup.org.reputation.balanceOf(accounts[1]); + // assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); + // }); + // + // + // it("execute proposeContributionReward param validate ", async function() { + // var testSetup = await setup(accounts); + // let BigNumber = require('bignumber.js'); + // let reputationReward = ((new BigNumber(2)).toPower(255).sub(1)).toString(10); + // var periodLength = 1; + // var numberOfPeriods = 2; + // + // try { + // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [0,0,0,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[1], + // {from:accounts[2]} + // ); + // assert(false, 'numberOfPeriods * _reputationChange should not overflow'); + // } catch (ex) { + // helpers.assertVMException(ex); + // } + // + // reputationReward = 12; + // var tokenReward = ((new BigNumber(2)).toPower(256).sub(1)).toString(10); + // var ethReward = 0; + // var externalTokenReward = 0; + // + // try { + // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[1], + // {from:accounts[2]} + // ); + // assert(false, 'numberOfPeriods * tokenReward should not overflow'); + // } catch (ex) { + // helpers.assertVMException(ex); + // } + // + // tokenReward = 0; + // ethReward = ((new BigNumber(2)).toPower(256).sub(1)).toString(10); + // externalTokenReward = 0; + // + // try { + // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[1], + // {from:accounts[2]} + // ); + // assert(false, 'numberOfPeriods * ethReward should not overflow'); + // } catch (ex) { + // helpers.assertVMException(ex); + // } + // + // tokenReward = 0; + // ethReward = 0; + // externalTokenReward = ((new BigNumber(2)).toPower(256).sub(1)).toString(10); + // + // try { + // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // web3.utils.asciiToHex("description"), + // reputationReward, + // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], + // testSetup.standardTokenMock.address, + // accounts[1], + // {from:accounts[2]} + // ); + // assert(false, 'numberOfPeriods * externalTokenReward should not overflow'); + // } catch (ex) { + // helpers.assertVMException(ex); + // } + // }); +}); From cb0883b9f4ff143980010fc4fea3295b50befb52 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Mon, 2 Dec 2019 19:18:22 +0200 Subject: [PATCH 06/21] wip add contributionrewardext test --- test/contributionrewardext.js | 356 +++++++++++++++++----------------- 1 file changed, 173 insertions(+), 183 deletions(-) diff --git a/test/contributionrewardext.js b/test/contributionrewardext.js index 42cad90b..885cb1e6 100644 --- a/test/contributionrewardext.js +++ b/test/contributionrewardext.js @@ -73,163 +73,153 @@ contract('ContributionRewardExt', accounts => { assert.equal(await testSetup.contributionRewardExt.votingMachine(),testSetup.contributionRewardExtParams.votingMachine.absoluteVote.address); }); - // it("proposeContributionReward log", async function() { - // var testSetup = await setup(accounts); - // var periodLength = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, - // "description-hash", - // 10, - // [1,2,3,periodLength,5], - // testSetup.standardTokenMock.address, - // accounts[0]); - // assert.equal(tx.logs.length, 1); - // assert.equal(tx.logs[0].event, "NewContributionProposal"); - // assert.equal(await helpers.getValueFromLogs(tx, '_avatar',0), testSetup.org.avatar.address, "Wrong log: _avatar"); - // assert.equal(await helpers.getValueFromLogs(tx, '_intVoteInterface',0), testSetup.contributionRewardExtParams.votingMachine.absoluteVote.address, "Wrong log: _intVoteInterface"); - // assert.equal(await helpers.getValueFromLogs(tx, '_descriptionHash',15), "description-hash", "Wrong log: _contributionDescription"); - // assert.equal(await helpers.getValueFromLogs(tx, '_reputationChange',0), 10, "Wrong log: _reputationChange"); - // var arr = await helpers.getValueFromLogs(tx, '_rewards',0); - // assert.equal(arr[0].words[0], 1, "Wrong log: _rewards"); - // assert.equal(arr[1].words[0], 2, "Wrong log: _rewards"); - // assert.equal(arr[2].words[0], 3, "Wrong log: _rewards"); - // assert.equal(arr[3].words[0], periodLength, "Wrong log: _rewards"); - // assert.equal(arr[4].words[0], 5, "Wrong log: _rewards"); - // assert.equal(await helpers.getValueFromLogs(tx, '_externalToken',0), testSetup.standardTokenMock.address, "Wrong log: _externalToken"); - // assert.equal(await helpers.getValueFromLogs(tx, '_beneficiary',0), accounts[0], "Wrong log: _beneficiary"); - // }); - // - // it("proposeContributionReward check beneficiary==0", async() => { - // var testSetup = await setup(accounts); - // var beneficiary = helpers.NULL_ADDRESS; - // var periodLength = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, - // web3.utils.asciiToHex("description"), - // 0, - // [0,0,0,periodLength,0], - // testSetup.standardTokenMock.address, - // beneficiary - // ); - // assert.equal(await helpers.getValueFromLogs(tx, '_beneficiary'),accounts[0]); - // }); - // - // it("execute proposeContributionReward yes ", async function() { - // var testSetup = await setup(accounts); - // var periodLength = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, - // web3.utils.asciiToHex("description"), - // 0, - // [0,0,0,periodLength,0], - // testSetup.standardTokenMock.address, - // accounts[0] - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // var organizationProposal = await testSetup.contributionRewardExt.organizationsProposals(testSetup.org.avatar.address,proposalId); - // assert.notEqual(organizationProposal[8],0);//executionTime - // }); - // - // it("execute proposeContributionReward mint reputation ", async function() { - // var testSetup = await setup(accounts); - // var reputationReward = 12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, - // web3.utils.asciiToHex("description"), - // reputationReward, - // [0,0,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[1] - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // await helpers.increaseTime(periodLength+1); - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,false,false,false]); - // assert.equal(tx.logs.length, 1); - // assert.equal(tx.logs[0].event, "RedeemReputation"); - // assert.equal(tx.logs[0].args._amount, reputationReward); - // var rep = await testSetup.org.reputation.balanceOf(accounts[1]); - // assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); - // }); - // - // it("execute proposeContributionReward mint tokens ", async function() { - // var testSetup = await setup(accounts); - // var reputationReward = 12; - // var nativeTokenReward = 12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,0,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[1] - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // await helpers.increaseTime(periodLength+1); - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,true,false,false]); - // var tokens = await testSetup.org.token.balanceOf(accounts[1]); - // assert.equal(tokens.toNumber(),nativeTokenReward); - // }); - // - // it("execute proposeContributionReward send ethers ", async function() { - // var testSetup = await setup(accounts); - // var reputationReward = 12; - // var nativeTokenReward = 12; - // var ethReward = 12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // //send some ether to the org avatar - // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // otherAvatar.address - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // await helpers.increaseTime(periodLength+1); - // await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); - // var eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward); - // }); - // - // it("execute proposeContributionReward send externalToken ", async function() { - // var testSetup = await setup(accounts); - // //give some tokens to organization avatar - // await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,30,{from:accounts[1]}); - // var reputationReward = 12; - // var nativeTokenReward = 12; - // var ethReward = 12; - // var externalTokenReward = 12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // //send some ether to the org avatar - // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // otherAvatar.address - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // await helpers.increaseTime(periodLength+1); - // await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,false,true]); - // var tokens = await testSetup.standardTokenMock.balanceOf(otherAvatar.address); - // assert.equal(tokens.toNumber(),externalTokenReward); - // }); - // + it("proposeContributionReward log", async function() { + var testSetup = await setup(accounts); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + "description-hash", + 10, + [1,2,3], + testSetup.standardTokenMock.address, + accounts[0], + helpers.NULL_ADDRESS); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "NewContributionProposal"); + assert.equal(await helpers.getValueFromLogs(tx, '_avatar',0), testSetup.org.avatar.address, "Wrong log: _avatar"); + assert.equal(await helpers.getValueFromLogs(tx, '_intVoteInterface',0), testSetup.contributionRewardExtParams.votingMachine.absoluteVote.address, "Wrong log: _intVoteInterface"); + assert.equal(await helpers.getValueFromLogs(tx, '_descriptionHash',15), "description-hash", "Wrong log: _contributionDescription"); + assert.equal(await helpers.getValueFromLogs(tx, '_reputationChange',0), 10, "Wrong log: _reputationChange"); + var arr = await helpers.getValueFromLogs(tx, '_rewards',0); + assert.equal(arr[0].words[0], 1, "Wrong log: _rewards"); + assert.equal(arr[1].words[0], 2, "Wrong log: _rewards"); + assert.equal(arr[2].words[0], 3, "Wrong log: _rewards"); + assert.equal(await helpers.getValueFromLogs(tx, '_externalToken',0), testSetup.standardTokenMock.address, "Wrong log: _externalToken"); + assert.equal(await helpers.getValueFromLogs(tx, '_beneficiary',0), accounts[0], "Wrong log: _beneficiary"); + }); + + it("proposeContributionReward check beneficiary==0", async() => { + var testSetup = await setup(accounts); + var beneficiary = helpers.NULL_ADDRESS; + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + 0, + [0,0,0], + testSetup.standardTokenMock.address, + beneficiary, + helpers.NULL_ADDRESS + ); + assert.equal(await helpers.getValueFromLogs(tx, '_beneficiary'),accounts[0]); + }); + + it("execute proposeContributionReward yes ", async function() { + var testSetup = await setup(accounts); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + 0, + [0,0,0], + testSetup.standardTokenMock.address, + accounts[0], + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + var organizationProposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.notEqual(organizationProposal.executionTime,0);//executionTime + }); + + it("execute proposeContributionReward mint reputation ", async function() { + var testSetup = await setup(accounts); + var reputationReward = 12; + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [0,0,0], + testSetup.standardTokenMock.address, + accounts[1], + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "RedeemReputation"); + assert.equal(tx.logs[0].args._amount, reputationReward); + var rep = await testSetup.org.reputation.balanceOf(accounts[1]); + assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); + }); + + it("execute proposeContributionReward mint tokens ", async function() { + var testSetup = await setup(accounts); + var reputationReward = 12; + var nativeTokenReward = 12; + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,0,0], + testSetup.standardTokenMock.address, + accounts[1], + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,true,false,false]); + var tokens = await testSetup.org.token.balanceOf(accounts[1]); + assert.equal(tokens.toNumber(),nativeTokenReward); + }); + + it("execute proposeContributionReward send ethers ", async function() { + var testSetup = await setup(accounts); + var reputationReward = 12; + var nativeTokenReward = 12; + var ethReward = 12; + //send some ether to the org avatar + var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,ethReward,0], + testSetup.standardTokenMock.address, + otherAvatar.address, + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); + var eth = await web3.eth.getBalance(otherAvatar.address); + assert.equal(eth,ethReward); + }); + + it("execute proposeContributionReward send externalToken ", async function() { + var testSetup = await setup(accounts); + //give some tokens to organization avatar + await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,30,{from:accounts[1]}); + var reputationReward = 12; + var nativeTokenReward = 12; + var ethReward = 12; + var externalTokenReward = 12; + //send some ether to the org avatar + var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,ethReward,externalTokenReward], + testSetup.standardTokenMock.address, + otherAvatar.address, + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.contributionRewardExt.redeem(proposalId,[false,false,false,true]); + var tokens = await testSetup.standardTokenMock.balanceOf(otherAvatar.address); + assert.equal(tokens.toNumber(),externalTokenReward); + }); + // it("execute proposeContributionReward proposal decision=='no' send externalToken ", async function() { // var testSetup = await setup(accounts); // var reputationReward = 12; @@ -241,7 +231,7 @@ contract('ContributionRewardExt', accounts => { // //send some ether to the org avatar // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [nativeTokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], @@ -250,12 +240,12 @@ contract('ContributionRewardExt', accounts => { // ); // //Vote with reputation to trigger execution // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // var organizationProposal = await testSetup.contributionRewardExt.organizationsProposals(testSetup.org.avatar.address,proposalId); + // var organizationProposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); // assert.equal(organizationProposal[5],otherAvatar.address);//beneficiary // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,0,0,helpers.NULL_ADDRESS,{from:accounts[2]}); // await helpers.increaseTime(periodLength+1); // try { - // await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,true,true,true]); + // await testSetup.contributionRewardExt.redeem(proposalId,[true,true,true,true]); // assert(false, 'redeem should revert because there was no positive voting'); // } catch (ex) { // helpers.assertVMException(ex); @@ -273,7 +263,7 @@ contract('ContributionRewardExt', accounts => { // //send some ether to the org avatar // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:12}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], @@ -285,7 +275,7 @@ contract('ContributionRewardExt', accounts => { // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); // await helpers.increaseTime(periodLength+1); // - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); // // assert.equal(tx.logs.length, 1); // assert.equal(tx.logs[0].event, "RedeemEther"); @@ -294,7 +284,7 @@ contract('ContributionRewardExt', accounts => { // assert.equal(eth,ethReward); // // //now try again on the same period - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); // assert.equal(tx.logs.length, 0); // eth = await web3.eth.getBalance(otherAvatar.address); // assert.equal(eth,ethReward); @@ -303,7 +293,7 @@ contract('ContributionRewardExt', accounts => { // await helpers.increaseTime(periodLength+1); // // - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); // assert.equal(tx.logs.length, 1); // assert.equal(tx.logs[0].event, "RedeemEther"); // assert.equal(tx.logs[0].args._amount, ethReward); @@ -314,7 +304,7 @@ contract('ContributionRewardExt', accounts => { // await helpers.increaseTime((periodLength*2)+1); // // - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); // assert.equal(tx.logs.length, 1); // assert.equal(tx.logs[0].event, "RedeemEther"); // assert.equal(tx.logs[0].args._amount, ethReward*2); @@ -324,7 +314,7 @@ contract('ContributionRewardExt', accounts => { // //now try again on 5th period - no ether on avatar should revert // await helpers.increaseTime(periodLength+1); // try { - // await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); // assert(false, 'redeem should revert because no ether left on avatar'); // } catch (ex) { // helpers.assertVMException(ex); @@ -332,7 +322,7 @@ contract('ContributionRewardExt', accounts => { // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:ethReward}); // // - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); // assert.equal(tx.logs.length, 1); // assert.equal(tx.logs[0].event, "RedeemEther"); // assert.equal(tx.logs[0].args._amount, ethReward); @@ -343,7 +333,7 @@ contract('ContributionRewardExt', accounts => { // // //cannot redeem any more.. // await helpers.increaseTime(periodLength+1); - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[false,false,true,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); // assert.equal(tx.logs.length, 0); // eth = await web3.eth.getBalance(otherAvatar.address); // assert.equal(eth,ethReward*5); @@ -355,7 +345,7 @@ contract('ContributionRewardExt', accounts => { // var reputationReward = -12; // var periodLength = 50; // var numberOfPeriods = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [0,0,0,periodLength,numberOfPeriods], @@ -366,7 +356,7 @@ contract('ContributionRewardExt', accounts => { // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); // await helpers.increaseTime(periodLength+1); - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,false,false,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); // assert.equal(tx.logs.length, 1); // assert.equal(tx.logs[0].event, "RedeemReputation"); // assert.equal(tx.logs[0].args._amount, reputationReward); @@ -380,7 +370,7 @@ contract('ContributionRewardExt', accounts => { // var reputationReward = -12; // var periodLength = 50; // var numberOfPeriods = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [0,0,0,periodLength,numberOfPeriods], @@ -409,7 +399,7 @@ contract('ContributionRewardExt', accounts => { // //send some ether to the org avatar // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], @@ -463,7 +453,7 @@ contract('ContributionRewardExt', accounts => { // //send some ether to the org avatar // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], @@ -520,7 +510,7 @@ contract('ContributionRewardExt', accounts => { // //send some ether to the org avatar // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], @@ -557,7 +547,7 @@ contract('ContributionRewardExt', accounts => { // var numberOfPeriods = 1; // //send some ether to the org avatar // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], @@ -585,7 +575,7 @@ contract('ContributionRewardExt', accounts => { // var reputationReward = 12; // var periodLength = 0; // var numberOfPeriods = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // var tx = await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [0,0,0,periodLength,numberOfPeriods], @@ -594,7 +584,7 @@ contract('ContributionRewardExt', accounts => { // {from:accounts[2]} // ); // try { - // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [0,0,0,periodLength,2], @@ -610,14 +600,14 @@ contract('ContributionRewardExt', accounts => { // //Vote with reputation to trigger execution // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,false,false,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); // assert.equal(tx.logs.length, 1); // assert.equal(tx.logs[0].event, "RedeemReputation"); // assert.equal(tx.logs[0].args._amount, reputationReward); // var rep = await testSetup.org.reputation.balanceOf(accounts[1]); // assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); // //try to redeem again. - // tx = await testSetup.contributionRewardExt.redeem(proposalId,testSetup.org.avatar.address,[true,false,false,false]); + // tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); // assert.equal(tx.logs.length, 0); // rep = await testSetup.org.reputation.balanceOf(accounts[1]); // assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); @@ -632,7 +622,7 @@ contract('ContributionRewardExt', accounts => { // var numberOfPeriods = 2; // // try { - // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [0,0,0,periodLength,numberOfPeriods], @@ -651,7 +641,7 @@ contract('ContributionRewardExt', accounts => { // var externalTokenReward = 0; // // try { - // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], @@ -669,7 +659,7 @@ contract('ContributionRewardExt', accounts => { // externalTokenReward = 0; // // try { - // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], @@ -687,7 +677,7 @@ contract('ContributionRewardExt', accounts => { // externalTokenReward = ((new BigNumber(2)).toPower(256).sub(1)).toString(10); // // try { - // await testSetup.contributionRewardExt.proposeContributionReward(testSetup.org.avatar.address, + // await testSetup.contributionRewardExt.proposeContributionReward( // web3.utils.asciiToHex("description"), // reputationReward, // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], From f765929a2953775f66c8980e4ed78e54f03cc9c7 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Tue, 3 Dec 2019 00:08:47 +0200 Subject: [PATCH 07/21] tests --- contracts/schemes/ContributionRewardExt.sol | 147 ++-- contracts/utils/Redeemer.sol | 107 ++- test/contributionrewardext.js | 921 ++++++++++---------- 3 files changed, 626 insertions(+), 549 deletions(-) diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index 83c0990c..fa65be5a 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -3,6 +3,7 @@ pragma solidity 0.5.13; import "@daostack/infra/contracts/votingMachines/IntVoteInterface.sol"; import "@daostack/infra/contracts/votingMachines/VotingMachineCallbacksInterface.sol"; import "../votingMachines/VotingMachineCallbacks.sol"; +import "../libs/SafeERC20.sol"; /** @@ -12,6 +13,7 @@ import "../votingMachines/VotingMachineCallbacks.sol"; */ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterface { using SafeMath for uint; + using SafeERC20 for address; event NewContributionProposal( address indexed _avatar, @@ -78,6 +80,12 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa Avatar public avatar; address public redeemer; + /** + * @dev enables this contract to receive ethers + */ + function() external payable { + } + /** * @dev initialize * @param _avatar the avatar to mint reputation from @@ -202,10 +210,8 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa proposal.reputationChange = 0; if (_proposal.beneficiary == address(this)) { - if (_proposal.reputationChange != 0) { - if (_proposal.reputationChange > 0) {//for now only mint(not burn) rep allowed from ext contract. - proposal.reputationChangeLeft = uint256(_proposal.reputationChange); - } + if (_proposal.reputationChange > 0) {//for now only mint(not burn) rep allowed from ext contract. + proposal.reputationChangeLeft = uint256(_proposal.reputationChange); } } else { reputation = _proposal.reputationChange; @@ -225,28 +231,6 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa } } - /** - * @dev redeemReputationFromExtContract redeem reward for proposal - * @param _proposalId the ID of the voting in the voting machine - * @param _beneficiary the beneficiary to mint reputation to. - * @param _reputation the reputation amount to mint - */ - function redeemReputationFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _reputation) - public - onlyRedeemer - { - ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //this will ensure sum zero of reputation. - proposal.reputationChangeLeft = proposal.reputationChangeLeft.sub(_reputation); - require( - Controller( - avatar.owner()).mintReputation(_reputation, _beneficiary, address(avatar))); - if (_reputation != 0) { - emit RedeemReputation(address(avatar), _proposalId, _beneficiary, int256(_reputation)); - } - } - /** * @dev RedeemNativeToken reward for proposal * @param _proposalId the ID of the voting in the voting machine @@ -272,27 +256,6 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa } } - /** - * @dev redeemNativeTokenFromExtContract redeem reward for proposal - * @param _proposalId the ID of the voting in the voting machine - * @param _beneficiary the beneficiary to mint tokens to. - * @param _amount the tokens amount to mint - */ - function redeemNativeTokenFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _amount) - public - onlyRedeemer - { - ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //this will ensure sum zero of reputation. - proposal.nativeTokenRewardLeft = proposal.nativeTokenRewardLeft.sub(_amount); - - if (_amount > 0) { - require(Controller(avatar.owner()).mintTokens(_amount, _beneficiary, address(avatar))); - emit RedeemNativeToken(address(avatar), _proposalId, _beneficiary, _amount); - } - } - /** * @dev RedeemEther reward for proposal * @param _proposalId the ID of the voting in the voting machine @@ -317,27 +280,6 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa } } - /** - * @dev redeemEtherFromExtContract redeem reward for proposal - * @param _proposalId the ID of the voting in the voting machine - * @param _beneficiary the beneficiary to send eth to. - * @param _amount eth amount to send - */ - function redeemEtherFromExtContract(bytes32 _proposalId, address payable _beneficiary, uint256 _amount) - public - onlyRedeemer - { - ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //this will ensure sum zero of reputation. - proposal.ethRewardLeft = proposal.ethRewardLeft.sub(_amount); - - if (_amount > 0) { - require(Controller(avatar.owner()).sendEther(_amount, _beneficiary, avatar)); - emit RedeemEther(address(avatar), _proposalId, _beneficiary, _amount); - } - } - /** * @dev RedeemNativeToken reward for proposal * @param _proposalId the ID of the voting in the voting machine @@ -368,6 +310,70 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa } } + /** + * @dev redeemReputationFromExtContract redeem reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @param _beneficiary the beneficiary to mint reputation to. + * @param _reputation the reputation amount to mint + */ + function redeemReputationFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _reputation) + public + onlyRedeemer + { + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //this will ensure sum zero of reputation. + proposal.reputationChangeLeft = proposal.reputationChangeLeft.sub(_reputation); + require( + Controller( + avatar.owner()).mintReputation(_reputation, _beneficiary, address(avatar))); + if (_reputation != 0) { + emit RedeemReputation(address(avatar), _proposalId, _beneficiary, int256(_reputation)); + } + } + + /** + * @dev redeemNativeTokenFromExtContract redeem reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @param _beneficiary the beneficiary to mint tokens to. + * @param _amount the tokens amount to mint + */ + function redeemNativeTokenFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _amount) + public + onlyRedeemer + { + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //this will ensure sum zero of reputation. + proposal.nativeTokenRewardLeft = proposal.nativeTokenRewardLeft.sub(_amount); + + if (_amount > 0) { + address(avatar.nativeToken()).safeTransfer(_beneficiary, _amount); + emit RedeemNativeToken(address(avatar), _proposalId, _beneficiary, _amount); + } + } + + /** + * @dev redeemEtherFromExtContract redeem reward for proposal + * @param _proposalId the ID of the voting in the voting machine + * @param _beneficiary the beneficiary to send eth to. + * @param _amount eth amount to send + */ + function redeemEtherFromExtContract(bytes32 _proposalId, address payable _beneficiary, uint256 _amount) + public + onlyRedeemer + { + ContributionProposal storage proposal = organizationProposals[_proposalId]; + require(proposal.executionTime != 0); + //this will ensure sum zero of reputation. + proposal.ethRewardLeft = proposal.ethRewardLeft.sub(_amount); + + if (_amount > 0) { + _beneficiary.transfer(_amount); + emit RedeemEther(address(avatar), _proposalId, _beneficiary, _amount); + } + } + /** * @dev redeemExternalTokenFromExtContract redeem reward for proposal * @param _proposalId the ID of the voting in the voting machine @@ -384,10 +390,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa if (proposal.externalToken != IERC20(0)) { if (_amount > 0) { - require( - Controller( - avatar.owner()) - .externalTokenTransfer(proposal.externalToken, _beneficiary, _amount, avatar)); + address(proposal.externalToken).safeTransfer(_beneficiary, _amount); emit RedeemExternalToken(address(avatar), _proposalId, _beneficiary, _amount); } } diff --git a/contracts/utils/Redeemer.sol b/contracts/utils/Redeemer.sol index f59d144e..1071ef0e 100644 --- a/contracts/utils/Redeemer.sol +++ b/contracts/utils/Redeemer.sol @@ -1,6 +1,7 @@ pragma solidity 0.5.13; import "../universalSchemes/ContributionReward.sol"; +import "../schemes/ContributionRewardExt.sol"; import "@daostack/infra/contracts/votingMachines/GenesisProtocol.sol"; import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; @@ -53,6 +54,82 @@ contract Redeemer { uint256 crEthReward, uint256 crExternalTokenReward) { + bool callContributionReward; + (gpRewards, gpDaoBountyReward, executed, winningVote, callContributionReward) = + genesisProtocolRedeem(_genesisProtocol, _proposalId, _beneficiary); + if (callContributionReward) { + //redeem from contributionReward only if it executed + if (_contributionReward.getProposalExecutionTime(_proposalId, address(_avatar)) > 0) { + (crReputationReward, crNativeTokenReward, crEthReward, crExternalTokenReward) = + contributionRewardRedeem(_contributionReward, _proposalId, _avatar); + } + } + } + + /** + * @dev helper to redeem rewards for a proposal + * It calls execute on the proposal if it is not yet executed. + * It tries to redeem reputation and stake from the GenesisProtocol. + * It tries to redeem proposal rewards from the contribution rewards scheme. + * This function does not emit events. + * A client should listen to GenesisProtocol and ContributionReward redemption events + * to monitor redemption operations. + * @param _contributionRewardExt contributionRewardExt + * @param _genesisProtocol genesisProtocol + * @param _proposalId the ID of the voting in the voting machine + * @param _beneficiary beneficiary + * @return gpRewards array + * gpRewards[0] - stakerTokenAmount + * gpRewards[1] - voterReputationAmount + * gpRewards[2] - proposerReputationAmount + * @return gpDaoBountyReward array + * gpDaoBountyReward[0] - staker dao bounty reward - + * will be zero for the case there is not enough tokens in avatar for the reward. + * gpDaoBountyReward[1] - staker potential dao bounty reward. + * @return executed bool true or false + * @return winningVote + * 1 - executed or closed and the winning vote is YES + * 2 - executed or closed and the winning vote is NO + * @return int256 crReputationReward Reputation - from ContributionReward + * @return int256 crNativeTokenReward NativeTokenReward - from ContributionReward + * @return int256 crEthReward Ether - from ContributionReward + * @return int256 crExternalTokenReward ExternalToken - from ContributionReward + */ + function redeemFromCRExt(ContributionRewardExt _contributionRewardExt, + GenesisProtocol _genesisProtocol, + bytes32 _proposalId, + address _beneficiary) + external + returns(uint[3] memory gpRewards, + uint[2] memory gpDaoBountyReward, + bool executed, + uint256 winningVote, + int256 crReputationReward, + uint256 crNativeTokenReward, + uint256 crEthReward, + uint256 crExternalTokenReward) + { + bool callContributionReward; + (gpRewards, gpDaoBountyReward, executed, winningVote, callContributionReward) = + genesisProtocolRedeem(_genesisProtocol, _proposalId, _beneficiary); + if (callContributionReward) { + //redeem from contributionReward only if it executed + if (_contributionRewardExt.getProposalExecutionTime(_proposalId) > 0) { + (crReputationReward, crNativeTokenReward, crEthReward, crExternalTokenReward) = + contributionRewardExtRedeem(_contributionRewardExt, _proposalId); + } + } + } + + function genesisProtocolRedeem(GenesisProtocol _genesisProtocol, + bytes32 _proposalId, + address _beneficiary) + private + returns(uint[3] memory gpRewards, + uint[2] memory gpDaoBountyReward, + bool executed, + uint256 winningVote, + bool callContributionReward) { GenesisProtocol.ProposalState pState = _genesisProtocol.state(_proposalId); if ((pState == GenesisProtocolLogic.ProposalState.Queued)|| @@ -70,11 +147,7 @@ contract Redeemer { _genesisProtocol.redeemDaoBounty(_proposalId, _beneficiary); } winningVote = _genesisProtocol.winningVote(_proposalId); - //redeem from contributionReward only if it executed - if (_contributionReward.getProposalExecutionTime(_proposalId, address(_avatar)) > 0) { - (crReputationReward, crNativeTokenReward, crEthReward, crExternalTokenReward) = - contributionRewardRedeem(_contributionReward, _proposalId, _avatar); - } + callContributionReward = true; } } @@ -105,4 +178,28 @@ contract Redeemer { } (reputation, nativeToken, eth, externalToken) = _contributionReward.redeem(_proposalId, _avatar, whatToRedeem); } + + function contributionRewardExtRedeem(ContributionRewardExt _contributionRewardExt, bytes32 _proposalId) + private + returns (int256 reputation, uint256 nativeToken, uint256 eth, uint256 externalToken) + { + bool[4] memory whatToRedeem; + whatToRedeem[0] = true; //reputation + whatToRedeem[1] = true; //nativeToken + uint256 ethReward = _contributionRewardExt.getProposalEthReward(_proposalId); + uint256 externalTokenReward = _contributionRewardExt.getProposalExternalTokenReward(_proposalId); + address externalTokenAddress = _contributionRewardExt.getProposalExternalToken(_proposalId); + if ((ethReward == 0) || (address(_contributionRewardExt.avatar()).balance < ethReward)) { + whatToRedeem[2] = false; + } else { + whatToRedeem[2] = true; + } + if ((externalTokenReward == 0) || + (IERC20(externalTokenAddress).balanceOf(address(_contributionRewardExt.avatar())) < externalTokenReward)) { + whatToRedeem[3] = false; + } else { + whatToRedeem[3] = true; + } + (reputation, nativeToken, eth, externalToken) = _contributionRewardExt.redeem(_proposalId, whatToRedeem); + } } diff --git a/test/contributionrewardext.js b/test/contributionrewardext.js index 885cb1e6..23a11783 100644 --- a/test/contributionrewardext.js +++ b/test/contributionrewardext.js @@ -42,7 +42,7 @@ const setupContributionRewardParams = async function( return contributionRewardParams; }; -const setup = async function (accounts,genesisProtocol = false,tokenAddress=0) { +const setup = async function (accounts,genesisProtocol = false,tokenAddress=0,service=helpers.NULL_ADDRESS) { var testSetup = new helpers.TestSetup(); testSetup.standardTokenMock = await ERC20Mock.new(accounts[1],100); testSetup.contributionRewardExt = await ContributionRewardExt.new(); @@ -59,7 +59,8 @@ const setup = async function (accounts,genesisProtocol = false,tokenAddress=0) { testSetup.contributionRewardExt, accounts,genesisProtocol, tokenAddress, - testSetup.org.avatar); + testSetup.org.avatar, + service); var permissions = "0x00000000"; await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address, [testSetup.contributionRewardExt.address], @@ -220,474 +221,450 @@ contract('ContributionRewardExt', accounts => { assert.equal(tokens.toNumber(),externalTokenReward); }); - // it("execute proposeContributionReward proposal decision=='no' send externalToken ", async function() { - // var testSetup = await setup(accounts); - // var reputationReward = 12; - // var nativeTokenReward = 12; - // var ethReward = 12; - // var externalTokenReward = 12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // //send some ether to the org avatar - // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // otherAvatar.address - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // var organizationProposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); - // assert.equal(organizationProposal[5],otherAvatar.address);//beneficiary - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,0,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // await helpers.increaseTime(periodLength+1); - // try { - // await testSetup.contributionRewardExt.redeem(proposalId,[true,true,true,true]); - // assert(false, 'redeem should revert because there was no positive voting'); - // } catch (ex) { - // helpers.assertVMException(ex); - // } - // }); - // - // - // it("redeem periods ether ", async function() { - // var testSetup = await setup(accounts); - // var reputationReward = 0; - // var nativeTokenReward = 0; - // var ethReward = 3; - // var periodLength = 50; - // var numberOfPeriods = 5; - // //send some ether to the org avatar - // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:12}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // otherAvatar.address - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // await helpers.increaseTime(periodLength+1); - // - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); - // - // assert.equal(tx.logs.length, 1); - // assert.equal(tx.logs[0].event, "RedeemEther"); - // assert.equal(tx.logs[0].args._amount, ethReward); - // var eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward); - // - // //now try again on the same period - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); - // assert.equal(tx.logs.length, 0); - // eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward); - // - // //now try again on 2nd period - // await helpers.increaseTime(periodLength+1); - // - // - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); - // assert.equal(tx.logs.length, 1); - // assert.equal(tx.logs[0].event, "RedeemEther"); - // assert.equal(tx.logs[0].args._amount, ethReward); - // eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward*2); - // - // //now try again on 4th period - // await helpers.increaseTime((periodLength*2)+1); - // - // - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); - // assert.equal(tx.logs.length, 1); - // assert.equal(tx.logs[0].event, "RedeemEther"); - // assert.equal(tx.logs[0].args._amount, ethReward*2); - // eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward*4); - // - // //now try again on 5th period - no ether on avatar should revert - // await helpers.increaseTime(periodLength+1); - // try { - // await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); - // assert(false, 'redeem should revert because no ether left on avatar'); - // } catch (ex) { - // helpers.assertVMException(ex); - // } - // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:ethReward}); - // - // - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); - // assert.equal(tx.logs.length, 1); - // assert.equal(tx.logs[0].event, "RedeemEther"); - // assert.equal(tx.logs[0].args._amount, ethReward); - // eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward*5); - // - // - // - // //cannot redeem any more.. - // await helpers.increaseTime(periodLength+1); - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[false,false,true,false]); - // assert.equal(tx.logs.length, 0); - // eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward*5); - // - // }); - // - // it("execute proposeContributionReward mint negative reputation ", async function() { - // var testSetup = await setup(accounts); - // var reputationReward = -12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [0,0,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[0] - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // await helpers.increaseTime(periodLength+1); - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); - // assert.equal(tx.logs.length, 1); - // assert.equal(tx.logs[0].event, "RedeemReputation"); - // assert.equal(tx.logs[0].args._amount, reputationReward); - // var rep = await testSetup.org.reputation.balanceOf(accounts[0]); - // assert.equal(rep.toNumber(),testSetup.reputationArray[0]+reputationReward); - // }); - // - // - // it("call execute should revert ", async function() { - // var testSetup = await setup(accounts); - // var reputationReward = -12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [0,0,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[0] - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // try { - // await testSetup.contributionRewardExt.executeProposal(proposalId,1); - // assert(false, 'only voting machine can call execute'); - // } catch (ex) { - // helpers.assertVMException(ex); - // } - // - // }); - // - // it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer", async function() { - // var standardTokenMock = await ERC20Mock.new(accounts[0],1000); - // var testSetup = await setup(accounts,true,standardTokenMock.address); - // var reputationReward = 12; - // var nativeTokenReward = 12; - // var ethReward = 12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // //send some ether to the org avatar - // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // otherAvatar.address - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[0]}); - // await helpers.increaseTime(periodLength+1); - // var arcUtils = await Redeemer.new(); - // var redeemRewards = await arcUtils.redeem.call(testSetup.contributionRewardExt.address, - // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, - // proposalId,testSetup.org.avatar.address, - // accounts[0]); - // assert.equal(redeemRewards[0][1],100); //redeemRewards[0] gpRewards - // assert.equal(redeemRewards[0][2],60); - // assert.equal(redeemRewards[1][0],0); //daoBountyRewards - // assert.equal(redeemRewards[1][1],0); //daoBountyRewards - // assert.equal(redeemRewards[2],false); //isExecuted - // assert.equal(redeemRewards[3],1); //winningVote - // assert.equal(redeemRewards[4],reputationReward); //crReputationReward - // assert.equal(redeemRewards[5],nativeTokenReward); //crNativeTokenReward - // assert.equal(redeemRewards[6],ethReward); //crEthReward - // assert.equal(redeemRewards[7],0); //crExternalTokenReward - // - // await arcUtils.redeem(testSetup.contributionRewardExt.address, - // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, - // proposalId,testSetup.org.avatar.address, - // accounts[0]); - // - // var eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward); - // assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),reputationReward); - // assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),nativeTokenReward); - // var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); - // var reputationGainAsVoter = 0; - // var proposingRepRewardConstA=60; - // var reputationGainAsProposer = proposingRepRewardConstA; - // assert.equal(reputation, 1000+reputationGainAsVoter + reputationGainAsProposer); - // }); - // - // it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer for un excuted boosted proposal", async function() { - // var standardTokenMock = await ERC20Mock.new(accounts[0],1000); - // var testSetup = await setup(accounts,true,standardTokenMock.address); - // var reputationReward = 12; - // var nativeTokenReward = 12; - // var ethReward = 12; - // var periodLength = 0; - // var numberOfPeriods = 1; - // //send some ether to the org avatar - // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // otherAvatar.address - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // - // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[1]}); - // await standardTokenMock.approve(testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address,1000); - // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.stake(proposalId,1,1000); - // await helpers.increaseTime(60+1); - // var arcUtils = await Redeemer.new(); - // var redeemRewards = await arcUtils.redeem.call(testSetup.contributionRewardExt.address, - // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, - // proposalId,testSetup.org.avatar.address, - // accounts[0]); - // assert.equal(redeemRewards[0][1],0); //redeemRewards[0] gpRewards - // assert.equal(redeemRewards[0][2],60); - // assert.equal(redeemRewards[1][0],0); //daoBountyRewards - // assert.equal(redeemRewards[1][1],15); //daoBountyRewards - // assert.equal(redeemRewards[2],true); //isExecuted - // assert.equal(redeemRewards[3],1); //winningVote - // assert.equal(redeemRewards[4],reputationReward); //crReputationReward - // assert.equal(redeemRewards[5],nativeTokenReward); //crNativeTokenReward - // assert.equal(redeemRewards[6],ethReward); //crEthReward - // assert.equal(redeemRewards[7],0); //crExternalTokenReward - // - // await arcUtils.redeem(testSetup.contributionRewardExt.address, - // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, - // proposalId,testSetup.org.avatar.address, - // accounts[0]); - // - // var eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,ethReward); - // assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),reputationReward); - // assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),nativeTokenReward); - // var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); - // var reputationGainAsVoter = 0; - // var proposingRepRewardConstA=60; - // var reputationGainAsProposer = proposingRepRewardConstA; - // assert.equal(reputation, 1000+reputationGainAsVoter + reputationGainAsProposer); - // }); - // - // it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer for negative proposal", async function() { - // var standardTokenMock = await ERC20Mock.new(accounts[0],1000); - // var testSetup = await setup(accounts,true,standardTokenMock.address); - // var reputationReward = 12; - // var nativeTokenReward = 12; - // var ethReward = 12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // //send some ether to the org avatar - // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // otherAvatar.address - // ); - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,2,0,helpers.NULL_ADDRESS,{from:accounts[0]}); - // await helpers.increaseTime(periodLength+1); - // var arcUtils = await Redeemer.new(); - // await arcUtils.redeem(testSetup.contributionRewardExt.address, - // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, - // proposalId, - // testSetup.org.avatar.address, - // accounts[0]); - // var eth = await web3.eth.getBalance(otherAvatar.address); - // assert.equal(eth,0); - // assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),0); - // assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),0); - // var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); - // //no reputation reward for proposer for negative proposal. - // //reputation reward for a single voter = 0 - // assert.equal(reputation, 1000); - // }); - // - // it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer ExpiredInQueue", async function() { - // var standardTokenMock = await ERC20Mock.new(accounts[0],1000); - // var testSetup = await setup(accounts,true,standardTokenMock.address); - // var reputationReward = 12; - // var nativeTokenReward = 12; - // var ethReward = 12; - // var periodLength = 50; - // var numberOfPeriods = 1; - // //send some ether to the org avatar - // var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [nativeTokenReward,ethReward,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // otherAvatar.address - // ); - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // - // await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[1]}); - // await helpers.increaseTime(60+1); - // var arcUtils = await Redeemer.new(); - // await arcUtils.redeem(testSetup.contributionRewardExt.address, - // testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, - // proposalId,testSetup.org.avatar.address, - // accounts[1]); - // var proposal = await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.proposals(proposalId); - // assert.equal(proposal.state,1); //ExpiredInQueue - // var reputation = await testSetup.org.reputation.balanceOf(accounts[1]); - // //accounts[1] redeems its deposit rep. - // assert.equal(reputation.toNumber(), 100); - // }); - // - // it("execute proposeContributionReward mint reputation with period 0 ", async function() { - // var testSetup = await setup(accounts); - // var reputationReward = 12; - // var periodLength = 0; - // var numberOfPeriods = 1; - // var tx = await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [0,0,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[1], - // {from:accounts[2]} - // ); - // try { - // await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [0,0,0,periodLength,2], - // testSetup.standardTokenMock.address, - // accounts[1], - // {from:accounts[2]} - // ); - // assert(false, 'if periodLength==0 so numberOfPeriods must be 1'); - // } catch (ex) { - // helpers.assertVMException(ex); - // } - // - // //Vote with reputation to trigger execution - // var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); - // await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); - // assert.equal(tx.logs.length, 1); - // assert.equal(tx.logs[0].event, "RedeemReputation"); - // assert.equal(tx.logs[0].args._amount, reputationReward); - // var rep = await testSetup.org.reputation.balanceOf(accounts[1]); - // assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); - // //try to redeem again. - // tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); - // assert.equal(tx.logs.length, 0); - // rep = await testSetup.org.reputation.balanceOf(accounts[1]); - // assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); - // }); - // - // - // it("execute proposeContributionReward param validate ", async function() { - // var testSetup = await setup(accounts); - // let BigNumber = require('bignumber.js'); - // let reputationReward = ((new BigNumber(2)).toPower(255).sub(1)).toString(10); - // var periodLength = 1; - // var numberOfPeriods = 2; - // - // try { - // await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [0,0,0,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[1], - // {from:accounts[2]} - // ); - // assert(false, 'numberOfPeriods * _reputationChange should not overflow'); - // } catch (ex) { - // helpers.assertVMException(ex); - // } - // - // reputationReward = 12; - // var tokenReward = ((new BigNumber(2)).toPower(256).sub(1)).toString(10); - // var ethReward = 0; - // var externalTokenReward = 0; - // - // try { - // await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[1], - // {from:accounts[2]} - // ); - // assert(false, 'numberOfPeriods * tokenReward should not overflow'); - // } catch (ex) { - // helpers.assertVMException(ex); - // } - // - // tokenReward = 0; - // ethReward = ((new BigNumber(2)).toPower(256).sub(1)).toString(10); - // externalTokenReward = 0; - // - // try { - // await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[1], - // {from:accounts[2]} - // ); - // assert(false, 'numberOfPeriods * ethReward should not overflow'); - // } catch (ex) { - // helpers.assertVMException(ex); - // } - // - // tokenReward = 0; - // ethReward = 0; - // externalTokenReward = ((new BigNumber(2)).toPower(256).sub(1)).toString(10); - // - // try { - // await testSetup.contributionRewardExt.proposeContributionReward( - // web3.utils.asciiToHex("description"), - // reputationReward, - // [tokenReward,ethReward,externalTokenReward,periodLength,numberOfPeriods], - // testSetup.standardTokenMock.address, - // accounts[1], - // {from:accounts[2]} - // ); - // assert(false, 'numberOfPeriods * externalTokenReward should not overflow'); - // } catch (ex) { - // helpers.assertVMException(ex); - // } - // }); + it("execute proposeContributionReward proposal decision=='no' send externalToken ", async function() { + var testSetup = await setup(accounts); + var reputationReward = 12; + var nativeTokenReward = 12; + var ethReward = 12; + var externalTokenReward = 12; + + //send some ether to the org avatar + var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,ethReward,externalTokenReward], + testSetup.standardTokenMock.address, + otherAvatar.address, + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + var organizationProposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(organizationProposal[5],otherAvatar.address);//beneficiary + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,0,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + + try { + await testSetup.contributionRewardExt.redeem(proposalId,[true,true,true,true]); + assert(false, 'redeem should revert because there was no positive voting'); + } catch (ex) { + helpers.assertVMException(ex); + } + }); + + it("execute proposeContributionReward mint negative reputation ", async function() { + var testSetup = await setup(accounts); + var reputationReward = -12; + + + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [0,0,0], + testSetup.standardTokenMock.address, + accounts[0], + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + + tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "RedeemReputation"); + assert.equal(tx.logs[0].args._amount, reputationReward); + var rep = await testSetup.org.reputation.balanceOf(accounts[0]); + assert.equal(rep.toNumber(),testSetup.reputationArray[0]+reputationReward); + }); + + + it("call execute should revert ", async function() { + var testSetup = await setup(accounts); + var reputationReward = -12; + + + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [0,0,0], + testSetup.standardTokenMock.address, + accounts[0], + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + try { + await testSetup.contributionRewardExt.executeProposal(proposalId,1); + assert(false, 'only voting machine can call execute'); + } catch (ex) { + helpers.assertVMException(ex); + } + + }); + + it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer", async function() { + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,true,standardTokenMock.address); + var reputationReward = 12; + var nativeTokenReward = 12; + var ethReward = 12; + + + //send some ether to the org avatar + var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,ethReward,0], + testSetup.standardTokenMock.address, + otherAvatar.address, + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[0]}); + + var arcUtils = await Redeemer.new(); + var redeemRewards = await arcUtils.redeemFromCRExt.call(testSetup.contributionRewardExt.address, + testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + proposalId, + accounts[0]); + assert.equal(redeemRewards[0][1],100); //redeemRewards[0] gpRewards + assert.equal(redeemRewards[0][2],60); + assert.equal(redeemRewards[1][0],0); //daoBountyRewards + assert.equal(redeemRewards[1][1],0); //daoBountyRewards + assert.equal(redeemRewards[2],false); //isExecuted + assert.equal(redeemRewards[3],1); //winningVote + assert.equal(redeemRewards[4],reputationReward); //crReputationReward + assert.equal(redeemRewards[5],nativeTokenReward); //crNativeTokenReward + assert.equal(redeemRewards[6],ethReward); //crEthReward + assert.equal(redeemRewards[7],0); //crExternalTokenReward + + await arcUtils.redeemFromCRExt(testSetup.contributionRewardExt.address, + testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + proposalId, + accounts[0]); + + var eth = await web3.eth.getBalance(otherAvatar.address); + assert.equal(eth,ethReward); + assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),reputationReward); + assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),nativeTokenReward); + var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); + var reputationGainAsVoter = 0; + var proposingRepRewardConstA=60; + var reputationGainAsProposer = proposingRepRewardConstA; + assert.equal(reputation, 1000+reputationGainAsVoter + reputationGainAsProposer); + }); + + it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer for un excuted boosted proposal", async function() { + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,true,standardTokenMock.address); + var reputationReward = 12; + var nativeTokenReward = 12; + var ethReward = 12; + //send some ether to the org avatar + var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,ethReward,0], + testSetup.standardTokenMock.address, + otherAvatar.address, + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[1]}); + + await standardTokenMock.approve(testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address,1000); + await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.stake(proposalId,1,1000); + await helpers.increaseTime(60+1); + var arcUtils = await Redeemer.new(); + + var redeemRewards = await arcUtils.redeemFromCRExt.call(testSetup.contributionRewardExt.address, + testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + proposalId, + accounts[0]); + + assert.equal(redeemRewards[0][1],0); //redeemRewards[0] gpRewards + assert.equal(redeemRewards[0][2],60); + assert.equal(redeemRewards[1][0],0); //daoBountyRewards + assert.equal(redeemRewards[1][1],15); //daoBountyRewards + assert.equal(redeemRewards[2],true); //isExecuted + assert.equal(redeemRewards[3],1); //winningVote + assert.equal(redeemRewards[4],reputationReward); //crReputationReward + assert.equal(redeemRewards[5],nativeTokenReward); //crNativeTokenReward + assert.equal(redeemRewards[6],ethReward); //crEthReward + assert.equal(redeemRewards[7],0); //crExternalTokenReward + + await arcUtils.redeemFromCRExt(testSetup.contributionRewardExt.address, + testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + proposalId, + accounts[0]); + + var eth = await web3.eth.getBalance(otherAvatar.address); + assert.equal(eth,ethReward); + assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),reputationReward); + assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),nativeTokenReward); + var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); + var reputationGainAsVoter = 0; + var proposingRepRewardConstA=60; + var reputationGainAsProposer = proposingRepRewardConstA; + assert.equal(reputation, 1000+reputationGainAsVoter + reputationGainAsProposer); + }); + + it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer for negative proposal", async function() { + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,true,standardTokenMock.address); + var reputationReward = 12; + var nativeTokenReward = 12; + var ethReward = 12; + + + //send some ether to the org avatar + var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,ethReward,0], + testSetup.standardTokenMock.address, + otherAvatar.address, + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,2,0,helpers.NULL_ADDRESS,{from:accounts[0]}); + + var arcUtils = await Redeemer.new(); + await arcUtils.redeemFromCRExt(testSetup.contributionRewardExt.address, + testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + proposalId, + accounts[0]); + var eth = await web3.eth.getBalance(otherAvatar.address); + assert.equal(eth,0); + assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),0); + assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),0); + var reputation = await testSetup.org.reputation.balanceOf(accounts[0]); + //no reputation reward for proposer for negative proposal. + //reputation reward for a single voter = 0 + assert.equal(reputation, 1000); + }); + + it("execute proposeContributionReward via genesisProtocol and redeem using Redeemer ExpiredInQueue", async function() { + var standardTokenMock = await ERC20Mock.new(accounts[0],1000); + var testSetup = await setup(accounts,true,standardTokenMock.address); + var reputationReward = 12; + var nativeTokenReward = 12; + var ethReward = 12; + + + //send some ether to the org avatar + var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,ethReward,0], + testSetup.standardTokenMock.address, + otherAvatar.address, + helpers.NULL_ADDRESS + ); + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + + await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[1]}); + await helpers.increaseTime(60+1); + var arcUtils = await Redeemer.new(); + await arcUtils.redeemFromCRExt(testSetup.contributionRewardExt.address, + testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.address, + proposalId, + accounts[1]); + var proposal = await testSetup.contributionRewardExtParams.votingMachine.genesisProtocol.proposals(proposalId); + assert.equal(proposal.state,1); //ExpiredInQueue + var reputation = await testSetup.org.reputation.balanceOf(accounts[1]); + //accounts[1] redeems its deposit rep. + assert.equal(reputation.toNumber(), 100); + }); + + it("execute proposeContributionReward mint reputation with period 0 ", async function() { + var testSetup = await setup(accounts); + var reputationReward = 12; + + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [0,0,0], + testSetup.standardTokenMock.address, + accounts[1], + helpers.NULL_ADDRESS, + {from:accounts[2]} + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "RedeemReputation"); + assert.equal(tx.logs[0].args._amount, reputationReward); + var rep = await testSetup.org.reputation.balanceOf(accounts[1]); + assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); + //try to redeem again. + tx = await testSetup.contributionRewardExt.redeem(proposalId,[true,false,false,false]); + assert.equal(tx.logs.length, 0); + rep = await testSetup.org.reputation.balanceOf(accounts[1]); + assert.equal(rep.toNumber(),testSetup.reputationArray[1]+reputationReward); + }); + + it("cannot initialize twice", async function() { + var testSetup = await setup(accounts); + try { + await testSetup.contributionRewardExt.initialize( + testSetup.org.avatar.address, + testSetup.contributionRewardExtParams.votingMachine.absoluteVote.address, + testSetup.contributionRewardExtParams.votingMachine.absoluteVote.address, + helpers.NULL_ADDRESS + ); + assert(false, 'cannot initialize twice'); + } catch (ex) { + helpers.assertVMException(ex); + } + }); + it("execute proposeContributionReward to self and redeem from external contract ", async function() { + var testSetup = await setup(accounts,false,0,accounts[0]); + var reputationReward = 12; + var nativeTokenReward = 12; + var ethReward = 12; + await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,30,{from:accounts[1]}); + var externalTokenReward = 12; + //send some ether to the org avatar + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + var tx = await testSetup.contributionRewardExt.proposeContributionReward( + web3.utils.asciiToHex("description"), + reputationReward, + [nativeTokenReward,ethReward,externalTokenReward], + testSetup.standardTokenMock.address, + testSetup.contributionRewardExt.address, + helpers.NULL_ADDRESS + ); + //Vote with reputation to trigger execution + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.contributionRewardExt.redeem(proposalId,[true,true,true,true]); + var eth = await web3.eth.getBalance(testSetup.contributionRewardExt.address); + assert.equal(eth,ethReward); + var otherAvatar = await Avatar.new('otheravatar', helpers.NULL_ADDRESS, helpers.NULL_ADDRESS); + + //redeem ether + try { + await testSetup.contributionRewardExt.redeemEtherFromExtContract(proposalId,otherAvatar.address,1,{from:accounts[1]}); + assert(false, 'only service contract can redeem'); + } catch (ex) { + helpers.assertVMException(ex); + } + tx = await testSetup.contributionRewardExt.redeemEtherFromExtContract(proposalId,otherAvatar.address,1); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "RedeemEther"); + assert.equal(tx.logs[0].args._amount, 1); + assert.equal(await web3.eth.getBalance(otherAvatar.address),1); + //cannot redeem more than the proposal reward + var proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(proposal.ethRewardLeft, ethReward - 1); + try { + await testSetup.contributionRewardExt.redeemEtherFromExtContract(proposalId,otherAvatar.address,proposal.ethRewardLeft+1); + assert(false, 'cannot redeem more than the proposal reward'); + } catch (ex) { + helpers.assertVMException(ex); + } + await testSetup.contributionRewardExt.redeemEtherFromExtContract(proposalId,otherAvatar.address,proposal.ethRewardLeft); + assert.equal(await web3.eth.getBalance(otherAvatar.address),ethReward); + proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(proposal.ethRewardLeft, 0); + + //redeem nativeToken + try { + await testSetup.contributionRewardExt.redeemNativeTokenFromExtContract(proposalId,otherAvatar.address,1,{from:accounts[1]}); + assert(false, 'only service contract can redeem'); + } catch (ex) { + helpers.assertVMException(ex); + } + tx = await testSetup.contributionRewardExt.redeemNativeTokenFromExtContract(proposalId,otherAvatar.address,1); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "RedeemNativeToken"); + assert.equal(tx.logs[0].args._amount, 1); + + assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),1); + //cannot redeem more than the proposal reward + proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(proposal.nativeTokenRewardLeft, nativeTokenReward - 1); + try { + await testSetup.contributionRewardExt.redeemNativeTokenFromExtContract(proposalId,otherAvatar.address,proposal.nativeTokenRewardLeft+1); + assert(false, 'cannot redeem more than the proposal reward'); + } catch (ex) { + helpers.assertVMException(ex); + } + await testSetup.contributionRewardExt.redeemNativeTokenFromExtContract(proposalId,otherAvatar.address,proposal.nativeTokenRewardLeft); + assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),nativeTokenReward); + proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(proposal.nativeTokenRewardLeft, 0); + + + //redeem externalToken + try { + await testSetup.contributionRewardExt.redeemExternalTokenFromExtContract(proposalId,otherAvatar.address,1,{from:accounts[1]}); + assert(false, 'only service contract can redeem'); + } catch (ex) { + helpers.assertVMException(ex); + } + tx = await testSetup.contributionRewardExt.redeemExternalTokenFromExtContract(proposalId,otherAvatar.address,1); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "RedeemExternalToken"); + assert.equal(tx.logs[0].args._amount, 1); + + assert.equal(await testSetup.standardTokenMock.balanceOf(otherAvatar.address),1); + //cannot redeem more than the proposal reward + proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(proposal.externalTokenRewardLeft, externalTokenReward - 1); + try { + await testSetup.contributionRewardExt.redeemExternalTokenFromExtContract(proposalId,otherAvatar.address,proposal.externalTokenRewardLeft+1); + assert(false, 'cannot redeem more than the proposal reward'); + } catch (ex) { + helpers.assertVMException(ex); + } + await testSetup.contributionRewardExt.redeemExternalTokenFromExtContract(proposalId,otherAvatar.address,proposal.externalTokenRewardLeft); + assert.equal(await testSetup.standardTokenMock.balanceOf(otherAvatar.address),externalTokenReward); + proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(proposal.externalTokenRewardLeft, 0); + + + //redeem reputation + try { + await testSetup.contributionRewardExt.redeemReputationFromExtContract(proposalId,otherAvatar.address,1,{from:accounts[1]}); + assert(false, 'only service contract can redeem'); + } catch (ex) { + helpers.assertVMException(ex); + } + tx = await testSetup.contributionRewardExt.redeemReputationFromExtContract(proposalId,otherAvatar.address,1); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "RedeemReputation"); + assert.equal(tx.logs[0].args._amount, 1); + + assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),1); + //cannot redeem more than the proposal reward + proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(proposal.reputationChangeLeft, reputationReward - 1); + try { + await testSetup.contributionRewardExt.redeemReputationFromExtContract(proposalId,otherAvatar.address,proposal.reputationChangeLeft+1); + assert(false, 'cannot redeem more than the proposal reward'); + } catch (ex) { + helpers.assertVMException(ex); + } + await testSetup.contributionRewardExt.redeemReputationFromExtContract(proposalId,otherAvatar.address,proposal.reputationChangeLeft); + assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),reputationReward); + proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + assert.equal(proposal.reputationChangeLeft, 0); + + }); + }); From 2e6d96b5dc7282e627487c4548b84b2cd0f86411 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Tue, 3 Dec 2019 14:27:34 +0200 Subject: [PATCH 08/21] tests --- contracts/schemes/Competition.sol | 165 +++++---- contracts/schemes/ContributionRewardExt.sol | 24 +- test/competition.js | 361 ++++++++++++++++++++ 3 files changed, 466 insertions(+), 84 deletions(-) create mode 100644 test/competition.js diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index 6d3ae8ee..66952ef0 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -11,13 +11,12 @@ contract Competition { event NewCompetitionProposal( bytes32 indexed _proposalId, uint256 _numberOfWinners, - uint256[MAX_NUMBER_OF_WINNERS] _rewardSplit, + uint256[] _rewardSplit, uint256 _startTime, uint256 _votingStartTime, uint256 _endTime, uint256 _numberOfVotesPerVoters, - address payable _contributionReward, //address of the contract to redeem from. - uint256 snapshotBlock + address payable _contributionReward //address of the contract to redeem from. ); event NewSuggestion( @@ -31,26 +30,28 @@ contract Competition { bytes32 indexed _proposalId, uint256 indexed _suggestionId, address indexed _voter, - uint256 reputation + uint256 _reputation + ); + + event SnapshotBlock( + bytes32 indexed _proposalId, + uint256 _snapshotBlock ); // A struct holding the data for a competition proposal - struct CompetitionProposal { + struct Proposal { uint256 numberOfWinners; - uint256[MAX_NUMBER_OF_WINNERS] rewardSplit; + uint256[] rewardSplit; uint256 startTime; uint256 votingStartTime; uint256 endTime; uint256 numberOfVotesPerVoters; address payable contributionReward; //address of the contract to redeem from. - } - - // A struct holding the data for a competition proposal - struct Proposal { - CompetitionProposal competitionProposal; uint256 snapshotBlock; - uint256 currentMinTopSuggestiosScore; - uint256[MAX_NUMBER_OF_WINNERS] topSuggestionsOrdered; + uint256[] topSuggestionsOrdered; + //mapping from suggestions totalVotes to the number of suggestions with the same totalVotes. + mapping(uint256=>uint256) tiesSuggestions; + mapping(address=>uint256) votesPerVoter; } struct Suggestion { @@ -62,12 +63,11 @@ contract Competition { //mapping from proposalID to Proposal //this is private due to avoid use of pragma experimental ABIEncoderV2; - mapping(bytes32=>Proposal) private proposals; + mapping(bytes32=>Proposal) public proposals; //mapping from suggestionId to Suggestion mapping(uint256=>Suggestion) public suggestions; - mapping(address=>uint256) public votesPerVoter; uint256 public suggestionsCounter; @@ -76,8 +76,7 @@ contract Competition { int256 _reputationChange, uint[3] memory _rewards, IERC20 _externalToken, - uint256 _numberOfWinners, - uint256[MAX_NUMBER_OF_WINNERS] memory _rewardSplit, + uint256[] memory _rewardSplit, uint256 _startTime, uint256 _votingStartTime, uint256 _endTime, @@ -87,19 +86,18 @@ contract Competition { public returns(bytes32 proposalId) { - require(_numberOfWinners <= MAX_NUMBER_OF_WINNERS, + uint256 numberOfWinners = _rewardSplit.length; + require(numberOfWinners <= MAX_NUMBER_OF_WINNERS, "number of winners greater than max allowed"); require(_votingStartTime < _endTime, "voting start time greater than end time"); require(_votingStartTime >= _startTime, "voting start time smaller than start time"); - require(_startTime < _endTime, - "start time greater than end time"); require(_numberOfVotesPerVoters > 0, "numberOfVotesPerVoters should be greater than 0"); uint256 totalRewardSplit; - for (uint256 i = 0; i < _numberOfWinners; i++) { + for (uint256 i = 0; i < numberOfWinners; i++) { totalRewardSplit = totalRewardSplit.add(_rewardSplit[i]); } require(totalRewardSplit == 100, "total rewards split is not 100%"); @@ -111,29 +109,32 @@ contract Competition { _externalToken, _contributionReward, msg.sender); - // solhint-disable-next-line not-rely-on-time - proposals[proposalId].snapshotBlock = block.number + (_votingStartTime.sub(now)).div(15); - - proposals[proposalId].competitionProposal = CompetitionProposal({ - numberOfWinners: _numberOfWinners, + uint256 startTime = _startTime; + if (startTime == 0) { + // solhint-disable-next-line not-rely-on-time + startTime = now; + } + proposals[proposalId] = Proposal({ + numberOfWinners: numberOfWinners, rewardSplit: _rewardSplit, - startTime: _startTime, + startTime: startTime, votingStartTime: _votingStartTime, endTime: _endTime, numberOfVotesPerVoters: _numberOfVotesPerVoters, - contributionReward: _contributionReward + contributionReward: _contributionReward, + snapshotBlock: 0, + topSuggestionsOrdered: new uint256[](numberOfWinners) }); emit NewCompetitionProposal( proposalId, - _numberOfWinners, + numberOfWinners, _rewardSplit, - _startTime, + startTime, _votingStartTime, _endTime, _numberOfVotesPerVoters, - _contributionReward, - proposals[proposalId].snapshotBlock + _contributionReward ); } @@ -145,9 +146,9 @@ contract Competition { returns(uint256) { // solhint-disable-next-line not-rely-on-time - require(proposals[_proposalId].competitionProposal.startTime <= now, "competition not started yet"); + require(proposals[_proposalId].startTime <= now, "competition not started yet"); // solhint-disable-next-line not-rely-on-time - require(proposals[_proposalId].competitionProposal.endTime > now, "competition ended"); + require(proposals[_proposalId].endTime > now, "competition ended"); suggestionsCounter = suggestionsCounter.add(1); suggestions[suggestionsCounter].proposalId = _proposalId; suggestions[suggestionsCounter].suggester = msg.sender; @@ -155,68 +156,87 @@ contract Competition { return suggestionsCounter; } + function setSnapshotBlock(bytes32 _proposalId) public { + // solhint-disable-next-line not-rely-on-time + require(proposals[_proposalId].votingStartTime < now, "voting period not started yet"); + if (proposals[_proposalId].snapshotBlock == 0) { + proposals[_proposalId].snapshotBlock = block.number; + emit SnapshotBlock(_proposalId, block.number); + } + } + function vote(uint256 _suggestionId) public returns(bool) { bytes32 proposalId = suggestions[_suggestionId].proposalId; require(proposalId != bytes32(0), "suggestion not exist"); - CompetitionProposal memory competitionProposal = proposals[proposalId].competitionProposal; + setSnapshotBlock(proposalId); + Proposal storage proposal = proposals[proposalId]; // solhint-disable-next-line not-rely-on-time - require(competitionProposal.votingStartTime > now, "votingh period not started yet"); - // solhint-disable-next-line not-rely-on-time - require(competitionProposal.endTime < now, "competition ended"); + require(proposal.endTime > now, "competition ended"); Suggestion storage suggestion = suggestions[_suggestionId]; require(suggestion.votes[msg.sender] == 0, "already voted on this suggestion"); - require(suggestion.votes[msg.sender] < competitionProposal.numberOfVotesPerVoters, + require(proposal.votesPerVoter[msg.sender] < proposal.numberOfVotesPerVoters, "exceed number of votes allowed"); - + proposal.votesPerVoter[msg.sender] = proposal.votesPerVoter[msg.sender].add(1); suggestion.votes[msg.sender] = suggestion.votes[msg.sender].add(1); - Avatar avatar = ContributionRewardExt(competitionProposal.contributionReward).avatar(); + Avatar avatar = ContributionRewardExt(proposal.contributionReward).avatar(); uint256 reputation = avatar.nativeReputation().balanceOfAt(msg.sender, proposals[proposalId].snapshotBlock); + require(reputation > 0, "voter has no reputation"); suggestion.totalVotes = suggestion.totalVotes.add(reputation); suggestion.votes[msg.sender] = reputation; + proposal.tiesSuggestions[suggestion.totalVotes] = proposal.tiesSuggestions[suggestion.totalVotes].add(1); refreshTopSuggestions(proposalId, _suggestionId); emit NewVote(proposalId, _suggestionId, msg.sender, reputation); return true; } function redeem(uint256 _suggestionId, address payable _beneficiary) public { + require(suggestions[_suggestionId].totalVotes > 0, "no one vote for this suggestion"); bytes32 proposalId = suggestions[_suggestionId].proposalId; require(proposalId != bytes32(0), "suggestion not exist"); - CompetitionProposal memory competitionProposal = proposals[proposalId].competitionProposal; + Proposal storage proposal = proposals[proposalId]; // solhint-disable-next-line not-rely-on-time - require(competitionProposal.endTime > now, "competition is still on"); + require(proposal.endTime > now, "competition is still on"); uint256 amount; //check if there is a win - for (uint256 i = 0; i < competitionProposal.numberOfWinners; i++) { - if (suggestions[proposals[proposalId].topSuggestionsOrdered[i]].suggester == _beneficiary) { + for (uint256 i = 0; i < proposal.numberOfWinners; i++) { + if (suggestions[proposal.topSuggestionsOrdered[i]].suggester == _beneficiary) { uint256 orderIndex = getOrderedIndexOfSuggestion(proposalId, _suggestionId); suggestions[_suggestionId].suggester = address(0); - amount = ContributionRewardExt(competitionProposal.contributionReward) - .getProposalExternalTokenReward(proposalId).mul(competitionProposal.rewardSplit[orderIndex]).div(100); - ContributionRewardExt(competitionProposal.contributionReward).redeemExternalTokenFromExtContract( + uint256 rewardPercentage = 0; + uint256 numberOfTieSuggestions = proposal.tiesSuggestions[suggestions[_suggestionId].totalVotes]; + //calc the reward percentage for this suggestion + for (uint256 j=orderIndex; j < (orderIndex+numberOfTieSuggestions); j++) { + rewardPercentage = rewardPercentage.add(proposal.rewardSplit[j]); + } + rewardPercentage = rewardPercentage.div(numberOfTieSuggestions); + + amount = ContributionRewardExt(proposal.contributionReward) + .getProposalExternalTokenReward(proposalId).mul(rewardPercentage).div(100); + ContributionRewardExt(proposal.contributionReward).redeemExternalTokenFromExtContract( proposalId, _beneficiary, amount); - amount = uint256(ContributionRewardExt(competitionProposal.contributionReward) - .getProposalReputationReward(proposalId)).mul(competitionProposal.rewardSplit[orderIndex]).div(100); - ContributionRewardExt(competitionProposal.contributionReward).redeemReputationFromExtContract( + amount = uint256(ContributionRewardExt(proposal.contributionReward) + .getProposalReputationReward(proposalId)).mul(rewardPercentage).div(100); + ContributionRewardExt(proposal.contributionReward).redeemReputationFromExtContract( proposalId, _beneficiary, amount); - amount = ContributionRewardExt(competitionProposal.contributionReward) - .getProposalEthReward(proposalId).mul(competitionProposal.rewardSplit[orderIndex]).div(100); - ContributionRewardExt(competitionProposal.contributionReward).redeemEtherFromExtContract( + amount = ContributionRewardExt(proposal.contributionReward) + .getProposalEthReward(proposalId).mul(rewardPercentage).div(100); + ContributionRewardExt(proposal.contributionReward).redeemEtherFromExtContract( proposalId, _beneficiary, amount); - amount = ContributionRewardExt(competitionProposal.contributionReward) - .getProposalNativeTokenReward(proposalId).mul(competitionProposal.rewardSplit[orderIndex]).div(100); - ContributionRewardExt(competitionProposal.contributionReward).redeemNativeTokenFromExtContract( + amount = ContributionRewardExt(proposal.contributionReward) + .getProposalNativeTokenReward(proposalId).mul(rewardPercentage).div(100); + ContributionRewardExt(proposal.contributionReward).redeemNativeTokenFromExtContract( proposalId, _beneficiary, amount); @@ -225,11 +245,25 @@ contract Competition { } } + function getOrderedIndexOfSuggestion(bytes32 _proposalId, uint256 _suggestionId) + public + view + returns(uint256 index) { + uint256[] memory topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; + /** get how many elements are greater than a given element + + how many elements are equal to agiven element **/ + for (uint256 i; i < proposals[_proposalId].numberOfWinners; i++) { + if (suggestions[topSuggestionsOrdered[i]].totalVotes > suggestions[_suggestionId].totalVotes) { + index++; + } + } + } + function refreshTopSuggestions(bytes32 _proposalId, uint256 _suggestionId) internal { - uint256[MAX_NUMBER_OF_WINNERS] storage topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; + uint256[] storage topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; /** get the index of the smallest element **/ uint256 smallest = 0; - for (uint256 i; i < proposals[_proposalId].competitionProposal.numberOfWinners; i++) { + for (uint256 i; i < proposals[_proposalId].numberOfWinners; i++) { if (suggestions[topSuggestionsOrdered[i]].totalVotes > smallest) { smallest++; } @@ -239,17 +273,4 @@ contract Competition { topSuggestionsOrdered[smallest] = _suggestionId; } } - - function getOrderedIndexOfSuggestion(bytes32 _proposalId, uint256 _suggestionId) - internal - view - returns(uint256 index) { - uint256[MAX_NUMBER_OF_WINNERS] memory topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; - /** get how many elements are greater than a given element **/ - for (uint256 i; i < proposals[_proposalId].competitionProposal.numberOfWinners; i++) { - if (suggestions[topSuggestionsOrdered[i]].totalVotes > suggestions[_suggestionId].totalVotes) { - index++; - } - } - } } diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index fa65be5a..952c15e5 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -65,9 +65,9 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa uint256 externalTokenRewardLeft; } - modifier onlyRedeemer() { - if (redeemer != address(0)) { - require(msg.sender == redeemer, "only redeemer allowed to redeem"); + modifier onlyRewarder() { + if (rewarder != address(0)) { + require(msg.sender == rewarder, "only rewarder allowed to redeem"); } _; } @@ -78,7 +78,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa bytes32 public voteParams; address public contractToCall; Avatar public avatar; - address public redeemer; + address public rewarder; /** * @dev enables this contract to receive ethers @@ -91,14 +91,14 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa * @param _avatar the avatar to mint reputation from * @param _votingMachine the voting machines address to * @param _voteParams voting machine parameters. - * @param _redeemer an address which allowed to redeem the contribution. - if _redeemer is 0 this param is agnored. + * @param _rewarder an address which allowed to redeem the contribution. + if _rewarder is 0 this param is agnored. */ function initialize( Avatar _avatar, IntVoteInterface _votingMachine, bytes32 _voteParams, - address _redeemer + address _rewarder ) external { @@ -107,7 +107,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa avatar = _avatar; votingMachine = _votingMachine; voteParams = _voteParams; - redeemer = _redeemer; + rewarder = _rewarder; } /** @@ -318,7 +318,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa */ function redeemReputationFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _reputation) public - onlyRedeemer + onlyRewarder { ContributionProposal storage proposal = organizationProposals[_proposalId]; require(proposal.executionTime != 0); @@ -340,7 +340,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa */ function redeemNativeTokenFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _amount) public - onlyRedeemer + onlyRewarder { ContributionProposal storage proposal = organizationProposals[_proposalId]; require(proposal.executionTime != 0); @@ -361,7 +361,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa */ function redeemEtherFromExtContract(bytes32 _proposalId, address payable _beneficiary, uint256 _amount) public - onlyRedeemer + onlyRewarder { ContributionProposal storage proposal = organizationProposals[_proposalId]; require(proposal.executionTime != 0); @@ -382,7 +382,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa */ function redeemExternalTokenFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _amount) public - onlyRedeemer { + onlyRewarder { ContributionProposal storage proposal = organizationProposals[_proposalId]; require(proposal.executionTime != 0); //this will ensure sum zero of reputation. diff --git a/test/competition.js b/test/competition.js new file mode 100644 index 00000000..4f7e34f7 --- /dev/null +++ b/test/competition.js @@ -0,0 +1,361 @@ +import * as helpers from './helpers'; +const constants = require('./constants'); +const ContributionRewardExt = artifacts.require("./ContributionRewardExt.sol"); +const ERC20Mock = artifacts.require('./test/ERC20Mock.sol'); +const DaoCreator = artifacts.require("./DaoCreator.sol"); +const ControllerCreator = artifacts.require("./ControllerCreator.sol"); +const DAOTracker = artifacts.require("./DAOTracker.sol"); +const Competition = artifacts.require("./Competition.sol"); + +export class ContributionRewardParams { + constructor() { + } +} + +const setupContributionRewardParams = async function( + contributionReward, + accounts, + genesisProtocol, + token, + avatar, + redeemer = helpers.NULL_ADDRESS + ) { + var contributionRewardParams = new ContributionRewardParams(); + if (genesisProtocol === true) { + contributionRewardParams.votingMachine = await helpers.setupGenesisProtocol(accounts,token,avatar,helpers.NULL_ADDRESS); + await contributionReward.initialize( avatar.address, + contributionRewardParams.votingMachine.genesisProtocol.address, + contributionRewardParams.votingMachine.params, + redeemer); + } else { + contributionRewardParams.votingMachine = await helpers.setupAbsoluteVote(helpers.NULL_ADDRESS,50,contributionReward.address); + await contributionReward.initialize( + avatar.address, + contributionRewardParams.votingMachine.absoluteVote.address, + contributionRewardParams.votingMachine.params, + redeemer + ); + } + return contributionRewardParams; +}; + +const setup = async function (accounts,genesisProtocol = false,tokenAddress=0,service=helpers.NULL_ADDRESS) { + var testSetup = new helpers.TestSetup(); + testSetup.standardTokenMock = await ERC20Mock.new(accounts[1],100); + testSetup.contributionRewardExt = await ContributionRewardExt.new(); + var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT}); + var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT}); + testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT}); + if (genesisProtocol) { + testSetup.reputationArray = [1000,100,0]; + } else { + testSetup.reputationArray = [2000,5000,7000]; + } + testSetup.org = await helpers.setupOrganizationWithArrays(testSetup.daoCreator,[accounts[0],accounts[1],accounts[2]],[1000,0,0],testSetup.reputationArray); + testSetup.contributionRewardExtParams= await setupContributionRewardParams( + testSetup.contributionRewardExt, + accounts,genesisProtocol, + tokenAddress, + testSetup.org.avatar, + service); + var permissions = "0x00000000"; + await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address, + [testSetup.contributionRewardExt.address], + [helpers.NULL_HASH],[permissions],"metaData"); + + testSetup.competition = await Competition.new(); + return testSetup; +}; + +const proposeCompetition = async function( + _testSetup, + _descriptionHash = "description-hash", + _reputationChange = 10, + _rewards = [1,2,3], + _rewardSplit = [50,25,15,10], + _startTime = 0, + _votingStartTime = 600, + _endTime = 1200, + _numberOfVotesPerVoters = 3, + ) { + + var block = await web3.eth.getBlock("latest"); + _testSetup.startTime = block.timestamp + _startTime; + _testSetup.votingStartTime = block.timestamp + _votingStartTime; + _testSetup.endTime = block.timestamp + _endTime; + var tx = await _testSetup.competition.proposeCompetition( + _descriptionHash, + _reputationChange, + _rewards = [1,2,3], + _testSetup.standardTokenMock.address, + _rewardSplit, + _testSetup.startTime, + _testSetup.votingStartTime, + _testSetup.endTime, + _numberOfVotesPerVoters, + _testSetup.contributionRewardExt.address + ); + + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "NewCompetitionProposal"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._numberOfWinners,_rewardSplit.length); + assert.equal(tx.logs[0].args._rewardSplit[0],_rewardSplit[0]); + assert.equal(tx.logs[0].args._rewardSplit[1],_rewardSplit[1]); + assert.equal(tx.logs[0].args._rewardSplit[2],_rewardSplit[2]); + assert.equal(tx.logs[0].args._rewardSplit[3],_rewardSplit[3]); + assert.equal(tx.logs[0].args._startTime,_testSetup.startTime); + assert.equal(tx.logs[0].args._votingStartTime,_testSetup.votingStartTime); + assert.equal(tx.logs[0].args._endTime,_testSetup.endTime); + assert.equal(tx.logs[0].args._numberOfVotesPerVoters,_numberOfVotesPerVoters); + assert.equal(tx.logs[0].args._contributionReward,_testSetup.contributionRewardExt.address); + + return proposalId; +}; + + +contract('Competition', accounts => { + + it("proposeCompetition log", async function() { + var testSetup = await setup(accounts); + await proposeCompetition(testSetup); + + var descriptionHash = "description-hash"; + var reputationChange = 10; + var rewards = [1,2,3]; + var rewardSplit = new Array(101).fill(0); + var startTime = 0; + var votingStartTime = 600; + var endTime = 1200; + rewardSplit[0]= 100; + try { + + await proposeCompetition(testSetup, + descriptionHash, + reputationChange, + rewards, + rewardSplit); + assert(false, 'number of winners should be <= 100'); + } catch (ex) { + helpers.assertVMException(ex); + } + rewardSplit = [50,25,15,0]; + try { + + await proposeCompetition(testSetup, + descriptionHash, + reputationChange, + rewards, + rewardSplit); + assert(false, 'total reward split should be 100%'); + } catch (ex) { + helpers.assertVMException(ex); + } + rewardSplit = [50,25,15,10]; + + try { + + await proposeCompetition(testSetup, + descriptionHash, + reputationChange, + rewards, + rewardSplit, + startTime, + endTime);//votingStartTime + assert(false, '_votingStartTime < _endTime'); + } catch (ex) { + helpers.assertVMException(ex); + } + + try { + + await proposeCompetition(testSetup, + descriptionHash, + reputationChange, + rewards, + rewardSplit, + votingStartTime,//startTime + votingStartTime-1);//votingStartTime + assert(false, '_votingStartTime >= _startTime,'); + } catch (ex) { + helpers.assertVMException(ex); + } + + try { + + await proposeCompetition(testSetup, + descriptionHash, + reputationChange, + rewards, + rewardSplit, + startTime,//startTime + votingStartTime, + endTime, + 0);//votingStartTime + assert(false, 'numberOfVotesPerVoters > 0'); + } catch (ex) { + helpers.assertVMException(ex); + } + }); + + it("suggest", async function() { + var testSetup = await setup(accounts); + var proposalId = await proposeCompetition(testSetup); + var tx = await testSetup.competition.suggest(proposalId,"suggestion"); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "NewSuggestion"); + assert.equal(tx.logs[0].args._suggestionId,1); + }); + + it("cannot suggest before start time", async function() { + var testSetup = await setup(accounts); + var descriptionHash = "description-hash"; + var reputationChange = 10; + var rewards = [1,2,3]; + var rewardSplit = [0,50,25,25]; + var proposalId = await proposeCompetition(testSetup, + descriptionHash, + reputationChange, + rewards, + rewardSplit, + 10//startTime + );//votingStartTime + try { + + await testSetup.competition.suggest(proposalId,"suggestion"); + assert(false, 'cannot suggest before start time'); + } catch (ex) { + helpers.assertVMException(ex); + } + await helpers.increaseTime(10+1); + await testSetup.competition.suggest(proposalId,"suggestion"); + }); + + it("cannot suggest after competition end", async function() { + var testSetup = await setup(accounts); + var proposalId = await proposeCompetition(testSetup);//votingStartTime + await testSetup.competition.suggest(proposalId,"suggestion"); + await helpers.increaseTime(1200+1); + try { + await testSetup.competition.suggest(proposalId,"suggestion"); + assert(false, 'cannot suggest after competition end'); + } catch (ex) { + helpers.assertVMException(ex); + } + }); + + it("vote", async function() { + var testSetup = await setup(accounts); + var proposalId = await proposeCompetition(testSetup); + var tx = await testSetup.competition.suggest(proposalId,"suggestion"); + var suggestionId = tx.logs[0].args._suggestionId; + + try { + await testSetup.competition.vote(suggestionId); + assert(false, 'vote before voting start time should fail'); + } catch (ex) { + helpers.assertVMException(ex); + } + await helpers.increaseTime(650); + + try { + await testSetup.competition.vote(suggestionId+1); + assert(false, 'vote on none valid suggestion'); + } catch (ex) { + helpers.assertVMException(ex); + } + var proposal = await testSetup.competition.proposals(proposalId); + tx = await testSetup.competition.vote(suggestionId); + + try { + await testSetup.competition.vote(suggestionId); + assert(false, 'can vote only one time on each suggestion'); + } catch (ex) { + helpers.assertVMException(ex); + } + + assert.equal(tx.logs.length, 2); + assert.equal(tx.logs[0].event, "SnapshotBlock"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._snapshotBlock,tx.logs[0].blockNumber); + + assert.equal(tx.logs[1].event, "NewVote"); + assert.equal(tx.logs[1].args._suggestionId,1); + assert.equal(tx.logs[1].args._reputation,testSetup.reputationArray[0]); + + //first vote set the snapshotBlock + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.vote(2); + proposal = await testSetup.competition.proposals(proposalId); + assert.equal(proposal.snapshotBlock, tx.logs[0].blockNumber); + + //3rd suggestion + await testSetup.competition.suggest(proposalId,"suggestion"); + //4th suggestion + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.vote(3); + + try { + await testSetup.competition.vote(4); + assert(false, 'cannot vote more than allowed per voter'); + } catch (ex) { + helpers.assertVMException(ex); + } + + }); + + + it("total votes", async function() { + var testSetup = await setup(accounts); + var proposalId = await proposeCompetition(testSetup); + var tx = await testSetup.competition.suggest(proposalId,"suggestion"); + var suggestionId = tx.logs[0].args._suggestionId; + await helpers.increaseTime(650); + await testSetup.competition.vote(suggestionId); + await testSetup.competition.vote(suggestionId,{from:accounts[1]}); + await testSetup.competition.vote(suggestionId,{from:accounts[2]}); + var suggestion = await testSetup.competition.suggestions(suggestionId); + assert.equal(suggestion.totalVotes, testSetup.reputationArray[0] +testSetup.reputationArray[1]+testSetup.reputationArray[2]); + }); + + it("getOrderedIndexOfSuggestion", async function() { + var testSetup = await setup(accounts); + var proposalId = await proposeCompetition(testSetup); + for (var i=0;i<20;i++) { + //submit 20 suggestion + await testSetup.competition.suggest(proposalId,"suggestion"); + } + await helpers.increaseTime(650); + await testSetup.competition.vote(10,{from:accounts[0]}); + await testSetup.competition.vote(16,{from:accounts[2]}); + await testSetup.competition.vote(5,{from:accounts[1]}); + + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,10),2); + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,5),1); + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,16),0); + + }); + + it("getOrderedIndexOfSuggestion equality case", async function() { + var testSetup = await setup(accounts); + var proposalId = await proposeCompetition(testSetup); + for (var i=0;i<20;i++) { + //submit 20 suggestion + await testSetup.competition.suggest(proposalId,"suggestion"); + } + await helpers.increaseTime(650); + await testSetup.competition.vote(10,{from:accounts[0]}); + await testSetup.competition.vote(16,{from:accounts[0]}); + await testSetup.competition.vote(5,{from:accounts[0]}); + + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,10),0); + + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,16),0); + + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,5),0); + + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,0),3); + + }); +}); From b0aec52f90300c45d7d19e179b096ff105785450 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Tue, 3 Dec 2019 18:36:36 +0200 Subject: [PATCH 09/21] fixes and cosmetics --- contracts/schemes/Competition.sol | 310 +++++++++++++++++++----------- test/competition.js | 172 +++++++++++++++-- 2 files changed, 354 insertions(+), 128 deletions(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index 66952ef0..6e5251e4 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -16,14 +16,20 @@ contract Competition { uint256 _votingStartTime, uint256 _endTime, uint256 _numberOfVotesPerVoters, - address payable _contributionReward //address of the contract to redeem from. + address payable _contributionRewardExt //address of the contract to redeem from. + ); + + event Redeem( + bytes32 indexed _proposalId, + uint256 indexed _suggestionId, + uint256 _rewardPercentage ); event NewSuggestion( bytes32 indexed _proposalId, uint256 indexed _suggestionId, string indexed _descriptionHash, - address suggester + address payable suggester ); event NewVote( @@ -46,8 +52,14 @@ contract Competition { uint256 votingStartTime; uint256 endTime; uint256 numberOfVotesPerVoters; - address payable contributionReward; //address of the contract to redeem from. + address payable contributionRewardExt; uint256 snapshotBlock; + //contributionRewardExt + uint256 reputationReward; + uint256 ethReward; + uint256 nativeTokenReward; + uint256 externalTokenReward; + uint256[] topSuggestionsOrdered; //mapping from suggestions totalVotes to the number of suggestions with the same totalVotes. mapping(uint256=>uint256) tiesSuggestions; @@ -57,44 +69,55 @@ contract Competition { struct Suggestion { uint256 totalVotes; bytes32 proposalId; - address suggester; + address payable suggester; mapping(address=>uint256) votes; } //mapping from proposalID to Proposal - //this is private due to avoid use of pragma experimental ABIEncoderV2; mapping(bytes32=>Proposal) public proposals; - //mapping from suggestionId to Suggestion mapping(uint256=>Suggestion) public suggestions; - - uint256 public suggestionsCounter; + /** + * @dev Submit a competion proposal + * @param _descriptionHash A hash of the proposal's description + * @param _reputationChange - Amount of reputation change requested .Can be negative. + * @param _rewards rewards array: + * rewards[0] - Amount of tokens requested per period + * rewards[1] - Amount of ETH requested per period + * rewards[2] - Amount of external tokens requested per period + * @param _externalToken Address of external token, if reward is requested there + * @param _rewardSplit an array of precentages which specify how to split the rewards + * between the winning suggestions + * @param _competitionParams competition parameters : + * _competitionParams[0] - competition startTime + * _competitionParams[1] - _votingStartTime competition voting start time + * _competitionParams[2] - _endTime competition end time + * _competitionParams[3] - _numberOfVotesPerVoters on how many suggestions a voter can vote + * @param _contributionRewardExt the contributionRewardExt scheme which + * manage and allocate the rewards for the competition. + * @return proposalId the proposal id. + */ function proposeCompetition( - string memory _descriptionHash, + string calldata _descriptionHash, int256 _reputationChange, - uint[3] memory _rewards, + uint[3] calldata _rewards, IERC20 _externalToken, - uint256[] memory _rewardSplit, - uint256 _startTime, - uint256 _votingStartTime, - uint256 _endTime, - uint256 _numberOfVotesPerVoters, - address payable _contributionReward //address of the contract to redeem from. + uint256[] calldata _rewardSplit, + uint256[4] calldata _competitionParams, + // uint256 _votingStartTime, + // uint256 _endTime, + // uint256 _numberOfVotesPerVoters, + address payable _contributionRewardExt //address of the contract to redeem from. ) - public - returns(bytes32 proposalId) - { + external + returns(bytes32 proposalId) { uint256 numberOfWinners = _rewardSplit.length; - require(numberOfWinners <= MAX_NUMBER_OF_WINNERS, - "number of winners greater than max allowed"); - require(_votingStartTime < _endTime, - "voting start time greater than end time"); - require(_votingStartTime >= _startTime, - "voting start time smaller than start time"); - require(_numberOfVotesPerVoters > 0, - "numberOfVotesPerVoters should be greater than 0"); + require(numberOfWinners <= MAX_NUMBER_OF_WINNERS, "number of winners greater than max allowed"); + require(_competitionParams[1] < _competitionParams[2], "voting start time greater than end time"); + require(_competitionParams[1] >= _competitionParams[0], "voting start time smaller than start time"); + require(_competitionParams[3] > 0, "numberOfVotesPerVoters should be greater than 0"); uint256 totalRewardSplit; for (uint256 i = 0; i < numberOfWinners; i++) { @@ -102,47 +125,54 @@ contract Competition { } require(totalRewardSplit == 100, "total rewards split is not 100%"); - proposalId = ContributionRewardExt(_contributionReward).proposeContributionReward( + proposalId = ContributionRewardExt(_contributionRewardExt).proposeContributionReward( _descriptionHash, _reputationChange, _rewards, _externalToken, - _contributionReward, + _contributionRewardExt, msg.sender); - uint256 startTime = _startTime; + uint256 startTime = _competitionParams[0]; if (startTime == 0) { // solhint-disable-next-line not-rely-on-time startTime = now; } - proposals[proposalId] = Proposal({ - numberOfWinners: numberOfWinners, - rewardSplit: _rewardSplit, - startTime: startTime, - votingStartTime: _votingStartTime, - endTime: _endTime, - numberOfVotesPerVoters: _numberOfVotesPerVoters, - contributionReward: _contributionReward, - snapshotBlock: 0, - topSuggestionsOrdered: new uint256[](numberOfWinners) - }); + + proposals[proposalId].numberOfWinners = numberOfWinners; + proposals[proposalId].rewardSplit = _rewardSplit; + proposals[proposalId].startTime = startTime; + proposals[proposalId].votingStartTime = _competitionParams[1]; + proposals[proposalId].endTime = _competitionParams[2]; + proposals[proposalId].numberOfVotesPerVoters = _competitionParams[3]; + proposals[proposalId].contributionRewardExt = _contributionRewardExt; + proposals[proposalId].reputationReward = uint256(_reputationChange); + proposals[proposalId].nativeTokenReward = _rewards[0]; + proposals[proposalId].ethReward = _rewards[1]; + proposals[proposalId].externalTokenReward = _rewards[2]; emit NewCompetitionProposal( proposalId, numberOfWinners, - _rewardSplit, + proposals[proposalId].rewardSplit, startTime, - _votingStartTime, - _endTime, - _numberOfVotesPerVoters, - _contributionReward + proposals[proposalId].votingStartTime, + proposals[proposalId].endTime, + proposals[proposalId].numberOfVotesPerVoters, + _contributionRewardExt ); } + /** + * @dev suggest a competion suggestion + * @param _proposalId the proposal id this suggestion is referring to. + * @param _descriptionHash a descriptionHash of the suggestion. + * @return suggestionId the suggestionId. + */ function suggest( bytes32 _proposalId, - string memory _descriptionHash + string calldata _descriptionHash ) - public + external returns(uint256) { // solhint-disable-next-line not-rely-on-time @@ -156,17 +186,13 @@ contract Competition { return suggestionsCounter; } - function setSnapshotBlock(bytes32 _proposalId) public { - // solhint-disable-next-line not-rely-on-time - require(proposals[_proposalId].votingStartTime < now, "voting period not started yet"); - if (proposals[_proposalId].snapshotBlock == 0) { - proposals[_proposalId].snapshotBlock = block.number; - emit SnapshotBlock(_proposalId, block.number); - } - } - + /** + * @dev vote on a suggestion + * @param _suggestionId suggestionId + * @return bool + */ function vote(uint256 _suggestionId) - public + external returns(bool) { bytes32 proposalId = suggestions[_suggestionId].proposalId; @@ -181,7 +207,7 @@ contract Competition { "exceed number of votes allowed"); proposal.votesPerVoter[msg.sender] = proposal.votesPerVoter[msg.sender].add(1); suggestion.votes[msg.sender] = suggestion.votes[msg.sender].add(1); - Avatar avatar = ContributionRewardExt(proposal.contributionReward).avatar(); + Avatar avatar = ContributionRewardExt(proposal.contributionRewardExt).avatar(); uint256 reputation = avatar.nativeReputation().balanceOfAt(msg.sender, proposals[proposalId].snapshotBlock); require(reputation > 0, "voter has no reputation"); suggestion.totalVotes = suggestion.totalVotes.add(reputation); @@ -192,59 +218,42 @@ contract Competition { return true; } - function redeem(uint256 _suggestionId, address payable _beneficiary) public { - require(suggestions[_suggestionId].totalVotes > 0, "no one vote for this suggestion"); - bytes32 proposalId = suggestions[_suggestionId].proposalId; - require(proposalId != bytes32(0), "suggestion not exist"); - Proposal storage proposal = proposals[proposalId]; - // solhint-disable-next-line not-rely-on-time - require(proposal.endTime > now, "competition is still on"); - uint256 amount; - //check if there is a win - for (uint256 i = 0; i < proposal.numberOfWinners; i++) { - if (suggestions[proposal.topSuggestionsOrdered[i]].suggester == _beneficiary) { - uint256 orderIndex = getOrderedIndexOfSuggestion(proposalId, _suggestionId); - suggestions[_suggestionId].suggester = address(0); - uint256 rewardPercentage = 0; - uint256 numberOfTieSuggestions = proposal.tiesSuggestions[suggestions[_suggestionId].totalVotes]; - //calc the reward percentage for this suggestion - for (uint256 j=orderIndex; j < (orderIndex+numberOfTieSuggestions); j++) { - rewardPercentage = rewardPercentage.add(proposal.rewardSplit[j]); - } - rewardPercentage = rewardPercentage.div(numberOfTieSuggestions); - - amount = ContributionRewardExt(proposal.contributionReward) - .getProposalExternalTokenReward(proposalId).mul(rewardPercentage).div(100); - ContributionRewardExt(proposal.contributionReward).redeemExternalTokenFromExtContract( - proposalId, - _beneficiary, - amount); - - amount = uint256(ContributionRewardExt(proposal.contributionReward) - .getProposalReputationReward(proposalId)).mul(rewardPercentage).div(100); - ContributionRewardExt(proposal.contributionReward).redeemReputationFromExtContract( - proposalId, - _beneficiary, - amount); - - amount = ContributionRewardExt(proposal.contributionReward) - .getProposalEthReward(proposalId).mul(rewardPercentage).div(100); - ContributionRewardExt(proposal.contributionReward).redeemEtherFromExtContract( - proposalId, - _beneficiary, - amount); - - amount = ContributionRewardExt(proposal.contributionReward) - .getProposalNativeTokenReward(proposalId).mul(rewardPercentage).div(100); - ContributionRewardExt(proposal.contributionReward).redeemNativeTokenFromExtContract( - proposalId, - _beneficiary, - amount); - break; + /** + * @dev redeem a winning suggestion reward + * @param _suggestionId suggestionId + * @param _beneficiary - the reward beneficiary. + * this parameter is take into account only if the msg.sender is the suggestion's suggester, + * otherwise the _beneficiary param is ignored and the beneficiary is suggestion's suggester. + */ + function redeem(uint256 _suggestionId, address payable _beneficiary) external { + address payable beneficiary = suggestions[_suggestionId].suggester; + if (msg.sender == suggestions[_suggestionId].suggester) { + //only suggester can redeem to other address + if (beneficiary != address(0)) { + beneficiary = _beneficiary; } } + _redeem(_suggestionId, beneficiary); } + /** + * @dev setSnapshotBlock set the block for the reputaion snapshot + * @param _proposalId the proposal id + */ + function setSnapshotBlock(bytes32 _proposalId) public { + // solhint-disable-next-line not-rely-on-time + require(proposals[_proposalId].votingStartTime < now, "voting period not started yet"); + if (proposals[_proposalId].snapshotBlock == 0) { + proposals[_proposalId].snapshotBlock = block.number; + emit SnapshotBlock(_proposalId, block.number); + } + } + + /** + * @dev getOrderedIndexOfSuggestion return the index of specific suggestion in the winners list. + * @param _proposalId proposal id + * @param _suggestionId suggestion id + */ function getOrderedIndexOfSuggestion(bytes32 _proposalId, uint256 _suggestionId) public view @@ -252,25 +261,92 @@ contract Competition { uint256[] memory topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; /** get how many elements are greater than a given element + how many elements are equal to agiven element **/ - for (uint256 i; i < proposals[_proposalId].numberOfWinners; i++) { + for (uint256 i; i < topSuggestionsOrdered.length; i++) { if (suggestions[topSuggestionsOrdered[i]].totalVotes > suggestions[_suggestionId].totalVotes) { index++; } } } - function refreshTopSuggestions(bytes32 _proposalId, uint256 _suggestionId) internal { + /** + * @dev refreshTopSuggestions this function maintain a winners list array. + * @param _proposalId proposal id + * @param _suggestionId suggestion id + */ + function refreshTopSuggestions(bytes32 _proposalId, uint256 _suggestionId) private { uint256[] storage topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; - /** get the index of the smallest element **/ - uint256 smallest = 0; - for (uint256 i; i < proposals[_proposalId].numberOfWinners; i++) { - if (suggestions[topSuggestionsOrdered[i]].totalVotes > smallest) { - smallest++; + if (topSuggestionsOrdered.length < proposals[_proposalId].numberOfWinners) { + topSuggestionsOrdered.push(_suggestionId); + } else { + /** get the index of the smallest element **/ + uint256 smallest = 0; + for (uint256 i; i < proposals[_proposalId].numberOfWinners; i++) { + if (suggestions[topSuggestionsOrdered[i]].totalVotes > smallest) { + smallest++; + } + } + + if (suggestions[topSuggestionsOrdered[smallest]].totalVotes < suggestions[_suggestionId].totalVotes) { + topSuggestionsOrdered[smallest] = _suggestionId; } } + } - if (suggestions[topSuggestionsOrdered[smallest]].totalVotes < suggestions[_suggestionId].totalVotes) { - topSuggestionsOrdered[smallest] = _suggestionId; + /** + * @dev redeem a winning suggestion reward + * @param _suggestionId suggestionId + * @param _beneficiary - the reward beneficiary + */ + function _redeem(uint256 _suggestionId, address payable _beneficiary) private { + bytes32 proposalId = suggestions[_suggestionId].proposalId; + require(proposalId != bytes32(0), "suggestion not exist"); + Proposal storage proposal = proposals[proposalId]; + // solhint-disable-next-line not-rely-on-time + require(proposal.endTime < now, "competition is still on"); + uint256 amount; + //check if there is a win + for (uint256 i = 0; i < proposal.topSuggestionsOrdered.length; i++) { + if (suggestions[proposal.topSuggestionsOrdered[i]].suggester != address(0)) { + uint256 orderIndex = getOrderedIndexOfSuggestion(proposalId, _suggestionId); + suggestions[_suggestionId].suggester = address(0); + uint256 rewardPercentage = 0; + uint256 numberOfTieSuggestions = proposal.tiesSuggestions[suggestions[_suggestionId].totalVotes]; + uint256 j; + //calc the reward percentage for this suggestion + for (j = orderIndex; j < (orderIndex+numberOfTieSuggestions); j++) { + rewardPercentage = rewardPercentage.add(proposal.rewardSplit[j]); + } + rewardPercentage = rewardPercentage.div(numberOfTieSuggestions); + uint256 rewardPercentageLeft = 0; + if (proposal.topSuggestionsOrdered.length < proposal.numberOfWinners) { + for (j = proposal.topSuggestionsOrdered.length; j < proposal.numberOfWinners; j++) { + rewardPercentageLeft = rewardPercentageLeft.add(proposal.rewardSplit[j]); + } + //if there are less winners than the proposal number of winners so divide the pre allocated + //left reward equally between the winners + rewardPercentage = + rewardPercentage.add(rewardPercentageLeft.div(proposal.topSuggestionsOrdered.length)); + } + + amount = proposal.externalTokenReward.mul(rewardPercentage).div(100); + ContributionRewardExt(proposal.contributionRewardExt).redeemExternalTokenFromExtContract( + proposalId, _beneficiary, amount); + + amount = proposal.reputationReward.mul(rewardPercentage).div(100); + ContributionRewardExt(proposal.contributionRewardExt).redeemReputationFromExtContract( + proposalId, _beneficiary, amount); + + amount = proposal.ethReward.mul(rewardPercentage).div(100); + ContributionRewardExt(proposal.contributionRewardExt).redeemEtherFromExtContract( + proposalId, _beneficiary, amount); + + amount = proposal.nativeTokenReward.mul(rewardPercentage).div(100); + ContributionRewardExt(proposal.contributionRewardExt).redeemNativeTokenFromExtContract( + proposalId, _beneficiary, amount); + emit Redeem(proposalId, _suggestionId, rewardPercentage); + break; + } } } + } diff --git a/test/competition.js b/test/competition.js index 4f7e34f7..67fc2ece 100644 --- a/test/competition.js +++ b/test/competition.js @@ -41,7 +41,7 @@ const setupContributionRewardParams = async function( const setup = async function (accounts,genesisProtocol = false,tokenAddress=0,service=helpers.NULL_ADDRESS) { var testSetup = new helpers.TestSetup(); - testSetup.standardTokenMock = await ERC20Mock.new(accounts[1],100); + testSetup.standardTokenMock = await ERC20Mock.new(accounts[1],100000); testSetup.contributionRewardExt = await ContributionRewardExt.new(); var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT}); var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT}); @@ -86,13 +86,13 @@ const proposeCompetition = async function( var tx = await _testSetup.competition.proposeCompetition( _descriptionHash, _reputationChange, - _rewards = [1,2,3], + _rewards, _testSetup.standardTokenMock.address, _rewardSplit, - _testSetup.startTime, + [_testSetup.startTime, _testSetup.votingStartTime, _testSetup.endTime, - _numberOfVotesPerVoters, + _numberOfVotesPerVoters], _testSetup.contributionRewardExt.address ); @@ -109,7 +109,7 @@ const proposeCompetition = async function( assert.equal(tx.logs[0].args._votingStartTime,_testSetup.votingStartTime); assert.equal(tx.logs[0].args._endTime,_testSetup.endTime); assert.equal(tx.logs[0].args._numberOfVotesPerVoters,_numberOfVotesPerVoters); - assert.equal(tx.logs[0].args._contributionReward,_testSetup.contributionRewardExt.address); + assert.equal(tx.logs[0].args._contributionRewardExt,_testSetup.contributionRewardExt.address); return proposalId; }; @@ -345,17 +345,167 @@ contract('Competition', accounts => { await testSetup.competition.suggest(proposalId,"suggestion"); } await helpers.increaseTime(650); - await testSetup.competition.vote(10,{from:accounts[0]}); - await testSetup.competition.vote(16,{from:accounts[0]}); + await testSetup.competition.vote(10,{from:accounts[1]}); + await testSetup.competition.vote(16,{from:accounts[1]}); await testSetup.competition.vote(5,{from:accounts[0]}); assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,10),0); - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,16),0); - - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,5),0); - + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,5),2); assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,0),3); }); + + it("redeem", async function() { + var testSetup = await setup(accounts); + await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,30,{from:accounts[1]}); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); + var proposalId = await proposeCompetition(testSetup); + await testSetup.competition.suggest(proposalId,"suggestion"); + try { + await testSetup.competition.redeem(1,accounts[0]); + assert(false, 'cannot redeem if no vote'); + } catch (ex) { + helpers.assertVMException(ex); + } + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[0]}); + await testSetup.contributionRewardExt.redeem(proposalId,[true,true,true,true]); + await helpers.increaseTime(650); + await testSetup.competition.vote(1,{from:accounts[1]}); + try { + await testSetup.competition.redeem(1,accounts[0]); + assert(false, 'cannot redeem if competion not ended yet'); + } catch (ex) { + helpers.assertVMException(ex); + } + await helpers.increaseTime(650); + var tx = await testSetup.competition.redeem(1,accounts[0]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "Redeem"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._rewardPercentage,100); + + await testSetup.contributionRewardExt.getPastEvents('RedeemReputation', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemReputation"); + assert.equal(events[0].args._beneficiary,accounts[0]); + assert.equal(events[0].args._amount,10); + }); + + await testSetup.contributionRewardExt.getPastEvents('RedeemEther', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemEther"); + assert.equal(events[0].args._beneficiary,accounts[0]); + assert.equal(events[0].args._amount,2); + }); + + await testSetup.contributionRewardExt.getPastEvents('RedeemNativeToken', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemNativeToken"); + assert.equal(events[0].args._beneficiary,accounts[0]); + assert.equal(events[0].args._amount,1); + }); + + await testSetup.contributionRewardExt.getPastEvents('RedeemExternalToken', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemExternalToken"); + assert.equal(events[0].args._beneficiary,accounts[0]); + assert.equal(events[0].args._amount,3); + }); + + }); + + it("redeem multipe suggestions", async function() { + var testSetup = await setup(accounts); + await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,3000,{from:accounts[1]}); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:2000}); + var proposalId = await proposeCompetition(testSetup,"description-hash",1000,[1000,2000,3000]); + + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[0]}); + await testSetup.contributionRewardExt.redeem(proposalId,[true,true,true,true]); + await helpers.increaseTime(650); + await testSetup.competition.vote(1,{from:accounts[0]}); + await testSetup.competition.vote(2,{from:accounts[1]}); + await testSetup.competition.vote(3,{from:accounts[2]}); + + await helpers.increaseTime(650); + var tx = await testSetup.competition.redeem(1,accounts[0]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "Redeem"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._rewardPercentage,18); + + await testSetup.contributionRewardExt.getPastEvents('RedeemReputation', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemReputation"); + assert.equal(events[0].args._beneficiary,accounts[0]); + assert.equal(events[0].args._amount,180); + }); + + await testSetup.contributionRewardExt.getPastEvents('RedeemEther', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemEther"); + assert.equal(events[0].args._beneficiary,accounts[0]); + assert.equal(events[0].args._amount,(2000*18/100)); + }); + + await testSetup.contributionRewardExt.getPastEvents('RedeemNativeToken', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemNativeToken"); + assert.equal(events[0].args._beneficiary,accounts[0]); + assert.equal(events[0].args._amount,(1000*18/100)); + }); + + await testSetup.contributionRewardExt.getPastEvents('RedeemExternalToken', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemExternalToken"); + assert.equal(events[0].args._beneficiary,accounts[0]); + assert.equal(events[0].args._amount,(3000*18/100)); + }); + tx = await testSetup.competition.redeem(2,accounts[0]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "Redeem"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._rewardPercentage,28); + + tx = await testSetup.competition.redeem(3,accounts[0]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "Redeem"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._rewardPercentage,53); + + }); }); From c9aea32ccb87360995380187b17c4df6d032b394 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Dec 2019 12:03:16 +0200 Subject: [PATCH 10/21] fix refreshTopSuggestions --- contracts/schemes/Competition.sol | 13 +++++-------- contracts/schemes/ContributionRewardExt.sol | 1 - 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index 6e5251e4..a50de105 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -106,9 +106,6 @@ contract Competition { IERC20 _externalToken, uint256[] calldata _rewardSplit, uint256[4] calldata _competitionParams, - // uint256 _votingStartTime, - // uint256 _endTime, - // uint256 _numberOfVotesPerVoters, address payable _contributionRewardExt //address of the contract to redeem from. ) external @@ -248,7 +245,7 @@ contract Competition { emit SnapshotBlock(_proposalId, block.number); } } - + /** * @dev getOrderedIndexOfSuggestion return the index of specific suggestion in the winners list. * @param _proposalId proposal id @@ -259,8 +256,7 @@ contract Competition { view returns(uint256 index) { uint256[] memory topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; - /** get how many elements are greater than a given element - + how many elements are equal to agiven element **/ + /** get how many elements are greater than a given element*/ for (uint256 i; i < topSuggestionsOrdered.length; i++) { if (suggestions[topSuggestionsOrdered[i]].totalVotes > suggestions[_suggestionId].totalVotes) { index++; @@ -281,8 +277,9 @@ contract Competition { /** get the index of the smallest element **/ uint256 smallest = 0; for (uint256 i; i < proposals[_proposalId].numberOfWinners; i++) { - if (suggestions[topSuggestionsOrdered[i]].totalVotes > smallest) { - smallest++; + if (suggestions[topSuggestionsOrdered[i]].totalVotes < + suggestions[topSuggestionsOrdered[smallest]].totalVotes) { + smallest = i; } } diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index 952c15e5..440842ec 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -76,7 +76,6 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa IntVoteInterface public votingMachine; bytes32 public voteParams; - address public contractToCall; Avatar public avatar; address public rewarder; From ed38e109f1eb86963293d28b8e31132f14440ace Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Dec 2019 12:57:51 +0200 Subject: [PATCH 11/21] add initilize function --- contracts/schemes/Competition.sol | 34 +++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index a50de105..07566605 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -78,6 +78,18 @@ contract Competition { //mapping from suggestionId to Suggestion mapping(uint256=>Suggestion) public suggestions; uint256 public suggestionsCounter; + address payable public contributionRewardExt; //address of the contract to redeem from. + + /** + * @dev initialize + * @param _contributionRewardExt the contributionRewardExt scheme which + * manage and allocate the rewards for the competition. + */ + function initialize(address payable _contributionRewardExt) external { + require(contributionRewardExt == address(0), "can be called only one time"); + require(_contributionRewardExt != address(0), "contributionRewardExt cannot be zero"); + contributionRewardExt = _contributionRewardExt; + } /** * @dev Submit a competion proposal @@ -95,8 +107,6 @@ contract Competition { * _competitionParams[1] - _votingStartTime competition voting start time * _competitionParams[2] - _endTime competition end time * _competitionParams[3] - _numberOfVotesPerVoters on how many suggestions a voter can vote - * @param _contributionRewardExt the contributionRewardExt scheme which - * manage and allocate the rewards for the competition. * @return proposalId the proposal id. */ function proposeCompetition( @@ -105,8 +115,7 @@ contract Competition { uint[3] calldata _rewards, IERC20 _externalToken, uint256[] calldata _rewardSplit, - uint256[4] calldata _competitionParams, - address payable _contributionRewardExt //address of the contract to redeem from. + uint256[4] calldata _competitionParams ) external returns(bytes32 proposalId) { @@ -122,12 +131,12 @@ contract Competition { } require(totalRewardSplit == 100, "total rewards split is not 100%"); - proposalId = ContributionRewardExt(_contributionRewardExt).proposeContributionReward( + proposalId = ContributionRewardExt(contributionRewardExt).proposeContributionReward( _descriptionHash, _reputationChange, _rewards, _externalToken, - _contributionRewardExt, + contributionRewardExt, msg.sender); uint256 startTime = _competitionParams[0]; if (startTime == 0) { @@ -141,7 +150,6 @@ contract Competition { proposals[proposalId].votingStartTime = _competitionParams[1]; proposals[proposalId].endTime = _competitionParams[2]; proposals[proposalId].numberOfVotesPerVoters = _competitionParams[3]; - proposals[proposalId].contributionRewardExt = _contributionRewardExt; proposals[proposalId].reputationReward = uint256(_reputationChange); proposals[proposalId].nativeTokenReward = _rewards[0]; proposals[proposalId].ethReward = _rewards[1]; @@ -155,7 +163,7 @@ contract Competition { proposals[proposalId].votingStartTime, proposals[proposalId].endTime, proposals[proposalId].numberOfVotesPerVoters, - _contributionRewardExt + contributionRewardExt ); } @@ -204,7 +212,7 @@ contract Competition { "exceed number of votes allowed"); proposal.votesPerVoter[msg.sender] = proposal.votesPerVoter[msg.sender].add(1); suggestion.votes[msg.sender] = suggestion.votes[msg.sender].add(1); - Avatar avatar = ContributionRewardExt(proposal.contributionRewardExt).avatar(); + Avatar avatar = ContributionRewardExt(contributionRewardExt).avatar(); uint256 reputation = avatar.nativeReputation().balanceOfAt(msg.sender, proposals[proposalId].snapshotBlock); require(reputation > 0, "voter has no reputation"); suggestion.totalVotes = suggestion.totalVotes.add(reputation); @@ -326,19 +334,19 @@ contract Competition { } amount = proposal.externalTokenReward.mul(rewardPercentage).div(100); - ContributionRewardExt(proposal.contributionRewardExt).redeemExternalTokenFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemExternalTokenFromExtContract( proposalId, _beneficiary, amount); amount = proposal.reputationReward.mul(rewardPercentage).div(100); - ContributionRewardExt(proposal.contributionRewardExt).redeemReputationFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemReputationFromExtContract( proposalId, _beneficiary, amount); amount = proposal.ethReward.mul(rewardPercentage).div(100); - ContributionRewardExt(proposal.contributionRewardExt).redeemEtherFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemEtherFromExtContract( proposalId, _beneficiary, amount); amount = proposal.nativeTokenReward.mul(rewardPercentage).div(100); - ContributionRewardExt(proposal.contributionRewardExt).redeemNativeTokenFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemNativeTokenFromExtContract( proposalId, _beneficiary, amount); emit Redeem(proposalId, _suggestionId, rewardPercentage); break; From f57aeac692fdab1e292a221b456737e345fbee2a Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Dec 2019 13:03:28 +0200 Subject: [PATCH 12/21] update tests --- test/competition.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/competition.js b/test/competition.js index 67fc2ece..1bd12e48 100644 --- a/test/competition.js +++ b/test/competition.js @@ -64,6 +64,7 @@ const setup = async function (accounts,genesisProtocol = false,tokenAddress=0,se [helpers.NULL_HASH],[permissions],"metaData"); testSetup.competition = await Competition.new(); + testSetup.competition.initialize(testSetup.contributionRewardExt.address); return testSetup; }; @@ -92,9 +93,8 @@ const proposeCompetition = async function( [_testSetup.startTime, _testSetup.votingStartTime, _testSetup.endTime, - _numberOfVotesPerVoters], - _testSetup.contributionRewardExt.address - ); + _numberOfVotesPerVoters] + ); var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); assert.equal(tx.logs.length, 1); From 500c2f105e1ce43a77665b7a42f1917e9809444f Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Dec 2019 13:07:29 +0200 Subject: [PATCH 13/21] naming address payable _suggester --- contracts/schemes/Competition.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index 07566605..a228040e 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -29,7 +29,7 @@ contract Competition { bytes32 indexed _proposalId, uint256 indexed _suggestionId, string indexed _descriptionHash, - address payable suggester + address payable _suggester ); event NewVote( From ced6964f32291cd82c48149c22ad2767376d3d40 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Dec 2019 13:35:03 +0200 Subject: [PATCH 14/21] remove redundent line --- contracts/schemes/Competition.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index a228040e..5c0160cb 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -211,7 +211,6 @@ contract Competition { require(proposal.votesPerVoter[msg.sender] < proposal.numberOfVotesPerVoters, "exceed number of votes allowed"); proposal.votesPerVoter[msg.sender] = proposal.votesPerVoter[msg.sender].add(1); - suggestion.votes[msg.sender] = suggestion.votes[msg.sender].add(1); Avatar avatar = ContributionRewardExt(contributionRewardExt).avatar(); uint256 reputation = avatar.nativeReputation().balanceOfAt(msg.sender, proposals[proposalId].snapshotBlock); require(reputation > 0, "voter has no reputation"); From fe5c1c2b53fb672ca3f955d9aca30230b2684478 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Dec 2019 15:45:25 +0200 Subject: [PATCH 15/21] update pr comments --- contracts/schemes/Competition.sol | 183 ++++++++++++++++-------------- test/competition.js | 40 +++++-- 2 files changed, 124 insertions(+), 99 deletions(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index 5c0160cb..28a43e3c 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -14,8 +14,9 @@ contract Competition { uint256[] _rewardSplit, uint256 _startTime, uint256 _votingStartTime, + uint256 _suggestionsEndTime, uint256 _endTime, - uint256 _numberOfVotesPerVoters, + uint256 _maxNumberOfVotesPerVoter, address payable _contributionRewardExt //address of the contract to redeem from. ); @@ -50,19 +51,18 @@ contract Competition { uint256[] rewardSplit; uint256 startTime; uint256 votingStartTime; + uint256 suggestionsEndTime; uint256 endTime; - uint256 numberOfVotesPerVoters; + uint256 maxNumberOfVotesPerVoter; address payable contributionRewardExt; uint256 snapshotBlock; - //contributionRewardExt uint256 reputationReward; uint256 ethReward; uint256 nativeTokenReward; uint256 externalTokenReward; - - uint256[] topSuggestionsOrdered; + uint256[] topSuggestions; //mapping from suggestions totalVotes to the number of suggestions with the same totalVotes. - mapping(uint256=>uint256) tiesSuggestions; + mapping(uint256=>uint256) suggestionsPerVote; mapping(address=>uint256) votesPerVoter; } @@ -106,7 +106,8 @@ contract Competition { * _competitionParams[0] - competition startTime * _competitionParams[1] - _votingStartTime competition voting start time * _competitionParams[2] - _endTime competition end time - * _competitionParams[3] - _numberOfVotesPerVoters on how many suggestions a voter can vote + * _competitionParams[3] - _maxNumberOfVotesPerVoter on how many suggestions a voter can vote + * _competitionParams[4] - _suggestionsEndTime suggestion submition end time * @return proposalId the proposal id. */ function proposeCompetition( @@ -115,22 +116,30 @@ contract Competition { uint[3] calldata _rewards, IERC20 _externalToken, uint256[] calldata _rewardSplit, - uint256[4] calldata _competitionParams + uint256[5] calldata _competitionParams ) external returns(bytes32 proposalId) { uint256 numberOfWinners = _rewardSplit.length; + uint256 startTime = _competitionParams[0]; + if (startTime == 0) { + // solhint-disable-next-line not-rely-on-time + startTime = now; + } + // solhint-disable-next-line not-rely-on-time + require(startTime >= now, "startTime should be greater than proposing time"); require(numberOfWinners <= MAX_NUMBER_OF_WINNERS, "number of winners greater than max allowed"); require(_competitionParams[1] < _competitionParams[2], "voting start time greater than end time"); - require(_competitionParams[1] >= _competitionParams[0], "voting start time smaller than start time"); - require(_competitionParams[3] > 0, "numberOfVotesPerVoters should be greater than 0"); - + require(_competitionParams[1] >= startTime, "voting start time smaller than start time"); + require(_competitionParams[3] > 0, "maxNumberOfVotesPerVoter should be greater than 0"); + require(_competitionParams[4] <= _competitionParams[2], + "suggestionsEndTime should be earlier than proposal end time"); + require(_competitionParams[4] > startTime, "suggestionsEndTime should be later than proposal start time"); uint256 totalRewardSplit; for (uint256 i = 0; i < numberOfWinners; i++) { totalRewardSplit = totalRewardSplit.add(_rewardSplit[i]); } require(totalRewardSplit == 100, "total rewards split is not 100%"); - proposalId = ContributionRewardExt(contributionRewardExt).proposeContributionReward( _descriptionHash, _reputationChange, @@ -138,18 +147,13 @@ contract Competition { _externalToken, contributionRewardExt, msg.sender); - uint256 startTime = _competitionParams[0]; - if (startTime == 0) { - // solhint-disable-next-line not-rely-on-time - startTime = now; - } - proposals[proposalId].numberOfWinners = numberOfWinners; proposals[proposalId].rewardSplit = _rewardSplit; proposals[proposalId].startTime = startTime; proposals[proposalId].votingStartTime = _competitionParams[1]; proposals[proposalId].endTime = _competitionParams[2]; - proposals[proposalId].numberOfVotesPerVoters = _competitionParams[3]; + proposals[proposalId].maxNumberOfVotesPerVoter = _competitionParams[3]; + proposals[proposalId].suggestionsEndTime = _competitionParams[4]; proposals[proposalId].reputationReward = uint256(_reputationChange); proposals[proposalId].nativeTokenReward = _rewards[0]; proposals[proposalId].ethReward = _rewards[1]; @@ -161,14 +165,15 @@ contract Competition { proposals[proposalId].rewardSplit, startTime, proposals[proposalId].votingStartTime, + proposals[proposalId].suggestionsEndTime, proposals[proposalId].endTime, - proposals[proposalId].numberOfVotesPerVoters, + proposals[proposalId].maxNumberOfVotesPerVoter, contributionRewardExt ); } /** - * @dev suggest a competion suggestion + * @dev submit a competion suggestion * @param _proposalId the proposal id this suggestion is referring to. * @param _descriptionHash a descriptionHash of the suggestion. * @return suggestionId the suggestionId. @@ -183,7 +188,7 @@ contract Competition { // solhint-disable-next-line not-rely-on-time require(proposals[_proposalId].startTime <= now, "competition not started yet"); // solhint-disable-next-line not-rely-on-time - require(proposals[_proposalId].endTime > now, "competition ended"); + require(proposals[_proposalId].suggestionsEndTime > now, "suggestions submition time is over"); suggestionsCounter = suggestionsCounter.add(1); suggestions[suggestionsCounter].proposalId = _proposalId; suggestions[suggestionsCounter].suggester = msg.sender; @@ -201,22 +206,26 @@ contract Competition { returns(bool) { bytes32 proposalId = suggestions[_suggestionId].proposalId; - require(proposalId != bytes32(0), "suggestion not exist"); + require(proposalId != bytes32(0), "suggestion does not exist"); setSnapshotBlock(proposalId); + Avatar avatar = ContributionRewardExt(contributionRewardExt).avatar(); + uint256 reputation = avatar.nativeReputation().balanceOfAt(msg.sender, proposals[proposalId].snapshotBlock); + require(reputation > 0, "voter had no reputation when snapshot was taken"); Proposal storage proposal = proposals[proposalId]; // solhint-disable-next-line not-rely-on-time require(proposal.endTime > now, "competition ended"); Suggestion storage suggestion = suggestions[_suggestionId]; require(suggestion.votes[msg.sender] == 0, "already voted on this suggestion"); - require(proposal.votesPerVoter[msg.sender] < proposal.numberOfVotesPerVoters, + require(proposal.votesPerVoter[msg.sender] < proposal.maxNumberOfVotesPerVoter, "exceed number of votes allowed"); proposal.votesPerVoter[msg.sender] = proposal.votesPerVoter[msg.sender].add(1); - Avatar avatar = ContributionRewardExt(contributionRewardExt).avatar(); - uint256 reputation = avatar.nativeReputation().balanceOfAt(msg.sender, proposals[proposalId].snapshotBlock); - require(reputation > 0, "voter has no reputation"); + if (suggestion.totalVotes > 0) { + proposal.suggestionsPerVote[suggestion.totalVotes] = + proposal.suggestionsPerVote[suggestion.totalVotes].sub(1); + } suggestion.totalVotes = suggestion.totalVotes.add(reputation); + proposal.suggestionsPerVote[suggestion.totalVotes] = proposal.suggestionsPerVote[suggestion.totalVotes].add(1); suggestion.votes[msg.sender] = reputation; - proposal.tiesSuggestions[suggestion.totalVotes] = proposal.tiesSuggestions[suggestion.totalVotes].add(1); refreshTopSuggestions(proposalId, _suggestionId); emit NewVote(proposalId, _suggestionId, msg.sender, reputation); return true; @@ -231,11 +240,10 @@ contract Competition { */ function redeem(uint256 _suggestionId, address payable _beneficiary) external { address payable beneficiary = suggestions[_suggestionId].suggester; - if (msg.sender == suggestions[_suggestionId].suggester) { + if ((msg.sender == suggestions[_suggestionId].suggester) && + (_beneficiary != address(0))) { //only suggester can redeem to other address - if (beneficiary != address(0)) { - beneficiary = _beneficiary; - } + beneficiary = _beneficiary; } _redeem(_suggestionId, beneficiary); } @@ -255,17 +263,18 @@ contract Competition { /** * @dev getOrderedIndexOfSuggestion return the index of specific suggestion in the winners list. - * @param _proposalId proposal id * @param _suggestionId suggestion id */ - function getOrderedIndexOfSuggestion(bytes32 _proposalId, uint256 _suggestionId) + function getOrderedIndexOfSuggestion(uint256 _suggestionId) public view returns(uint256 index) { - uint256[] memory topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; + bytes32 proposalId = suggestions[_suggestionId].proposalId; + require(proposalId != bytes32(0), "suggestion does not exist"); + uint256[] memory topSuggestions = proposals[proposalId].topSuggestions; /** get how many elements are greater than a given element*/ - for (uint256 i; i < topSuggestionsOrdered.length; i++) { - if (suggestions[topSuggestionsOrdered[i]].totalVotes > suggestions[_suggestionId].totalVotes) { + for (uint256 i; i < topSuggestions.length; i++) { + if (suggestions[topSuggestions[i]].totalVotes > suggestions[_suggestionId].totalVotes) { index++; } } @@ -273,25 +282,30 @@ contract Competition { /** * @dev refreshTopSuggestions this function maintain a winners list array. + * it will check if the given suggestion is among the top suggestions, and if so, + * update the list of top suggestions * @param _proposalId proposal id * @param _suggestionId suggestion id */ function refreshTopSuggestions(bytes32 _proposalId, uint256 _suggestionId) private { - uint256[] storage topSuggestionsOrdered = proposals[_proposalId].topSuggestionsOrdered; - if (topSuggestionsOrdered.length < proposals[_proposalId].numberOfWinners) { - topSuggestionsOrdered.push(_suggestionId); + uint256[] storage topSuggestions = proposals[_proposalId].topSuggestions; + if (topSuggestions.length < proposals[_proposalId].numberOfWinners) { + topSuggestions.push(_suggestionId); } else { /** get the index of the smallest element **/ uint256 smallest = 0; for (uint256 i; i < proposals[_proposalId].numberOfWinners; i++) { - if (suggestions[topSuggestionsOrdered[i]].totalVotes < - suggestions[topSuggestionsOrdered[smallest]].totalVotes) { + if (suggestions[topSuggestions[i]].totalVotes < + suggestions[topSuggestions[smallest]].totalVotes) { smallest = i; + } else if (topSuggestions[i] == _suggestionId) { + //the suggestion is already in the topSuggestions list + return; } } - if (suggestions[topSuggestionsOrdered[smallest]].totalVotes < suggestions[_suggestionId].totalVotes) { - topSuggestionsOrdered[smallest] = _suggestionId; + if (suggestions[topSuggestions[smallest]].totalVotes < suggestions[_suggestionId].totalVotes) { + topSuggestions[smallest] = _suggestionId; } } } @@ -303,54 +317,49 @@ contract Competition { */ function _redeem(uint256 _suggestionId, address payable _beneficiary) private { bytes32 proposalId = suggestions[_suggestionId].proposalId; - require(proposalId != bytes32(0), "suggestion not exist"); Proposal storage proposal = proposals[proposalId]; // solhint-disable-next-line not-rely-on-time require(proposal.endTime < now, "competition is still on"); - uint256 amount; - //check if there is a win - for (uint256 i = 0; i < proposal.topSuggestionsOrdered.length; i++) { - if (suggestions[proposal.topSuggestionsOrdered[i]].suggester != address(0)) { - uint256 orderIndex = getOrderedIndexOfSuggestion(proposalId, _suggestionId); - suggestions[_suggestionId].suggester = address(0); - uint256 rewardPercentage = 0; - uint256 numberOfTieSuggestions = proposal.tiesSuggestions[suggestions[_suggestionId].totalVotes]; - uint256 j; - //calc the reward percentage for this suggestion - for (j = orderIndex; j < (orderIndex+numberOfTieSuggestions); j++) { - rewardPercentage = rewardPercentage.add(proposal.rewardSplit[j]); - } - rewardPercentage = rewardPercentage.div(numberOfTieSuggestions); - uint256 rewardPercentageLeft = 0; - if (proposal.topSuggestionsOrdered.length < proposal.numberOfWinners) { - for (j = proposal.topSuggestionsOrdered.length; j < proposal.numberOfWinners; j++) { - rewardPercentageLeft = rewardPercentageLeft.add(proposal.rewardSplit[j]); - } - //if there are less winners than the proposal number of winners so divide the pre allocated - //left reward equally between the winners - rewardPercentage = - rewardPercentage.add(rewardPercentageLeft.div(proposal.topSuggestionsOrdered.length)); - } - - amount = proposal.externalTokenReward.mul(rewardPercentage).div(100); - ContributionRewardExt(contributionRewardExt).redeemExternalTokenFromExtContract( - proposalId, _beneficiary, amount); - - amount = proposal.reputationReward.mul(rewardPercentage).div(100); - ContributionRewardExt(contributionRewardExt).redeemReputationFromExtContract( - proposalId, _beneficiary, amount); - - amount = proposal.ethReward.mul(rewardPercentage).div(100); - ContributionRewardExt(contributionRewardExt).redeemEtherFromExtContract( - proposalId, _beneficiary, amount); - - amount = proposal.nativeTokenReward.mul(rewardPercentage).div(100); - ContributionRewardExt(contributionRewardExt).redeemNativeTokenFromExtContract( - proposalId, _beneficiary, amount); - emit Redeem(proposalId, _suggestionId, rewardPercentage); - break; + require(suggestions[_suggestionId].suggester != address(0), + "suggestion was already redeemed"); + uint256 orderIndex = getOrderedIndexOfSuggestion(_suggestionId); + require(orderIndex < proposal.topSuggestions.length, "suggestion is not in winners list"); + suggestions[_suggestionId].suggester = address(0); + uint256 rewardPercentage = 0; + uint256 numberOfTieSuggestions = proposal.suggestionsPerVote[suggestions[_suggestionId].totalVotes]; + uint256 j; + //calc the reward percentage for this suggestion + for (j = orderIndex; j < (orderIndex+numberOfTieSuggestions); j++) { + rewardPercentage = rewardPercentage.add(proposal.rewardSplit[j]); + } + rewardPercentage = rewardPercentage.div(numberOfTieSuggestions); + uint256 rewardPercentageLeft = 0; + if (proposal.topSuggestions.length < proposal.numberOfWinners) { + //if there are less winners than the proposal number of winners so divide the pre allocated + //left reward equally between the winners + for (j = proposal.topSuggestions.length; j < proposal.numberOfWinners; j++) { + rewardPercentageLeft = rewardPercentageLeft.add(proposal.rewardSplit[j]); } + rewardPercentage = + rewardPercentage.add(rewardPercentageLeft.div(proposal.topSuggestions.length)); } + uint256 amount; + amount = proposal.externalTokenReward.mul(rewardPercentage).div(100); + ContributionRewardExt(contributionRewardExt).redeemExternalTokenFromExtContract( + proposalId, _beneficiary, amount); + + amount = proposal.reputationReward.mul(rewardPercentage).div(100); + ContributionRewardExt(contributionRewardExt).redeemReputationFromExtContract( + proposalId, _beneficiary, amount); + + amount = proposal.ethReward.mul(rewardPercentage).div(100); + ContributionRewardExt(contributionRewardExt).redeemEtherFromExtContract( + proposalId, _beneficiary, amount); + + amount = proposal.nativeTokenReward.mul(rewardPercentage).div(100); + ContributionRewardExt(contributionRewardExt).redeemNativeTokenFromExtContract( + proposalId, _beneficiary, amount); + emit Redeem(proposalId, _suggestionId, rewardPercentage); } } diff --git a/test/competition.js b/test/competition.js index 1bd12e48..b3556ed9 100644 --- a/test/competition.js +++ b/test/competition.js @@ -77,13 +77,15 @@ const proposeCompetition = async function( _startTime = 0, _votingStartTime = 600, _endTime = 1200, - _numberOfVotesPerVoters = 3, + _maxNumberOfVotesPerVoter = 3, + _suggestionsEndTime = 1200 ) { var block = await web3.eth.getBlock("latest"); _testSetup.startTime = block.timestamp + _startTime; _testSetup.votingStartTime = block.timestamp + _votingStartTime; _testSetup.endTime = block.timestamp + _endTime; + _testSetup.suggestionsEndTime = block.timestamp + _suggestionsEndTime; var tx = await _testSetup.competition.proposeCompetition( _descriptionHash, _reputationChange, @@ -93,7 +95,8 @@ const proposeCompetition = async function( [_testSetup.startTime, _testSetup.votingStartTime, _testSetup.endTime, - _numberOfVotesPerVoters] + _maxNumberOfVotesPerVoter, + _testSetup.suggestionsEndTime] ); var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); @@ -108,8 +111,10 @@ const proposeCompetition = async function( assert.equal(tx.logs[0].args._startTime,_testSetup.startTime); assert.equal(tx.logs[0].args._votingStartTime,_testSetup.votingStartTime); assert.equal(tx.logs[0].args._endTime,_testSetup.endTime); - assert.equal(tx.logs[0].args._numberOfVotesPerVoters,_numberOfVotesPerVoters); + assert.equal(tx.logs[0].args._maxNumberOfVotesPerVoter,_maxNumberOfVotesPerVoter); assert.equal(tx.logs[0].args._contributionRewardExt,_testSetup.contributionRewardExt.address); + assert.equal(tx.logs[0].args._suggestionsEndTime,_testSetup.suggestionsEndTime); + return proposalId; }; @@ -193,7 +198,7 @@ contract('Competition', accounts => { votingStartTime, endTime, 0);//votingStartTime - assert(false, 'numberOfVotesPerVoters > 0'); + assert(false, 'maxNumberOfVotesPerVoter > 0'); } catch (ex) { helpers.assertVMException(ex); } @@ -331,9 +336,9 @@ contract('Competition', accounts => { await testSetup.competition.vote(16,{from:accounts[2]}); await testSetup.competition.vote(5,{from:accounts[1]}); - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,10),2); - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,5),1); - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,16),0); + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(10),2); + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(5),1); + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(16),0); }); @@ -349,11 +354,22 @@ contract('Competition', accounts => { await testSetup.competition.vote(16,{from:accounts[1]}); await testSetup.competition.vote(5,{from:accounts[0]}); - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,10),0); - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,16),0); - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,5),2); - assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(proposalId,0),3); - + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(10),0); + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(16),0); + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(5),2); + try { + await testSetup.competition.getOrderedIndexOfSuggestion(0); + assert(false, 'revert if suggestion does not exist'); + } catch (ex) { + helpers.assertVMException(ex); + } + try { + await testSetup.competition.getOrderedIndexOfSuggestion(21); + assert(false, 'revert if suggestion does not exist'); + } catch (ex) { + helpers.assertVMException(ex); + } + assert.equal(await testSetup.competition.getOrderedIndexOfSuggestion(1),3); }); it("redeem", async function() { From 456cbfff02881278dd01e9fd6350538917eeebc3 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Dec 2019 16:57:45 +0200 Subject: [PATCH 16/21] add sendLeftOverFunds --- contracts/schemes/Competition.sol | 31 +++++++++++++++++++++ contracts/schemes/ContributionRewardExt.sol | 12 ++++---- test/competition.js | 20 +++++++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index 28a43e3c..a6d409b5 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -261,6 +261,37 @@ contract Competition { } } + /** + * @dev sendLeftOverFund send letf over funds back to the dao. + * @param _proposalId the proposal id + */ + function sendLeftOverFunds(bytes32 _proposalId) public { + // solhint-disable-next-line not-rely-on-time + require(proposals[_proposalId].endTime < now, "competition is still on"); + uint256[] memory topSuggestions = proposals[_proposalId].topSuggestions; + for (uint256 i; i < topSuggestions.length; i++) { + require(suggestions[topSuggestions[i]].suggester == address(0), "not all winning suggestions redeemed"); + } + + (, , , , , , , + uint256 nativeTokenRewardLeft, + , + uint256 ethRewardLeft, + uint256 externalTokenRewardLeft) + = ContributionRewardExt(contributionRewardExt).organizationProposals(_proposalId); + + Avatar avatar = ContributionRewardExt(contributionRewardExt).avatar(); + + ContributionRewardExt(contributionRewardExt).redeemExternalTokenFromExtContract( + _proposalId, address(avatar), externalTokenRewardLeft); + + ContributionRewardExt(contributionRewardExt).redeemEtherFromExtContract( + _proposalId, address(avatar), ethRewardLeft); + + ContributionRewardExt(contributionRewardExt).redeemNativeTokenFromExtContract( + _proposalId, address(avatar), nativeTokenRewardLeft); + } + /** * @dev getOrderedIndexOfSuggestion return the index of specific suggestion in the winners list. * @param _suggestionId suggestion id diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index 440842ec..ecff0b66 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -299,13 +299,11 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa if (proposal.externalToken != IERC20(0) && _proposal.externalTokenReward > 0) { amount = _proposal.externalTokenReward; - if (amount > 0) { - require( - Controller( - avatar.owner()) - .externalTokenTransfer(_proposal.externalToken, _proposal.beneficiary, amount, avatar)); - emit RedeemExternalToken(address(avatar), _proposalId, _proposal.beneficiary, amount); - } + require( + Controller( + avatar.owner()) + .externalTokenTransfer(_proposal.externalToken, _proposal.beneficiary, amount, avatar)); + emit RedeemExternalToken(address(avatar), _proposalId, _proposal.beneficiary, amount); } } diff --git a/test/competition.js b/test/competition.js index b3556ed9..19199a95 100644 --- a/test/competition.js +++ b/test/competition.js @@ -517,11 +517,31 @@ contract('Competition', accounts => { assert.equal(tx.logs[0].args._proposalId,proposalId); assert.equal(tx.logs[0].args._rewardPercentage,28); + try { + await testSetup.competition.sendLeftOverFunds(proposalId); + assert(false, 'cannot sendLeftOverFunds because not all proposals redeemed yet'); + } catch (ex) { + helpers.assertVMException(ex); + } + tx = await testSetup.competition.redeem(3,accounts[0]); assert.equal(tx.logs.length, 1); assert.equal(tx.logs[0].event, "Redeem"); assert.equal(tx.logs[0].args._proposalId,proposalId); assert.equal(tx.logs[0].args._rewardPercentage,53); + var proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); + + tx = await testSetup.competition.sendLeftOverFunds(proposalId); + await testSetup.contributionRewardExt.getPastEvents('RedeemExternalToken', { + fromBlock: tx.blockNumber, + toBlock: 'latest' + }) + .then(function(events){ + assert.equal(events[0].event,"RedeemExternalToken"); + assert.equal(events[0].args._beneficiary,testSetup.org.avatar.address); + assert.equal(events[0].args._amount.toNumber(),proposal.externalTokenRewardLeft.toNumber()); + }); + }); }); From 19db2020ae3337418c235b88dbe3f38c1a992886 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Thu, 5 Dec 2019 02:11:58 +0200 Subject: [PATCH 17/21] naming + tests --- contracts/schemes/Competition.sol | 25 ++- contracts/schemes/ContributionRewardExt.sol | 201 +++++++++++--------- contracts/utils/Redeemer.sol | 2 +- package-lock.json | 13 +- package.json | 2 +- test/competition.js | 45 ++++- test/contributionrewardext.js | 34 ++-- 7 files changed, 191 insertions(+), 131 deletions(-) diff --git a/contracts/schemes/Competition.sol b/contracts/schemes/Competition.sol index a6d409b5..9d6f1a19 100644 --- a/contracts/schemes/Competition.sol +++ b/contracts/schemes/Competition.sol @@ -273,22 +273,21 @@ contract Competition { require(suggestions[topSuggestions[i]].suggester == address(0), "not all winning suggestions redeemed"); } - (, , , , , , , - uint256 nativeTokenRewardLeft, - , + (, , , , , , + uint256 nativeTokenRewardLeft, , uint256 ethRewardLeft, - uint256 externalTokenRewardLeft) + uint256 externalTokenRewardLeft,) = ContributionRewardExt(contributionRewardExt).organizationProposals(_proposalId); Avatar avatar = ContributionRewardExt(contributionRewardExt).avatar(); - ContributionRewardExt(contributionRewardExt).redeemExternalTokenFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemExternalTokenByRewarder( _proposalId, address(avatar), externalTokenRewardLeft); - ContributionRewardExt(contributionRewardExt).redeemEtherFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemEtherByRewarder( _proposalId, address(avatar), ethRewardLeft); - ContributionRewardExt(contributionRewardExt).redeemNativeTokenFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemNativeTokenByRewarder( _proposalId, address(avatar), nativeTokenRewardLeft); } @@ -359,8 +358,8 @@ contract Competition { uint256 rewardPercentage = 0; uint256 numberOfTieSuggestions = proposal.suggestionsPerVote[suggestions[_suggestionId].totalVotes]; uint256 j; - //calc the reward percentage for this suggestion - for (j = orderIndex; j < (orderIndex+numberOfTieSuggestions); j++) { + //calc the reward percentage for this suggestion + for (j = orderIndex; j < (orderIndex+numberOfTieSuggestions) && j < proposal.rewardSplit.length; j++) { rewardPercentage = rewardPercentage.add(proposal.rewardSplit[j]); } rewardPercentage = rewardPercentage.div(numberOfTieSuggestions); @@ -376,19 +375,19 @@ contract Competition { } uint256 amount; amount = proposal.externalTokenReward.mul(rewardPercentage).div(100); - ContributionRewardExt(contributionRewardExt).redeemExternalTokenFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemExternalTokenByRewarder( proposalId, _beneficiary, amount); amount = proposal.reputationReward.mul(rewardPercentage).div(100); - ContributionRewardExt(contributionRewardExt).redeemReputationFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemReputationByRewarder( proposalId, _beneficiary, amount); amount = proposal.ethReward.mul(rewardPercentage).div(100); - ContributionRewardExt(contributionRewardExt).redeemEtherFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemEtherByRewarder( proposalId, _beneficiary, amount); amount = proposal.nativeTokenReward.mul(rewardPercentage).div(100); - ContributionRewardExt(contributionRewardExt).redeemNativeTokenFromExtContract( + ContributionRewardExt(contributionRewardExt).redeemNativeTokenByRewarder( proposalId, _beneficiary, amount); emit Redeem(proposalId, _suggestionId, rewardPercentage); } diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index ecff0b66..30ebbae8 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -10,6 +10,8 @@ import "../libs/SafeERC20.sol"; * @title A scheme for proposing and rewarding contributions to an organization * @dev An agent can ask an organization to recognize a contribution and reward * him with token, reputation, ether or any combination. + * The contract enable to assign a rewarder, which, after the contributionreward has been accepted, + * can then later distribute the assets as it would like. */ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterface { using SafeMath for uint; @@ -58,16 +60,16 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa IERC20 externalToken; uint256 externalTokenReward; address payable beneficiary; - uint256 executionTime; uint256 nativeTokenRewardLeft; uint256 reputationChangeLeft; uint256 ethRewardLeft; uint256 externalTokenRewardLeft; + bool acceptedByVotingMachine; } modifier onlyRewarder() { if (rewarder != address(0)) { - require(msg.sender == rewarder, "only rewarder allowed to redeem"); + require(msg.sender == rewarder, "msg.sender is not authorized"); } _; } @@ -88,8 +90,8 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa /** * @dev initialize * @param _avatar the avatar to mint reputation from - * @param _votingMachine the voting machines address to - * @param _voteParams voting machine parameters. + * @param _votingMachine the voting machines address + * @param _voteParams voting machine parameters * @param _rewarder an address which allowed to redeem the contribution. if _rewarder is 0 this param is agnored. */ @@ -111,19 +113,17 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa /** * @dev execution of proposals, can only be called by the voting machine in which the vote is held. - * @param _proposalId the ID of the voting in the voting machine + * @param _proposalId the ID of the proposal in the voting machine * @param _decision a parameter of the voting result, 1 yes and 2 is no. */ function executeProposal(bytes32 _proposalId, int256 _decision) external onlyVotingMachine(_proposalId) returns(bool) { - require(organizationProposals[_proposalId].executionTime == 0); + require(organizationProposals[_proposalId].acceptedByVotingMachine == false); require(organizationProposals[_proposalId].beneficiary != address(0)); - // Check if vote was successful: if (_decision == 1) { - // solhint-disable-next-line not-rely-on-time - organizationProposals[_proposalId].executionTime = now; + organizationProposals[_proposalId].acceptedByVotingMachine = true; } emit ProposalExecuted(address(avatar), _proposalId, _decision); return true; @@ -134,9 +134,9 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa * @param _descriptionHash A hash of the proposal's description * @param _reputationChange - Amount of reputation change requested .Can be negative. * @param _rewards rewards array: - * rewards[0] - Amount of tokens requested per period - * rewards[1] - Amount of ETH requested per period - * rewards[2] - Amount of external tokens requested per period + * rewards[0] - Amount of tokens requested + * rewards[1] - Amount of ETH requested + * rewards[2] - Amount of external tokens * @param _externalToken Address of external token, if reward is requested there * @param _beneficiary Who gets the rewards. if equal to 0 the beneficiary will be msg.sender. * @param _proposer proposer . if equal to 0 the proposer will be msg.sender. @@ -150,13 +150,13 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa address _proposer ) public - returns(bytes32) + returns(bytes32 proposalId) { address proposer = _proposer; if (proposer == address(0)) { proposer = msg.sender; } - bytes32 contributionId = votingMachine.propose(2, voteParams, proposer, address(avatar)); + proposalId = votingMachine.propose(2, voteParams, proposer, address(avatar)); address payable beneficiary = _beneficiary; if (beneficiary == address(0)) { beneficiary = msg.sender; @@ -169,17 +169,17 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa externalToken: _externalToken, externalTokenReward: _rewards[2], beneficiary: beneficiary, - executionTime: 0, nativeTokenRewardLeft: 0, reputationChangeLeft: 0, ethRewardLeft: 0, - externalTokenRewardLeft: 0 + externalTokenRewardLeft: 0, + acceptedByVotingMachine: false }); - organizationProposals[contributionId] = proposal; + organizationProposals[proposalId] = proposal; emit NewContributionProposal( address(avatar), - contributionId, + proposalId, address(votingMachine), _descriptionHash, _reputationChange, @@ -189,11 +189,10 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa proposer ); - proposalsInfo[address(votingMachine)][contributionId] = ProposalInfo({ + proposalsInfo[address(votingMachine)][proposalId] = ProposalInfo({ blockNumber:block.number, avatar:avatar }); - return contributionId; } /** @@ -202,31 +201,33 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa * @return reputation the redeemed reputation. */ function redeemReputation(bytes32 _proposalId) public returns(int256 reputation) { - ContributionProposal memory _proposal = organizationProposals[_proposalId]; ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //set proposal reward to zero to prevent reentrancy attack. - proposal.reputationChange = 0; - - if (_proposal.beneficiary == address(this)) { - if (_proposal.reputationChange > 0) {//for now only mint(not burn) rep allowed from ext contract. - proposal.reputationChangeLeft = uint256(_proposal.reputationChange); + require(proposal.acceptedByVotingMachine, "proposal was not accepted by the voting machine"); + + //if the beneficiary is the current contract, we are not minting the rep to it + //but instead refer to a mechanism in which the rep can be minted by the current contract + //per request of the rewarder + if (proposal.beneficiary == address(this)) { + if (proposal.reputationChangeLeft == 0) {//for now only mint(not burn) rep allowed from ext contract. + proposal.reputationChangeLeft = uint256(proposal.reputationChange); } } else { - reputation = _proposal.reputationChange; - } - - if (reputation > 0) { - require( - Controller( - avatar.owner()).mintReputation(uint(reputation), _proposal.beneficiary, address(avatar))); - } else if (reputation < 0) { - require( - Controller( - avatar.owner()).burnReputation(uint(reputation*(-1)), _proposal.beneficiary, address(avatar))); - } - if (reputation != 0) { - emit RedeemReputation(address(avatar), _proposalId, _proposal.beneficiary, reputation); + reputation = proposal.reputationChange; + //set proposal reward to zero to prevent reentrancy attack. + proposal.reputationChange = 0; + + if (reputation > 0) { + require( + Controller( + avatar.owner()).mintReputation(uint(reputation), proposal.beneficiary, address(avatar))); + } else if (reputation < 0) { + require( + Controller( + avatar.owner()).burnReputation(uint(reputation*(-1)), proposal.beneficiary, address(avatar))); + } + if (reputation != 0) { + emit RedeemReputation(address(avatar), _proposalId, proposal.beneficiary, reputation); + } } } @@ -237,21 +238,21 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa */ function redeemNativeToken(bytes32 _proposalId) public returns(uint256 amount) { - ContributionProposal memory _proposal = organizationProposals[_proposalId]; ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //set proposal rewards to zero to prevent reentrancy attack. - proposal.nativeTokenReward = 0; + require(proposal.acceptedByVotingMachine, "proposal was not accepted by the voting machine"); - if (_proposal.beneficiary == address(this)) { - if (_proposal.nativeTokenReward != 0) { - proposal.nativeTokenRewardLeft = _proposal.nativeTokenReward; + if (proposal.beneficiary == address(this)) { + //ensure nativeTokenRewardLeft can be set only one time + if (proposal.nativeTokenRewardLeft == 0) { + proposal.nativeTokenRewardLeft = proposal.nativeTokenReward; } } - amount = _proposal.nativeTokenReward; + amount = proposal.nativeTokenReward; + //set proposal rewards to zero to prevent reentrancy attack. + proposal.nativeTokenReward = 0; if (amount > 0) { - require(Controller(avatar.owner()).mintTokens(amount, _proposal.beneficiary, address(avatar))); - emit RedeemNativeToken(address(avatar), _proposalId, _proposal.beneficiary, amount); + require(Controller(avatar.owner()).mintTokens(amount, proposal.beneficiary, address(avatar))); + emit RedeemNativeToken(address(avatar), _proposalId, proposal.beneficiary, amount); } } @@ -261,21 +262,20 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa * @return amount ether redeemed amount */ function redeemEther(bytes32 _proposalId) public returns(uint256 amount) { - ContributionProposal memory _proposal = organizationProposals[_proposalId]; ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //set proposal rewards to zero to prevent reentrancy attack. - proposal.ethReward = 0; - if (_proposal.beneficiary == address(this)) { - if (_proposal.ethReward != 0) { - proposal.ethRewardLeft = _proposal.ethReward; + require(proposal.acceptedByVotingMachine, "proposal was not accepted by the voting machine"); + + if (proposal.beneficiary == address(this)) { + if (proposal.ethRewardLeft == 0) { + proposal.ethRewardLeft = proposal.ethReward; } } - amount = _proposal.ethReward; - + amount = proposal.ethReward; + //set proposal rewards to zero to prevent reentrancy attack. + proposal.ethReward = 0; if (amount > 0) { - require(Controller(avatar.owner()).sendEther(amount, _proposal.beneficiary, avatar)); - emit RedeemEther(address(avatar), _proposalId, _proposal.beneficiary, amount); + require(Controller(avatar.owner()).sendEther(amount, proposal.beneficiary, avatar)); + emit RedeemEther(address(avatar), _proposalId, proposal.beneficiary, amount); } } @@ -285,42 +285,45 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa * @return amount the external token redeemed amount */ function redeemExternalToken(bytes32 _proposalId) public returns(uint256 amount) { - ContributionProposal memory _proposal = organizationProposals[_proposalId]; ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //set proposal rewards to zero to prevent reentrancy attack. - proposal.externalTokenReward = 0; + require(proposal.acceptedByVotingMachine, "proposal was not accepted by the voting machine"); - if (_proposal.beneficiary == address(this)) { - if (_proposal.externalTokenReward != 0) { - proposal.externalTokenRewardLeft = _proposal.externalTokenReward; + + if (proposal.beneficiary == address(this)) { + if (proposal.externalTokenRewardLeft == 0) { + proposal.externalTokenRewardLeft = proposal.externalTokenReward; } } - if (proposal.externalToken != IERC20(0) && _proposal.externalTokenReward > 0) { - amount = _proposal.externalTokenReward; + if (proposal.externalToken != IERC20(0) && proposal.externalTokenReward > 0) { + amount = proposal.externalTokenReward; + //set proposal rewards to zero to prevent reentrancy attack. + proposal.externalTokenReward = 0; require( Controller( avatar.owner()) - .externalTokenTransfer(_proposal.externalToken, _proposal.beneficiary, amount, avatar)); - emit RedeemExternalToken(address(avatar), _proposalId, _proposal.beneficiary, amount); + .externalTokenTransfer(proposal.externalToken, proposal.beneficiary, amount, avatar)); + emit RedeemExternalToken(address(avatar), _proposalId, proposal.beneficiary, amount); } } /** - * @dev redeemReputationFromExtContract redeem reward for proposal + * @dev redeemReputationByRewarder redeem reward for proposal * @param _proposalId the ID of the voting in the voting machine * @param _beneficiary the beneficiary to mint reputation to. * @param _reputation the reputation amount to mint */ - function redeemReputationFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _reputation) + function redeemReputationByRewarder(bytes32 _proposalId, address _beneficiary, uint256 _reputation) public onlyRewarder { ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //this will ensure sum zero of reputation. - proposal.reputationChangeLeft = proposal.reputationChangeLeft.sub(_reputation); + require(proposal.acceptedByVotingMachine, "proposal was not accepted by the voting machine"); + //this will ensure sum zero of reputation + //and that there was a privious call to redeemReputation function. + proposal.reputationChangeLeft = + proposal.reputationChangeLeft.sub(_reputation, + "cannot redeem more reputation than allocated for this proposal or no redeemReputation was called"); require( Controller( avatar.owner()).mintReputation(_reputation, _beneficiary, address(avatar))); @@ -330,19 +333,22 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa } /** - * @dev redeemNativeTokenFromExtContract redeem reward for proposal + * @dev redeemNativeTokenByRewarder redeem reward for proposal * @param _proposalId the ID of the voting in the voting machine * @param _beneficiary the beneficiary to mint tokens to. * @param _amount the tokens amount to mint */ - function redeemNativeTokenFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _amount) + function redeemNativeTokenByRewarder(bytes32 _proposalId, address _beneficiary, uint256 _amount) public onlyRewarder { ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); - //this will ensure sum zero of reputation. - proposal.nativeTokenRewardLeft = proposal.nativeTokenRewardLeft.sub(_amount); + require(proposal.acceptedByVotingMachine, "proposal was not accepted by the voting machine"); + //this will ensure sum zero of reputation + //and that there was a privious call to redeemNativeToken function. + proposal.nativeTokenRewardLeft = + proposal.nativeTokenRewardLeft.sub(_amount, + "cannot redeem more tokens than allocated for this proposal or no redeemNativeToken was called"); if (_amount > 0) { address(avatar.nativeToken()).safeTransfer(_beneficiary, _amount); @@ -351,19 +357,21 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa } /** - * @dev redeemEtherFromExtContract redeem reward for proposal + * @dev redeemEtherByRewarder redeem reward for proposal * @param _proposalId the ID of the voting in the voting machine * @param _beneficiary the beneficiary to send eth to. * @param _amount eth amount to send */ - function redeemEtherFromExtContract(bytes32 _proposalId, address payable _beneficiary, uint256 _amount) + function redeemEtherByRewarder(bytes32 _proposalId, address payable _beneficiary, uint256 _amount) public onlyRewarder { ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); + require(proposal.acceptedByVotingMachine, "proposal was not accepted by the voting machine"); //this will ensure sum zero of reputation. - proposal.ethRewardLeft = proposal.ethRewardLeft.sub(_amount); + //and that there was a privious call to redeemEther function. + proposal.ethRewardLeft = proposal.ethRewardLeft.sub(_amount, + "cannot redeem more Ether than allocated for this proposal or no redeemEther was called"); if (_amount > 0) { _beneficiary.transfer(_amount); @@ -372,18 +380,21 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa } /** - * @dev redeemExternalTokenFromExtContract redeem reward for proposal + * @dev redeemExternalTokenByRewarder redeem reward for proposal * @param _proposalId the ID of the voting in the voting machine * @param _beneficiary the beneficiary to send the external token to. * @param _amount the amount of external token to send */ - function redeemExternalTokenFromExtContract(bytes32 _proposalId, address _beneficiary, uint256 _amount) + function redeemExternalTokenByRewarder(bytes32 _proposalId, address _beneficiary, uint256 _amount) public onlyRewarder { ContributionProposal storage proposal = organizationProposals[_proposalId]; - require(proposal.executionTime != 0); + require(proposal.acceptedByVotingMachine, "proposal was not accepted by the voting machine"); //this will ensure sum zero of reputation. - proposal.externalTokenRewardLeft = proposal.externalTokenRewardLeft.sub(_amount); + //and that there was a privious call to redeemExternalToken function. + proposal.externalTokenRewardLeft = + proposal.externalTokenRewardLeft.sub(_amount, + "cannot redeem more tokens than allocated for this proposal or no redeemExternalToken was called"); if (proposal.externalToken != IERC20(0)) { if (_amount > 0) { @@ -396,7 +407,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa /** * @dev redeem rewards for proposal * @param _proposalId the ID of the voting in the voting machine - * @param _whatToRedeem whatToRedeem array: + * @param _whatToRedeem whatToRedeem array of boolean values: * whatToRedeem[0] - reputation * whatToRedeem[1] - nativeTokenReward * whatToRedeem[2] - Ether @@ -445,8 +456,8 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa return organizationProposals[_proposalId].nativeTokenReward; } - function getProposalExecutionTime(bytes32 _proposalId) public view returns (uint256) { - return organizationProposals[_proposalId].executionTime; + function getProposalAcceptedByVotingMachine(bytes32 _proposalId) public view returns (bool) { + return organizationProposals[_proposalId].acceptedByVotingMachine; } } diff --git a/contracts/utils/Redeemer.sol b/contracts/utils/Redeemer.sol index 1071ef0e..231e8ad4 100644 --- a/contracts/utils/Redeemer.sol +++ b/contracts/utils/Redeemer.sol @@ -114,7 +114,7 @@ contract Redeemer { genesisProtocolRedeem(_genesisProtocol, _proposalId, _beneficiary); if (callContributionReward) { //redeem from contributionReward only if it executed - if (_contributionRewardExt.getProposalExecutionTime(_proposalId) > 0) { + if (_contributionRewardExt.getProposalAcceptedByVotingMachine(_proposalId)) { (crReputationReward, crNativeTokenReward, crEthReward, crExternalTokenReward) = contributionRewardExtRedeem(_contributionRewardExt, _proposalId); } diff --git a/package-lock.json b/package-lock.json index 1e69956d..fc3a92e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -145,6 +145,13 @@ "requires": { "ethereumjs-abi": "^0.6.5", "openzeppelin-solidity": "2.3.0" + }, + "dependencies": { + "openzeppelin-solidity": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", + "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==" + } } }, "@resolver-engine/core": { @@ -5132,9 +5139,9 @@ } }, "openzeppelin-solidity": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.3.0.tgz", - "integrity": "sha512-QYeiPLvB1oSbDt6lDQvvpx7k8ODczvE474hb2kLXZBPKMsxKT1WxTCHBYrCU7kS7hfAku4DcJ0jqOyL+jvjwQw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/openzeppelin-solidity/-/openzeppelin-solidity-2.4.0.tgz", + "integrity": "sha512-533gc5jkspxW5YT0qJo02Za5q1LHwXK9CJCc48jNj/22ncNM/3M/3JfWLqfpB90uqLwOKOovpl0JfaMQTR+gXQ==" }, "optionator": { "version": "0.8.3", diff --git a/package.json b/package.json index b2eea1ef..2977b5d0 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "dependencies": { "@daostack/infra": "0.0.1-rc.15", "math": "0.0.3", - "openzeppelin-solidity": "2.3.0", + "openzeppelin-solidity": "2.4.0", "truffle-flattener": "^1.4.2" }, "peerDependencies": { diff --git a/test/competition.js b/test/competition.js index 19199a95..c39af7a2 100644 --- a/test/competition.js +++ b/test/competition.js @@ -121,7 +121,7 @@ const proposeCompetition = async function( contract('Competition', accounts => { - + it("proposeCompetition log", async function() { var testSetup = await setup(accounts); await proposeCompetition(testSetup); @@ -544,4 +544,47 @@ contract('Competition', accounts => { }); }); + + it("redeem multipe suggestions - multiple smallers suggestion", async function() { + var testSetup = await setup(accounts); + await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,3000,{from:accounts[1]}); + await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:2000}); + var proposalId = await proposeCompetition(testSetup,"description-hash",1000,[1000,2000,3000]); + + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + await testSetup.competition.suggest(proposalId,"suggestion"); + + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); + await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[0]}); + await testSetup.contributionRewardExt.redeem(proposalId,[true,true,true,true]); + await helpers.increaseTime(650); + await testSetup.competition.vote(1,{from:accounts[2]}); + await testSetup.competition.vote(2,{from:accounts[1]}); + await testSetup.competition.vote(3,{from:accounts[1]}); + await testSetup.competition.vote(4,{from:accounts[0]}); + await testSetup.competition.vote(5,{from:accounts[0]}); + await testSetup.competition.vote(6,{from:accounts[0]}); + await helpers.increaseTime(650); + var tx = await testSetup.competition.redeem(4,accounts[0]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "Redeem"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._rewardPercentage,3); + + tx = await testSetup.competition.redeem(5,accounts[0]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "Redeem"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._rewardPercentage,3); + + tx = await testSetup.competition.redeem(6,accounts[0]); + assert.equal(tx.logs.length, 1); + assert.equal(tx.logs[0].event, "Redeem"); + assert.equal(tx.logs[0].args._proposalId,proposalId); + assert.equal(tx.logs[0].args._rewardPercentage,3); + }); }); diff --git a/test/contributionrewardext.js b/test/contributionrewardext.js index 23a11783..55cd3e3e 100644 --- a/test/contributionrewardext.js +++ b/test/contributionrewardext.js @@ -125,7 +125,7 @@ contract('ContributionRewardExt', accounts => { var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); await testSetup.contributionRewardExtParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[2]}); var organizationProposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); - assert.notEqual(organizationProposal.executionTime,0);//executionTime + assert.notEqual(organizationProposal.acceptedByVotingMachine,0);//acceptedByVotingMachine }); it("execute proposeContributionReward mint reputation ", async function() { @@ -558,12 +558,12 @@ contract('ContributionRewardExt', accounts => { //redeem ether try { - await testSetup.contributionRewardExt.redeemEtherFromExtContract(proposalId,otherAvatar.address,1,{from:accounts[1]}); + await testSetup.contributionRewardExt.redeemEtherByRewarder(proposalId,otherAvatar.address,1,{from:accounts[1]}); assert(false, 'only service contract can redeem'); } catch (ex) { helpers.assertVMException(ex); } - tx = await testSetup.contributionRewardExt.redeemEtherFromExtContract(proposalId,otherAvatar.address,1); + tx = await testSetup.contributionRewardExt.redeemEtherByRewarder(proposalId,otherAvatar.address,1); assert.equal(tx.logs.length, 1); assert.equal(tx.logs[0].event, "RedeemEther"); assert.equal(tx.logs[0].args._amount, 1); @@ -572,24 +572,24 @@ contract('ContributionRewardExt', accounts => { var proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); assert.equal(proposal.ethRewardLeft, ethReward - 1); try { - await testSetup.contributionRewardExt.redeemEtherFromExtContract(proposalId,otherAvatar.address,proposal.ethRewardLeft+1); + await testSetup.contributionRewardExt.redeemEtherByRewarder(proposalId,otherAvatar.address,proposal.ethRewardLeft+1); assert(false, 'cannot redeem more than the proposal reward'); } catch (ex) { helpers.assertVMException(ex); } - await testSetup.contributionRewardExt.redeemEtherFromExtContract(proposalId,otherAvatar.address,proposal.ethRewardLeft); + await testSetup.contributionRewardExt.redeemEtherByRewarder(proposalId,otherAvatar.address,proposal.ethRewardLeft); assert.equal(await web3.eth.getBalance(otherAvatar.address),ethReward); proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); assert.equal(proposal.ethRewardLeft, 0); //redeem nativeToken try { - await testSetup.contributionRewardExt.redeemNativeTokenFromExtContract(proposalId,otherAvatar.address,1,{from:accounts[1]}); + await testSetup.contributionRewardExt.redeemNativeTokenByRewarder(proposalId,otherAvatar.address,1,{from:accounts[1]}); assert(false, 'only service contract can redeem'); } catch (ex) { helpers.assertVMException(ex); } - tx = await testSetup.contributionRewardExt.redeemNativeTokenFromExtContract(proposalId,otherAvatar.address,1); + tx = await testSetup.contributionRewardExt.redeemNativeTokenByRewarder(proposalId,otherAvatar.address,1); assert.equal(tx.logs.length, 1); assert.equal(tx.logs[0].event, "RedeemNativeToken"); assert.equal(tx.logs[0].args._amount, 1); @@ -599,12 +599,12 @@ contract('ContributionRewardExt', accounts => { proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); assert.equal(proposal.nativeTokenRewardLeft, nativeTokenReward - 1); try { - await testSetup.contributionRewardExt.redeemNativeTokenFromExtContract(proposalId,otherAvatar.address,proposal.nativeTokenRewardLeft+1); + await testSetup.contributionRewardExt.redeemNativeTokenByRewarder(proposalId,otherAvatar.address,proposal.nativeTokenRewardLeft+1); assert(false, 'cannot redeem more than the proposal reward'); } catch (ex) { helpers.assertVMException(ex); } - await testSetup.contributionRewardExt.redeemNativeTokenFromExtContract(proposalId,otherAvatar.address,proposal.nativeTokenRewardLeft); + await testSetup.contributionRewardExt.redeemNativeTokenByRewarder(proposalId,otherAvatar.address,proposal.nativeTokenRewardLeft); assert.equal(await testSetup.org.token.balanceOf(otherAvatar.address),nativeTokenReward); proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); assert.equal(proposal.nativeTokenRewardLeft, 0); @@ -612,12 +612,12 @@ contract('ContributionRewardExt', accounts => { //redeem externalToken try { - await testSetup.contributionRewardExt.redeemExternalTokenFromExtContract(proposalId,otherAvatar.address,1,{from:accounts[1]}); + await testSetup.contributionRewardExt.redeemExternalTokenByRewarder(proposalId,otherAvatar.address,1,{from:accounts[1]}); assert(false, 'only service contract can redeem'); } catch (ex) { helpers.assertVMException(ex); } - tx = await testSetup.contributionRewardExt.redeemExternalTokenFromExtContract(proposalId,otherAvatar.address,1); + tx = await testSetup.contributionRewardExt.redeemExternalTokenByRewarder(proposalId,otherAvatar.address,1); assert.equal(tx.logs.length, 1); assert.equal(tx.logs[0].event, "RedeemExternalToken"); assert.equal(tx.logs[0].args._amount, 1); @@ -627,12 +627,12 @@ contract('ContributionRewardExt', accounts => { proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); assert.equal(proposal.externalTokenRewardLeft, externalTokenReward - 1); try { - await testSetup.contributionRewardExt.redeemExternalTokenFromExtContract(proposalId,otherAvatar.address,proposal.externalTokenRewardLeft+1); + await testSetup.contributionRewardExt.redeemExternalTokenByRewarder(proposalId,otherAvatar.address,proposal.externalTokenRewardLeft+1); assert(false, 'cannot redeem more than the proposal reward'); } catch (ex) { helpers.assertVMException(ex); } - await testSetup.contributionRewardExt.redeemExternalTokenFromExtContract(proposalId,otherAvatar.address,proposal.externalTokenRewardLeft); + await testSetup.contributionRewardExt.redeemExternalTokenByRewarder(proposalId,otherAvatar.address,proposal.externalTokenRewardLeft); assert.equal(await testSetup.standardTokenMock.balanceOf(otherAvatar.address),externalTokenReward); proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); assert.equal(proposal.externalTokenRewardLeft, 0); @@ -640,12 +640,12 @@ contract('ContributionRewardExt', accounts => { //redeem reputation try { - await testSetup.contributionRewardExt.redeemReputationFromExtContract(proposalId,otherAvatar.address,1,{from:accounts[1]}); + await testSetup.contributionRewardExt.redeemReputationByRewarder(proposalId,otherAvatar.address,1,{from:accounts[1]}); assert(false, 'only service contract can redeem'); } catch (ex) { helpers.assertVMException(ex); } - tx = await testSetup.contributionRewardExt.redeemReputationFromExtContract(proposalId,otherAvatar.address,1); + tx = await testSetup.contributionRewardExt.redeemReputationByRewarder(proposalId,otherAvatar.address,1); assert.equal(tx.logs.length, 1); assert.equal(tx.logs[0].event, "RedeemReputation"); assert.equal(tx.logs[0].args._amount, 1); @@ -655,12 +655,12 @@ contract('ContributionRewardExt', accounts => { proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); assert.equal(proposal.reputationChangeLeft, reputationReward - 1); try { - await testSetup.contributionRewardExt.redeemReputationFromExtContract(proposalId,otherAvatar.address,proposal.reputationChangeLeft+1); + await testSetup.contributionRewardExt.redeemReputationByRewarder(proposalId,otherAvatar.address,proposal.reputationChangeLeft+1); assert(false, 'cannot redeem more than the proposal reward'); } catch (ex) { helpers.assertVMException(ex); } - await testSetup.contributionRewardExt.redeemReputationFromExtContract(proposalId,otherAvatar.address,proposal.reputationChangeLeft); + await testSetup.contributionRewardExt.redeemReputationByRewarder(proposalId,otherAvatar.address,proposal.reputationChangeLeft); assert.equal(await testSetup.org.reputation.balanceOf(otherAvatar.address),reputationReward); proposal = await testSetup.contributionRewardExt.organizationProposals(proposalId); assert.equal(proposal.reputationChangeLeft, 0); From b1cd463cb66808e916ad6c320c4e77f876cb26e9 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Thu, 5 Dec 2019 02:21:02 +0200 Subject: [PATCH 18/21] comments --- contracts/schemes/ContributionRewardExt.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index 30ebbae8..daa3a12d 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -10,7 +10,8 @@ import "../libs/SafeERC20.sol"; * @title A scheme for proposing and rewarding contributions to an organization * @dev An agent can ask an organization to recognize a contribution and reward * him with token, reputation, ether or any combination. - * The contract enable to assign a rewarder, which, after the contributionreward has been accepted, + * This scheme extend the functionality of the ContributionReward scheme. + * It enable to assign a rewarder, which, after the contributionreward has been accepted, * can then later distribute the assets as it would like. */ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterface { From 2949a828d20ce3002dca975b209ea2beb84f48bc Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Thu, 5 Dec 2019 08:05:56 +0200 Subject: [PATCH 19/21] bump version to 0.0.1-rc.35 increase GAS_LIMIT to 62000000 due to new openzeppeling-solidity --- package-lock.json | 2 +- package.json | 4 ++-- test/constants.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc3a92e5..ac351d89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@daostack/arc", - "version": "0.0.1-rc.34", + "version": "0.0.1-rc.35", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2977b5d0..eadf0321 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@daostack/arc", - "version": "0.0.1-rc.34", + "version": "0.0.1-rc.35", "description": "A platform for building DAOs", "files": [ "contracts/", @@ -12,7 +12,7 @@ "tsconfig.json" ], "config": { - "gasLimit": "6100000" + "gasLimit": "6200000" }, "scripts": { "test": "cross-conf-env run-with-ganache --ganache-cmd 'npm run ganache' 'npm run truffle compile && npm run truffle migrate && npm run truffle test'", diff --git a/test/constants.js b/test/constants.js index ab71e6ac..04c7b377 100644 --- a/test/constants.js +++ b/test/constants.js @@ -1,3 +1,3 @@ -const ARC_GAS_LIMIT = 6100000; +const ARC_GAS_LIMIT = 6200000; module.exports = { ARC_GAS_LIMIT }; From 25b3f82c7057f62abe03e1161484d257ed6666cb Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Thu, 5 Dec 2019 09:06:57 +0200 Subject: [PATCH 20/21] better test timing setup --- test/competition.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/competition.js b/test/competition.js index c39af7a2..c505d5d1 100644 --- a/test/competition.js +++ b/test/competition.js @@ -121,7 +121,7 @@ const proposeCompetition = async function( contract('Competition', accounts => { - + it("proposeCompetition log", async function() { var testSetup = await setup(accounts); await proposeCompetition(testSetup); @@ -241,7 +241,7 @@ contract('Competition', accounts => { var testSetup = await setup(accounts); var proposalId = await proposeCompetition(testSetup);//votingStartTime await testSetup.competition.suggest(proposalId,"suggestion"); - await helpers.increaseTime(1200+1); + await helpers.increaseTime(1200+100); try { await testSetup.competition.suggest(proposalId,"suggestion"); assert(false, 'cannot suggest after competition end'); From 4734ffd60bfc106ce5e1aa2e8087c23b449c3458 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Thu, 5 Dec 2019 09:48:40 +0200 Subject: [PATCH 21/21] suggestionEndTime test --- contracts/schemes/ContributionRewardExt.sol | 1 + test/competition.js | 52 +++++++++++++++++++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/contracts/schemes/ContributionRewardExt.sol b/contracts/schemes/ContributionRewardExt.sol index daa3a12d..d921a1b5 100644 --- a/contracts/schemes/ContributionRewardExt.sol +++ b/contracts/schemes/ContributionRewardExt.sol @@ -313,6 +313,7 @@ contract ContributionRewardExt is VotingMachineCallbacks, ProposalExecuteInterfa * @param _proposalId the ID of the voting in the voting machine * @param _beneficiary the beneficiary to mint reputation to. * @param _reputation the reputation amount to mint + * note: burn reputation is not supported via this function */ function redeemReputationByRewarder(bytes32 _proposalId, address _beneficiary, uint256 _reputation) public diff --git a/test/competition.js b/test/competition.js index c505d5d1..c78299b2 100644 --- a/test/competition.js +++ b/test/competition.js @@ -74,7 +74,7 @@ const proposeCompetition = async function( _reputationChange = 10, _rewards = [1,2,3], _rewardSplit = [50,25,15,10], - _startTime = 0, + _startTime = 10, _votingStartTime = 600, _endTime = 1200, _maxNumberOfVotesPerVoter = 3, @@ -115,7 +115,6 @@ const proposeCompetition = async function( assert.equal(tx.logs[0].args._contributionRewardExt,_testSetup.contributionRewardExt.address); assert.equal(tx.logs[0].args._suggestionsEndTime,_testSetup.suggestionsEndTime); - return proposalId; }; @@ -197,7 +196,7 @@ contract('Competition', accounts => { startTime,//startTime votingStartTime, endTime, - 0);//votingStartTime + 0); assert(false, 'maxNumberOfVotesPerVoter > 0'); } catch (ex) { helpers.assertVMException(ex); @@ -207,12 +206,47 @@ contract('Competition', accounts => { it("suggest", async function() { var testSetup = await setup(accounts); var proposalId = await proposeCompetition(testSetup); + await helpers.increaseTime(20); var tx = await testSetup.competition.suggest(proposalId,"suggestion"); assert.equal(tx.logs.length, 1); assert.equal(tx.logs[0].event, "NewSuggestion"); assert.equal(tx.logs[0].args._suggestionId,1); }); + it("cannot suggest after suggestionEndTime", async function() { + var descriptionHash = "description-hash"; + var reputationChange = 10; + var rewards = [1,2,3]; + var rewardSplit = [100]; + var startTime = 10; + var votingStartTime = 600; + var endTime = 1200; + var maxNumberOfVotesPerVoter = 3; + var testSetup = await setup(accounts); + var proposalId = await proposeCompetition(testSetup, + descriptionHash, + reputationChange, + rewards, + rewardSplit, + startTime, + votingStartTime, + endTime, + maxNumberOfVotesPerVoter, + 200);//suggestionEndTime + await helpers.increaseTime(20);//increase time for suggestion + await testSetup.competition.suggest(proposalId,"suggestion"); + //increase time after suggestion end time + await helpers.increaseTime(250); + try { + + await testSetup.competition.suggest(proposalId,"suggestion"); + assert(false, 'cannot suggest after suggestionEndTime'); + } catch (ex) { + helpers.assertVMException(ex); + } + + }); + it("cannot suggest before start time", async function() { var testSetup = await setup(accounts); var descriptionHash = "description-hash"; @@ -224,7 +258,7 @@ contract('Competition', accounts => { reputationChange, rewards, rewardSplit, - 10//startTime + 20//startTime );//votingStartTime try { @@ -233,13 +267,14 @@ contract('Competition', accounts => { } catch (ex) { helpers.assertVMException(ex); } - await helpers.increaseTime(10+1); + await helpers.increaseTime(20); await testSetup.competition.suggest(proposalId,"suggestion"); }); it("cannot suggest after competition end", async function() { var testSetup = await setup(accounts); var proposalId = await proposeCompetition(testSetup);//votingStartTime + await helpers.increaseTime(20); await testSetup.competition.suggest(proposalId,"suggestion"); await helpers.increaseTime(1200+100); try { @@ -253,6 +288,7 @@ contract('Competition', accounts => { it("vote", async function() { var testSetup = await setup(accounts); var proposalId = await proposeCompetition(testSetup); + await helpers.increaseTime(20); var tx = await testSetup.competition.suggest(proposalId,"suggestion"); var suggestionId = tx.logs[0].args._suggestionId; @@ -314,6 +350,7 @@ contract('Competition', accounts => { it("total votes", async function() { var testSetup = await setup(accounts); var proposalId = await proposeCompetition(testSetup); + await helpers.increaseTime(20); var tx = await testSetup.competition.suggest(proposalId,"suggestion"); var suggestionId = tx.logs[0].args._suggestionId; await helpers.increaseTime(650); @@ -327,6 +364,7 @@ contract('Competition', accounts => { it("getOrderedIndexOfSuggestion", async function() { var testSetup = await setup(accounts); var proposalId = await proposeCompetition(testSetup); + await helpers.increaseTime(20); for (var i=0;i<20;i++) { //submit 20 suggestion await testSetup.competition.suggest(proposalId,"suggestion"); @@ -345,6 +383,7 @@ contract('Competition', accounts => { it("getOrderedIndexOfSuggestion equality case", async function() { var testSetup = await setup(accounts); var proposalId = await proposeCompetition(testSetup); + await helpers.increaseTime(20); for (var i=0;i<20;i++) { //submit 20 suggestion await testSetup.competition.suggest(proposalId,"suggestion"); @@ -377,6 +416,7 @@ contract('Competition', accounts => { await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,30,{from:accounts[1]}); await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:20}); var proposalId = await proposeCompetition(testSetup); + await helpers.increaseTime(20); await testSetup.competition.suggest(proposalId,"suggestion"); try { await testSetup.competition.redeem(1,accounts[0]); @@ -449,6 +489,7 @@ contract('Competition', accounts => { await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,3000,{from:accounts[1]}); await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:2000}); var proposalId = await proposeCompetition(testSetup,"description-hash",1000,[1000,2000,3000]); + await helpers.increaseTime(20); await testSetup.competition.suggest(proposalId,"suggestion"); await testSetup.competition.suggest(proposalId,"suggestion"); @@ -550,6 +591,7 @@ contract('Competition', accounts => { await testSetup.standardTokenMock.transfer(testSetup.org.avatar.address,3000,{from:accounts[1]}); await web3.eth.sendTransaction({from:accounts[0],to:testSetup.org.avatar.address, value:2000}); var proposalId = await proposeCompetition(testSetup,"description-hash",1000,[1000,2000,3000]); + await helpers.increaseTime(20); await testSetup.competition.suggest(proposalId,"suggestion"); await testSetup.competition.suggest(proposalId,"suggestion");