diff --git a/contracts/modules/Checkpoint/WeightedVoteCheckpoint.sol b/contracts/modules/Checkpoint/WeightedVoteCheckpoint.sol new file mode 100644 index 000000000..70acd5a19 --- /dev/null +++ b/contracts/modules/Checkpoint/WeightedVoteCheckpoint.sol @@ -0,0 +1,141 @@ +pragma solidity ^0.4.24; + +import "./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; + } + +} diff --git a/contracts/modules/Checkpoint/WeightedVoteCheckpointFactory.sol b/contracts/modules/Checkpoint/WeightedVoteCheckpointFactory.sol new file mode 100644 index 000000000..6fcf9b2d9 --- /dev/null +++ b/contracts/modules/Checkpoint/WeightedVoteCheckpointFactory.sol @@ -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; + } +} diff --git a/test/g_general_permission_manager.js b/test/g_general_permission_manager.js index 0cf090030..b04f8a18d 100644 --- a/test/g_general_permission_manager.js +++ b/test/g_general_permission_manager.js @@ -1,22 +1,32 @@ -import latestTime from "./helpers/latestTime"; -import { signData } from "./helpers/signData"; -import { pk } from "./helpers/testprivateKey"; -import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; -import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; -import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; -import { catchRevert } from "./helpers/exceptions"; -import { setUpPolymathNetwork, deployGPMAndVerifyed, deployDummySTOAndVerifyed } from "./helpers/createInstances"; - -const DummySTO = artifacts.require("./DummySTO.sol"); -const SecurityToken = artifacts.require("./SecurityToken.sol"); -const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); -const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); - -const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port - -contract("GeneralPermissionManager", accounts => { +import latestTime from './helpers/latestTime'; +import {signData} from './helpers/signData'; +import { pk } from './helpers/testprivateKey'; +import { duration, ensureException, promisifyLogWatch, latestBlock } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { encodeProxyCall, encodeModuleCall } from './helpers/encodeCall'; + +const PolymathRegistry = artifacts.require('./PolymathRegistry.sol') +const DummySTOFactory = artifacts.require('./DummySTOFactory.sol'); +const DummySTO = artifacts.require('./DummySTO.sol'); +const ModuleRegistry = artifacts.require('./ModuleRegistry.sol'); +const ModuleRegistryProxy = artifacts.require('./ModuleRegistryProxy.sol'); +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const SecurityTokenRegistry = artifacts.require('./SecurityTokenRegistry.sol'); +const SecurityTokenRegistryProxy = artifacts.require('./SecurityTokenRegistryProxy.sol'); +const FeatureRegistry = artifacts.require('./FeatureRegistry.sol'); +const STFactory = artifacts.require('./STFactory.sol'); +const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol'); +const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const PolyTokenFaucet = artifacts.require('./PolyTokenFaucet.sol'); + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('GeneralPermissionManager', accounts => { + // Accounts Variable declaration let account_polymath; let account_issuer; @@ -74,15 +84,17 @@ contract("GeneralPermissionManager", accounts => { const initRegFee = web3.utils.toWei("250"); // Dummy STO details - const startTime = latestTime() + duration.seconds(5000); // Start time will be 5000 seconds more than the latest time - const endTime = startTime + duration.days(80); // Add 80 days more - const cap = web3.utils.toWei("10", "ether"); + const startTime = latestTime() + duration.seconds(5000); // Start time will be 5000 seconds more than the latest time + const endTime = startTime + duration.days(80); // Add 80 days more + const cap = web3.utils.toWei('10', 'ether'); const someString = "A string which is not used"; - const STOParameters = ["uint256", "uint256", "uint256", "string"]; + const STOParameters = ['uint256', 'uint256', 'uint256', 'string']; + const STRProxyParameters = ['address', 'address', 'uint256', 'uint256', 'address', 'address']; + const MRProxyParameters = ['address', 'address']; let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, someString]); - before(async () => { + before(async() => { // Accounts setup account_polymath = accounts[0]; account_issuer = accounts[1]; @@ -96,54 +108,149 @@ contract("GeneralPermissionManager", accounts => { account_delegate2 = accounts[6]; account_delegate3 = accounts[5]; - // Step 1: Deploy the genral PM ecosystem - let instances = await setUpPolymathNetwork(account_polymath, token_owner); - - [ - I_PolymathRegistry, - I_PolyToken, - I_FeatureRegistry, - I_ModuleRegistry, - I_ModuleRegistryProxy, - I_MRProxied, - I_GeneralTransferManagerFactory, - I_STFactory, - I_SecurityTokenRegistry, - I_SecurityTokenRegistryProxy, - I_STRProxied - ] = instances; - + + // ----------- POLYMATH NETWORK Configuration ------------ + + // Step 0: Deploy the PolymathRegistry + I_PolymathRegistry = await PolymathRegistry.new({from: account_polymath}); + + // Step 1: Deploy the token Faucet and Mint tokens for token_owner + I_PolyToken = await PolyTokenFaucet.new(); + await I_PolyToken.getTokens((10000 * Math.pow(10, 18)), token_owner); + + // Step 2: Deploy the FeatureRegistry + + I_FeatureRegistry = await FeatureRegistry.new( + I_PolymathRegistry.address, + { + from: account_polymath + }); + + // STEP 3: Deploy the ModuleRegistry + + I_ModuleRegistry = await ModuleRegistry.new({from:account_polymath}); + // Step 3 (b): Deploy the proxy and attach the implementation contract to it + I_ModuleRegistryProxy = await ModuleRegistryProxy.new({from:account_polymath}); + let bytesMRProxy = encodeProxyCall(MRProxyParameters, [I_PolymathRegistry.address, account_polymath]); + await I_ModuleRegistryProxy.upgradeToAndCall("1.0.0", I_ModuleRegistry.address, bytesMRProxy, {from: account_polymath}); + I_MRProxied = await ModuleRegistry.at(I_ModuleRegistryProxy.address); + + // STEP 4: Deploy the GeneralTransferManagerFactory + + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_GeneralTransferManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManagerFactory contract was not deployed" + ); // STEP 5: Deploy the GeneralDelegateManagerFactory - [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_GeneralPermissionManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralDelegateManagerFactory contract was not deployed" + ); + // STEP 6: Deploy the GeneralDelegateManagerFactory - [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); + + P_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(I_PolyToken.address, web3.utils.toWei("500","ether"), 0, 0, {from:account_polymath}); + + assert.notEqual( + P_GeneralPermissionManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralDelegateManagerFactory contract was not deployed" + ); + // STEP 7: Deploy the DummySTOFactory - [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + I_DummySTOFactory = await DummySTOFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_DummySTOFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "DummySTOFactory contract was not deployed" + ); + + + // Step 8: Deploy the STFactory contract + + I_STFactory = await STFactory.new(I_GeneralTransferManagerFactory.address, {from : account_polymath }); + + assert.notEqual( + I_STFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "STFactory contract was not deployed", + ); + + // Step 9: Deploy the SecurityTokenRegistry contract + + I_SecurityTokenRegistry = await SecurityTokenRegistry.new({from: account_polymath }); + + assert.notEqual( + I_SecurityTokenRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "SecurityTokenRegistry contract was not deployed", + ); + + // Step 10: Deploy the proxy and attach the implementation contract to it. + I_SecurityTokenRegistryProxy = await SecurityTokenRegistryProxy.new({from: account_polymath}); + let bytesProxy = encodeProxyCall(STRProxyParameters, [I_PolymathRegistry.address, I_STFactory.address, initRegFee, initRegFee, I_PolyToken.address, account_polymath]); + await I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, {from: account_polymath}); + I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + + // Step 11: update the registries addresses from the PolymathRegistry contract + await I_PolymathRegistry.changeAddress("PolyToken", I_PolyToken.address, {from: account_polymath}) + await I_PolymathRegistry.changeAddress("ModuleRegistry", I_ModuleRegistryProxy.address, {from: account_polymath}); + await I_PolymathRegistry.changeAddress("FeatureRegistry", I_FeatureRegistry.address, {from: account_polymath}); + await I_PolymathRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistryProxy.address, {from: account_polymath}); + await I_MRProxied.updateFromRegistry({from: account_polymath}); + + // STEP 8: Register the Modules with the ModuleRegistry contract + + // (A) : Register the GeneralTransferManagerFactory + await I_MRProxied.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + + // (B) : Register the GeneralDelegateManagerFactory + await I_MRProxied.registerModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); + + // (B) : Register the Paid GeneralDelegateManagerFactory + await I_MRProxied.registerModule(P_GeneralPermissionManagerFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(P_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); + + // (C) : Register the STOFactory + await I_MRProxied.registerModule(I_DummySTOFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_DummySTOFactory.address, true, { from: account_polymath }); // Printing all the contract addresses console.log(` --------------------- Polymath Network Smart Contracts: --------------------- - PolymathRegistry: ${I_PolymathRegistry.address} - SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} - SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} - ModuleRegistryProxy ${I_ModuleRegistryProxy.address} - ModuleRegistry: ${I_ModuleRegistry.address} - FeatureRegistry: ${I_FeatureRegistry.address} + PolymathRegistry: ${PolymathRegistry.address} + SecurityTokenRegistryProxy: ${SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${SecurityTokenRegistry.address} + ModuleRegistryProxy ${ModuleRegistryProxy.address} + ModuleRegistry: ${ModuleRegistry.address} + FeatureRegistry: ${FeatureRegistry.address} - STFactory: ${I_STFactory.address} - GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} - GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address} + STFactory: ${STFactory.address} + GeneralTransferManagerFactory: ${GeneralTransferManagerFactory.address} + GeneralPermissionManagerFactory: ${GeneralPermissionManagerFactory.address} DummySTOFactory: ${I_DummySTOFactory.address} ----------------------------------------------------------------------------- `); }); - describe("Generate the SecurityToken", async () => { + describe("Generate the SecurityToken", async() => { + it("Should register the ticker before the generation of the security token", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); assert.equal(tx.logs[0].args._owner, token_owner); assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); }); @@ -151,47 +258,50 @@ contract("GeneralPermissionManager", accounts => { it("Should generate the new security token with the same symbol as registered above", async () => { await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner}); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); // Verify that GeneralTransferManager module get added successfully or not assert.equal(log.args._types[0].toNumber(), 2); - assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); }); it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; + I_GeneralTransferManager = GeneralTransferManager.at(moduleData); }); it("Should successfully attach the General permission manager factory with the security token", async () => { + let errorThrown = false; await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); - await catchRevert( - I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { - from: token_owner - }) - ); + try { + const tx = await I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { from: token_owner }); + } catch(error) { + console.log(` tx -> failed because Token is not paid`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); }); it("Should successfully attach the General permission manager factory with the security token", async () => { let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); - const tx = await I_SecurityToken.addModule( - P_GeneralPermissionManagerFactory.address, - "0x", - web3.utils.toWei("500", "ether"), - 0, - { from: token_owner } - ); + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), {from: token_owner}); + const tx = await I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { from: token_owner }); assert.equal(tx.logs[3].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( - web3.utils.toAscii(tx.logs[3].args._name).replace(/\u0000/g, ""), + web3.utils.toAscii(tx.logs[3].args._name) + .replace(/\u0000/g, ''), "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); @@ -203,7 +313,8 @@ contract("GeneralPermissionManager", accounts => { const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); assert.equal( - web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), "GeneralPermissionManager", "GeneralPermissionManagerFactory module was not added" ); @@ -212,34 +323,47 @@ contract("GeneralPermissionManager", accounts => { }); - describe("General Permission Manager test cases", async () => { - it("Get the init data", async () => { - let tx = await I_GeneralPermissionManager.getInitFunction.call(); - assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ""), 0); - }); - - it("Should fail in adding the permission to the delegate --msg.sender doesn't have permission", async () => { - await catchRevert(I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: account_investor1 })); - }); + describe("General Permission Manager test cases", async() => { - it("Should fail to provide the permission-- because delegate is not yet added", async () => { - await catchRevert( - I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, "WHITELIST", true, { - from: token_owner - }) - ); + it("Get the init data", async() => { + let tx = await I_GeneralPermissionManager.getInitFunction.call(); + assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ''),0); }); it("Should fail in adding the delegate -- msg.sender doesn't have permission", async() => { - await catchRevert( - I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: account_investor1}) - ); + let errorThrown = false; + try { + let tx = await I_GeneralPermissionManager.addDelegate(account_delegate, delegateDetails, { from: account_investor1}); + } catch(error) { + console.log(` tx revert -> msg.sender doesn't have permission`.grey); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); }); it("Should fail in adding the delegate -- no delegate details provided", async() => { - await catchRevert( - I_GeneralPermissionManager.addDelegate(account_delegate, '', { from: account_investor1}) - ); + let errorThrown = false; + try { + let tx = await I_GeneralPermissionManager.addDelegate(account_delegate, '', { from: account_investor1}); + } catch(error) { + console.log(` tx revert -> delegate details were not provided`.grey); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); + + it("Should fail to provide the permission -- because delegate is not yet added", async() => { + let errorThrown = false; + try { + let tx = await I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, "WHITELIST", true, {from: token_owner}); + } catch(error) { + console.log(` tx revert -> Delegate is not yet added`.grey); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); }); it("Should successfuly add the delegate", async() => { @@ -247,35 +371,29 @@ contract("GeneralPermissionManager", accounts => { assert.equal(tx.logs[0].args._delegate, account_delegate); }); - it("Should fail to provide the permission", async () => { - await catchRevert( - I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, "WHITELIST", true, { - from: account_investor1 - }) - ); + it("Should fail to provide the permission", async() => { + let errorThrown = false; + try { + let tx = await I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, "WHITELIST", true, {from: account_investor1}); + } catch(error) { + console.log(` tx revert -> msg.sender doesn't have permission`.grey); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); }); - it("Should check the permission", async () => { - assert.isFalse( - await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST") - ); + it("Should check the permission", async() => { + assert.isFalse(await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST")); }); - it("Should provide the permission", async () => { - let tx = await I_GeneralPermissionManager.changePermission( - account_delegate, - I_GeneralTransferManager.address, - "WHITELIST", - true, - { from: token_owner } - ); + it("Should provide the permission", async() => { + let tx = await I_GeneralPermissionManager.changePermission(account_delegate, I_GeneralTransferManager.address, "WHITELIST", true, {from: token_owner}); assert.equal(tx.logs[0].args._delegate, account_delegate); }); - it("Should check the permission", async () => { - assert.isTrue( - await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST") - ); + it("Should check the permission", async() => { + assert.isTrue(await I_GeneralPermissionManager.checkPermission.call(account_delegate, I_GeneralTransferManager.address, "WHITELIST")); }); it("Should check the delegate details", async() => { @@ -285,9 +403,12 @@ contract("GeneralPermissionManager", accounts => { "Wrong delegate address get checked"); }); - it("Should get the permission of the general permission manager contract", async () => { + it("Should get the permission of the general permission manager contract", async() => { let tx = await I_GeneralPermissionManager.getPermissions.call(); - assert.equal(web3.utils.toAscii(tx[0]).replace(/\u0000/g, ""), "CHANGE_PERMISSION", "Wrong permissions"); + assert.equal(web3.utils.toAscii(tx[0]) + .replace(/\u0000/g, ''), + "CHANGE_PERMISSION", + "Wrong permissions"); }); it("Should return all delegates", async() => { @@ -343,32 +464,30 @@ contract("GeneralPermissionManager", accounts => { }); - describe("General Permission Manager Factory test cases", async () => { - it("should get the exact details of the factory", async () => { - assert.equal(await I_GeneralPermissionManagerFactory.setupCost.call(), 0); - assert.equal((await I_GeneralPermissionManagerFactory.getTypes.call())[0], 1); - assert.equal(await I_GeneralPermissionManagerFactory.getVersion.call(), "1.0.0"); - assert.equal( - web3.utils.toAscii(await I_GeneralPermissionManagerFactory.getName.call()).replace(/\u0000/g, ""), - "GeneralPermissionManager", - "Wrong Module added" - ); - assert.equal( - await I_GeneralPermissionManagerFactory.getDescription.call(), - "Manage permissions within the Security Token and attached modules", - "Wrong Module added" - ); - assert.equal(await I_GeneralPermissionManagerFactory.getTitle.call(), "General Permission Manager", "Wrong Module added"); - assert.equal( - await I_GeneralPermissionManagerFactory.getInstructions.call(), - "Add and remove permissions for the SecurityToken and associated modules. Permission types should be encoded as bytes32 values, and attached using the withPerm modifier to relevant functions.No initFunction required.", - "Wrong Module added" - ); + describe("General Permission Manager Factory test cases", async() => { + it("should get the exact details of the factory", async() => { + assert.equal(await I_GeneralPermissionManagerFactory.setupCost.call(),0); + assert.equal((await I_GeneralPermissionManagerFactory.getTypes.call())[0],1); + assert.equal(web3.utils.toAscii(await I_GeneralPermissionManagerFactory.getName.call()) + .replace(/\u0000/g, ''), + "GeneralPermissionManager", + "Wrong Module added"); + assert.equal(await I_GeneralPermissionManagerFactory.getDescription.call(), + "Manage permissions within the Security Token and attached modules", + "Wrong Module added"); + assert.equal(await I_GeneralPermissionManagerFactory.getTitle.call(), + "General Permission Manager", + "Wrong Module added"); + assert.equal(await I_GeneralPermissionManagerFactory.getInstructions.call(), + "Add and remove permissions for the SecurityToken and associated modules. Permission types should be encoded as bytes32 values, and attached using the withPerm modifier to relevant functions.No initFunction required.", + "Wrong Module added"); + }); - it("Should get the tags of the factory", async () => { + it("Should get the tags of the factory", async() => { let tags = await I_GeneralPermissionManagerFactory.getTags.call(); - assert.equal(tags.length, 0); + assert.equal(tags.length,0); }); }); + }); diff --git a/test/x_weighted_vote_checkpoint.js b/test/x_weighted_vote_checkpoint.js new file mode 100644 index 000000000..6adbce5a0 --- /dev/null +++ b/test/x_weighted_vote_checkpoint.js @@ -0,0 +1,536 @@ +import latestTime from './helpers/latestTime'; +import {signData} from './helpers/signData'; +import { pk } from './helpers/testprivateKey'; +import { duration, ensureException, promisifyLogWatch, latestBlock } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { encodeProxyCall, encodeModuleCall } from './helpers/encodeCall'; + +const PolymathRegistry = artifacts.require('./PolymathRegistry.sol') +const DummySTOFactory = artifacts.require('./DummySTOFactory.sol'); +const DummySTO = artifacts.require('./DummySTO.sol'); +const ModuleRegistry = artifacts.require('./ModuleRegistry.sol'); +const ModuleRegistryProxy = artifacts.require('./ModuleRegistryProxy.sol'); +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const SecurityTokenRegistry = artifacts.require('./SecurityTokenRegistry.sol'); +const SecurityTokenRegistryProxy = artifacts.require('./SecurityTokenRegistryProxy.sol'); +const FeatureRegistry = artifacts.require('./FeatureRegistry.sol'); +const STFactory = artifacts.require('./STFactory.sol'); +const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol'); +const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const PolyTokenFaucet = artifacts.require('./PolyTokenFaucet.sol'); + +const WeightedVoteCheckpointFactory = artifacts.require('./WeightedVoteCheckpointFactory.sol'); +const WeightedVoteCheckpoint = artifacts.require('./WeightedVoteCheckpoint'); + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('WeightedVoteCheckpoint', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let token_owner_pk; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + let account_temp; + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_SecurityTokenRegistryProxy; + let I_GeneralTransferManagerFactory; + let I_GeneralPermissionManager; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_DummySTOFactory; + let I_STFactory; + let I_SecurityToken; + let I_MRProxied; + let I_STRProxied; + let I_DummySTO; + let I_PolyToken; + let I_PolymathRegistry; + let I_WeightedVoteCheckpointFactory; + let P_WeightedVoteCheckpointFactory; + let I_WeightedVoteCheckpoint; + let P_WeightedVoteCheckpoint; + + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + const delegateDetails = "Hello I am legit delegate"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + const checkpointKey = 4; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + // Dummy STO details + const startTime = latestTime() + duration.seconds(5000); // Start time will be 5000 seconds more than the latest time + const endTime = startTime + duration.days(80); // Add 80 days more + const cap = web3.utils.toWei('10', 'ether'); + const someString = "A string which is not used"; + const STOParameters = ['uint256', 'uint256', 'uint256', 'string']; + const STRProxyParameters = ['address', 'address', 'uint256', 'uint256', 'address', 'address']; + const MRProxyParameters = ['address', 'address']; + + let bytesSTO = encodeModuleCall(STOParameters, [startTime, endTime, cap, someString]); + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + token_owner_pk = pk.account_1; + + account_investor1 = accounts[6]; + account_investor2 = accounts[7]; + account_investor3 = accounts[8]; + account_investor4 = accounts[9]; + account_temp = accounts[2]; + + + // ----------- POLYMATH NETWORK Configuration ------------ + + // Step 0: Deploy the PolymathRegistry + I_PolymathRegistry = await PolymathRegistry.new({from: account_polymath}); + + // Step 1: Deploy the token Faucet and Mint tokens for token_owner + I_PolyToken = await PolyTokenFaucet.new(); + await I_PolyToken.getTokens((10000 * Math.pow(10, 18)), token_owner); + + // Step 2: Deploy the FeatureRegistry + + I_FeatureRegistry = await FeatureRegistry.new( + I_PolymathRegistry.address, + { + from: account_polymath + }); + + // STEP 3: Deploy the ModuleRegistry + + I_ModuleRegistry = await ModuleRegistry.new({from:account_polymath}); + // Step 3 (b): Deploy the proxy and attach the implementation contract to it + I_ModuleRegistryProxy = await ModuleRegistryProxy.new({from:account_polymath}); + let bytesMRProxy = encodeProxyCall(MRProxyParameters, [I_PolymathRegistry.address, account_polymath]); + await I_ModuleRegistryProxy.upgradeToAndCall("1.0.0", I_ModuleRegistry.address, bytesMRProxy, {from: account_polymath}); + I_MRProxied = await ModuleRegistry.at(I_ModuleRegistryProxy.address); + + // STEP 4: Deploy the GeneralTransferManagerFactory + + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_GeneralTransferManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManagerFactory contract was not deployed" + ); + + // STEP 5: Deploy the WeightedVoteCheckpointFactory + + I_WeightedVoteCheckpointFactory = await WeightedVoteCheckpointFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + assert.notEqual( + I_WeightedVoteCheckpointFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "WeightedVoteCheckpointFactory contract was not deployed" + ); + console.log("deployed weight vote factory to "+I_WeightedVoteCheckpointFactory.address); + + // STEP 6: Deploy the WeightedVoteCheckpointFactory with fees + + P_WeightedVoteCheckpointFactory = await WeightedVoteCheckpointFactory.new(I_PolyToken.address, web3.utils.toWei("500","ether"), 0, 0, {from:account_polymath}); + + assert.notEqual( + P_WeightedVoteCheckpointFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "WeightedVoteCheckpointFactory contract with fees was not deployed" + ); + + // STEP 7: Deploy the DummySTOFactory + + I_DummySTOFactory = await DummySTOFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_DummySTOFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "DummySTOFactory contract was not deployed" + ); + + + // Step 8: Deploy the STFactory contract + + I_STFactory = await STFactory.new(I_GeneralTransferManagerFactory.address, {from : account_polymath }); + + assert.notEqual( + I_STFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "STFactory contract was not deployed", + ); + + // Step 9: Deploy the SecurityTokenRegistry contract + + I_SecurityTokenRegistry = await SecurityTokenRegistry.new({from: account_polymath }); + + assert.notEqual( + I_SecurityTokenRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "SecurityTokenRegistry contract was not deployed", + ); + + // Step 10: Deploy the proxy and attach the implementation contract to it. + I_SecurityTokenRegistryProxy = await SecurityTokenRegistryProxy.new({from: account_polymath}); + let bytesProxy = encodeProxyCall(STRProxyParameters, [I_PolymathRegistry.address, I_STFactory.address, initRegFee, initRegFee, I_PolyToken.address, account_polymath]); + await I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, {from: account_polymath}); + I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + + // Step 11: update the registries addresses from the PolymathRegistry contract + await I_PolymathRegistry.changeAddress("PolyToken", I_PolyToken.address, {from: account_polymath}) + await I_PolymathRegistry.changeAddress("ModuleRegistry", I_ModuleRegistryProxy.address, {from: account_polymath}); + await I_PolymathRegistry.changeAddress("FeatureRegistry", I_FeatureRegistry.address, {from: account_polymath}); + await I_PolymathRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistryProxy.address, {from: account_polymath}); + await I_MRProxied.updateFromRegistry({from: account_polymath}); + + // STEP 8: Register the Modules with the ModuleRegistry contract + + // (A) : Register the GeneralTransferManagerFactory + await I_MRProxied.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + + // (B) : Register the GeneralDelegateManagerFactory + await I_MRProxied.registerModule(I_WeightedVoteCheckpointFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_WeightedVoteCheckpointFactory.address, true, { from: account_polymath }); + + // (B) : Register the Paid GeneralDelegateManagerFactory + await I_MRProxied.registerModule(P_WeightedVoteCheckpointFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(P_WeightedVoteCheckpointFactory.address, true, { from: account_polymath }); + + // (C) : Register the STOFactory + await I_MRProxied.registerModule(I_DummySTOFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_DummySTOFactory.address, true, { from: account_polymath }); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${PolymathRegistry.address} + SecurityTokenRegistryProxy: ${SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${SecurityTokenRegistry.address} + ModuleRegistryProxy ${ModuleRegistryProxy.address} + ModuleRegistry: ${ModuleRegistry.address} + FeatureRegistry: ${FeatureRegistry.address} + + STFactory: ${STFactory.address} + GeneralTransferManagerFactory: ${GeneralTransferManagerFactory.address} + GeneralPermissionManagerFactory: ${GeneralPermissionManagerFactory.address} + + DummySTOFactory: ${I_DummySTOFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async() => { + + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner}); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; + I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + }); + + it("Should fail to attach the WeightedVoteCheckpoint module to the security token if fee not paid", async () => { + let errorThrown = false; + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + try { + const tx = await I_SecurityToken.addModule(P_WeightedVoteCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { from: token_owner }); + } catch(error) { + console.log(` tx -> failed because setup fee is not paid`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should successfully attach the WeightedVoteCheckpoint module to the security token after fees been paid", async () => { + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), {from: token_owner}); + const tx = await I_SecurityToken.addModule(P_WeightedVoteCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { from: token_owner }); + console.log("weightVoteFactory PAID Address is " + P_WeightedVoteCheckpointFactory.address); + console.log(tx.logs); + assert.equal(tx.logs[3].args._types[0].toNumber(), checkpointKey, "WeightedVoteCheckpoint doesn't get deployed"); + assert.equal(web3.utils.hexToUtf8(tx.logs[3].args._name),"WeightedVoteCheckpoint","WeightedVoteCheckpoint module was not added"); + P_WeightedVoteCheckpoint = WeightedVoteCheckpoint.at(tx.logs[3].args._module); + }); + + it("Should successfully attach the Weighted Vote Checkpoint factory with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_WeightedVoteCheckpointFactory.address, "0x", 0, 0, { from: token_owner }); + console.log("weightVoteFactory Address is " + I_WeightedVoteCheckpointFactory.address); + console.log(tx.logs); + assert.equal(tx.logs[2].args._types[0].toNumber(), checkpointKey, "WeightedVoteCheckpoint doesn't get deployed"); + assert.equal(web3.utils.hexToUtf8(tx.logs[2].args._name),"WeightedVoteCheckpoint","WeightedVoteCheckpoint module was not added"); + I_WeightedVoteCheckpoint = WeightedVoteCheckpoint.at(tx.logs[2].args._module); + }); + }); + + describe("Preparation", async() => { + it("Should successfully mint tokens for first investor account", async() => { + await I_GeneralTransferManager.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(30), + true, + { + from: account_issuer, + gas: 500000 + }); + await I_SecurityToken.mint(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); + assert.equal(await I_SecurityToken.balanceOf(account_investor1), web3.utils.toWei('1', 'ether')); + }); + + it("Should successfully mint tokens for second investor account", async() => { + await I_GeneralTransferManager.modifyWhitelist( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(30), + true, + { + from: account_issuer, + gas: 500000 + }); + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('2', 'ether'), { from: token_owner }); + assert.equal(await I_SecurityToken.balanceOf(account_investor2), web3.utils.toWei('2', 'ether')); + }); + }); + + describe("Create ballot", async() => { + + it("Should fail to create a new ballot if not owner", async() => { + let errorThrown = false; + try { + let tx = await I_WeightedVoteCheckpoint.createBallot(duration.hours(2), { from: account_temp }); + } catch(error) { + console.log(` tx -> failed because msg.sender is not owner`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should successfully create a new ballot", async() => { + let tx = await I_WeightedVoteCheckpoint.createBallot(duration.hours(2), { from: token_owner }); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "New ballot should be created at checkpoint 1"); + }); + }); + + describe("Create custom ballot", async() => { + + it("Should fail to create a new custom ballot with endTime before startTime", async() => { + let errorThrown = false; + try { + let startTime = latestTime() + duration.minutes(10); + let endTime = latestTime() + duration.minutes(5); + let tx = await I_WeightedVoteCheckpoint.createCustomBallot(startTime,endTime, 1, { from: token_owner }); + } catch(error) { + console.log(` tx -> failed because endTime before startTime`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should fail to create a new custom ballot if checkpointId does not exist", async() => { + let errorThrown = false; + try { + let startTime = latestTime() + duration.minutes(10); + let endTime = latestTime() + duration.minutes(15); + let tx = await I_WeightedVoteCheckpoint.createCustomBallot(startTime,endTime, 10, { from: token_owner }); + } catch(error) { + console.log(` tx -> failed because checkpointId does not exist`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should fail to create a new custom ballot if not owner", async() => { + let errorThrown = false; + try { + let startTime = latestTime() + duration.minutes(10); + let endTime = latestTime() + duration.minutes(15); + let tx = await I_WeightedVoteCheckpoint.createCustomBallot(startTime,endTime, 1, { from: account_temp }); + } catch(error) { + console.log(` tx -> failed because msg.sender is not owner`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should successfully create a new custom ballot", async() => { + let startTime = latestTime() + duration.minutes(10); + let endTime = latestTime() + duration.minutes(15); + let tx = await I_WeightedVoteCheckpoint.createCustomBallot(startTime,endTime, 1, { from: token_owner }); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "New ballot should be created at checkpoint 1"); + }); + }); + + describe("Cast vote", async() => { + + it("Should fail to cast a vote if token balance is zero", async() => { + let errorThrown = false; + try { + let tx = await I_WeightedVoteCheckpoint.castVote(true,0, { from: account_investor3 }); + } catch(error) { + console.log(` tx -> failed because token balance is zero`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should fail to cast a vote if voting period has not started", async() => { + let errorThrown = false; + try { + let tx = await I_WeightedVoteCheckpoint.castVote(true,1, { from: account_investor1 }); + } catch(error) { + console.log(` tx -> failed because voting period has not started`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should fail to cast a vote if voting period has ended", async() => { + await increaseTime(duration.minutes(20)); + let errorThrown = false; + try { + let tx = await I_WeightedVoteCheckpoint.castVote(true,1, { from: account_investor1 }); + } catch(error) { + console.log(` tx -> failed because voting period has ended`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Should successfully cast a vote from first investor", async() => { + let tx = await I_WeightedVoteCheckpoint.castVote(false, 0, { from: account_investor1 }); + + console.log(tx.logs); + + assert.equal(tx.logs[0].args._investor, account_investor1, "Failed to record vote"); + assert.equal(tx.logs[0].args._vote, false, "Failed to record vote"); + assert.equal(tx.logs[0].args._weight, web3.utils.toWei('1', 'ether'), "Failed to record vote"); + assert.equal(tx.logs[0].args._ballotId, 0, "Failed to record vote"); + assert.equal(tx.logs[0].args._time, latestTime(), "Failed to record vote"); + }); + + it("Should successfully cast a vote from second investor", async() => { + let tx = await I_WeightedVoteCheckpoint.castVote(true, 0, { from: account_investor2 }); + + assert.equal(tx.logs[0].args._investor, account_investor2, "Failed to record vote"); + assert.equal(tx.logs[0].args._vote, true, "Failed to record vote"); + assert.equal(tx.logs[0].args._weight, web3.utils.toWei('2', 'ether'), "Failed to record vote"); + assert.equal(tx.logs[0].args._ballotId, 0, "Failed to record vote"); + assert.equal(tx.logs[0].args._time, latestTime(), "Failed to record vote"); + }); + + it("Should fail to cast a vote again", async() => { + let errorThrown = false; + try { + let tx = await I_WeightedVoteCheckpoint.castVote(false,0, { from: account_investor1 }); + } catch(error) { + console.log(` tx -> failed because holder already voted`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + }); + + describe("Get results", async() => { + + it("Should successfully get the results", async() => { + let tx = await I_WeightedVoteCheckpoint.getResults(0, { from: token_owner }); + assert.equal(tx[0], web3.utils.toWei('2', 'ether'), "Failed to get results"); + assert.equal(tx[1], web3.utils.toWei('1', 'ether'), "Failed to get results"); + assert.equal(tx[2], 0, "Failed to get results"); + }); + }); + + describe("Active/Deactive Ballot", async() => { + + it("Should successfully deactive the ballot", async() => { + let tx = await I_WeightedVoteCheckpoint.setActiveStatsBallot(0, false, { from: token_owner }); + let tx2 = await I_WeightedVoteCheckpoint.ballots(0, { from: token_owner }); + assert.equal(tx2[7], false); + }); + + it("Should fail to cast a vote if ballot is deactivated", async() => { + let errorThrown = false; + try { + let tx = await I_WeightedVoteCheckpoint.castVote(true,0, { from: account_investor1 }); + } catch(error) { + console.log(` tx -> failed because ballot is deactivated`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + + it("Should successfully active the same ballot again", async() => { + let tx = await I_WeightedVoteCheckpoint.setActiveStatsBallot(0, true, { from: token_owner }); + let tx2 = await I_WeightedVoteCheckpoint.ballots(0, { from: token_owner }); + assert.equal(tx2[7], true); + }); + }); + +});