diff --git a/conf/config.json b/conf/config.json index 182f5f5..ba25277 100644 --- a/conf/config.json +++ b/conf/config.json @@ -1,12 +1,17 @@ { - "RegistryDefaults": { + "paramDefaults": { "minDeposit": 50000, - "minParamDeposit": 500000, + "pMinDeposit": 500000, "applyStageLength": 600, - "commitPeriodLength": 600, - "revealPeriodLength": 600, + "pApplyStageLength": 1200, + "commitStageLength": 600, + "pCommitStageLength": 1200, + "revealStageLength": 600, + "pRevealStageLength": 1200, "dispensationPct": 50, - "voteQuorum": 50 + "pDispensationPct": 50, + "voteQuorum": 50, + "pVoteQuorum": 50 }, "TokenAddress": "0x337cDDa6D41A327c5ad456166CCB781a9722AFf9" } diff --git a/contracts/AttributeStore.sol b/contracts/AttributeStore.sol index ff41cb2..d51468c 100644 --- a/contracts/AttributeStore.sol +++ b/contracts/AttributeStore.sol @@ -1,17 +1,17 @@ -pragma solidity^0.4.11; - -library AttributeStore { - struct Data { - mapping(bytes32 => uint) store; - } - - function getAttribute(Data storage self, bytes32 UUID, string attrName) returns (uint) { - bytes32 key = sha3(UUID, attrName); - return self.store[key]; - } - - function attachAttribute(Data storage self, bytes32 UUID, string attrName, uint attrVal) { - bytes32 key = sha3(UUID, attrName); - self.store[key] = attrVal; - } -} +pragma solidity^0.4.11; + +library AttributeStore { + struct Data { + mapping(bytes32 => uint) store; + } + + function getAttribute(Data storage self, bytes32 UUID, string attrName) returns (uint) { + bytes32 key = sha3(UUID, attrName); + return self.store[key]; + } + + function attachAttribute(Data storage self, bytes32 UUID, string attrName, uint attrVal) { + bytes32 key = sha3(UUID, attrName); + self.store[key] = attrVal; + } +} diff --git a/contracts/DLL.sol b/contracts/DLL.sol index cb28a56..ee6b81f 100644 --- a/contracts/DLL.sol +++ b/contracts/DLL.sol @@ -1,39 +1,39 @@ -pragma solidity^0.4.11; - -library DLL { - struct Node { - uint next; - uint prev; - } - - struct Data { - mapping(uint => Node) dll; - } - - function getNext(Data storage self, uint curr) returns (uint) { - return self.dll[curr].next; - } - - function getPrev(Data storage self, uint curr) returns (uint) { - return self.dll[curr].prev; - } - - function insert(Data storage self, uint prev, uint curr, uint next) { - self.dll[curr].prev = prev; - self.dll[curr].next = next; - - self.dll[prev].next = curr; - self.dll[next].prev = curr; - } - - function remove(Data storage self, uint curr) { - uint next = getNext(self, curr); - uint prev = getPrev(self, curr); - - self.dll[next].prev = prev; - self.dll[prev].next = next; - - self.dll[curr].next = curr; - self.dll[curr].prev = curr; - } -} \ No newline at end of file +pragma solidity^0.4.11; + +library DLL { + struct Node { + uint next; + uint prev; + } + + struct Data { + mapping(uint => Node) dll; + } + + function getNext(Data storage self, uint curr) returns (uint) { + return self.dll[curr].next; + } + + function getPrev(Data storage self, uint curr) returns (uint) { + return self.dll[curr].prev; + } + + function insert(Data storage self, uint prev, uint curr, uint next) { + self.dll[curr].prev = prev; + self.dll[curr].next = next; + + self.dll[prev].next = curr; + self.dll[next].prev = curr; + } + + function remove(Data storage self, uint curr) { + uint next = getNext(self, curr); + uint prev = getPrev(self, curr); + + self.dll[next].prev = prev; + self.dll[prev].next = next; + + self.dll[curr].next = curr; + self.dll[curr].prev = curr; + } +} diff --git a/contracts/PLCRVoting.sol b/contracts/PLCRVoting.sol index 7e35249..4583faa 100644 --- a/contracts/PLCRVoting.sol +++ b/contracts/PLCRVoting.sol @@ -1,391 +1,391 @@ -pragma solidity ^0.4.8; -import "./historical/HumanStandardToken.sol"; -import "./DLL.sol"; -import "./AttributeStore.sol"; - -/** -@title Partial-Lock-Commit-Reveal Voting scheme with ERC20 tokens -@author Team: Aspyn Palatnick, Cem Ozer, Yorke Rhodes -*/ -contract PLCRVoting { - - - event VoteCommitted(address voter, uint pollID, uint numTokens); - event VoteRevealed(address voter, uint pollID, uint numTokens, uint choice); - event PollCreated(uint voteQuorum, uint commitDuration, uint revealDuration, uint pollID); - event VotingRightsGranted(address voter, uint numTokens); - event VotingRightsWithdrawn(address voter, uint numTokens); - - /// maps user's address to voteToken balance - mapping(address => uint) public voteTokenBalance; - - struct Poll { - uint commitEndDate; /// expiration date of commit period for poll - uint revealEndDate; /// expiration date of reveal period for poll - uint voteQuorum; /// number of votes required for a proposal to pass - uint votesFor; /// tally of votes supporting proposal - uint votesAgainst; /// tally of votes countering proposal - } - - /// maps pollID to Poll struct - mapping(uint => Poll) public pollMap; - uint pollNonce; - - using DLL for DLL.Data; - mapping(address => DLL.Data) dllMap; - - using AttributeStore for AttributeStore.Data; - AttributeStore.Data store; - - // ============ - // CONSTRUCTOR: - // ============ - - uint constant INITIAL_POLL_NONCE = 0; - HumanStandardToken public token; - - /** - @dev Initializes voteQuorum, commitDuration, revealDuration, and pollNonce in addition to token contract and trusted mapping - @param _tokenAddr The address where the ERC20 token contract is deployed - */ - function PLCRVoting(address _tokenAddr) { - token = HumanStandardToken(_tokenAddr); - pollNonce = INITIAL_POLL_NONCE; - } - - // ================ - // TOKEN INTERFACE: - // ================ - - /** - @notice Loads _numTokens ERC20 tokens into the voting contract for one-to-one voting rights - @dev Assumes that msg.sender has approved voting contract to spend on their behalf - @param _numTokens The number of votingTokens desired in exchange for ERC20 tokens - */ - function requestVotingRights(uint _numTokens) external { - require(token.balanceOf(msg.sender) >= _numTokens); - require(token.transferFrom(msg.sender, this, _numTokens)); - voteTokenBalance[msg.sender] += _numTokens; - VotingRightsGranted(msg.sender, _numTokens); - } - - /** - @notice Withdraw _numTokens ERC20 tokens from the voting contract, revoking these voting rights - @param _numTokens The number of ERC20 tokens desired in exchange for voting rights - */ - function withdrawVotingRights(uint _numTokens) external { - uint availableTokens = voteTokenBalance[msg.sender] - getLockedTokens(msg.sender); - require(availableTokens >= _numTokens); - require(token.transfer(msg.sender, _numTokens)); - voteTokenBalance[msg.sender] -= _numTokens; - VotingRightsWithdrawn(msg.sender, _numTokens); - } - - /** - @dev Unlocks tokens locked in unrevealed vote where poll has ended - @param _pollID Integer identifier associated with the target poll - */ - function rescueTokens(uint _pollID) external { - require(pollEnded(_pollID)); - require(!hasBeenRevealed(msg.sender, _pollID)); - - dllMap[msg.sender].remove(_pollID); - } - - // ================= - // VOTING INTERFACE: - // ================= - - /** - @notice Commits vote using hash of choice and secret salt to conceal vote until reveal - @param _pollID Integer identifier associated with target poll - @param _secretHash Commit keccak256 hash of voter's choice and salt (tightly packed in this order) - @param _numTokens The number of tokens to be committed towards the target poll - @param _prevPollID The ID of the poll that the user has voted the maximum number of tokens in which is still less than or equal to numTokens - */ - function commitVote(uint _pollID, bytes32 _secretHash, uint _numTokens, uint _prevPollID) external { - require(commitPeriodActive(_pollID)); - require(voteTokenBalance[msg.sender] >= _numTokens); // prevent user from overspending - require(_pollID != 0); // prevent user from committing to zero node placeholder - - // TODO: Move all insert validation into the DLL lib - // Check if _prevPollID exists - require(_prevPollID == 0 || getCommitHash(msg.sender, _prevPollID) != 0); - - uint nextPollID = dllMap[msg.sender].getNext(_prevPollID); - - // if nextPollID is equal to _pollID, _pollID is being updated, - nextPollID = (nextPollID == _pollID) ? dllMap[msg.sender].getNext(_pollID) : nextPollID; - - require(validPosition(_prevPollID, nextPollID, msg.sender, _numTokens)); - dllMap[msg.sender].insert(_prevPollID, _pollID, nextPollID); - - bytes32 UUID = attrUUID(msg.sender, _pollID); - - store.attachAttribute(UUID, "numTokens", _numTokens); - store.attachAttribute(UUID, "commitHash", uint(_secretHash)); - - VoteCommitted(msg.sender, _pollID, _numTokens); - } - - /** - @dev Compares previous and next poll's committed tokens for sorting purposes - @param _prevID Integer identifier associated with previous poll in sorted order - @param _nextID Integer identifier associated with next poll in sorted order - @param _voter Address of user to check DLL position for - @param _numTokens The number of tokens to be committed towards the poll (used for sorting) - @return valid Boolean indication of if the specified position maintains the sort - */ - function validPosition(uint _prevID, uint _nextID, address _voter, uint _numTokens) public constant returns (bool valid) { - bool prevValid = (_numTokens >= getNumTokens(_voter, _prevID)); - // if next is zero node, _numTokens does not need to be greater - bool nextValid = (_numTokens <= getNumTokens(_voter, _nextID) || _nextID == 0); - return prevValid && nextValid; - } - - /** - @notice Reveals vote with choice and secret salt used in generating commitHash to attribute committed tokens - @param _pollID Integer identifier associated with target poll - @param _voteOption Vote choice used to generate commitHash for associated poll - @param _salt Secret number used to generate commitHash for associated poll - */ - function revealVote(uint _pollID, uint _voteOption, uint _salt) external { - // Make sure the reveal period is active - require(revealPeriodActive(_pollID)); - require(!hasBeenRevealed(msg.sender, _pollID)); // prevent user from revealing multiple times - require(sha3(_voteOption, _salt) == getCommitHash(msg.sender, _pollID)); // compare resultant hash from inputs to original commitHash - - uint numTokens = getNumTokens(msg.sender, _pollID); - - if (_voteOption == 1) // apply numTokens to appropriate poll choice - pollMap[_pollID].votesFor += numTokens; - else - pollMap[_pollID].votesAgainst += numTokens; - - dllMap[msg.sender].remove(_pollID); // remove the node referring to this vote upon reveal - - VoteRevealed(msg.sender, _pollID, numTokens, _voteOption); - } - - /** - @param _pollID Integer identifier associated with target poll - @param _salt Arbitrarily chosen integer used to generate secretHash - @return correctVotes Number of tokens voted for winning option - */ - function getNumPassingTokens(address _voter, uint _pollID, uint _salt) public constant returns (uint correctVotes) { - require(pollEnded(_pollID)); - require(hasBeenRevealed(_voter, _pollID)); - - uint winningChoice = isPassed(_pollID) ? 1 : 0; - bytes32 winnerHash = sha3(winningChoice, _salt); - bytes32 commitHash = getCommitHash(_voter, _pollID); - - return (winnerHash == commitHash) ? getNumTokens(_voter, _pollID) : 0; - } - - // ================== - // POLLING INTERFACE: - // ================== - - /** - @dev Initiates a poll with canonical configured parameters at pollID emitted by PollCreated event - @param _voteQuorum Type of majority (out of 100) that is necessary for poll to be successful - @param _commitDuration Length of desired commit period in seconds - @param _revealDuration Length of desired reveal period in seconds - */ - function startPoll(uint _voteQuorum, uint _commitDuration, uint _revealDuration) public returns (uint pollID) { - pollNonce = pollNonce + 1; - - pollMap[pollNonce] = Poll({ - voteQuorum: _voteQuorum, - commitEndDate: block.timestamp + _commitDuration, - revealEndDate: block.timestamp + _commitDuration + _revealDuration, - votesFor: 0, - votesAgainst: 0 - }); - - PollCreated(_voteQuorum, _commitDuration, _revealDuration, pollNonce); - return pollNonce; - } - - /** - @notice Determines if proposal has passed - @dev Check if votesFor out of totalVotes exceeds votesQuorum (requires pollEnded) - @param _pollID Integer identifier associated with target poll - */ - function isPassed(uint _pollID) constant public returns (bool passed) { - require(pollEnded(_pollID)); - - Poll memory poll = pollMap[_pollID]; - return (100 * poll.votesFor) > (poll.voteQuorum * (poll.votesFor + poll.votesAgainst)); - } - - // ---------------- - // POLLING HELPERS: - // ---------------- - - /** - @dev Gets the total winning votes for reward distribution purposes - @param _pollID Integer identifier associated with target poll - @return Total number of votes committed to the winning option for specified poll - */ - function getTotalNumberOfTokensForWinningOption(uint _pollID) constant public returns (uint numTokens) { - require(pollEnded(_pollID)); - - if (isPassed(_pollID)) - return pollMap[_pollID].votesFor; - else - return pollMap[_pollID].votesAgainst; - } - - /** - @notice Determines if poll is over - @dev Checks isExpired for specified poll's revealEndDate - @return Boolean indication of whether polling period is over - */ - function pollEnded(uint _pollID) constant public returns (bool ended) { - require(pollExists(_pollID)); - - return isExpired(pollMap[_pollID].revealEndDate); - } - - /** - @notice Checks if the commit period is still active for the specified poll - @dev Checks isExpired for the specified poll's commitEndDate - @param _pollID Integer identifier associated with target poll - @return Boolean indication of isCommitPeriodActive for target poll - */ - function commitPeriodActive(uint _pollID) constant public returns (bool active) { - require(pollExists(_pollID)); - - return !isExpired(pollMap[_pollID].commitEndDate); - } - - /** - @notice Checks if the reveal period is still active for the specified poll - @dev Checks isExpired for the specified poll's revealEndDate - @param _pollID Integer identifier associated with target poll - */ - function revealPeriodActive(uint _pollID) constant public returns (bool active) { - require(pollExists(_pollID)); - - return !isExpired(pollMap[_pollID].revealEndDate) && !commitPeriodActive(_pollID); - } - - /** - @dev Checks if user has already revealed for specified poll - @param _voter Address of user to check against - @param _pollID Integer identifier associated with target poll - @return Boolean indication of whether user has already revealed - */ - function hasBeenRevealed(address _voter, uint _pollID) constant public returns (bool revealed) { - require(pollExists(_pollID)); - - uint prevID = dllMap[_voter].getPrev(_pollID); - uint nextID = dllMap[_voter].getNext(_pollID); - - return (prevID == _pollID) && (nextID == _pollID); - } - - /** - @dev Checks if a poll exists, throws if the provided poll is in an impossible state - @param _pollID The pollID whose existance is to be evaluated. - @return Boolean Indicates whether a poll exists for the provided pollID - */ - function pollExists(uint _pollID) constant public returns (bool exists) { - uint commitEndDate = pollMap[_pollID].commitEndDate; - uint revealEndDate = pollMap[_pollID].revealEndDate; - - assert(!(commitEndDate == 0 && revealEndDate != 0)); - assert(!(commitEndDate != 0 && revealEndDate == 0)); - - if(commitEndDate == 0 || revealEndDate == 0) { return false; } - return true; - } - - // --------------------------- - // DOUBLE-LINKED-LIST HELPERS: - // --------------------------- - - /** - @dev Gets the bytes32 commitHash property of target poll - @param _voter Address of user to check against - @param _pollID Integer identifier associated with target poll - @return Bytes32 hash property attached to target poll - */ - function getCommitHash(address _voter, uint _pollID) constant public returns (bytes32 commitHash) { - return bytes32(store.getAttribute(attrUUID(_voter, _pollID), "commitHash")); - } - - /** - @dev Wrapper for getAttribute with attrName="numTokens" - @param _voter Address of user to check against - @param _pollID Integer identifier associated with target poll - @return Number of tokens committed to poll in sorted poll-linked-list - */ - function getNumTokens(address _voter, uint _pollID) constant public returns (uint numTokens) { - return store.getAttribute(attrUUID(_voter, _pollID), "numTokens"); - } - - /** - @dev Gets top element of sorted poll-linked-list - @param _voter Address of user to check against - @return Integer identifier to poll with maximum number of tokens committed to it - */ - function getLastNode(address _voter) constant public returns (uint pollID) { - return dllMap[_voter].getPrev(0); - } - - /** - @dev Gets the numTokens property of getLastNode - @param _voter Address of user to check against - @return Maximum number of tokens committed in poll specified - */ - function getLockedTokens(address _voter) constant public returns (uint numTokens) { - return getNumTokens(_voter, getLastNode(_voter)); - } - - /** - @dev Gets the prevNode a new node should be inserted after given the sort factor - @param _voter The voter whose DLL will be searched - @param _numTokens The value for the numTokens attribute in the node to be inserted - @return the node which the propoded node should be inserted after - */ - function getInsertPointForNumTokens(address _voter, uint _numTokens) - constant public returns (uint prevNode) { - uint nodeID = getLastNode(_voter); - uint tokensInNode = getNumTokens(_voter, nodeID); - - while(tokensInNode != 0) { - tokensInNode = getNumTokens(_voter, nodeID); - if(tokensInNode < _numTokens) { - return nodeID; - } - nodeID = dllMap[_voter].getPrev(nodeID); - } - - return nodeID; - } - - // ---------------- - // GENERAL HELPERS: - // ---------------- - - /** - @dev Checks if an expiration date has been reached - @param _terminationDate Integer timestamp of date to compare current timestamp with - @return expired Boolean indication of whether the terminationDate has passed - */ - function isExpired(uint _terminationDate) constant public returns (bool expired) { - return (block.timestamp > _terminationDate); - } - - /** - @dev Generates an identifier which associates a user and a poll together - @param _pollID Integer identifier associated with target poll - @return UUID Hash which is deterministic from _user and _pollID - */ - function attrUUID(address _user, uint _pollID) public constant returns (bytes32 UUID) { - return sha3(_user, _pollID); - } -} +pragma solidity ^0.4.8; +import "./historical/HumanStandardToken.sol"; +import "./DLL.sol"; +import "./AttributeStore.sol"; + +/** +@title Partial-Lock-Commit-Reveal Voting scheme with ERC20 tokens +@author Team: Aspyn Palatnick, Cem Ozer, Yorke Rhodes +*/ +contract PLCRVoting { + + + event VoteCommitted(address voter, uint pollID, uint numTokens); + event VoteRevealed(address voter, uint pollID, uint numTokens, uint choice); + event PollCreated(uint voteQuorum, uint commitDuration, uint revealDuration, uint pollID); + event VotingRightsGranted(address voter, uint numTokens); + event VotingRightsWithdrawn(address voter, uint numTokens); + + /// maps user's address to voteToken balance + mapping(address => uint) public voteTokenBalance; + + struct Poll { + uint commitEndDate; /// expiration date of commit period for poll + uint revealEndDate; /// expiration date of reveal period for poll + uint voteQuorum; /// number of votes required for a proposal to pass + uint votesFor; /// tally of votes supporting proposal + uint votesAgainst; /// tally of votes countering proposal + } + + /// maps pollID to Poll struct + mapping(uint => Poll) public pollMap; + uint pollNonce; + + using DLL for DLL.Data; + mapping(address => DLL.Data) dllMap; + + using AttributeStore for AttributeStore.Data; + AttributeStore.Data store; + + // ============ + // CONSTRUCTOR: + // ============ + + uint constant INITIAL_POLL_NONCE = 0; + HumanStandardToken public token; + + /** + @dev Initializes voteQuorum, commitDuration, revealDuration, and pollNonce in addition to token contract and trusted mapping + @param _tokenAddr The address where the ERC20 token contract is deployed + */ + function PLCRVoting(address _tokenAddr) { + token = HumanStandardToken(_tokenAddr); + pollNonce = INITIAL_POLL_NONCE; + } + + // ================ + // TOKEN INTERFACE: + // ================ + + /** + @notice Loads _numTokens ERC20 tokens into the voting contract for one-to-one voting rights + @dev Assumes that msg.sender has approved voting contract to spend on their behalf + @param _numTokens The number of votingTokens desired in exchange for ERC20 tokens + */ + function requestVotingRights(uint _numTokens) external { + require(token.balanceOf(msg.sender) >= _numTokens); + require(token.transferFrom(msg.sender, this, _numTokens)); + voteTokenBalance[msg.sender] += _numTokens; + VotingRightsGranted(msg.sender, _numTokens); + } + + /** + @notice Withdraw _numTokens ERC20 tokens from the voting contract, revoking these voting rights + @param _numTokens The number of ERC20 tokens desired in exchange for voting rights + */ + function withdrawVotingRights(uint _numTokens) external { + uint availableTokens = voteTokenBalance[msg.sender] - getLockedTokens(msg.sender); + require(availableTokens >= _numTokens); + require(token.transfer(msg.sender, _numTokens)); + voteTokenBalance[msg.sender] -= _numTokens; + VotingRightsWithdrawn(msg.sender, _numTokens); + } + + /** + @dev Unlocks tokens locked in unrevealed vote where poll has ended + @param _pollID Integer identifier associated with the target poll + */ + function rescueTokens(uint _pollID) external { + require(pollEnded(_pollID)); + require(!hasBeenRevealed(msg.sender, _pollID)); + + dllMap[msg.sender].remove(_pollID); + } + + // ================= + // VOTING INTERFACE: + // ================= + + /** + @notice Commits vote using hash of choice and secret salt to conceal vote until reveal + @param _pollID Integer identifier associated with target poll + @param _secretHash Commit keccak256 hash of voter's choice and salt (tightly packed in this order) + @param _numTokens The number of tokens to be committed towards the target poll + @param _prevPollID The ID of the poll that the user has voted the maximum number of tokens in which is still less than or equal to numTokens + */ + function commitVote(uint _pollID, bytes32 _secretHash, uint _numTokens, uint _prevPollID) external { + require(commitStageActive(_pollID)); + require(voteTokenBalance[msg.sender] >= _numTokens); // prevent user from overspending + require(_pollID != 0); // prevent user from committing to zero node placeholder + + // TODO: Move all insert validation into the DLL lib + // Check if _prevPollID exists + require(_prevPollID == 0 || getCommitHash(msg.sender, _prevPollID) != 0); + + uint nextPollID = dllMap[msg.sender].getNext(_prevPollID); + + // if nextPollID is equal to _pollID, _pollID is being updated, + nextPollID = (nextPollID == _pollID) ? dllMap[msg.sender].getNext(_pollID) : nextPollID; + + require(validPosition(_prevPollID, nextPollID, msg.sender, _numTokens)); + dllMap[msg.sender].insert(_prevPollID, _pollID, nextPollID); + + bytes32 UUID = attrUUID(msg.sender, _pollID); + + store.attachAttribute(UUID, "numTokens", _numTokens); + store.attachAttribute(UUID, "commitHash", uint(_secretHash)); + + VoteCommitted(msg.sender, _pollID, _numTokens); + } + + /** + @dev Compares previous and next poll's committed tokens for sorting purposes + @param _prevID Integer identifier associated with previous poll in sorted order + @param _nextID Integer identifier associated with next poll in sorted order + @param _voter Address of user to check DLL position for + @param _numTokens The number of tokens to be committed towards the poll (used for sorting) + @return valid Boolean indication of if the specified position maintains the sort + */ + function validPosition(uint _prevID, uint _nextID, address _voter, uint _numTokens) public constant returns (bool valid) { + bool prevValid = (_numTokens >= getNumTokens(_voter, _prevID)); + // if next is zero node, _numTokens does not need to be greater + bool nextValid = (_numTokens <= getNumTokens(_voter, _nextID) || _nextID == 0); + return prevValid && nextValid; + } + + /** + @notice Reveals vote with choice and secret salt used in generating commitHash to attribute committed tokens + @param _pollID Integer identifier associated with target poll + @param _voteOption Vote choice used to generate commitHash for associated poll + @param _salt Secret number used to generate commitHash for associated poll + */ + function revealVote(uint _pollID, uint _voteOption, uint _salt) external { + // Make sure the reveal period is active + require(revealStageActive(_pollID)); + require(!hasBeenRevealed(msg.sender, _pollID)); // prevent user from revealing multiple times + require(sha3(_voteOption, _salt) == getCommitHash(msg.sender, _pollID)); // compare resultant hash from inputs to original commitHash + + uint numTokens = getNumTokens(msg.sender, _pollID); + + if (_voteOption == 1) // apply numTokens to appropriate poll choice + pollMap[_pollID].votesFor += numTokens; + else + pollMap[_pollID].votesAgainst += numTokens; + + dllMap[msg.sender].remove(_pollID); // remove the node referring to this vote upon reveal + + VoteRevealed(msg.sender, _pollID, numTokens, _voteOption); + } + + /** + @param _pollID Integer identifier associated with target poll + @param _salt Arbitrarily chosen integer used to generate secretHash + @return correctVotes Number of tokens voted for winning option + */ + function getNumPassingTokens(address _voter, uint _pollID, uint _salt) public constant returns (uint correctVotes) { + require(pollEnded(_pollID)); + require(hasBeenRevealed(_voter, _pollID)); + + uint winningChoice = isPassed(_pollID) ? 1 : 0; + bytes32 winnerHash = sha3(winningChoice, _salt); + bytes32 commitHash = getCommitHash(_voter, _pollID); + + return (winnerHash == commitHash) ? getNumTokens(_voter, _pollID) : 0; + } + + // ================== + // POLLING INTERFACE: + // ================== + + /** + @dev Initiates a poll with canonical configured parameters at pollID emitted by PollCreated event + @param _voteQuorum Type of majority (out of 100) that is necessary for poll to be successful + @param _commitDuration Length of desired commit period in seconds + @param _revealDuration Length of desired reveal period in seconds + */ + function startPoll(uint _voteQuorum, uint _commitDuration, uint _revealDuration) public returns (uint pollID) { + pollNonce = pollNonce + 1; + + pollMap[pollNonce] = Poll({ + voteQuorum: _voteQuorum, + commitEndDate: block.timestamp + _commitDuration, + revealEndDate: block.timestamp + _commitDuration + _revealDuration, + votesFor: 0, + votesAgainst: 0 + }); + + PollCreated(_voteQuorum, _commitDuration, _revealDuration, pollNonce); + return pollNonce; + } + + /** + @notice Determines if proposal has passed + @dev Check if votesFor out of totalVotes exceeds votesQuorum (requires pollEnded) + @param _pollID Integer identifier associated with target poll + */ + function isPassed(uint _pollID) constant public returns (bool passed) { + require(pollEnded(_pollID)); + + Poll memory poll = pollMap[_pollID]; + return (100 * poll.votesFor) > (poll.voteQuorum * (poll.votesFor + poll.votesAgainst)); + } + + // ---------------- + // POLLING HELPERS: + // ---------------- + + /** + @dev Gets the total winning votes for reward distribution purposes + @param _pollID Integer identifier associated with target poll + @return Total number of votes committed to the winning option for specified poll + */ + function getTotalNumberOfTokensForWinningOption(uint _pollID) constant public returns (uint numTokens) { + require(pollEnded(_pollID)); + + if (isPassed(_pollID)) + return pollMap[_pollID].votesFor; + else + return pollMap[_pollID].votesAgainst; + } + + /** + @notice Determines if poll is over + @dev Checks isExpired for specified poll's revealEndDate + @return Boolean indication of whether polling period is over + */ + function pollEnded(uint _pollID) constant public returns (bool ended) { + require(pollExists(_pollID)); + + return isExpired(pollMap[_pollID].revealEndDate); + } + + /** + @notice Checks if the commit period is still active for the specified poll + @dev Checks isExpired for the specified poll's commitEndDate + @param _pollID Integer identifier associated with target poll + @return Boolean indication of isCommitStageActive for target poll + */ + function commitStageActive(uint _pollID) constant public returns (bool active) { + require(pollExists(_pollID)); + + return !isExpired(pollMap[_pollID].commitEndDate); + } + + /** + @notice Checks if the reveal period is still active for the specified poll + @dev Checks isExpired for the specified poll's revealEndDate + @param _pollID Integer identifier associated with target poll + */ + function revealStageActive(uint _pollID) constant public returns (bool active) { + require(pollExists(_pollID)); + + return !isExpired(pollMap[_pollID].revealEndDate) && !commitStageActive(_pollID); + } + + /** + @dev Checks if user has already revealed for specified poll + @param _voter Address of user to check against + @param _pollID Integer identifier associated with target poll + @return Boolean indication of whether user has already revealed + */ + function hasBeenRevealed(address _voter, uint _pollID) constant public returns (bool revealed) { + require(pollExists(_pollID)); + + uint prevID = dllMap[_voter].getPrev(_pollID); + uint nextID = dllMap[_voter].getNext(_pollID); + + return (prevID == _pollID) && (nextID == _pollID); + } + + /** + @dev Checks if a poll exists, throws if the provided poll is in an impossible state + @param _pollID The pollID whose existance is to be evaluated. + @return Boolean Indicates whether a poll exists for the provided pollID + */ + function pollExists(uint _pollID) constant public returns (bool exists) { + uint commitEndDate = pollMap[_pollID].commitEndDate; + uint revealEndDate = pollMap[_pollID].revealEndDate; + + assert(!(commitEndDate == 0 && revealEndDate != 0)); + assert(!(commitEndDate != 0 && revealEndDate == 0)); + + if(commitEndDate == 0 || revealEndDate == 0) { return false; } + return true; + } + + // --------------------------- + // DOUBLE-LINKED-LIST HELPERS: + // --------------------------- + + /** + @dev Gets the bytes32 commitHash property of target poll + @param _voter Address of user to check against + @param _pollID Integer identifier associated with target poll + @return Bytes32 hash property attached to target poll + */ + function getCommitHash(address _voter, uint _pollID) constant public returns (bytes32 commitHash) { + return bytes32(store.getAttribute(attrUUID(_voter, _pollID), "commitHash")); + } + + /** + @dev Wrapper for getAttribute with attrName="numTokens" + @param _voter Address of user to check against + @param _pollID Integer identifier associated with target poll + @return Number of tokens committed to poll in sorted poll-linked-list + */ + function getNumTokens(address _voter, uint _pollID) constant public returns (uint numTokens) { + return store.getAttribute(attrUUID(_voter, _pollID), "numTokens"); + } + + /** + @dev Gets top element of sorted poll-linked-list + @param _voter Address of user to check against + @return Integer identifier to poll with maximum number of tokens committed to it + */ + function getLastNode(address _voter) constant public returns (uint pollID) { + return dllMap[_voter].getPrev(0); + } + + /** + @dev Gets the numTokens property of getLastNode + @param _voter Address of user to check against + @return Maximum number of tokens committed in poll specified + */ + function getLockedTokens(address _voter) constant public returns (uint numTokens) { + return getNumTokens(_voter, getLastNode(_voter)); + } + + /** + @dev Gets the prevNode a new node should be inserted after given the sort factor + @param _voter The voter whose DLL will be searched + @param _numTokens The value for the numTokens attribute in the node to be inserted + @return the node which the propoded node should be inserted after + */ + function getInsertPointForNumTokens(address _voter, uint _numTokens) + constant public returns (uint prevNode) { + uint nodeID = getLastNode(_voter); + uint tokensInNode = getNumTokens(_voter, nodeID); + + while(tokensInNode != 0) { + tokensInNode = getNumTokens(_voter, nodeID); + if(tokensInNode < _numTokens) { + return nodeID; + } + nodeID = dllMap[_voter].getPrev(nodeID); + } + + return nodeID; + } + + // ---------------- + // GENERAL HELPERS: + // ---------------- + + /** + @dev Checks if an expiration date has been reached + @param _terminationDate Integer timestamp of date to compare current timestamp with + @return expired Boolean indication of whether the terminationDate has passed + */ + function isExpired(uint _terminationDate) constant public returns (bool expired) { + return (block.timestamp > _terminationDate); + } + + /** + @dev Generates an identifier which associates a user and a poll together + @param _pollID Integer identifier associated with target poll + @return UUID Hash which is deterministic from _user and _pollID + */ + function attrUUID(address _user, uint _pollID) public constant returns (bytes32 UUID) { + return sha3(_user, _pollID); + } +} diff --git a/contracts/Parameterizer.sol b/contracts/Parameterizer.sol index ce970cb..22d3285 100644 --- a/contracts/Parameterizer.sol +++ b/contracts/Parameterizer.sol @@ -1,96 +1,321 @@ -pragma solidity^0.4.11; - -import "./PLCRVoting.sol"; -import "./historical/StandardToken.sol"; - -contract Parameterizer { - mapping(bytes32 => uint) public params; - - struct ParamProposal { - string name; - uint value; - address owner; - uint deposit; - } - - // maps pollIDs to intended data change if poll passes - mapping(uint => ParamProposal) public proposalMap; - - // Global Variables - StandardToken public token; - PLCRVoting public voting; - - /// @param _minDeposit minimum deposit for listing to be whitelisted - /// @param _minParamDeposit minimum deposit to propose a parameter change - /// @param _applyStageLen length of period in which applicants wait to be whitelisted - /// @param _dispensationPct percentage of losing party's deposit distributed to winning party - /// @param _commitPeriodLen length of commit period for voting - /// @param _revealPeriodLen length of reveal period for voting - /// @param _voteQuorum type of majority out of 100 necessary for vote success - - function Parameterizer( - address tokenAddr, - uint _minDeposit, - uint _minParamDeposit, - uint _applyStageLen, - uint _commitPeriodLen, - uint _revealPeriodLen, - uint _dispensationPct, - uint _voteQuorum - ) { - token = StandardToken(tokenAddr); - voting = new PLCRVoting(tokenAddr); - - set("minDeposit", _minDeposit); - set("minParamDeposit", _minParamDeposit); - set("applyStageLen", _applyStageLen); - set("commitPeriodLen", _commitPeriodLen); - set("revealPeriodLen", _revealPeriodLen); - set("dispensationPct", _dispensationPct); - set("voteQuorum", _voteQuorum); - } - - // changes parameter within canonical mapping - function set(string name, uint value) internal { - params[sha3(name)] = value; - } - - // gets parameter by string name from hashMap - function get(string name) public constant returns (uint value) { - return params[sha3(name)]; - } - - // starts poll and takes tokens from msg.sender - function changeParameter(string name, uint value) returns (uint) { - uint deposit = get("minParamDeposit"); - require(token.transferFrom(msg.sender, this, deposit)); // escrow tokens (deposit amt) - - uint pollID = voting.startPoll( - get("voteQuorum"), - get("commitPeriodLen"), - get("revealPeriodLen") - ); - - // attach name and value to pollID - proposalMap[pollID] = ParamProposal({ - name: name, - value: value, - owner: msg.sender, - deposit: deposit - }); - - return pollID; - } - - // updates canonical mapping with evaluation of poll result - function processProposal(uint pollID) { - ParamProposal storage prop = proposalMap[pollID]; - // check isPassed ==> update params mapping using set - if (voting.isPassed(pollID)) { - set(prop.name, prop.value); - } - // release escrowed tokens - require(token.transfer(prop.owner, prop.deposit)); - prop.deposit = 0; // prevent double-withdrawal - } -} +pragma solidity^0.4.11; + +import "./PLCRVoting.sol"; +import "./historical/StandardToken.sol"; + +contract Parameterizer { + + // ------ + // EVENTS + // ------ + + event _ReparameterizationProposal(address proposer, string name, uint value, bytes32 propID); + event _NewChallenge(address challenger, bytes32 propID, uint pollID); + + mapping(bytes32 => uint) public params; + + struct ParamProposal { + uint appExpiry; + uint challengeID; + uint deposit; + string name; + address owner; + uint processBy; + uint value; + } + + struct Challenge { + uint rewardPool; // (remaining) pool of tokens distributed amongst winning voters + address challenger; // owner of Challenge + bool resolved; // indication of if challenge is resolved + uint stake; // number of tokens at risk for either party during challenge + uint totalTokens; // (remaining) amount of tokens used for voting by the winning side + } + + // maps pollIDs to intended data change if poll passes + mapping(bytes32 => ParamProposal) public proposalMap; + + // maps challengeIDs to associated challenge data + mapping(uint => Challenge) public challengeMap; + + // maps challengeIDs and address to token claim data + mapping(uint => mapping(address => bool)) public tokenClaims; + + + // Global Variables + StandardToken public token; + PLCRVoting public voting; + uint public PROCESSBY = 604800; // 7 days + + /** + @dev constructor + @param _tokenAddr address of the token which parameterizes this system + @param _plcrAddr address of a PLCR voting contract for the provided token + @param _minDeposit minimum deposit for listing to be whitelisted + @param _pMinDeposit minimum deposit to propose a reparameterization + @param _applyStageLen period over which applicants wait to be whitelisted + @param _pApplyStageLen period over which reparmeterization proposals wait to be processed + @param _dispensationPct percentage of losing party's deposit distributed to winning party + @param _pDispensationPct percentage of losing party's deposit distributed to winning party in parameterizer + @param _commitStageLen length of commit period for voting + @param _pCommitStageLen length of commit period for voting in parameterizer + @param _revealStageLen length of reveal period for voting + @param _pRevealStageLen length of reveal period for voting in parameterizer + @param _voteQuorum type of majority out of 100 necessary for vote success + @param _pVoteQuorum type of majority out of 100 necessary for vote success in parameterizer + */ + function Parameterizer( + address _tokenAddr, + address _plcrAddr, + uint _minDeposit, + uint _pMinDeposit, + uint _applyStageLen, + uint _pApplyStageLen, + uint _commitStageLen, + uint _pCommitStageLen, + uint _revealStageLen, + uint _pRevealStageLen, + uint _dispensationPct, + uint _pDispensationPct, + uint _voteQuorum, + uint _pVoteQuorum + ) { + token = StandardToken(_tokenAddr); + voting = PLCRVoting(_plcrAddr); + + set("minDeposit", _minDeposit); + set("pMinDeposit", _pMinDeposit); + set("applyStageLen", _applyStageLen); + set("pApplyStageLen", _pApplyStageLen); + set("commitStageLen", _commitStageLen); + set("pCommitStageLen", _pCommitStageLen); + set("revealStageLen", _revealStageLen); + set("pRevealStageLen", _pRevealStageLen); + set("dispensationPct", _dispensationPct); + set("pDispensationPct", _pDispensationPct); + set("voteQuorum", _voteQuorum); + set("pVoteQuorum", _pVoteQuorum); + } + + // ----------------------- + // TOKEN HOLDER INTERFACE: + // ----------------------- + + /** + @notice propose a reparamaterization of the key _name's value to _value. + @param _name the name of the proposed param to be set + @param _value the proposed value to set the param to be set + */ + function proposeReparameterization(string _name, uint _value) public returns (bytes32) { + uint deposit = get("pMinDeposit"); + bytes32 propID = keccak256(_name, _value); + + require(!propExists(propID)); // Forbid duplicate proposals + require(get(_name) != _value); // Forbid NOOP reparameterizations + require(token.transferFrom(msg.sender, this, deposit)); // escrow tokens (deposit amt) + + // attach name and value to pollID + proposalMap[propID] = ParamProposal({ + appExpiry: now + get("pApplyStageLen"), + challengeID: 0, + deposit: deposit, + name: _name, + owner: msg.sender, + processBy: now + get("pApplyStageLen") + get("pCommitStageLen") + + get("pRevealStageLen") + PROCESSBY, + value: _value + }); + + _ReparameterizationProposal(msg.sender, _name, _value, propID); + return propID; + } + + /** + @notice challenge the provided proposal ID, and put tokens at stake to do so. + @param _propID the proposal ID to challenge + */ + function challengeReparameterization(bytes32 _propID) public returns (uint challengeID) { + ParamProposal memory prop = proposalMap[_propID]; + uint deposit = get("pMinDeposit"); + + require(propExists(_propID) && prop.challengeID == 0); + + //take tokens from challenger + require(token.transferFrom(msg.sender, this, deposit)); + //start poll + uint pollID = voting.startPoll( + get("pVoteQuorum"), + get("pCommitStageLen"), + get("pRevealStageLen") + ); + + challengeMap[pollID] = Challenge({ + challenger: msg.sender, + rewardPool: ((100 - get("pDispensationPct")) * deposit) / 100, + stake: deposit, + resolved: false, + totalTokens: 0 + }); + + proposalMap[_propID].challengeID = pollID; // update listing to store most recent challenge + + _NewChallenge(msg.sender, _propID, pollID); + return pollID; + } + + /** + @notice for the provided proposal ID, set it, resolve its challenge, or delete it depending on whether it can be set, has a challenge which can be resolved, or if its "process by" date has passed + @param _propID the proposal ID to make a determination and state transition for + */ + function processProposal(bytes32 _propID) public { + ParamProposal storage prop = proposalMap[_propID]; + + if (canBeSet(_propID)) { + set(prop.name, prop.value); + } else if (challengeCanBeResolved(_propID)) { + resolveChallenge(_propID); + } else if (now > prop.processBy) { + require(token.transfer(prop.owner, prop.deposit)); + } else { + revert(); + } + + delete proposalMap[_propID]; + } + + /** + @notice claim the tokens owed for the msg.sender in the provided challenge + @param _challengeID the challenge ID to claim tokens for + @param _salt the salt used to vote in the challenge being withdrawn for + */ + function claimReward(uint _challengeID, uint _salt) public { + // ensure voter has not already claimed tokens and challenge results have been processed + require(tokenClaims[_challengeID][msg.sender] == false); + require(challengeMap[_challengeID].resolved = true); + + uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID, _salt); + uint reward = calculateVoterReward(msg.sender, _challengeID, _salt); + + // subtract voter's information to preserve the participation ratios of other voters + // compared to the remaining pool of rewards + challengeMap[_challengeID].totalTokens -= voterTokens; + challengeMap[_challengeID].rewardPool -= reward; + + require(token.transfer(msg.sender, reward)); + + // ensures a voter cannot claim tokens again + tokenClaims[_challengeID][msg.sender] = true; + } + + // -------- + // GETTERS: + // -------- + + /** + @dev Calculate the provided voter's token reward for the given poll + @param _voter Address of the voter whose reward balance is to be returned + @param _challengeID pollID of the challenge a reward balance is being queried for + @param _salt the salt for the voter's commit hash in the given poll + @return a uint indicating the voter's reward in nano-adToken + */ + function calculateVoterReward(address _voter, uint _challengeID, uint _salt) + public constant returns (uint) { + uint totalTokens = challengeMap[_challengeID].totalTokens; + uint rewardPool = challengeMap[_challengeID].rewardPool; + uint voterTokens = voting.getNumPassingTokens(_voter, _challengeID, _salt); + return (voterTokens * rewardPool) / totalTokens; + } + + /** + @notice Determines whether a proposal passed its application stage without a challenge + @param _propID The proposal ID for which to determine whether its application stage passed without a challenge + */ + function canBeSet(bytes32 _propID) constant public returns (bool) { + ParamProposal memory prop = proposalMap[_propID]; + + return (now > prop.appExpiry && now < prop.processBy && prop.challengeID == 0); + } + + /** + @notice Determines whether a proposal exists for the provided proposal ID + @param _propID The proposal ID whose existance is to be determined + */ + function propExists(bytes32 _propID) constant public returns (bool) { + return proposalMap[_propID].processBy > 0; + } + + /** + @notice Determines whether the provided proposal ID has a challenge which can be resolved + @param _propID The proposal ID whose challenge to inspect + */ + function challengeCanBeResolved(bytes32 _propID) constant public returns (bool) { + ParamProposal memory prop = proposalMap[_propID]; + Challenge memory challenge = challengeMap[prop.challengeID]; + + return (prop.challengeID > 0 && challenge.resolved == false && + voting.pollEnded(prop.challengeID)); + } + + /** + @notice Determines the number of tokens to awarded to the winning party in a challenge + @param _challengeID The challengeID to determine a reward for + */ + function determineReward(uint _challengeID) public constant returns (uint) { + if(voting.getTotalNumberOfTokensForWinningOption(_challengeID) == 0) { + // Edge case, nobody voted, give all tokens to the winner. + return 2 * challengeMap[_challengeID].stake; + } + + return (2 * challengeMap[_challengeID].stake) - challengeMap[_challengeID].rewardPool; + } + + /** + @notice gets the parameter keyed by the provided name value from the params mapping + @param _name the key whose value is to be determined + */ + function get(string _name) public constant returns (uint value) { + return params[keccak256(_name)]; + } + + // ---------------- + // PRIVATE FUNCTIONS: + // ---------------- + + /** + @dev sets the param keted by the provided name to the provided value + @param _name the name of the param to be set + @param _value the value to set the param to be set + */ + function set(string _name, uint _value) private { + params[keccak256(_name)] = _value; + } + + /** + @dev resolves a challenge for the provided _propID. It must be checked in advance whether the _propID has a challenge on it + @param _propID the proposal ID whose challenge is to be resolved. + */ + function resolveChallenge(bytes32 _propID) private { + ParamProposal memory prop = proposalMap[_propID]; + + // set flag on challenge being processed + challengeMap[prop.challengeID].resolved = true; + + // winner gets back their full staked deposit, and dispensationPct*loser's stake + uint reward = determineReward(prop.challengeID); + + if (voting.isPassed(prop.challengeID)) { // The challenge failed + if(prop.processBy > now) { + set(prop.name, prop.value); + } + require(token.transfer(prop.owner, reward)); + } + else { // The challenge succeeded + require(token.transfer(challengeMap[prop.challengeID].challenger, reward)); + } + + // store the total tokens used for voting by the winning side for reward purposes + challengeMap[prop.challengeID].totalTokens = + voting.getTotalNumberOfTokensForWinningOption(prop.challengeID); + } +} + diff --git a/contracts/Registry.sol b/contracts/Registry.sol index 2d35960..2f55e4f 100644 --- a/contracts/Registry.sol +++ b/contracts/Registry.sol @@ -1,365 +1,366 @@ -pragma solidity ^0.4.11; - -import "./historical/StandardToken.sol"; -import "./PLCRVoting.sol"; -import "./Parameterizer.sol"; - -contract Registry { - - // ------ - // EVENTS - // ------ - - event _Application(string domain, uint deposit); - event _Challenge(string domain, uint deposit, uint pollID); - event _Deposit(string domain, uint added, uint newTotal); - event _Withdrawal(string domain, uint withdrew, uint newTotal); - event _NewDomainWhitelisted(string domain); - event _ApplicationRemoved(string domain); - event _ListingRemoved(string domain); - event _ChallengeFailed(uint challengeID); - event _ChallengeSucceeded(uint challengeID); - event _RewardClaimed(address voter, uint challengeID, uint reward); - - struct Listing { - uint applicationExpiry; // expiration date of apply stage - bool whitelisted; // indicates registry status - address owner; // owner of Listing - uint unstakedDeposit; // number of unlocked tokens with potential risk if challenged - uint challengeID; // identifier of canonical challenge - } - - struct Challenge { - uint rewardPool; // (remaining) pool of tokens distributed amongst winning voters - address challenger; // owner of Challenge - bool resolved; // indication of if challenge is resolved - uint stake; // number of tokens at risk for either party during challenge - uint totalTokens; // (remaining) amount of tokens used for voting by the winning side - } - - // maps challengeIDs to associated challenge data - mapping(uint => Challenge) public challengeMap; - // maps domainHashes to associated listing data - mapping(bytes32 => Listing) public listingMap; - // maps challengeIDs and address to token claim data - mapping(uint => mapping(address => bool)) public tokenClaims; - - // Global Variables - StandardToken public token; - PLCRVoting public voting; - Parameterizer public parameterizer; - - // ------------ - // CONSTRUCTOR: - // ------------ - - function Registry( - address _tokenAddr, - address _paramsAddr - ) { - token = StandardToken(_tokenAddr); - parameterizer = Parameterizer(_paramsAddr); - voting = new PLCRVoting(_tokenAddr); - } - - // -------------------- - // PUBLISHER INTERFACE: - // -------------------- - - //Allow a user to start an application - //take tokens from user and set apply stage end time - function apply(string domain, uint amount) external { - require(!isWhitelisted(domain)); - require(!appExists(domain)); - require(amount >= parameterizer.get("minDeposit")); - - //set owner - Listing storage listing = listingMap[sha3(domain)]; - listing.owner = msg.sender; - - //transfer tokens - require(token.transferFrom(listing.owner, this, amount)); - - //set apply stage end time - listing.applicationExpiry = block.timestamp + parameterizer.get("applyStageLen"); - listing.unstakedDeposit = amount; - - _Application(domain, amount); - } - - //Allow the owner of a domain in the listing to increase their deposit - function deposit(string domain, uint amount) external { - Listing storage listing = listingMap[sha3(domain)]; - - require(listing.owner == msg.sender); - require(token.transferFrom(msg.sender, this, amount)); - - listing.unstakedDeposit += amount; - - _Deposit(domain, amount, listing.unstakedDeposit); - } - - //Allow the owner of a domain in the listing to withdraw - //tokens not locked in a challenge (unstaked). - //The publisher's domain remains whitelisted - function withdraw(string domain, uint amount) external { - Listing storage listing = listingMap[sha3(domain)]; - - require(listing.owner == msg.sender); - require(amount <= listing.unstakedDeposit); - require(listing.unstakedDeposit - amount >= parameterizer.get("minDeposit")); - - require(token.transfer(msg.sender, amount)); - - listing.unstakedDeposit -= amount; - - _Withdrawal(domain, amount, listing.unstakedDeposit); - } - - //Allow the owner of a domain to remove the domain from the whitelist - //Return all tokens to the owner - function exit(string domain) external { - Listing storage listing = listingMap[sha3(domain)]; - - require(msg.sender == listing.owner); - require(isWhitelisted(domain)); - // cannot exit during ongoing challenge - require(listing.challengeID == 0 || challengeMap[listing.challengeID].resolved); - - //remove domain & return tokens - resetListing(domain); - } - - // ----------------------- - // TOKEN HOLDER INTERFACE: - // ----------------------- - - //start a poll for a domain in the apply stage or already on the whitelist - //tokens are taken from the challenger and the publisher's tokens are locked - function challenge(string domain) external returns (uint challengeID) { - bytes32 domainHash = sha3(domain); - Listing storage listing = listingMap[domainHash]; - //to be challenged, domain must be in apply stage or already on the whitelist - require(appExists(domain) || listing.whitelisted); - // prevent multiple challenges - require(listing.challengeID == 0 || challengeMap[listing.challengeID].resolved); - uint deposit = parameterizer.get("minDeposit"); - if (listing.unstakedDeposit < deposit) { - // not enough tokens, publisher auto-delisted - resetListing(domain); - return 0; - } - //take tokens from challenger - require(token.transferFrom(msg.sender, this, deposit)); - //start poll - uint pollID = voting.startPoll( - parameterizer.get("voteQuorum"), - parameterizer.get("commitPeriodLen"), - parameterizer.get("revealPeriodLen") - ); - - challengeMap[pollID] = Challenge({ - challenger: msg.sender, - rewardPool: ((100 - parameterizer.get("dispensationPct")) * deposit) / 100, - stake: deposit, - resolved: false, - totalTokens: 0 - }); - - listingMap[domainHash].challengeID = pollID; // update listing to store most recent challenge - listingMap[domainHash].unstakedDeposit -= deposit; // lock tokens for listing during challenge - - _Challenge(domain, deposit, pollID); - return pollID; - } - - /** - @notice updates a domain's status from application to listing, or resolves a challenge if one exists - @param _domain The domain whose status is being updated - */ - function updateStatus(string _domain) public { - if (canBeWhitelisted(_domain)) { - whitelistApplication(_domain); - _NewDomainWhitelisted(_domain); - } else if (challengeCanBeResolved(_domain)) { - resolveChallenge(_domain); - } else { - revert(); - } - } - - // ---------------- - // TOKEN FUNCTIONS: - // ---------------- - - // called by voter to claim reward for each completed vote - // someone must call updateStatus() before this can be called - function claimReward(uint _challengeID, uint _salt) public { - // ensure voter has not already claimed tokens and challenge results have been processed - require(tokenClaims[_challengeID][msg.sender] == false); - require(challengeMap[_challengeID].resolved = true); - - uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID, _salt); - uint reward = calculateVoterReward(msg.sender, _challengeID, _salt); - - // subtract voter's information to preserve the participation ratios of other voters - // compared to the remaining pool of rewards - challengeMap[_challengeID].totalTokens -= voterTokens; - challengeMap[_challengeID].rewardPool -= reward; - - require(token.transfer(msg.sender, reward)); - - // ensures a voter cannot claim tokens again - - tokenClaims[_challengeID][msg.sender] = true; - - _RewardClaimed(msg.sender, _challengeID, reward); - } - - /** - @dev Calculate the provided voter's token reward for the given poll - @param _voter Address of the voter whose reward balance is to be returned - @param _challengeID pollID of the challenge a reward balance is being queried for - @param _salt the salt for the voter's commit hash in the given poll - @return a uint indicating the voter's reward in nano-adToken - */ - function calculateVoterReward(address _voter, uint _challengeID, uint _salt) - public constant returns (uint) { - uint totalTokens = challengeMap[_challengeID].totalTokens; - uint rewardPool = challengeMap[_challengeID].rewardPool; - uint voterTokens = voting.getNumPassingTokens(_voter, _challengeID, _salt); - return (voterTokens * rewardPool) / totalTokens; - } - - // -------- - // GETTERS: - // -------- - - /** - @dev determines whether a domain is an application which can be whitelisted - @param _domain the domain whose status should be examined - */ - function canBeWhitelisted(string _domain) constant public returns (bool) { - bytes32 domainHash = sha3(_domain); - uint challengeID = listingMap[domainHash].challengeID; - - // TODO: change name of appExists to appWasMade. - if (appExists(_domain) && isExpired(listingMap[domainHash].applicationExpiry) && - !isWhitelisted(_domain) && - (challengeID == 0 || challengeMap[challengeID].resolved == true)) - { return true; } - - return false; - } - - //return true if domain is whitelisted - function isWhitelisted(string domain) constant public returns (bool whitelisted) { - return listingMap[sha3(domain)].whitelisted; - } - - //return true if apply(domain) was called for this domain - function appExists(string domain) constant public returns (bool exists) { - return listingMap[sha3(domain)].applicationExpiry > 0; - } - - // return true if the listing has an unresolved challenge - function challengeExists(string _domain) constant public returns (bool) { - bytes32 domainHash = sha3(_domain); - uint challengeID = listingMap[domainHash].challengeID; - - return (listingMap[domainHash].challengeID > 0 && !challengeMap[challengeID].resolved); - } - - /** - @notice determines whether voting has concluded in a challenge for a given domain. Throws if no challenge exists. - @param _domain a domain with an unresolved challenge - */ - function challengeCanBeResolved(string _domain) constant public returns (bool) { - bytes32 domainHash = sha3(_domain); - uint challengeID = listingMap[domainHash].challengeID; - - require(challengeExists(_domain)); - - return voting.pollEnded(challengeID); - } - - /** - @notice Determines the number of tokens to awarded to the winning party in a challenge - @param _challengeID The challengeID to determine a reward for - */ - function determineReward(uint _challengeID) public constant returns (uint) { - require(!challengeMap[_challengeID].resolved && voting.pollEnded(_challengeID)); - - if(voting.getTotalNumberOfTokensForWinningOption(_challengeID) == 0) { - // Edge case, nobody voted, give all tokens to the winner. - return 2 * challengeMap[_challengeID].stake; - } - - return (2 * challengeMap[_challengeID].stake) - challengeMap[_challengeID].rewardPool; - } - - //return true if termDate has passed - function isExpired(uint termDate) constant public returns (bool expired) { - return termDate < block.timestamp; - } - - //delete listing from whitelist and return tokens to owner - function resetListing(string domain) internal { - bytes32 domainHash = sha3(domain); - Listing storage listing = listingMap[domainHash]; - //transfer any remaining balance back to the owner - if (listing.unstakedDeposit > 0) - require(token.transfer(listing.owner, listing.unstakedDeposit)); - delete listingMap[domainHash]; - } - - // ---------------- - // PRIVATE FUNCTIONS: - // ---------------- - - /** - @dev determines the winner in a challenge, rewards them tokens, and either whitelists or de-whitelists the domain - @param _domain a domain with an unresolved challenge - */ - function resolveChallenge(string _domain) private { - bytes32 domainHash = sha3(_domain); - uint challengeID = listingMap[domainHash].challengeID; - - // winner gets back their full staked deposit, and dispensationPct*loser's stake - uint reward = determineReward(challengeID); - bool wasWhitelisted = isWhitelisted(_domain); - - if (voting.isPassed(challengeID)) { // The challenge failed - whitelistApplication(_domain); - listingMap[domainHash].unstakedDeposit += reward; // give stake back to applicant - - _ChallengeFailed(challengeID); - if (!wasWhitelisted) { _NewDomainWhitelisted(_domain); } - } - else { // The challenge succeeded - resetListing(_domain); - require(token.transfer(challengeMap[challengeID].challenger, reward)); - - _ChallengeSucceeded(challengeID); - if (wasWhitelisted) { _ListingRemoved(_domain); } - else { _ApplicationRemoved(_domain); } - } - - // set flag on challenge being processed - challengeMap[challengeID].resolved = true; - - // store the total tokens used for voting by the winning side for reward purposes - challengeMap[challengeID].totalTokens = - voting.getTotalNumberOfTokensForWinningOption(challengeID); - } - - /** - @dev Called by updateStatus if the applicationExpiry date passed without a challenge being made - @param _domain the domainHash to whitelist - */ - function whitelistApplication(string _domain) private { - bytes32 domainHash = sha3(_domain); - - listingMap[domainHash].whitelisted = true; - } -} +pragma solidity ^0.4.11; + +import "./historical/StandardToken.sol"; +import "./PLCRVoting.sol"; +import "./Parameterizer.sol"; + +contract Registry { + + // ------ + // EVENTS + // ------ + + event _Application(string domain, uint deposit); + event _Challenge(string domain, uint deposit, uint pollID); + event _Deposit(string domain, uint added, uint newTotal); + event _Withdrawal(string domain, uint withdrew, uint newTotal); + event _NewDomainWhitelisted(string domain); + event _ApplicationRemoved(string domain); + event _ListingRemoved(string domain); + event _ChallengeFailed(uint challengeID); + event _ChallengeSucceeded(uint challengeID); + event _RewardClaimed(address voter, uint challengeID, uint reward); + + struct Listing { + uint applicationExpiry; // expiration date of apply stage + bool whitelisted; // indicates registry status + address owner; // owner of Listing + uint unstakedDeposit; // number of unlocked tokens with potential risk if challenged + uint challengeID; // identifier of canonical challenge + } + + struct Challenge { + uint rewardPool; // (remaining) pool of tokens distributed amongst winning voters + address challenger; // owner of Challenge + bool resolved; // indication of if challenge is resolved + uint stake; // number of tokens at risk for either party during challenge + uint totalTokens; // (remaining) amount of tokens used for voting by the winning side + } + + // maps challengeIDs to associated challenge data + mapping(uint => Challenge) public challengeMap; + // maps domainHashes to associated listing data + mapping(bytes32 => Listing) public listingMap; + // maps challengeIDs and address to token claim data + mapping(uint => mapping(address => bool)) public tokenClaims; + + // Global Variables + StandardToken public token; + PLCRVoting public voting; + Parameterizer public parameterizer; + + // ------------ + // CONSTRUCTOR: + // ------------ + + function Registry( + address _tokenAddr, + address _plcrAddr, + address _paramsAddr + ) { + token = StandardToken(_tokenAddr); + parameterizer = Parameterizer(_paramsAddr); + voting = PLCRVoting(_plcrAddr); + } + + // -------------------- + // PUBLISHER INTERFACE: + // -------------------- + + //Allow a user to start an application + //take tokens from user and set apply stage end time + function apply(string domain, uint amount) external { + require(!isWhitelisted(domain)); + require(!appExists(domain)); + require(amount >= parameterizer.get("minDeposit")); + + //set owner + Listing storage listing = listingMap[sha3(domain)]; + listing.owner = msg.sender; + + //transfer tokens + require(token.transferFrom(listing.owner, this, amount)); + + //set apply stage end time + listing.applicationExpiry = block.timestamp + parameterizer.get("applyStageLen"); + listing.unstakedDeposit = amount; + + _Application(domain, amount); + } + + //Allow the owner of a domain in the listing to increase their deposit + function deposit(string domain, uint amount) external { + Listing storage listing = listingMap[sha3(domain)]; + + require(listing.owner == msg.sender); + require(token.transferFrom(msg.sender, this, amount)); + + listing.unstakedDeposit += amount; + + _Deposit(domain, amount, listing.unstakedDeposit); + } + + //Allow the owner of a domain in the listing to withdraw + //tokens not locked in a challenge (unstaked). + //The publisher's domain remains whitelisted + function withdraw(string domain, uint amount) external { + Listing storage listing = listingMap[sha3(domain)]; + + require(listing.owner == msg.sender); + require(amount <= listing.unstakedDeposit); + require(listing.unstakedDeposit - amount >= parameterizer.get("minDeposit")); + + require(token.transfer(msg.sender, amount)); + + listing.unstakedDeposit -= amount; + + _Withdrawal(domain, amount, listing.unstakedDeposit); + } + + //Allow the owner of a domain to remove the domain from the whitelist + //Return all tokens to the owner + function exit(string domain) external { + Listing storage listing = listingMap[sha3(domain)]; + + require(msg.sender == listing.owner); + require(isWhitelisted(domain)); + // cannot exit during ongoing challenge + require(listing.challengeID == 0 || challengeMap[listing.challengeID].resolved); + + //remove domain & return tokens + resetListing(domain); + } + + // ----------------------- + // TOKEN HOLDER INTERFACE: + // ----------------------- + + //start a poll for a domain in the apply stage or already on the whitelist + //tokens are taken from the challenger and the publisher's tokens are locked + function challenge(string domain) external returns (uint challengeID) { + bytes32 domainHash = sha3(domain); + Listing storage listing = listingMap[domainHash]; + //to be challenged, domain must be in apply stage or already on the whitelist + require(appExists(domain) || listing.whitelisted); + // prevent multiple challenges + require(listing.challengeID == 0 || challengeMap[listing.challengeID].resolved); + uint deposit = parameterizer.get("minDeposit"); + if (listing.unstakedDeposit < deposit) { + // not enough tokens, publisher auto-delisted + resetListing(domain); + return 0; + } + //take tokens from challenger + require(token.transferFrom(msg.sender, this, deposit)); + //start poll + uint pollID = voting.startPoll( + parameterizer.get("voteQuorum"), + parameterizer.get("commitStageLen"), + parameterizer.get("revealStageLen") + ); + + challengeMap[pollID] = Challenge({ + challenger: msg.sender, + rewardPool: ((100 - parameterizer.get("dispensationPct")) * deposit) / 100, + stake: deposit, + resolved: false, + totalTokens: 0 + }); + + listingMap[domainHash].challengeID = pollID; // update listing to store most recent challenge + listingMap[domainHash].unstakedDeposit -= deposit; // lock tokens for listing during challenge + + _Challenge(domain, deposit, pollID); + return pollID; + } + + /** + @notice updates a domain's status from application to listing, or resolves a challenge if one exists + @param _domain The domain whose status is being updated + */ + function updateStatus(string _domain) public { + if (canBeWhitelisted(_domain)) { + whitelistApplication(_domain); + _NewDomainWhitelisted(_domain); + } else if (challengeCanBeResolved(_domain)) { + resolveChallenge(_domain); + } else { + revert(); + } + } + + // ---------------- + // TOKEN FUNCTIONS: + // ---------------- + + // called by voter to claim reward for each completed vote + // someone must call updateStatus() before this can be called + function claimReward(uint _challengeID, uint _salt) public { + // ensure voter has not already claimed tokens and challenge results have been processed + require(tokenClaims[_challengeID][msg.sender] == false); + require(challengeMap[_challengeID].resolved = true); + + uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID, _salt); + uint reward = calculateVoterReward(msg.sender, _challengeID, _salt); + + // subtract voter's information to preserve the participation ratios of other voters + // compared to the remaining pool of rewards + challengeMap[_challengeID].totalTokens -= voterTokens; + challengeMap[_challengeID].rewardPool -= reward; + + require(token.transfer(msg.sender, reward)); + + // ensures a voter cannot claim tokens again + + tokenClaims[_challengeID][msg.sender] = true; + + _RewardClaimed(msg.sender, _challengeID, reward); + } + + /** + @dev Calculate the provided voter's token reward for the given poll + @param _voter Address of the voter whose reward balance is to be returned + @param _challengeID pollID of the challenge a reward balance is being queried for + @param _salt the salt for the voter's commit hash in the given poll + @return a uint indicating the voter's reward in nano-adToken + */ + function calculateVoterReward(address _voter, uint _challengeID, uint _salt) + public constant returns (uint) { + uint totalTokens = challengeMap[_challengeID].totalTokens; + uint rewardPool = challengeMap[_challengeID].rewardPool; + uint voterTokens = voting.getNumPassingTokens(_voter, _challengeID, _salt); + return (voterTokens * rewardPool) / totalTokens; + } + + // -------- + // GETTERS: + // -------- + + /** + @dev determines whether a domain is an application which can be whitelisted + @param _domain the domain whose status should be examined + */ + function canBeWhitelisted(string _domain) constant public returns (bool) { + bytes32 domainHash = sha3(_domain); + uint challengeID = listingMap[domainHash].challengeID; + + // TODO: change name of appExists to appWasMade. + if (appExists(_domain) && isExpired(listingMap[domainHash].applicationExpiry) && + !isWhitelisted(_domain) && + (challengeID == 0 || challengeMap[challengeID].resolved == true)) + { return true; } + + return false; + } + + //return true if domain is whitelisted + function isWhitelisted(string domain) constant public returns (bool whitelisted) { + return listingMap[sha3(domain)].whitelisted; + } + + //return true if apply(domain) was called for this domain + function appExists(string domain) constant public returns (bool exists) { + return listingMap[sha3(domain)].applicationExpiry > 0; + } + + // return true if the listing has an unresolved challenge + function challengeExists(string _domain) constant public returns (bool) { + bytes32 domainHash = sha3(_domain); + uint challengeID = listingMap[domainHash].challengeID; + + return (listingMap[domainHash].challengeID > 0 && !challengeMap[challengeID].resolved); + } + + /** + @notice determines whether voting has concluded in a challenge for a given domain. Throws if no challenge exists. + @param _domain a domain with an unresolved challenge + */ + function challengeCanBeResolved(string _domain) constant public returns (bool) { + bytes32 domainHash = sha3(_domain); + uint challengeID = listingMap[domainHash].challengeID; + + require(challengeExists(_domain)); + + return voting.pollEnded(challengeID); + } + + /** + @notice Determines the number of tokens to awarded to the winning party in a challenge + @param _challengeID The challengeID to determine a reward for + */ + function determineReward(uint _challengeID) public constant returns (uint) { + require(!challengeMap[_challengeID].resolved && voting.pollEnded(_challengeID)); + + if(voting.getTotalNumberOfTokensForWinningOption(_challengeID) == 0) { + // Edge case, nobody voted, give all tokens to the winner. + return 2 * challengeMap[_challengeID].stake; + } + + return (2 * challengeMap[_challengeID].stake) - challengeMap[_challengeID].rewardPool; + } + + //return true if termDate has passed + function isExpired(uint termDate) constant public returns (bool expired) { + return termDate < block.timestamp; + } + + //delete listing from whitelist and return tokens to owner + function resetListing(string domain) internal { + bytes32 domainHash = sha3(domain); + Listing storage listing = listingMap[domainHash]; + //transfer any remaining balance back to the owner + if (listing.unstakedDeposit > 0) + require(token.transfer(listing.owner, listing.unstakedDeposit)); + delete listingMap[domainHash]; + } + + // ---------------- + // PRIVATE FUNCTIONS: + // ---------------- + + /** + @dev determines the winner in a challenge, rewards them tokens, and either whitelists or de-whitelists the domain + @param _domain a domain with an unresolved challenge + */ + function resolveChallenge(string _domain) private { + bytes32 domainHash = sha3(_domain); + uint challengeID = listingMap[domainHash].challengeID; + + // winner gets back their full staked deposit, and dispensationPct*loser's stake + uint reward = determineReward(challengeID); + bool wasWhitelisted = isWhitelisted(_domain); + + if (voting.isPassed(challengeID)) { // The challenge failed + whitelistApplication(_domain); + listingMap[domainHash].unstakedDeposit += reward; // give stake back to applicant + + _ChallengeFailed(challengeID); + if (!wasWhitelisted) { _NewDomainWhitelisted(_domain); } + } + else { // The challenge succeeded + resetListing(_domain); + require(token.transfer(challengeMap[challengeID].challenger, reward)); + + _ChallengeSucceeded(challengeID); + if (wasWhitelisted) { _ListingRemoved(_domain); } + else { _ApplicationRemoved(_domain); } + } + + // set flag on challenge being processed + challengeMap[challengeID].resolved = true; + + // store the total tokens used for voting by the winning side for reward purposes + challengeMap[challengeID].totalTokens = + voting.getTotalNumberOfTokensForWinningOption(challengeID); + } + + /** + @dev Called by updateStatus if the applicationExpiry date passed without a challenge being made + @param _domain the domainHash to whitelist + */ + function whitelistApplication(string _domain) private { + bytes32 domainHash = sha3(_domain); + + listingMap[domainHash].whitelisted = true; + } +} diff --git a/migrations/3_deploy_contracts.js b/migrations/3_deploy_contracts.js index f5383e6..c304962 100644 --- a/migrations/3_deploy_contracts.js +++ b/migrations/3_deploy_contracts.js @@ -29,6 +29,15 @@ module.exports = (deployer, network, accounts) => { return approveRegistryFor(addresses.slice(1)); } + async function approveParameterizerFor(addresses) { + const token = Token.at(tokenAddress); + const user = addresses[0]; + const balanceOfUser = await token.balanceOf(user); + await token.approve(Parameterizer.address, balanceOfUser, { from: user }); + if (addresses.length === 1) { return true; } + return approveParameterizerFor(addresses.slice(1)); + } + async function approvePLCRFor(addresses) { const token = Token.at(tokenAddress); const registry = await Registry.deployed(); @@ -42,11 +51,12 @@ module.exports = (deployer, network, accounts) => { await buyTokensFor(accounts); await approveRegistryFor(accounts); + await approveParameterizerFor(accounts); await approvePLCRFor(accounts); } const adchainConfig = JSON.parse(fs.readFileSync('./conf/config.json')); - const parameterizerConfig = adchainConfig.RegistryDefaults; + const parameterizerConfig = adchainConfig.paramDefaults; let tokenAddress = adchainConfig.TokenAddress; deployer.deploy(DLL); @@ -66,26 +76,38 @@ module.exports = (deployer, network, accounts) => { const sale = await Sale.deployed(); tokenAddress = await sale.token.call(); } - return deployer.deploy(Parameterizer, + return deployer.deploy(PLCRVoting, tokenAddress, - parameterizerConfig.minDeposit, - parameterizerConfig.minParamDeposit, - parameterizerConfig.applyStageLength, - parameterizerConfig.commitPeriodLength, - parameterizerConfig.revealPeriodLength, - parameterizerConfig.dispensationPct, - parameterizerConfig.voteQuorum, ); }) .then(() => - deployer.deploy(Registry, + deployer.deploy(Parameterizer, tokenAddress, - Parameterizer.address, - ), - ) - .then(async () => { - if (network === 'development') { - await setupForTests(tokenAddress); - } - }).catch((err) => { throw err; }); + PLCRVoting.address, + parameterizerConfig.minDeposit, + parameterizerConfig.pMinDeposit, + parameterizerConfig.applyStageLength, + parameterizerConfig.pApplyStageLength, + parameterizerConfig.commitStageLength, + parameterizerConfig.pCommitStageLength, + parameterizerConfig.revealStageLength, + parameterizerConfig.pRevealStageLength, + parameterizerConfig.dispensationPct, + parameterizerConfig.pDispensationPct, + parameterizerConfig.voteQuorum, + parameterizerConfig.pVoteQuorum, + ) + .then(() => + deployer.deploy(Registry, + tokenAddress, + PLCRVoting.address, + Parameterizer.address, + ), + ) + .then(async () => { + if (network === 'development') { + await setupForTests(tokenAddress); + } + }).catch((err) => { throw err; }), + ); }; diff --git a/test/parameterizer.js b/test/parameterizer.js index 13b50f2..4afa660 100644 --- a/test/parameterizer.js +++ b/test/parameterizer.js @@ -1,156 +1,405 @@ /* eslint-env mocha */ /* global artifacts assert contract */ - -// const HttpProvider = require('ethjs-provider-http'); -// const EthRPC = require('ethjs-rpc'); - -// const ethRPC = new EthRPC(new HttpProvider('http://localhost:8545')); -// const abi = require('ethereumjs-abi'); - -// const PLCRVoting = artifacts.require('./PLCRVoting.sol'); const Parameterizer = artifacts.require('./Parameterizer.sol'); +const Token = artifacts.require('./historical/HumanStandardToken.sol'); const fs = require('fs'); +const BN = require('bignumber.js'); +const utils = require('./utils'); const adchainConfig = JSON.parse(fs.readFileSync('./conf/config.json')); -const paramConfig = adchainConfig.RegistryDefaults; +const paramConfig = adchainConfig.paramDefaults; + +const bigTen = number => new BN(number.toString(10), 10); +const getReceiptValue = (receipt, arg) => receipt.logs[0].args[arg]; + +contract('Parameterizer', (accounts) => { + describe('Function: proposeReparameterization', () => { + const [proposer, secondProposer] = accounts; + const pMinDeposit = bigTen(paramConfig.pMinDeposit); + + it('should add a new reparameterization proposal', async () => { + const parameterizer = await Parameterizer.deployed(); + const token = Token.at(await parameterizer.token.call()); + + const applicantStartingBalance = await token.balanceOf.call(proposer); + + const receipt = await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51', + ); + + const propID = getReceiptValue(receipt, 'propID'); + const paramProposal = await parameterizer.proposalMap.call(propID); + + assert.strictEqual(paramProposal[6].toString(10), '51', 'The reparameterization proposal ' + + 'was not created, or not created correctly.'); + + const applicantFinalBalance = await token.balanceOf.call(proposer); + const expected = applicantStartingBalance.sub(pMinDeposit); + assert.strictEqual(applicantFinalBalance.toString(10), expected.toString(10), + 'tokens were not properly transferred from proposer'); + }); + + it('should not allow a NOOP reparameterization', async () => { + const parameterizer = await Parameterizer.deployed(); + + try { + await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51', + ); + assert(false, 'Performed NOOP reparameterization'); + } catch (err) { + assert(utils.isEVMException(err), err.toString()); + } + }); + + it('should not allow a reparameterization for a proposal that already exists', async () => { + const parameterizer = await Parameterizer.deployed(); + const token = Token.at(await parameterizer.token.call()); + + const applicantStartingBalance = await token.balanceOf.call(secondProposer); + + try { + await utils.as( + secondProposer, parameterizer.proposeReparameterization, 'voteQuorum', '51', + ); + assert(false, 'should not have been able to make duplicate proposal'); + } catch (err) { + assert(utils.isEVMException(err), err.toString()); + } + + const applicantEndingBalance = await token.balanceOf.call(secondProposer); + + assert.strictEqual(applicantEndingBalance.toString(10), applicantStartingBalance.toString(10), 'starting balance and ' + + 'ending balance should have been equal'); + }); + }); +}); + +contract('Parameterizer', (accounts) => { + describe('Function: challengeReparameterization', () => { + const [proposer, challenger, voter] = accounts; + + it('should leave parameters unchanged if a proposal loses a challenge', async () => { + const parameterizer = await Parameterizer.deployed(); + const token = Token.at(await parameterizer.token.call()); + + const proposerStartingBalance = await token.balanceOf.call(proposer); + const challengerStartingBalance = await token.balanceOf.call(challenger); + + const receipt = await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51', + ); + + const propID = receipt.logs[0].args.propID; + + await utils.as(challenger, parameterizer.challengeReparameterization, propID); + + await utils.increaseTime( + paramConfig.pCommitStageLength + paramConfig.pRevealStageLength + 1, + ); + + await parameterizer.processProposal(propID); + + const voteQuorum = await parameterizer.get('voteQuorum'); + assert.strictEqual(voteQuorum.toString(10), '50', 'The proposal succeeded which ' + + 'should have been successfully challenged'); + + const proposerFinalBalance = await token.balanceOf.call(proposer); + const proposerExpected = proposerStartingBalance.sub(new BN(paramConfig.pMinDeposit, 10)); + assert.strictEqual(proposerFinalBalance.toString(10), proposerExpected.toString(10), + 'The challenge loser\'s token balance is not as expected'); + + // Edge case, challenger gets both deposits back because there were no voters + const challengerFinalBalance = await token.balanceOf.call(challenger); + const challengerExpected = challengerStartingBalance.add(new BN(paramConfig.pMinDeposit, 10)); + assert.strictEqual(challengerFinalBalance.toString(10), challengerExpected.toString(10), + 'The challenge winner\'s token balance is not as expected'); + }); + + it('should set new parameters if a proposal wins a challenge', async () => { + const parameterizer = await Parameterizer.deployed(); + const token = Token.at(await parameterizer.token.call()); + const voting = await utils.getVoting(); + + const proposerStartingBalance = await token.balanceOf.call(proposer); + const challengerStartingBalance = await token.balanceOf.call(challenger); + + const proposalReceipt = await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51', + ); + + const propID = proposalReceipt.logs[0].args.propID; + + const challengeReceipt = + await utils.as(challenger, parameterizer.challengeReparameterization, propID); + + const challengeID = challengeReceipt.logs[0].args.pollID; + + await utils.commitVote(challengeID, '1', '10', '420', voter); + await utils.increaseTime(paramConfig.pCommitStageLength + 1); + + await utils.as(voter, voting.revealVote, challengeID, '1', '420'); + await utils.increaseTime(paramConfig.pRevealStageLength + 1); + + await parameterizer.processProposal(propID); + + const voteQuorum = await parameterizer.get('voteQuorum'); + assert.strictEqual(voteQuorum.toString(10), '51', 'The proposal failed which ' + + 'should have succeeded'); + + const proposerFinalBalance = await token.balanceOf.call(proposer); + const proposerExpected = proposerStartingBalance.add( + new BN(paramConfig.pMinDeposit, 10).mul( + new BN(paramConfig.pDispensationPct, 10).div(new BN('100', 10)), + ), + ); + assert.strictEqual(proposerFinalBalance.toString(10), proposerExpected.toString(10), + 'The challenge winner\'s token balance is not as expected'); + + const challengerFinalBalance = await token.balanceOf.call(challenger); + const challengerExpected = challengerStartingBalance.sub(new BN(paramConfig.pMinDeposit, 10)); + assert.strictEqual(challengerFinalBalance.toString(10), challengerExpected.toString(10), + 'The challenge loser\'s token balance is not as expected'); + }); + }); +}); + +contract('Parameterizer', (accounts) => { + describe('Function: processProposal', () => { + const [proposer, challenger, voter] = accounts; + + it('should set new parameters if a proposal went unchallenged', async () => { + const parameterizer = await Parameterizer.deployed(); + + const receipt = await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51', + ); + + await utils.increaseTime( + paramConfig.pApplyStageLength + 1, + ); + + const propID = receipt.logs[0].args.propID; + await parameterizer.processProposal(propID); + + const voteQuorum = await parameterizer.get.call('voteQuorum'); + assert.strictEqual(voteQuorum.toString(10), '51', + 'A proposal which went unchallenged failed to update its parameter', + ); + }); + + it('should not set new parameters if a proposal\'s processBy date has passed', async () => { + const parameterizer = await Parameterizer.deployed(); + + const receipt = await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '69', + ); + + const propID = receipt.logs[0].args.propID; + const paramProp = await parameterizer.proposalMap.call(propID); + const processBy = paramProp[5]; + await utils.increaseTime(processBy.toNumber() + 1); + + await parameterizer.processProposal(propID); + + const voteQuorum = await parameterizer.get.call('voteQuorum'); + assert.strictEqual(voteQuorum.toString(10), '51', + 'A proposal whose processBy date passed was able to update the parameterizer', + ); + }); + + it('should not set new parameters if a proposal\'s processBy date has passed, ' + + 'but should resolve any challenges against the domain', async () => { + const parameterizer = await Parameterizer.deployed(); + const token = Token.at(await parameterizer.token.call()); + const voting = await utils.getVoting(); + + const proposerStartingBalance = await token.balanceOf.call(proposer); + const challengerStartingBalance = await token.balanceOf.call(challenger); + + const receipt = await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '69', + ); + + const propID = receipt.logs[0].args.propID; + + const challengeReceipt = + await utils.as(challenger, parameterizer.challengeReparameterization, propID); + + const pollID = challengeReceipt.logs[0].args.pollID; + await utils.commitVote(pollID, '0', '10', '420', voter); + await utils.increaseTime(paramConfig.pCommitStageLength + 1); + + await utils.as(voter, voting.revealVote, pollID, '0', '420'); + + const paramProp = await parameterizer.proposalMap.call(propID); + const processBy = paramProp[5]; + await utils.increaseTime(processBy.toNumber() + 1); + + await parameterizer.processProposal(propID); + + const voteQuorum = await parameterizer.get.call('voteQuorum'); + assert.strictEqual(voteQuorum.toString(10), '51', + 'A proposal whose processBy date passed was able to update the parameterizer', + ); + + const proposerFinalBalance = await token.balanceOf.call(proposer); + const proposerExpected = proposerStartingBalance.sub(new BN(paramConfig.pMinDeposit, 10)); + assert.strictEqual(proposerFinalBalance.toString(10), proposerExpected.toString(10), + 'The challenge loser\'s token balance is not as expected', + ); + + const challengerFinalBalance = await token.balanceOf.call(challenger); + const challengerExpected = challengerStartingBalance.add( + new BN(paramConfig.pMinDeposit, 10).mul( + new BN(paramConfig.pDispensationPct, 10).div(new BN('100', 10)), + ), + ); + assert.strictEqual(challengerFinalBalance.toString(10), challengerExpected.toString(10), + 'The challenge winner\'s token balance is not as expected'); + }); + }); +}); + +contract('Parameterizer', (accounts) => { + describe('Function: claimReward', () => { + const [proposer, challenger, voterAlice, voterBob] = accounts; + + it('should give the correct number of tokens to a voter on the winning side.', async () => { + const parameterizer = await Parameterizer.deployed(); + const token = Token.at(await parameterizer.token.call()); + const voting = await utils.getVoting(); + + const voterAliceStartingBalance = await token.balanceOf.call(voterAlice); + + const proposalReceipt = await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '51', + ); + + const propID = proposalReceipt.logs[0].args.propID; + + const challengeReceipt = + await utils.as(challenger, parameterizer.challengeReparameterization, propID); + + const challengeID = challengeReceipt.logs[0].args.pollID; + + await utils.commitVote(challengeID, '1', '10', '420', voterAlice); + await utils.increaseTime(paramConfig.pCommitStageLength + 1); + + await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); + await utils.increaseTime(paramConfig.pRevealStageLength + 1); + + await parameterizer.processProposal(propID); + + await utils.as(voterAlice, parameterizer.claimReward, challengeID, '420'); + await utils.as(voterAlice, voting.withdrawVotingRights, '10'); + + const voterAliceFinalBalance = await token.balanceOf.call(voterAlice); + const voterAliceExpected = voterAliceStartingBalance.add( + new BN(paramConfig.pMinDeposit, 10).mul((new BN('100', 10).sub( + new BN(paramConfig.pDispensationPct, 10))).div(new BN('100', 10)), + ), + ); + assert.strictEqual(voterAliceFinalBalance.toString(10), voterAliceExpected.toString(10), + 'A voterAlice\'s token balance is not as expected after claiming a reward'); + }); + + it('should give the correct number of tokens to multiple voters on the winning side.', + async () => { + const parameterizer = await Parameterizer.deployed(); + // const token = Token.at(await parameterizer.token.call()); + const voting = await utils.getVoting(); + + // const voterAliceStartingBalance = await token.balanceOf.call(voterAlice); + // const voterBobStartingBalance = await token.balanceOf.call(voterBob); + + const proposalReceipt = await utils.as( + proposer, parameterizer.proposeReparameterization, 'voteQuorum', '52', + ); + + const propID = proposalReceipt.logs[0].args.propID; + + const challengeReceipt = + await utils.as(challenger, parameterizer.challengeReparameterization, propID); + + const challengeID = challengeReceipt.logs[0].args.pollID; + + await utils.commitVote(challengeID, '1', '10', '420', voterAlice); + await utils.commitVote(challengeID, '1', '20', '420', voterBob); + await utils.increaseTime(paramConfig.pCommitStageLength + 1); + + await utils.as(voterAlice, voting.revealVote, challengeID, '1', '420'); + await utils.as(voterBob, voting.revealVote, challengeID, '1', '420'); + await utils.increaseTime(paramConfig.pRevealStageLength + 1); + + await parameterizer.processProposal(propID); + + const voterAliceReward = await parameterizer.calculateVoterReward.call(voterAlice, + challengeID, '420'); + await utils.as(voterAlice, parameterizer.claimReward, challengeID, '420'); + await utils.as(voterAlice, voting.withdrawVotingRights, '10'); + + const voterBobReward = await parameterizer.calculateVoterReward.call(voterBob, + challengeID, '420'); + await utils.as(voterBob, parameterizer.claimReward, challengeID, '420'); + await utils.as(voterBob, voting.withdrawVotingRights, '20'); + + // TODO: do better than approximately. + assert.approximately( + voterBobReward.toNumber(10), + voterAliceReward.mul(new BN('2', 10)).toNumber(10), + 1, + 'Rewards were not properly distributed between voters', + ); + // TODO: add asserts for final balances + }); + + it('should give zero tokens to a voter who cannot reveal a vote on the winning side.'); + }); +}); + +contract('Parameterizer', () => { + describe('Function: calculateVoterReward', () => { + it('should return the correct number of tokens to voter on the winning side.'); + it('should return zero tokens to a voter who cannot reveal a vote on the winning side.'); + }); +}); + +contract('Parameterizer', () => { + describe('Function: canBeSet', () => { + it('should true if a proposal passed its application stage with no challenge'); + it('should false if a proposal did not pass its application stage with no challenge'); + }); +}); + +contract('Parameterizer', () => { + describe('Function: propExists', () => { + it('should true if a proposal exists for the provided propID'); + it('should false if no proposal exists for the provided propID'); + }); +}); contract('Parameterizer', () => { - /* - async function increaseTime(seconds) { - return new Promise((resolve, reject) => ethRPC.sendAsync({ - method: 'evm_increaseTime', - params: [seconds], - }, (err) => { - if (err) reject(err); - resolve(); - })) - .then(() => new Promise((resolve, reject) => ethRPC.sendAsync({ - method: 'evm_mine', - params: [], - }, (err) => { - if (err) reject(err); - resolve(); - }))); - } - - async function getParamVoting() { - const param = await Parameterizer.deployed(); - const votingAddr = await param.voting.call(); - const voting = await PLCRVoting.at(votingAddr); - return voting; - } - - function getSecretHash(vote, salt) { - return `0x${abi.soliditySHA3(['uint', 'uint'], - [vote, salt]).toString('hex')}`; - } - */ - - it('should get a parameter', async () => { - const param = await Parameterizer.deployed(); - const result = await param.get.call('minDeposit'); - assert.equal(result, paramConfig.minDeposit, 'minDeposit param has wrong value'); + describe('Function: challengeCanBeResolved', () => { + it('should true if a challenge is ready to be resolved'); + it('should false if a challenge is not ready to be resolved'); }); +}); - /* - it('should fail to change parameter', async () => { - const param = await Parameterizer.deployed(); - const voting = await getParamVoting(); - const salt = 1; - const voteOption = 0; - - // changeParameter() - let result = await param.changeParameter('minDeposit', 20, { from: accounts[1] }); - const pollID = result.receipt.logs[1].data; - const hash = getSecretHash(voteOption, salt); - - // vote against with accounts[1:3] - - // commit - const tokensArg = 10; - const cpa = await voting.commitPeriodActive.call(pollID); - assert.equal(cpa, true, 'commit period should be active'); - - await voting.commitVote(pollID, hash, tokensArg, pollID - 1, { from: accounts[1] }); - let numTokens = await voting.getNumTokens(pollID, { from: accounts[1] }); - assert.equal(numTokens, tokensArg, 'wrong num tok committed'); - - await voting.commitVote(pollID, hash, tokensArg, pollID - 1, { from: accounts[2] }); - numTokens = await voting.getNumTokens(pollID, { from: accounts[2] }); - assert.equal(numTokens, tokensArg, 'wrong num tok committed'); - - // inc time - await increaseTime(paramConfig.commitPeriodLength + 1); - let rpa = await voting.revealPeriodActive.call(pollID); - assert.equal(rpa, true, 'reveal period should be active'); - - // reveal - await voting.revealVote(pollID, salt, voteOption, { from: accounts[1] }); - await voting.revealVote(pollID, salt, voteOption, { from: accounts[2] }); - - // inc time - await increaseTime(paramConfig.commitPeriodLength + 1); - rpa = await voting.revealPeriodActive.call(pollID); - assert.equal(rpa, false, 'reveal period should not be active'); - - // processProposal - const pollResult = await voting.isPassed.call(pollID); - assert.equal(pollResult, false, 'poll should not have passed'); - await param.processProposal(pollID); - // should be no change to params - result = await param.get.call('minDeposit'); - assert.equal(result.toString(10), paramConfig.minDeposit, 'minDeposit should not change'); +contract('Parameterizer', () => { + describe('Function: determineReward', () => { + it('should return the correct number of tokens to be granted to the winning entity in a challenge.'); }); +}); - it('should change parameter', async () => { - const param = await Parameterizer.deployed(); - const voting = await getParamVoting(); - const salt = 1; - const voteOption = 1; - - // changeParameter() - const newMinDeposit = 20; - let result = await param.changeParameter('minDeposit', newMinDeposit, { from: accounts[1] }); - const pollID = result.receipt.logs[1].data; - const hash = getSecretHash(voteOption, salt); - - // vote for with accounts[1:3] - - // commit - const tokensArg = 10; - const cpa = await voting.commitPeriodActive.call(pollID); - assert.equal(cpa, true, 'commit period should be active'); - - await voting.commitVote(pollID, hash, tokensArg, pollID - 1, { from: accounts[1] }); - let numTokens = await voting.getNumTokens(pollID, { from: accounts[1] }); - assert.equal(numTokens, tokensArg, 'wrong num tok committed'); - - await voting.commitVote(pollID, hash, tokensArg, pollID - 1, { from: accounts[2] }); - numTokens = await voting.getNumTokens(pollID, { from: accounts[2] }); - assert.equal(numTokens, tokensArg, 'wrong num tok committed'); - - // inc time - await increaseTime(paramConfig.commitPeriodLength + 1); - let rpa = await voting.revealPeriodActive.call(pollID); - assert.equal(rpa, true, 'reveal period should be active'); - - // reveal - await voting.revealVote(pollID, salt, voteOption, { from: accounts[1] }); - await voting.revealVote(pollID, salt, voteOption, { from: accounts[2] }); - - // inc time - await increaseTime(paramConfig.commitPeriodLength + 1); - rpa = await voting.revealPeriodActive.call(pollID); - assert.equal(rpa, false, 'reveal period should not be active'); - - // processProposal - const pollResult = await voting.isPassed.call(pollID); - assert.equal(pollResult, true, 'poll should not have passed'); - await param.processProposal(pollID); - // should be no change to params - result = await param.get.call('minDeposit'); - assert.equal(result.toString(10), newMinDeposit, 'minDeposit should not change'); +contract('Parameterizer', () => { + describe('Function: get', () => { + it('should get a parameter', async () => { + const param = await Parameterizer.deployed(); + const result = await param.get.call('minDeposit'); + assert.equal(result, paramConfig.minDeposit, 'minDeposit param has wrong value'); + }); }); - */ }); + diff --git a/test/registry.js b/test/registry.js index b063385..ecd7737 100644 --- a/test/registry.js +++ b/test/registry.js @@ -7,7 +7,7 @@ const fs = require('fs'); const BN = require('bignumber.js'); const adchainConfig = JSON.parse(fs.readFileSync('./conf/config.json')); -const paramConfig = adchainConfig.RegistryDefaults; +const paramConfig = adchainConfig.paramDefaults; const utils = require('./utils.js'); @@ -181,7 +181,7 @@ contract('Registry', (accounts) => { await utils.as(applicant, registry.apply, domain, minDeposit); await utils.as(challenger, registry.challenge, domain); - const plcrComplete = paramConfig.revealPeriodLength + paramConfig.commitPeriodLength + 1; + const plcrComplete = paramConfig.revealStageLength + paramConfig.commitStageLength + 1; await utils.increaseTime(plcrComplete); await registry.updateStatus(domain); @@ -341,7 +341,7 @@ contract('Registry', (accounts) => { await utils.as(applicant, registry.apply, domain, paramConfig.minDeposit); await utils.challengeAndGetPollID(domain, challenger); await utils.increaseTime( - paramConfig.commitPeriodLength + paramConfig.revealPeriodLength + 1, + paramConfig.commitStageLength + paramConfig.revealStageLength + 1, ); await registry.updateStatus(domain); @@ -368,7 +368,7 @@ contract('Registry', (accounts) => { await utils.challengeAndGetPollID(domain, challenger); await utils.increaseTime( - paramConfig.commitPeriodLength + paramConfig.revealPeriodLength + 1, + paramConfig.commitStageLength + paramConfig.revealStageLength + 1, ); await registry.updateStatus(domain); @@ -393,9 +393,9 @@ contract('Registry', (accounts) => { await utils.as(applicant, registry.apply, domain, minDeposit); const pollID = await utils.challengeAndGetPollID(domain, challenger); await utils.commitVote(pollID, 1, 10, 420, voter); - await utils.increaseTime(paramConfig.commitPeriodLength + 1); + await utils.increaseTime(paramConfig.commitStageLength + 1); await utils.as(voter, voting.revealVote, pollID, 1, 420); - await utils.increaseTime(paramConfig.revealPeriodLength + 1); + await utils.increaseTime(paramConfig.revealStageLength + 1); await registry.updateStatus(domain); const isWhitelisted = await registry.isWhitelisted.call(domain); @@ -419,9 +419,9 @@ contract('Registry', (accounts) => { const pollID = await utils.challengeAndGetPollID(domain, challenger); await utils.commitVote(pollID, 1, 10, 420, voter); - await utils.increaseTime(paramConfig.commitPeriodLength + 1); + await utils.increaseTime(paramConfig.commitStageLength + 1); await utils.as(voter, voting.revealVote, pollID, 1, 420); - await utils.increaseTime(paramConfig.revealPeriodLength + 1); + await utils.increaseTime(paramConfig.revealStageLength + 1); await registry.updateStatus(domain); const isWhitelisted = await registry.isWhitelisted.call(domain); @@ -504,7 +504,7 @@ contract('Registry', (accounts) => { ); // Clean up state, remove consensys.net (it fails its challenge due to draw) - await utils.increaseTime(paramConfig.commitPeriodLength + paramConfig.revealPeriodLength + 1); + await utils.increaseTime(paramConfig.commitStageLength + paramConfig.revealStageLength + 1); await registry.updateStatus(domain); }); @@ -544,7 +544,7 @@ contract('Registry', (accounts) => { // challenge with accounts[1] await registry.challenge(domain, { from: challenger }); - await utils.increaseTime(paramConfig.revealPeriodLength + paramConfig.commitPeriodLength + 1); + await utils.increaseTime(paramConfig.revealStageLength + paramConfig.commitStageLength + 1); await registry.updateStatus(domain); // should not have been added to whitelist @@ -563,7 +563,7 @@ contract('Registry', (accounts) => { const pollID = await utils.challengeAndGetPollID(domain, challenger); // Make sure it's cool to commit - const cpa = await voting.commitPeriodActive.call(pollID); + const cpa = await voting.commitStageActive.call(pollID); assert.strictEqual(cpa, true, 'Commit period should be active'); // Virgin commit @@ -576,19 +576,19 @@ contract('Registry', (accounts) => { assert.strictEqual(numTokens.toString(10), tokensArg.toString(10), 'Should have committed the correct number of tokens'); // Reveal - await utils.increaseTime(paramConfig.commitPeriodLength + 1); + await utils.increaseTime(paramConfig.commitStageLength + 1); // Make sure commit period is inactive - const commitPeriodActive = await voting.commitPeriodActive.call(pollID); - assert.strictEqual(commitPeriodActive, false, 'Commit period should be inactive'); + const commitStageActive = await voting.commitStageActive.call(pollID); + assert.strictEqual(commitStageActive, false, 'Commit period should be inactive'); // Make sure reveal period is active - let rpa = await voting.revealPeriodActive.call(pollID); + let rpa = await voting.revealStageActive.call(pollID); assert.strictEqual(rpa, true, 'Reveal period should be active'); await voting.revealVote(pollID, voteOption, salt, { from: voter }); // End reveal period - await utils.increaseTime(paramConfig.revealPeriodLength + 1); - rpa = await voting.revealPeriodActive.call(pollID); + await utils.increaseTime(paramConfig.revealStageLength + 1); + rpa = await voting.revealStageActive.call(pollID); assert.strictEqual(rpa, false, 'Reveal period should not be active'); // updateStatus diff --git a/test/utils.js b/test/utils.js index e2c7932..7eb1e3a 100644 --- a/test/utils.js +++ b/test/utils.js @@ -14,7 +14,7 @@ const Registry = artifacts.require('Registry.sol'); const Token = artifacts.require('historical/Token.sol'); const adchainConfig = JSON.parse(fs.readFileSync('./conf/config.json')); -const paramConfig = adchainConfig.RegistryDefaults; +const paramConfig = adchainConfig.paramDefaults; const utils = { getVoting: async () => { @@ -22,6 +22,7 @@ const utils = { const votingAddr = await registry.voting.call(); return PLCRVoting.at(votingAddr); }, + increaseTime: async seconds => new Promise((resolve, reject) => ethRPC.sendAsync({ method: 'evm_increaseTime', @@ -37,28 +38,34 @@ const utils = { if (err) reject(err); resolve(); }))), + getVoteSaltHash: (vote, salt) => ( `0x${abi.soliditySHA3(['uint', 'uint'], [vote, salt]).toString('hex')}` ), + getDomainHash: domain => ( `0x${abi.soliditySHA3(['string'], [domain]).toString('hex')}` ), + buyTokens: async (address, etherAmount) => { const sale = await Sale.deployed(); await sale.purchaseTokens({ from: address, value: etherAmount }); }, + approvePLCR: async (address, adtAmount) => { const registry = await Registry.deployed(); const plcrAddr = await registry.voting.call(); const token = Token.at(await registry.token.call()); await token.approve(plcrAddr, adtAmount, { from: address }); }, + addToWhitelist: async (domain, deposit, actor) => { const registry = await Registry.deployed(); await utils.as(actor, registry.apply, domain, deposit); await utils.increaseTime(paramConfig.applyStageLength + 1); await utils.as(actor, registry.updateStatus, domain); }, + as: (actor, fn, ...args) => { function detectSendObject(potentialSendObj) { function hasOwnProperty(obj, prop) { @@ -82,9 +89,11 @@ const utils = { const sendObject = { from: actor }; return fn(...args, sendObject); }, + isEVMException: err => ( err.toString().includes('invalid opcode') ), + getUnstakedDeposit: async (domain) => { const registry = await Registry.deployed(); // hash the domain so we can identify in listingMap @@ -95,11 +104,13 @@ const utils = { const unstakedDeposit = await listing[3]; return unstakedDeposit.toString(); }, + challengeAndGetPollID: async (domain, actor) => { const registry = await Registry.deployed(); const receipt = await utils.as(actor, registry.challenge, domain); return receipt.logs[0].args.pollID; }, + commitVote: async (pollID, voteOption, tokensArg, salt, voter) => { const voting = await utils.getVoting(); const hash = utils.getVoteSaltHash(voteOption, salt);