diff --git a/contracts/validator/contract/XDCValidator.sol b/contracts/validator/contract/XDCValidator.sol new file mode 100644 index 0000000000..e35159507a --- /dev/null +++ b/contracts/validator/contract/XDCValidator.sol @@ -0,0 +1,339 @@ +pragma solidity ^0.4.21; + +// This contract is under development. +// Refer to readme for further details. + + +library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + // uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return a / b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} + + + +contract XDCValidator { + using SafeMath for uint256; + + event Vote(address _voter, address _candidate, uint256 _cap); + event Unvote(address _voter, address _candidate, uint256 _cap); + event Propose(address _owner, address _candidate, uint256 _cap); + event Resign(address _owner, address _candidate); + event Withdraw(address _owner, uint256 _blockNumber, uint256 _cap); + + struct ValidatorState { + address owner; + bool isCandidate; + uint256 cap; + mapping(address => uint256) voters; + } + + struct WithdrawState { + mapping(uint256 => uint256) caps; + uint256[] blockNumbers; + } + + mapping(address => WithdrawState) withdrawsState; + + mapping(address => ValidatorState) validatorsState; + mapping(address => address[]) voters; + + // Mapping structures added for KYC feature. + mapping(address => string) public KYCString; + mapping(address => uint) public invalidKYCCount; + mapping(address => mapping(address => bool)) public hasVotedInvalid; + mapping(address => address[]) public ownerToCandidate; + address[] public owners; + + address[] public candidates; + + uint256 public candidateCount = 0; + uint256 public minCandidateCap; + uint256 public minVoterCap; + uint256 public maxValidatorNumber; + uint256 public candidateWithdrawDelay; + uint256 public voterWithdrawDelay; + + modifier onlyValidCandidateCap { + // anyone can deposit X XDC to become a candidate + require(msg.value >= minCandidateCap); + _; + } + + modifier onlyValidVoterCap { + + require(msg.value >= minVoterCap); + _; + } + + modifier onlyKYCWhitelisted { + if(bytes(KYCString[msg.sender]).length != 0) + {_;} + else{ + if (ownerToCandidate[msg.sender].length > 0) + {_;} + } + } + + modifier onlyOwner(address _candidate) { + require(validatorsState[_candidate].owner == msg.sender); + _; + } + + modifier onlyCandidate(address _candidate) { + require(validatorsState[_candidate].isCandidate); + _; + } + + modifier onlyValidCandidate (address _candidate) { + require(validatorsState[_candidate].isCandidate); + _; + } + + modifier onlyNotCandidate (address _candidate) { + require(!validatorsState[_candidate].isCandidate); + _; + } + + modifier onlyValidVote (address _candidate, uint256 _cap) { + require(validatorsState[_candidate].voters[msg.sender] >= _cap); + if (validatorsState[_candidate].owner == msg.sender) { + require(validatorsState[_candidate].voters[msg.sender].sub(_cap) >= minCandidateCap); + } + _; + } + + modifier onlyValidWithdraw (uint256 _blockNumber, uint _index) { + require(_blockNumber > 0); + require(block.number >= _blockNumber); + require(withdrawsState[msg.sender].caps[_blockNumber] > 0); + require(withdrawsState[msg.sender].blockNumbers[_index] == _blockNumber); + _; + } + + function XDCValidator ( + address[] _candidates, + uint256[] _caps, + address _firstOwner, + uint256 _minCandidateCap, + uint256 _minVoterCap, + uint256 _maxValidatorNumber, + uint256 _candidateWithdrawDelay, + uint256 _voterWithdrawDelay + ) public { + minCandidateCap = _minCandidateCap; + minVoterCap = _minVoterCap; + maxValidatorNumber = _maxValidatorNumber; + candidateWithdrawDelay = _candidateWithdrawDelay; + voterWithdrawDelay = _voterWithdrawDelay; + candidateCount = _candidates.length; + owners.push(_firstOwner); + for (uint256 i = 0; i < _candidates.length; i++) { + candidates.push(_candidates[i]); + validatorsState[_candidates[i]] = ValidatorState({ + owner: _firstOwner, + isCandidate: true, + cap: _caps[i] + }); + voters[_candidates[i]].push(_firstOwner); + ownerToCandidate[_firstOwner].push(_candidates[i]); + validatorsState[_candidates[i]].voters[_firstOwner] = minCandidateCap; + } + } + + + // uploadKYC : anyone can upload a KYC; its not equivalent to becoming an owner. + function uploadKYC(string kychash) external { + require(bytes(KYCString[msg.sender]).length==0); + KYCString[msg.sender]=kychash; + } + + // propose : any non-candidate who has uploaded its KYC can become an owner by proposing a candidate. + function propose(address _candidate) external payable onlyValidCandidateCap onlyKYCWhitelisted onlyNotCandidate(_candidate) { + uint256 cap = validatorsState[_candidate].cap.add(msg.value); + candidates.push(_candidate); + validatorsState[_candidate] = ValidatorState({ + owner: msg.sender, + isCandidate: true, + cap: cap + }); + validatorsState[_candidate].voters[msg.sender] = validatorsState[_candidate].voters[msg.sender].add(msg.value); + candidateCount = candidateCount.add(1); + if (ownerToCandidate[msg.sender].length ==0){ + owners.push(msg.sender); + + } + ownerToCandidate[msg.sender].push(_candidate); + voters[_candidate].push(msg.sender); + emit Propose(msg.sender, _candidate, msg.value); + } + + function vote(address _candidate) external payable onlyValidVoterCap onlyValidCandidate(_candidate) { + validatorsState[_candidate].cap = validatorsState[_candidate].cap.add(msg.value); + if (validatorsState[_candidate].voters[msg.sender] == 0) { + voters[_candidate].push(msg.sender); + } + validatorsState[_candidate].voters[msg.sender] = validatorsState[_candidate].voters[msg.sender].add(msg.value); + emit Vote(msg.sender, _candidate, msg.value); + } + + function getCandidates() public view returns(address[]) { + return candidates; + } + + function getCandidateCap(address _candidate) public view returns(uint256) { + return validatorsState[_candidate].cap; + } + + function getCandidateOwner(address _candidate) public view returns(address) { + return validatorsState[_candidate].owner; + } + + function getVoterCap(address _candidate, address _voter) public view returns(uint256) { + return validatorsState[_candidate].voters[_voter]; + } + + function getVoters(address _candidate) public view returns(address[]) { + return voters[_candidate]; + } + + function isCandidate(address _candidate) public view returns(bool) { + return validatorsState[_candidate].isCandidate; + } + + function getWithdrawBlockNumbers() public view returns(uint256[]) { + return withdrawsState[msg.sender].blockNumbers; + } + + function getWithdrawCap(uint256 _blockNumber) public view returns(uint256) { + return withdrawsState[msg.sender].caps[_blockNumber]; + } + + function unvote(address _candidate, uint256 _cap) public onlyValidVote(_candidate, _cap) { + validatorsState[_candidate].cap = validatorsState[_candidate].cap.sub(_cap); + validatorsState[_candidate].voters[msg.sender] = validatorsState[_candidate].voters[msg.sender].sub(_cap); + + // refund after delay X blocks + uint256 withdrawBlockNumber = voterWithdrawDelay.add(block.number); + withdrawsState[msg.sender].caps[withdrawBlockNumber] = withdrawsState[msg.sender].caps[withdrawBlockNumber].add(_cap); + withdrawsState[msg.sender].blockNumbers.push(withdrawBlockNumber); + + emit Unvote(msg.sender, _candidate, _cap); + } + + function resign(address _candidate) public onlyOwner(_candidate) onlyCandidate(_candidate) { + validatorsState[_candidate].isCandidate = false; + candidateCount = candidateCount.sub(1); + for (uint256 i = 0; i < candidates.length; i++) { + if (candidates[i] == _candidate) { + delete candidates[i]; + break; + } + } + uint256 cap = validatorsState[_candidate].voters[msg.sender]; + validatorsState[_candidate].cap = validatorsState[_candidate].cap.sub(cap); + validatorsState[_candidate].voters[msg.sender] = 0; + // refunding after resigning X blocks + uint256 withdrawBlockNumber = candidateWithdrawDelay.add(block.number); + withdrawsState[msg.sender].caps[withdrawBlockNumber] = withdrawsState[msg.sender].caps[withdrawBlockNumber].add(cap); + withdrawsState[msg.sender].blockNumbers.push(withdrawBlockNumber); + emit Resign(msg.sender, _candidate); + } + + // voteInvalidKYC : any candidate can vote for invalid KYC i.e. a particular candidate's owner has uploaded a bad KYC. + // On securing 75% votes against an owner ( not candidate ), owner & all its candidates will lose their funds. + function voteInvalidKYC(address _invalidCandidate) onlyValidCandidate(msg.sender) onlyValidCandidate(_invalidCandidate) public { + address candidateOwner = getCandidateOwner(msg.sender); + address _invalidMasternode = getCandidateOwner(_invalidCandidate); + require(!hasVotedInvalid[candidateOwner][_invalidMasternode]); + hasVotedInvalid[candidateOwner][_invalidMasternode] = true; + invalidKYCCount[_invalidMasternode] += 1; + if( invalidKYCCount[_invalidMasternode]*100/getOwnerCount() >= 75 ){ + // 75% owners say that the KYC is invalid + for (uint i=0;i