Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add weighted voting module #578

Merged
merged 23 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
90fa6d9
add weighted voting module
SatyamSB Mar 4, 2019
828f2b0
Merge branch 'dev-3.0.0' into weighted-voting-module
satyamakgec Mar 4, 2019
c461738
test file addition
SatyamSB Mar 5, 2019
3db5a07
add test cases of the voting module
SatyamSB Mar 6, 2019
a3427d0
Merge branch 'weighted-voting-module' of https://github.com/PolymathN…
SatyamSB Mar 6, 2019
393d45e
Merge branch 'dev-3.0.0' into weighted-voting-module
satyamakgec Mar 6, 2019
a35af66
Merge branch 'dev-3.0.0' into weighted-voting-module
satyamakgec Mar 6, 2019
2d7088d
Merge branch 'dev-3.0.0' into weighted-voting-module
satyamakgec Mar 7, 2019
50d2ff5
minor fix
SatyamSB Mar 7, 2019
bce762e
improvements and fixes
SatyamSB Mar 14, 2019
03cf6b7
Merge branch 'dev-3.0.0' into weighted-voting-module
satyamakgec Mar 14, 2019
2b5e97e
factory fix
SatyamSB Mar 14, 2019
746454c
minor fix
SatyamSB Mar 14, 2019
56b1b10
Merge branch 'dev-3.0.0' into weighted-voting-module
adamdossa Mar 18, 2019
35d8815
add overflow check
SatyamSB Mar 18, 2019
cd4e1e9
Merge branch 'weighted-voting-module' of https://github.com/PolymathN…
SatyamSB Mar 18, 2019
616348c
Merge branch 'dev-3.0.0' into weighted-voting-module
adamdossa Mar 22, 2019
7d369c7
Merge branch 'dev-3.0.0' into weighted-voting-module
adamdossa Mar 28, 2019
1db9687
Merge branch 'dev-3.0.0' into weighted-voting-module
satyamakgec Apr 1, 2019
68aec1d
minor fixes
SatyamSB Apr 1, 2019
6ba0de0
Merge branch 'dev-3.0.0' into weighted-voting-module
satyamakgec Apr 9, 2019
2941a9c
Merge branch 'dev-3.0.0' into weighted-voting-module
satyamakgec Apr 10, 2019
04fbdcc
minor test fix
SatyamSB Apr 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 216 additions & 0 deletions contracts/modules/Experimental/Checkpoints/WeightedVoteCheckpoint.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
pragma solidity ^0.5.0;

import "../../Module.sol";
import "../../Checkpoint/ICheckpoint.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";

