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

Voting Module #338

Closed
wants to merge 9 commits into from
141 changes: 141 additions & 0 deletions contracts/modules/Experimental/Checkpoint/WeightedVoteCheckpoint.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
pragma solidity ^0.4.24;

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

/**
* @title Checkpoint module for token weighted vote
* @notice This voting system uses public votes
*/
contract WeightedVoteCheckpoint is ICheckpoint, Module {
using SafeMath for uint256;

struct Ballot {
uint256 checkpointId;
uint256 totalSupply;
uint256 startTime;
uint256 endTime;
uint256 cumulativeYes;
uint256 cumulativeNo;
uint256 numVotes;
mapping(address => Vote) voteByAddress;
bool isActive;
}

Ballot[] public ballots;

struct Vote {
uint256 time;
uint256 weight;
bool vote;
}

event BallotCreated(uint256 _startTime, uint256 _endTime, uint256 _ballotId, uint256 _checkpointId);
event VoteCasted(uint256 _ballotId, uint256 _time, address indexed _investor, uint256 _weight, bool _vote);
event BallotActiveStatsChanged(uint256 _ballotId, bool _isActive);

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

}

/**
* @notice Queries the result of a given ballot
* @param _ballotId Id of the target ballot
* @return uint256 cummulativeYes
* @return uint256 cummulativeNo
* @return uint256 totalAbstain
* @return uint256 remainingTime
*/
function getResults(uint256 _ballotId) public view returns (uint256 cummulativeYes, uint256 cummulativeNo, uint256 totalAbstain, uint256 remainingTime) {
uint256 abstain = (ballots[_ballotId].totalSupply.sub(ballots[_ballotId].cumulativeYes)).sub(ballots[_ballotId].cumulativeNo);
uint256 time = (ballots[_ballotId].endTime > now) ? ballots[_ballotId].endTime.sub(now) : 0;
return (ballots[_ballotId].cumulativeYes, ballots[_ballotId].cumulativeNo, abstain, time);
}

/**
* @notice Allows a token holder to cast their vote on a specific ballot
* @param _vote The vote (true/false) in favor or against the proposal
* @param _ballotId The index of the target ballot
* @return bool success
*/
function castVote(bool _vote, uint256 _ballotId) public returns (bool) {
require(now > ballots[_ballotId].startTime && now < ballots[_ballotId].endTime, "Voting period is not active.");
require(ballots[_ballotId].voteByAddress[msg.sender].time == 0, "Token holder has already voted.");
require(ballots[_ballotId].isActive == true, "This ballot is deactiveated.");
uint256 checkpointId = ballots[_ballotId].checkpointId;
uint256 weight = ISecurityToken(securityToken).balanceOfAt(msg.sender,checkpointId);
require(weight > 0, "Token Holder balance is zero.");
ballots[_ballotId].voteByAddress[msg.sender].time = now;
ballots[_ballotId].voteByAddress[msg.sender].weight = weight;
ballots[_ballotId].voteByAddress[msg.sender].vote = _vote;
ballots[_ballotId].numVotes = ballots[_ballotId].numVotes.add(1);
if (_vote == true) {
ballots[_ballotId].cumulativeYes = ballots[_ballotId].cumulativeYes.add(weight);
} else {
ballots[_ballotId].cumulativeNo = ballots[_ballotId].cumulativeNo.add(weight);
}
emit VoteCasted(_ballotId, now, msg.sender, weight, _vote);
return true;
}

/**
* @notice Allows the token issuer to create a ballot
* @param _duration The duration of the voting period in seconds
* @return bool success
*/
function createBallot(uint256 _duration) public onlyOwner {
require(_duration > 0, "Incorrect ballot duration.");
uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint();
uint256 endTime = now.add(_duration);
createCustomBallot(now, endTime, checkpointId);
}

/**
* @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
* @return bool success
*/
function createCustomBallot(uint256 _startTime, uint256 _endTime, uint256 _checkpointId) public onlyOwner {
require(_endTime > _startTime, "Ballot end time must be later than start time.");
uint256 ballotId = ballots.length;
uint256 supplyAtCheckpoint = ISecurityToken(securityToken).totalSupplyAt(_checkpointId);
ballots.push(Ballot(_checkpointId,supplyAtCheckpoint,_startTime,_endTime,0,0,0, true));
emit BallotCreated(_startTime, _endTime, ballotId, _checkpointId);
}

