quorum/permission/contract/VoterManager.sol

251 lines
10 KiB
Solidity

pragma solidity ^0.5.3;
import "./PermissionsUpgradable.sol";
/** @title Voter manager contract
* @notice This contract holds implementation logic for all account voter and
voting functionality. This can be called only by the implementation
contract only. there are few view functions exposed as public and
can be called directly. these are invoked by quorum for populating
permissions data in cache
* @dev each voting record has an attribute operation type (opType)
which denotes the activity type which is pending approval. This can
have the following values:
0 - None - indicates no pending records for the org
1 - New org add activity
2 - Org suspension activity
3 - Revoke of org suspension
4 - Assigning admin role for a new account
5 - Blacklisted node recovery
6 - Blacklisted account recovery
*/
contract VoterManager {
PermissionsUpgradable private permUpgradable;
struct PendingOpDetails {
string orgId;
string enodeId;
address account;
uint256 opType;
}
struct Voter {
address vAccount;
bool active;
}
struct OrgVoterDetails {
string orgId;
uint256 voterCount;
uint256 validVoterCount;
uint256 voteCount;
PendingOpDetails pendingOp;
Voter [] voterList;
mapping(address => uint256) voterIndex;
mapping(uint256 => mapping(address => bool)) votingStatus;
}
OrgVoterDetails [] private orgVoterList;
mapping(bytes32 => uint256) private VoterOrgIndex;
uint256 private orgNum = 0;
// events related to managing voting accounts for the org
event VoterAdded(string _orgId, address _vAccount);
event VoterDeleted(string _orgId, address _vAccount);
event VotingItemAdded(string _orgId);
event VoteProcessed(string _orgId);
/** @notice confirms that the caller is the address of implementation
contract
*/
modifier onlyImplementation {
require(msg.sender == permUpgradable.getPermImpl(), "invalid caller");
_;
}
/** @notice checks if account is a valid voter record and belongs to the org
passed
* @param _orgId - org id
* @param _vAccount - voter account passed
*/
modifier voterExists(string memory _orgId, address _vAccount) {
require(_checkVoterExists(_orgId, _vAccount) == true, "must be a voter");
_;
}
/** @notice constructor. sets the permissions upgradable address
*/
constructor (address _permUpgradable) public {
permUpgradable = PermissionsUpgradable(_permUpgradable);
}
/** @notice function to add a new voter account to the organization
* @param _orgId org id
* @param _vAccount - voter account
* @dev voter capability is currently enabled for network level activities
only. voting is not available for org related activities
*/
function addVoter(string calldata _orgId, address _vAccount) external
onlyImplementation {
// check if the org exists
if (VoterOrgIndex[keccak256(abi.encode(_orgId))] == 0) {
orgNum++;
VoterOrgIndex[keccak256(abi.encode(_orgId))] = orgNum;
uint256 id = orgVoterList.length++;
orgVoterList[id].orgId = _orgId;
orgVoterList[id].voterCount = 1;
orgVoterList[id].validVoterCount = 1;
orgVoterList[id].voteCount = 0;
orgVoterList[id].pendingOp.orgId = "";
orgVoterList[id].pendingOp.enodeId = "";
orgVoterList[id].pendingOp.account = address(0);
orgVoterList[id].pendingOp.opType = 0;
orgVoterList[id].voterIndex[_vAccount] = orgVoterList[id].voterCount;
orgVoterList[id].voterList.push(Voter(_vAccount, true));
}
else {
uint256 id = _getVoterOrgIndex(_orgId);
// check if the voter is already present in the list
if (orgVoterList[id].voterIndex[_vAccount] == 0) {
orgVoterList[id].voterCount++;
orgVoterList[id].voterIndex[_vAccount] = orgVoterList[id].voterCount;
orgVoterList[id].voterList.push(Voter(_vAccount, true));
orgVoterList[id].validVoterCount++;
}
else {
uint256 vid = _getVoterIndex(_orgId, _vAccount);
require(orgVoterList[id].voterList[vid].active != true, "already a voter");
orgVoterList[id].voterList[vid].active = true;
orgVoterList[id].validVoterCount++;
}
}
emit VoterAdded(_orgId, _vAccount);
}
/** @notice function to delete a voter account from the organization
* @param _orgId org id
* @param _vAccount - voter account
* @dev voter capability is currently enabled for network level activities
only. voting is not available for org related activities
*/
function deleteVoter(string calldata _orgId, address _vAccount) external
onlyImplementation
voterExists(_orgId, _vAccount) {
uint256 id = _getVoterOrgIndex(_orgId);
uint256 vId = _getVoterIndex(_orgId, _vAccount);
orgVoterList[id].validVoterCount --;
orgVoterList[id].voterList[vId].active = false;
emit VoterDeleted(_orgId, _vAccount);
}
/** @notice function to a voting item for network admin accounts to vote
* @param _authOrg org id of the authorizing org. it will be network admin org
* @param _orgId - org id for which the voting record is being created
* @param _enodeId - enode id for which the voting record is being created
* @param _account - account id for which the voting record is being created
* @param _pendingOp - operation for which voting is being done
*/
function addVotingItem(string calldata _authOrg, string calldata _orgId,
string calldata _enodeId, address _account, uint256 _pendingOp)
external onlyImplementation {
// check if anything is pending approval for the org.
// If yes another item cannot be added
require((_checkPendingOp(_authOrg, 0)),
"items pending for approval. new item cannot be added");
uint256 id = _getVoterOrgIndex(_authOrg);
orgVoterList[id].pendingOp.orgId = _orgId;
orgVoterList[id].pendingOp.enodeId = _enodeId;
orgVoterList[id].pendingOp.account = _account;
orgVoterList[id].pendingOp.opType = _pendingOp;
// initialize vote status for voter accounts
for (uint256 i = 0; i < orgVoterList[id].voterList.length; i++) {
if (orgVoterList[id].voterList[i].active) {
orgVoterList[id].votingStatus[id][orgVoterList[id].voterList[i].vAccount] = false;
}
}
// set vote count to zero
orgVoterList[id].voteCount = 0;
emit VotingItemAdded(_authOrg);
}
/** @notice function processing vote of a voter account
* @param _authOrg org id of the authorizing org. it will be network admin org
* @param _vAccount - account id of the voter
* @param _pendingOp - operation which is being approved
* @return success of the voter process. either true or false
*/
function processVote(string calldata _authOrg, address _vAccount, uint256 _pendingOp)
external onlyImplementation voterExists(_authOrg, _vAccount) returns (bool) {
// check something if anything is pending approval
require(_checkPendingOp(_authOrg, _pendingOp) == true, "nothing to approve");
uint256 id = _getVoterOrgIndex(_authOrg);
// check if vote is already processed
require(orgVoterList[id].votingStatus[id][_vAccount] != true, "cannot double vote");
orgVoterList[id].voteCount++;
orgVoterList[id].votingStatus[id][_vAccount] = true;
emit VoteProcessed(_authOrg);
if (orgVoterList[id].voteCount > orgVoterList[id].validVoterCount / 2) {
// majority achieved, clean up pending op
orgVoterList[id].pendingOp.orgId = "";
orgVoterList[id].pendingOp.enodeId = "";
orgVoterList[id].pendingOp.account = address(0);
orgVoterList[id].pendingOp.opType = 0;
return true;
}
return false;
}
/** @notice returns the details of any pending operation to be approved
* @param _orgId org id. this will be the org id of network admin org
*/
function getPendingOpDetails(string calldata _orgId) external view
onlyImplementation returns (string memory, string memory, address, uint256){
uint256 orgIndex = _getVoterOrgIndex(_orgId);
return (orgVoterList[orgIndex].pendingOp.orgId, orgVoterList[orgIndex].pendingOp.enodeId,
orgVoterList[orgIndex].pendingOp.account, orgVoterList[orgIndex].pendingOp.opType);
}
/** @notice checks if the voter account exists and is linked to the org
* @param _orgId org id
* @param _vAccount voter account id
* @return true or false
*/
function _checkVoterExists(string memory _orgId, address _vAccount)
internal view returns (bool){
uint256 orgIndex = _getVoterOrgIndex(_orgId);
if (orgVoterList[orgIndex].voterIndex[_vAccount] == 0) {
return false;
}
uint256 voterIndex = _getVoterIndex(_orgId, _vAccount);
return orgVoterList[orgIndex].voterList[voterIndex].active;
}
/** @notice checks if the pending operation exists or not
* @param _orgId org id
* @param _pendingOp type of operation
* @return true or false
*/
function _checkPendingOp(string memory _orgId, uint256 _pendingOp)
internal view returns (bool){
return (orgVoterList[_getVoterOrgIndex(_orgId)].pendingOp.opType == _pendingOp);
}
/** @notice returns the voter account index
*/
function _getVoterIndex(string memory _orgId, address _vAccount)
internal view returns (uint256) {
uint256 orgIndex = _getVoterOrgIndex(_orgId);
return orgVoterList[orgIndex].voterIndex[_vAccount] - 1;
}
/** @notice returns the org index for the org from voter list
*/
function _getVoterOrgIndex(string memory _orgId)
internal view returns (uint256) {
return VoterOrgIndex[keccak256(abi.encode(_orgId))] - 1;
}
}