/**
* @title Checkpoint module for token weighted vote
* @notice This voting system uses public votes
* @notice In this module every token holder has voting right (Should be greater than zero)
* Tally will be calculated as per the weight (balance of the token holder)
*/
contract WeightedVoteCheckpoint is ICheckpoint, Module {
using SafeMath for uint256;

struct Ballot {
uint256 checkpointId;
uint256 totalSupply;
uint64 startTime;
uint64 endTime;
uint64 totalNumVotes;
uint56 totalProposals;
uint8 isActive;
mapping(uint256 => uint256) proposalToVote;
mapping(address => uint256) voteByAddress;
}

satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
Ballot[] ballots;

event BallotCreated(uint256 _startTime, uint256 _endTime, uint256 _ballotId, uint256 _checkpointId, uint256 _noOfProposals);
event VoteCasted(uint256 indexed _ballotId, uint256 indexed _proposalId, address indexed _investor, uint256 _weight);
satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
event BallotStatusChanged(uint256 _ballotId, bool _isActive);

/**
* @notice Constructor
* @param _securityToken Address of the security token
* @param _polyToken Address of the polytoken
*/
constructor(address _securityToken, address _polyToken)
public
Module(_securityToken, _polyToken)
{

}

/**
* @notice This function returns the signature of configure function
*/
function getInitFunction() external pure returns(bytes4) {
return bytes4(0);
}

/**
* @notice Allows the token issuer to create a ballot
* @param _duration The duration of the voting period in seconds
* @param _noOfProposals Number of proposals
*/
function createBallot(uint256 _duration, uint256 _noOfProposals) external withPerm(ADMIN) {
require(_duration > 0, "Incorrect ballot duration.");
uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint();
uint256 endTime = now.add(_duration);
_createCustomBallot(now, endTime, checkpointId, _noOfProposals);
}

function _createCustomBallot(uint256 _startTime, uint256 _endTime, uint256 _checkpointId, uint256 _noOfProposals) internal {
require(_noOfProposals > 1, "Incorrect proposals no");
require(_endTime > _startTime, "Times are not valid");
satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
uint256 ballotId = ballots.length;
uint256 supplyAtCheckpoint = ISecurityToken(securityToken).totalSupplyAt(_checkpointId);
ballots.push(
Ballot(
_checkpointId, supplyAtCheckpoint, uint64(_startTime), uint64(_endTime), uint64(0), uint56(_noOfProposals), uint8(1)
)
);
emit BallotCreated(_startTime, _endTime, ballotId, _checkpointId, _noOfProposals);
}

/**
* @notice Allows the token issuer to create a ballot with custom settings
* @param _startTime Start time of the voting period in Unix Epoch time
* @param _endTime End time of the voting period in Unix Epoch time
* @param _checkpointId Index of the checkpoint to use for token balances
* @param _noOfProposals Number of proposals
*/
function createCustomBallot(uint256 _startTime, uint256 _endTime, uint256 _checkpointId, uint256 _noOfProposals) external withPerm(ADMIN) {
require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId(), "Invalid checkpoint Id");
_createCustomBallot(_startTime, _endTime, _checkpointId, _noOfProposals);
satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @notice Allows a token holder to cast their vote on a specific ballot
* @param _ballotId The index of the target ballot
* @param _proposalId Id of the proposal which investor want to vote for proposal
*/
function castVote(uint256 _ballotId, uint256 _proposalId) external {
require(_ballotId < ballots.length, "Incorrect ballot Id");
Ballot storage ballot = ballots[_ballotId];

uint256 weight = ISecurityToken(securityToken).balanceOfAt(msg.sender, ballot.checkpointId);
require(weight > 0, "weight should be > 0");
require(ballot.totalProposals >= _proposalId && _proposalId > 0, "Incorrect proposals Id");
satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
require(now >= ballot.startTime && now <= ballot.endTime, "Voting period is not active");
require(ballot.voteByAddress[msg.sender] == 0, "Token holder has already voted");
require(ballot.isActive == uint8(1), "Ballot is not active");

ballot.voteByAddress[msg.sender] = _proposalId;
satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
ballot.totalNumVotes = uint64(uint256(ballot.totalNumVotes).add(uint256(1)));
satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
ballot.proposalToVote[_proposalId] = ballot.proposalToVote[_proposalId].add(weight);
emit VoteCasted(_ballotId, _proposalId, msg.sender, weight);
}

/**
* @notice Allows the token issuer to set the active stats of a ballot
* @param _ballotId The index of the target ballot
* @param _isActive The bool value of the active stats of the ballot
* @return bool success
*/
function changeBallotStatus(uint256 _ballotId, bool _isActive) external withPerm(ADMIN) {
require(uint64(now) < ballots[_ballotId].endTime, "Already ended");
satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
uint8 activeStatus = 0;
if (_isActive)
activeStatus = 1;
require(ballots[_ballotId].isActive != activeStatus, "Active state unchanged");
ballots[_ballotId].isActive = activeStatus;
emit BallotStatusChanged(_ballotId, _isActive);
}

/**
* @notice Queries the result of a given ballot
* @param _ballotId Id of the target ballot
* @return uint256 voteWeighting
* @return uint256 tieWith
* @return uint256 winningProposal
* @return uint256 remainingTime
* @return uint256 totalVotes

*/
function getBallotResults(uint256 _ballotId) external view returns (
uint256[] memory,
uint256[] memory,
satyamakgec marked this conversation as resolved.
Show resolved Hide resolved
uint256 winningProposal,
uint256 remainingTime,
uint256 totalVotes
) {
if (_ballotId >= ballots.length)
return (new uint256[](0), new uint256[](0), winningProposal, remainingTime, totalVotes);

Ballot storage ballot = ballots[_ballotId];
uint256 counter = 0;
uint256 maxWeight = 0;
uint256 i;
for (i = 0; i < ballot.totalProposals; i++) {
if (maxWeight < ballot.proposalToVote[i+1]) {
maxWeight = ballot.proposalToVote[i+1];
winningProposal = i + 1;
}
}
for (i = 0; i < ballot.totalProposals; i++) {
if (maxWeight == ballot.proposalToVote[i+1])
counter ++;
}
uint256[] memory voteWeighting = new uint256[](ballot.totalProposals);
uint256[] memory tieWith = new uint256[](counter);
counter = 0;
for (i = 0; i < ballot.totalProposals; i++) {
voteWeighting[i] = ballot.proposalToVote[i+1];
if (maxWeight == ballot.proposalToVote[i+1]) {
tieWith[counter] = i+1;
counter ++;
}
}
if (ballot.endTime >= uint64(now))
remainingTime = uint256(ballot.endTime).sub(now);
totalVotes = uint256(ballot.totalNumVotes);
return (voteWeighting, tieWith, winningProposal, remainingTime, totalVotes);
}

/**
* @notice Get the voted proposal
* @param _ballotId Id of the ballot
* @param _voter Address of the voter
*/
function getSelectedProposal(uint256 _ballotId, address _voter) external view returns(uint256 proposalId) {
if (_ballotId >= ballots.length)
return 0;
return ballots[_ballotId].voteByAddress[_voter];
}

/**
* @notice Get the stats of the ballot
* @param _ballotId The index of the target ballot
*/
function getBallotStats(uint256 _ballotId) external view returns(uint256, uint256, uint64, uint64, uint64, uint64, bool) {
Ballot memory ballot = ballots[_ballotId];
return (
ballot.checkpointId,
ballot.totalSupply,
ballot.startTime,
ballot.endTime,
ballot.totalNumVotes,
ballot.totalProposals,
(ballot.isActive == 1 ? true : false)
);
}

maxsam4 marked this conversation as resolved.
Show resolved Hide resolved
/**
* @notice Return the permissions flag that are associated with STO
* @return bytes32 array
*/
function getPermissions() public view returns(bytes32[] memory) {
bytes32[] memory allPermissions = new bytes32[](1);
allPermissions[0] = ADMIN;
return allPermissions;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
pragma solidity ^0.5.0;

import "./WeightedVoteCheckpoint.sol";
import "../../ModuleFactory.sol";

/**
* @title Factory for deploying WeightedVoteCheckpoint module
*/
contract WeightedVoteCheckpointFactory is ModuleFactory {

/**
* @notice Constructor
* @param _setupCost Setup cost of the module
* @param _usageCost Usage cost of the module
* @param _polymathRegistry Address of the Polymath registry
*/
constructor (uint256 _setupCost, uint256 _usageCost, address _polymathRegistry) public
ModuleFactory(_setupCost, _usageCost, _polymathRegistry)
{
initialVersion = "3.0.0";
name = "WeightedVoteCheckpoint";
title = "Weighted Vote Checkpoint";
description = "Weighted votes based on token amount";
typesData.push(4);
tagsData.push("Vote");
tagsData.push("Transparent");
tagsData.push("Checkpoint");
compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0));
compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0));

}