/**
* @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 setActiveStatsBallot(uint256 _ballotId, bool _isActive) public onlyOwner {
require(now < ballots[_ballotId].endTime, "This ballot has already ended.");
require(ballots[_ballotId].isActive != _isActive, "Active state unchanged");
ballots[_ballotId].isActive = _isActive;
emit BallotActiveStatsChanged(_ballotId, _isActive);
}


function getInitFunction() public returns(bytes4) {
return bytes4(0);
}

/**
* @notice Return the permissions flag that are associated with STO
* @return bytes32 array
*/
function getPermissions() public view returns(bytes32[]) {
bytes32[] memory allPermissions = new bytes32[](0);
return allPermissions;
}

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

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

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

/**
* @notice Constructor
* @param _polyAddress Address of the polytoken
* @param _setupCost Setup cost of the module
* @param _usageCost Usage cost of the module
* @param _subscriptionCost Subscription cost of the module
*/
constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public
ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost)
{
version = "1.0.0";
name = "WeightedVoteCheckpoint";
title = "Weighted Vote Checkpoint";
description = "Weighted votes based on token amount";
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 /* _data */) external returns(address) {
if (setupCost > 0)
require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom because of sufficent Allowance is not provided");
return address(new WeightedVoteCheckpoint(msg.sender, address(polyToken)));
}

/**
* @notice Type of the Module factory
*/
function getTypes() external view returns(uint8[]) {
uint8[] memory res = new uint8[](1);
res[0] = 4;
return res;
}

/**
* @notice Get the name of the Module
*/
function getName() public view returns(bytes32) {
return name;
}

/**
* @notice Get the description of the Module
*/
function getDescription() external view returns(string) {
return description;
}

/**
* @notice Get the title of the Module
*/
function getTitle() external view returns(string) {
return title;
}

/**
* @notice Get the version of the Module
*/
function getVersion() external view returns(string) {
return version;
}

/**
* @notice Get the setup cost of the module
*/
function getSetupCost() external view returns (uint256) {
return setupCost;
}

/**
* @notice Get the Instructions that helped to used the module
*/
function getInstructions() external view returns(string) {
return "Create a vote which allows token holders to vote on an issue with a weight proportional to their balances at the point the vote is created.";
}

/**
* @notice Get the tags related to the module factory
*/
function getTags() external view returns(bytes32[]) {
bytes32[] memory availableTags = new bytes32[](0);
return availableTags;
}
}
15 changes: 15 additions & 0 deletions test/helpers/createInstances.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const ERC20DividendCheckpointFactory = artifacts.require("./ERC20DividendCheckpo
const EtherDividendCheckpointFactory = artifacts.require("./EtherDividendCheckpointFactory.sol");
const ManualApprovalTransferManagerFactory = artifacts.require("./ManualApprovalTransferManagerFactory.sol");
const SingleTradeVolumeRestrictionManagerFactory = artifacts.require('./SingleTradeVolumeRestrictionTMFactory.sol');
const WeightedVoteCheckpointFactory = artifacts.require('./WeightedVoteCheckpointFactory.sol');
const TrackedRedemptionFactory = artifacts.require("./TrackedRedemptionFactory.sol");
const PercentageTransferManagerFactory = artifacts.require("./PercentageTransferManagerFactory.sol");
const ScheduledCheckpointFactory = artifacts.require('./ScheduledCheckpointFactory.sol');
Expand Down Expand Up @@ -42,6 +43,7 @@ let I_ScheduledCheckpointFactory;
let I_MockBurnFactory;
let I_MockWrongTypeBurnFactory;
let I_SingleTradeVolumeRestrictionManagerFactory;
let I_WeightedVoteCheckpointFactory;
let I_ManualApprovalTransferManagerFactory;
let I_VolumeRestrictionTransferManagerFactory;
let I_PercentageTransferManagerFactory;
Expand Down Expand Up @@ -423,6 +425,19 @@ export async function deployMockWrongTypeRedemptionAndVerifyed(accountPolymath,
}


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

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



/// Helper function
function mergeReturn(returnData) {
Expand Down
Loading