diff --git a/contracts/mocks/MockBurnFactory.sol b/contracts/mocks/MockBurnFactory.sol index 5c9e7e793..efaacea21 100644 --- a/contracts/mocks/MockBurnFactory.sol +++ b/contracts/mocks/MockBurnFactory.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.24; import "./MockRedemptionManager.sol"; -import "../modules/Burn/TrackedRedemptionFactory.sol"; +import "../modules/Experimental/Burn/TrackedRedemptionFactory.sol"; /** * @title Mock Contract Not fit for production environment diff --git a/contracts/mocks/MockRedemptionManager.sol b/contracts/mocks/MockRedemptionManager.sol index 58177e280..70bc9bc7e 100644 --- a/contracts/mocks/MockRedemptionManager.sol +++ b/contracts/mocks/MockRedemptionManager.sol @@ -1,12 +1,12 @@ pragma solidity ^0.4.24; -import "../modules/Burn/TrackedRedemption.sol"; +import "../modules/Experimental/Burn/TrackedRedemption.sol"; /** * @title Burn module for burning tokens and keeping track of burnt amounts */ contract MockRedemptionManager is TrackedRedemption { - + mapping (address => uint256) tokenToRedeem; event RedeemedTokenByOwner(address _investor, address _byWhoom, uint256 _value, uint256 _timestamp); diff --git a/contracts/modules/Burn/TrackedRedemption.sol b/contracts/modules/Experimental/Burn/TrackedRedemption.sol similarity index 93% rename from contracts/modules/Burn/TrackedRedemption.sol rename to contracts/modules/Experimental/Burn/TrackedRedemption.sol index 493a74c02..0a907b467 100644 --- a/contracts/modules/Burn/TrackedRedemption.sol +++ b/contracts/modules/Experimental/Burn/TrackedRedemption.sol @@ -1,8 +1,8 @@ pragma solidity ^0.4.24; -import "./IBurn.sol"; -import "../Module.sol"; -import "../../interfaces/ISecurityToken.sol"; +import "../../Burn/IBurn.sol"; +import "../../Module.sol"; +import "../../../interfaces/ISecurityToken.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** @@ -41,7 +41,7 @@ contract TrackedRedemption is IBurn, Module { redeemedTokens[msg.sender] = redeemedTokens[msg.sender].add(_value); emit Redeemed(msg.sender, _value, now); } - + /** * @notice Returns the permissions flag that are associated with CountTransferManager */ diff --git a/contracts/modules/Burn/TrackedRedemptionFactory.sol b/contracts/modules/Experimental/Burn/TrackedRedemptionFactory.sol similarity index 98% rename from contracts/modules/Burn/TrackedRedemptionFactory.sol rename to contracts/modules/Experimental/Burn/TrackedRedemptionFactory.sol index abcdbadc3..41cfe7e4c 100644 --- a/contracts/modules/Burn/TrackedRedemptionFactory.sol +++ b/contracts/modules/Experimental/Burn/TrackedRedemptionFactory.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.24; import "./TrackedRedemption.sol"; -import "../ModuleFactory.sol"; +import "../../ModuleFactory.sol"; /** * @title Factory for deploying GeneralTransferManager module diff --git a/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol b/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol new file mode 100644 index 000000000..097a44f50 --- /dev/null +++ b/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol @@ -0,0 +1,155 @@ +pragma solidity ^0.4.24; + +import "./../../Checkpoint/ICheckpoint.sol"; +import "../../TransferManager/ITransferManager.sol"; +import "../../../interfaces/ISecurityToken.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +/** + * @title Burn module for burning tokens and keeping track of burnt amounts + */ +contract ScheduledCheckpoint is ICheckpoint, ITransferManager { + using SafeMath for uint256; + + struct Schedule { + bytes32 name; + uint256 startTime; + uint256 nextTime; + uint256 interval; + uint256 index; + uint256[] checkpointIds; + uint256[] timestamps; + uint256[] periods; + } + + bytes32[] public names; + + mapping (bytes32 => Schedule) public schedules; + + event AddSchedule(bytes32 _name, uint256 _startTime, uint256 _interval, uint256 _timestamp); + event RemoveSchedule(bytes32 _name, uint256 _timestamp); + + /** + * @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 This function returns the signature of configure function + */ + function getInitFunction() public pure returns (bytes4) { + return bytes4(0); + } + + /** + * @notice adds a new schedule for checkpoints + * @param _name name of the new schedule (must be unused) + * @param _startTime start time of the schedule (first checkpoint) + * @param _interval interval at which checkpoints should be created + */ + function addSchedule(bytes32 _name, uint256 _startTime, uint256 _interval) external onlyOwner { + require(_startTime > now, "Start time must be in the future"); + require(schedules[_name].name == bytes32(0), "Name already in use"); + schedules[_name].name = _name; + schedules[_name].startTime = _startTime; + schedules[_name].nextTime = _startTime; + schedules[_name].interval = _interval; + schedules[_name].index = names.length; + names.push(_name); + emit AddSchedule(_name, _startTime, _interval, now); + } + + /** + * @notice removes a schedule for checkpoints + * @param _name name of the schedule to be removed + */ + function removeSchedule(bytes32 _name) external onlyOwner { + require(schedules[_name].name == _name, "Name does not exist"); + uint256 index = schedules[_name].index; + names[index] = names[names.length - 1]; + names.length--; + if (index != names.length) { + schedules[names[index]].index = index; + } + delete schedules[_name]; + emit RemoveSchedule(_name, now); + } + + + /** + * @notice Used to create checkpoints that correctly reflect balances + * @param _isTransfer whether or not an actual transfer is occuring + * @return always returns Result.NA + */ + function verifyTransfer(address /* _from */, address /* _to */, uint256 /* _amount */, bytes /* _data */, bool _isTransfer) public returns(Result) { + require(_isTransfer == false || msg.sender == securityToken, "Sender is not owner"); + if (paused || !_isTransfer) { + return Result.NA; + } + _updateAll(); + return Result.NA; + } + + /** + * @notice gets schedule details + * @param _name name of the schedule + */ + function getSchedule(bytes32 _name) view external returns(bytes32, uint256, uint256, uint256, uint256[], uint256[], uint256[]) { + return ( + schedules[_name].name, + schedules[_name].startTime, + schedules[_name].nextTime, + schedules[_name].interval, + schedules[_name].checkpointIds, + schedules[_name].timestamps, + schedules[_name].periods + ); + } + + /** + * @notice manually triggers update outside of transfer request for named schedule (can be used to reduce user gas costs) + * @param _name name of the schedule + */ + function update(bytes32 _name) external onlyOwner { + _update(_name); + } + + function _update(bytes32 _name) internal { + Schedule storage schedule = schedules[_name]; + if (schedule.nextTime <= now) { + uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); + uint256 periods = now.sub(schedule.nextTime).div(schedule.interval).add(1); + schedule.timestamps.push(schedule.nextTime); + schedule.nextTime = periods.mul(schedule.interval).add(schedule.nextTime); + schedule.checkpointIds.push(checkpointId); + schedule.periods.push(periods); + } + } + + /** + * @notice manually triggers update outside of transfer request for all schedules (can be used to reduce user gas costs) + */ + function updateAll() onlyOwner external { + _updateAll(); + } + + function _updateAll() internal { + uint256 i; + for (i = 0; i < names.length; i++) { + _update(names[i]); + } + } + + /** + * @notice Return the permissions flag that are associated with CountTransferManager + */ + function getPermissions() view external returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](0); + return allPermissions; + } +} diff --git a/contracts/modules/Experimental/Mixed/ScheduledCheckpointFactory.sol b/contracts/modules/Experimental/Mixed/ScheduledCheckpointFactory.sol new file mode 100644 index 000000000..1b5daac29 --- /dev/null +++ b/contracts/modules/Experimental/Mixed/ScheduledCheckpointFactory.sol @@ -0,0 +1,102 @@ +pragma solidity ^0.4.24; + +import "./ScheduledCheckpoint.sol"; +import "../../ModuleFactory.sol"; + +/** + * @title Factory for deploying EtherDividendCheckpoint module + */ +contract ScheduledCheckpointFactory 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 = "ScheduledCheckpoint"; + title = "Schedule Checkpoints"; + description = "Allows you to schedule checkpoints in the future"; + 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"); + address scheduledCheckpoint = new ScheduledCheckpoint(msg.sender, address(polyToken)); + emit GenerateModuleFromFactory(scheduledCheckpoint, getName(), address(this), msg.sender, setupCost, now); + return scheduledCheckpoint; + } + + /** + * @notice Type of the Module factory + */ + function getTypes() external view returns(uint8[]) { + uint8[] memory res = new uint8[](2); + res[0] = 4; + res[1] = 2; + 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 "Schedule a series of future checkpoints by specifying a start time and interval of each checkpoint"; + } + + /** + * @notice Get the tags related to the module factory + */ + function getTags() external view returns(bytes32[]) { + bytes32[] memory availableTags = new bytes32[](2); + availableTags[0] = "Scheduled"; + availableTags[1] = "Checkpoint"; + return availableTags; + } +} diff --git a/contracts/modules/TransferManager/SingleTradeVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTM.sol similarity index 99% rename from contracts/modules/TransferManager/SingleTradeVolumeRestrictionTM.sol rename to contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTM.sol index d86fe5f64..d8e412620 100644 --- a/contracts/modules/TransferManager/SingleTradeVolumeRestrictionTM.sol +++ b/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTM.sol @@ -1,5 +1,6 @@ pragma solidity ^0.4.24; -import "./ITransferManager.sol"; + +import "./../../TransferManager/ITransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** @@ -317,4 +318,4 @@ contract SingleTradeVolumeRestrictionTM is ITransferManager { allPermissions[0] = ADMIN; return allPermissions; } -} \ No newline at end of file +} diff --git a/contracts/modules/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol b/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol similarity index 97% rename from contracts/modules/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol rename to contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol index d92a55813..78a4698fe 100644 --- a/contracts/modules/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol +++ b/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol @@ -1,8 +1,9 @@ pragma solidity ^0.4.24; -import "./../ModuleFactory.sol"; +import "./../../ModuleFactory.sol"; import "./SingleTradeVolumeRestrictionTM.sol"; -import "../../libraries/Util.sol"; +import "../../../libraries/Util.sol"; + /** * @title Factory for deploying SingleTradeVolumeRestrictionManager */ diff --git a/test/helpers/createInstances.js b/test/helpers/createInstances.js index f72ab54e3..2021d350e 100644 --- a/test/helpers/createInstances.js +++ b/test/helpers/createInstances.js @@ -14,6 +14,7 @@ const ManualApprovalTransferManagerFactory = artifacts.require("./ManualApproval const SingleTradeVolumeRestrictionManagerFactory = artifacts.require('./SingleTradeVolumeRestrictionTMFactory.sol'); const TrackedRedemptionFactory = artifacts.require("./TrackedRedemptionFactory.sol"); const PercentageTransferManagerFactory = artifacts.require("./PercentageTransferManagerFactory.sol"); +const ScheduledCheckpointFactory = artifacts.require('./ScheduledCheckpointFactory.sol'); const USDTieredSTOFactory = artifacts.require("./USDTieredSTOFactory.sol"); const USDTieredSTOProxyFactory = artifacts.require("./USDTieredSTOProxyFactory"); const ManualApprovalTransferManager = artifacts.require("./ManualApprovalTransferManager"); @@ -37,6 +38,7 @@ const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); let I_USDTieredSTOProxyFactory; let I_USDTieredSTOFactory; let I_TrackedRedemptionFactory; +let I_ScheduledCheckpointFactory; let I_MockBurnFactory; let I_MockWrongTypeBurnFactory; let I_SingleTradeVolumeRestrictionManagerFactory; @@ -265,6 +267,18 @@ export async function deploySingleTradeVolumeRMAndVerified(accountPolymath, MRPr return new Array(I_SingleTradeVolumeRestrictionManagerFactory); } +export async function deployScheduleCheckpointAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { + I_ScheduledCheckpointFactory = await ScheduledCheckpointFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); + assert.notEqual( + I_ScheduledCheckpointFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "ScheduledCheckpointFactory contract was not deployed" + ); + + await registerAndVerifyByMR(I_ScheduledCheckpointFactory.address, accountPolymath, MRProxyInstance); + return new Array(I_ScheduledCheckpointFactory); +} + /// Deploy the Permission Manager export async function deployGPMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { @@ -311,7 +325,7 @@ export async function deployCappedSTOAndVerifyed(accountPolymath, MRProxyInstanc export async function deployPresaleSTOAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { I_PreSaleSTOFactory = await PreSaleSTOFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); - + assert.notEqual( I_PreSaleSTOFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", @@ -326,14 +340,14 @@ export async function deployUSDTieredSTOAndVerified(accountPolymath, MRProxyInst I_USDTieredSTOProxyFactory = await USDTieredSTOProxyFactory.new({from: accountPolymath}); I_USDTieredSTOFactory = await USDTieredSTOFactory.new(polyToken, setupCost, 0, 0, I_USDTieredSTOProxyFactory.address, { from: accountPolymath }); - + assert.notEqual( I_USDTieredSTOFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", "USDTieredSTOFactory contract was not deployed" ); - await registerAndVerifyByMR(I_USDTieredSTOFactory.address, accountPolymath, MRProxyInstance); + await registerAndVerifyByMR(I_USDTieredSTOFactory.address, accountPolymath, MRProxyInstance); return new Array(I_USDTieredSTOFactory); } @@ -419,4 +433,4 @@ function mergeReturn(returnData) { } } return returnArray; -} \ No newline at end of file +} diff --git a/test/y_scheduled_checkpoints.js b/test/y_scheduled_checkpoints.js new file mode 100644 index 000000000..5fcc03a74 --- /dev/null +++ b/test/y_scheduled_checkpoints.js @@ -0,0 +1,389 @@ +import latestTime from './helpers/latestTime'; +import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { encodeProxyCall, encodeModuleCall } from './helpers/encodeCall'; +import { setUpPolymathNetwork, deployScheduleCheckpointAndVerified } from "./helpers/createInstances"; + +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const ScheduledCheckpoint = artifacts.require('./ScheduledCheckpoint.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('ScheduledCheckpoint', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + + // 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_ScheduledCheckpointFactory; + let I_GeneralPermissionManager; + let I_ScheduledCheckpoint; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_STRProxied; + let I_MRProxied; + let I_STFactory; + let I_SecurityToken; + let I_PolyToken; + let I_PolymathRegistry; + + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + + account_investor1 = accounts[7]; + account_investor2 = accounts[8]; + account_investor3 = accounts[9]; + + // 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; + + // STEP 2: Deploy the ScheduleCheckpointModule + [I_ScheduledCheckpointFactory] = await deployScheduleCheckpointAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistry: ${I_ModuleRegistry.address} + ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.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); + + }); + + }); + + describe("Buy tokens using on-chain whitelist", async() => { + + it("Should successfully attach the ScheduledCheckpoint with the security token", async () => { + await I_SecurityToken.changeGranularity(1, {from: token_owner}); + const tx = await I_SecurityToken.addModule(I_ScheduledCheckpointFactory.address, "", 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), 4, "ScheduledCheckpoint doesn't get deployed"); + assert.equal(tx.logs[2].args._types[1].toNumber(), 2, "ScheduledCheckpoint doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "ScheduledCheckpoint", + "ScheduledCheckpoint module was not added" + ); + I_ScheduledCheckpoint = ScheduledCheckpoint.at(tx.logs[2].args._module); + }); + + let startTime; + let interval; + it("Should create a daily checkpoint", async () => { + startTime = latestTime() + 100; + interval = 24 * 60 * 60; + console.log("Creating scheduled CP: " + startTime, interval); + await I_ScheduledCheckpoint.addSchedule("CP1", startTime, interval, {from: token_owner}); + console.log("2: " + latestTime()); + }); + + it("Remove (temp) daily checkpoint", async () => { + let snap_Id = await takeSnapshot(); + await I_ScheduledCheckpoint.removeSchedule("CP1", {from: token_owner}); + await revertToSnapshot(snap_Id); + }); + + it("Should Buy the tokens for account_investor1", async() => { + // Add the Investor in to the whitelist + console.log("3: " + latestTime()); + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 6000000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Jump time + console.log("4: " + latestTime()); + + await increaseTime(5000); + // We should be after the first scheduled checkpoint, and before the second + console.log("5: " + latestTime()); + + assert.isTrue(latestTime() > startTime); + assert.isTrue(latestTime() <= startTime + interval); + console.log("6: " + latestTime()); + + // Mint some tokens + await I_SecurityToken.mint(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + }); + + it("Should have checkpoint created with correct balances", async() => { + let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); + checkSchedule(cp1, "CP1", startTime, startTime + interval, interval, [1], [startTime], [1]); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 1)).toNumber(), 0); + }); + + it("Should Buy some more tokens for account_investor2", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 6000000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); + + // We should be after the first scheduled checkpoint, and before the second + assert.isTrue(latestTime() > startTime); + assert.isTrue(latestTime() <= startTime + interval); + + // Mint some tokens + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + }); + + it("No additional checkpoints created", async() => { + let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); + checkSchedule(cp1, "CP1", startTime, startTime + interval, interval, [1], [startTime], [1]); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 1)).toNumber(), 0); + }); + + it("Add a new token holder - account_investor3", async() => { + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor3, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 6000000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Jump time + await increaseTime(interval); + // We should be after the first scheduled checkpoint, and before the second + assert.isTrue(latestTime() > startTime + interval); + assert.isTrue(latestTime() <= startTime + (2 * interval)); + + // Add the Investor in to the whitelist + // Mint some tokens + await I_SecurityToken.mint(account_investor3, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + }); + + it("Should have new checkpoint created with correct balances", async() => { + let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); + checkSchedule(cp1, "CP1", startTime, startTime + (2 * interval), interval, [1, 2], [startTime, startTime + interval], [1, 1]); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 2)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 2)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 2)).toNumber(), web3.utils.toWei('1', 'ether')); + }); + + it("Should have correct balances for investor 3 after new checkpoint", async() => { + // Jump time + await increaseTime(2 * interval); + // We should be after the first scheduled checkpoint, and before the second + assert.isTrue(latestTime() > startTime + (3 * interval)); + assert.isTrue(latestTime() <= startTime + (4 * interval)); + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('0.5', 'ether'), { from: account_investor1 }); + let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); + checkSchedule(cp1, "CP1", startTime, startTime + (4 * interval), interval, [1, 2, 3], [startTime, startTime + interval, startTime + (2 * interval)], [1, 1, 2]); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 2)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 2)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 2)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + + }); + + it("Manually update checkpoints", async() => { + await increaseTime(interval); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let cp1 = await I_ScheduledCheckpoint.getSchedule("CP1"); + checkSchedule(cp1, "CP1", startTime, startTime + (5 * interval), interval, [1, 2, 3, 4], [startTime, startTime + interval, startTime + (2 * interval), startTime + (4 * interval)], [1, 1, 2, 1]); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 2)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor3, 4)).toNumber(), web3.utils.toWei('1.5', 'ether')); + + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 2)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor2, 4)).toNumber(), web3.utils.toWei('1', 'ether')); + + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 0)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 1)).toNumber(), 0); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 2)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 3)).toNumber(), web3.utils.toWei('1', 'ether')); + assert.equal((await I_SecurityToken.balanceOfAt(account_investor1, 4)).toNumber(), web3.utils.toWei('0.5', 'ether')); + + }); + + it("Should get the permission", async() => { + let perm = await I_ScheduledCheckpoint.getPermissions.call(); + assert.equal(perm.length, 0); + }); + + }); + +}); + +function checkSchedule(schedule, name, startTime, nextTime, interval, checkpoints, timestamps, periods) { + assert.equal(web3.utils.toAscii(schedule[0]).replace(/\u0000/g, ''), name); + assert.equal(schedule[1].toNumber(), startTime); + assert.equal(schedule[2].toNumber(), nextTime); + assert.equal(schedule[3].toNumber(), interval); + assert.equal(schedule[4].length, checkpoints.length); + for (let i = 0; i < checkpoints.length; i++) { + assert.equal(schedule[4][i].toNumber(), checkpoints[i]); + } + assert.equal(schedule[5].length, timestamps.length); + for (let i = 0; i < timestamps.length; i++) { + assert.equal(schedule[5][i].toNumber(), timestamps[i]); + } + assert.equal(schedule[6].length, periods.length); + for (let i = 0; i < periods.length; i++) { + assert.equal(schedule[6][i].toNumber(), periods[i]); + } +}