/**
* @notice used to launch the Module with the help of factory
* @return address Contract address of the Module
*/
function deploy(bytes calldata _data) external returns(address) {
address weightedVoteCheckpoint = address(new WeightedVoteCheckpoint(msg.sender, IPolymathRegistry(polymathRegistry).getAddress("PolyToken")));
_initializeModule(weightedVoteCheckpoint, _data);
return weightedVoteCheckpoint;
}

}
16 changes: 16 additions & 0 deletions test/helpers/createInstances.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const VolumeRestrictionTMFactory = artifacts.require("./VolumeRestrictionTMFacto
const VolumeRestrictionTM = artifacts.require("./VolumeRestrictionTM.sol");
const VestingEscrowWalletFactory = artifacts.require("./VestingEscrowWalletFactory.sol");
const VestingEscrowWallet = artifacts.require("./VestingEscrowWallet.sol");
const WeightedVoteCheckpointFactory = artifacts.require("./WeightedVoteCheckpointFactory.sol");

const Web3 = require("web3");
let BN = Web3.utils.BN;
Expand All @@ -75,6 +76,7 @@ let I_ERC20DividendCheckpointLogic;
let I_ERC20DividendCheckpointFactory;
let I_GeneralPermissionManagerLogic;
let I_VolumeRestrictionTMFactory;
let I_WeightedVoteCheckpointFactory;
let I_GeneralPermissionManagerFactory;
let I_GeneralTransferManagerLogic;
let I_GeneralTransferManagerFactory;
Expand Down Expand Up @@ -590,3 +592,17 @@ export async function deploySignedTMAndVerifyed(accountPolymath, MRProxyInstance
await registerAndVerifyByMR(I_SignedTransferManagerFactory.address, accountPolymath, MRProxyInstance);
return new Array(I_SignedTransferManagerFactory);
}

// Deploy the voting modules

export async function deployWeightedVoteCheckpoint(accountPolymath, MRProxyInstance, setupCost) {
I_WeightedVoteCheckpointFactory = await WeightedVoteCheckpointFactory.new(setupCost, new BN(0), I_PolymathRegistry.address, { from: accountPolymath });
assert.notEqual(
I_WeightedVoteCheckpointFactory.address.valueOf(),
"0x0000000000000000000000000000000000000000",
"WeightedVoteCheckpointFactory contract was not deployed"
);

await registerAndVerifyByMR(I_WeightedVoteCheckpointFactory.address, accountPolymath, MRProxyInstance);
return new Array(I_WeightedVoteCheckpointFactory);
}
Loading