diff --git a/contracts/modules/Wallet/IWallet.sol b/contracts/modules/Wallet/IWallet.sol
new file mode 100644
index 000000000..ea5c918d8
--- /dev/null
+++ b/contracts/modules/Wallet/IWallet.sol
@@ -0,0 +1,19 @@
+pragma solidity ^0.4.24;
+
+import "../../Pausable.sol";
+import "../Module.sol";
+
+/**
+ * @title Interface to be implemented by all Wallet modules
+ * @dev abstract contract
+ */
+contract IWallet is Module, Pausable {
+
+ function unpause() public onlyOwner {
+ super._unpause();
+ }
+
+ function pause() public onlyOwner {
+ super._pause();
+ }
+}
diff --git a/contracts/modules/Wallet/VestingEscrowWallet.sol b/contracts/modules/Wallet/VestingEscrowWallet.sol
new file mode 100644
index 000000000..17f6dbb08
--- /dev/null
+++ b/contracts/modules/Wallet/VestingEscrowWallet.sol
@@ -0,0 +1,558 @@
+pragma solidity ^0.4.24;
+
+import "openzeppelin-solidity/contracts/math/SafeMath.sol";
+import "./VestingEscrowWalletStorage.sol";
+import "./IWallet.sol";
+
+/**
+ * @title Wallet for core vesting escrow functionality
+ */
+contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet {
+ using SafeMath for uint256;
+
+ bytes32 public constant ADMIN = "ADMIN";
+
+ // States used to represent the status of the schedule
+ enum State {CREATED, STARTED, COMPLETED}
+
+ // Emit when new schedule is added
+ event AddSchedule(
+ address indexed _beneficiary,
+ bytes32 _templateName,
+ uint256 _startTime
+ );
+ // Emit when schedule is modified
+ event ModifySchedule(
+ address indexed _beneficiary,
+ bytes32 _templateName,
+ uint256 _startTime
+ );
+ // Emit when all schedules are revoked for user
+ event RevokeAllSchedules(address indexed _beneficiary);
+ // Emit when schedule is revoked
+ event RevokeSchedule(address indexed _beneficiary, bytes32 _templateName);
+ // Emit when tokes are deposited to wallet
+ event DepositTokens(uint256 _numberOfTokens, address _sender);
+ // Emit when all unassigned tokens are sent to treasury
+ event SendToTreasury(uint256 _numberOfTokens, address _sender);
+ // Emit when is sent tokes to user
+ event SendTokens(address indexed _beneficiary, uint256 _numberOfTokens);
+ // Emit when template is added
+ event AddTemplate(bytes32 _name, uint256 _numberOfTokens, uint256 _duration, uint256 _frequency);
+ // Emit when template is removed
+ event RemoveTemplate(bytes32 _name);
+ // Emit when the treasury wallet gets changed
+ event TreasuryWalletChanged(address _newWallet, address _oldWallet);
+
+ /**
+ * @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 the configure function
+ */
+ function getInitFunction() public pure returns (bytes4) {
+ return bytes4(keccak256("configure(address)"));
+ }
+
+ /**
+ * @notice Used to initialize the treasury wallet address
+ * @param _treasuryWallet Address of the treasury wallet
+ */
+ function configure(address _treasuryWallet) public onlyFactory {
+ require(_treasuryWallet != address(0), "Invalid address");
+ treasuryWallet = _treasuryWallet;
+ }
+
+ /**
+ * @notice Used to change the treasury wallet address
+ * @param _newTreasuryWallet Address of the treasury wallet
+ */
+ function changeTreasuryWallet(address _newTreasuryWallet) public onlyOwner {
+ require(_newTreasuryWallet != address(0));
+ emit TreasuryWalletChanged(_newTreasuryWallet, treasuryWallet);
+ treasuryWallet = _newTreasuryWallet;
+ }
+
+ /**
+ * @notice Used to deposit tokens from treasury wallet to the vesting escrow wallet
+ * @param _numberOfTokens Number of tokens that should be deposited
+ */
+ function depositTokens(uint256 _numberOfTokens) external withPerm(ADMIN) {
+ _depositTokens(_numberOfTokens);
+ }
+
+ function _depositTokens(uint256 _numberOfTokens) internal {
+ require(_numberOfTokens > 0, "Should be > 0");
+ require(
+ ISecurityToken(securityToken).transferFrom(msg.sender, address(this), _numberOfTokens),
+ "Failed transferFrom due to insufficent Allowance provided"
+ );
+ unassignedTokens = unassignedTokens.add(_numberOfTokens);
+ emit DepositTokens(_numberOfTokens, msg.sender);
+ }
+
+ /**
+ * @notice Sends unassigned tokens to the treasury wallet
+ * @param _amount Amount of tokens that should be send to the treasury wallet
+ */
+ function sendToTreasury(uint256 _amount) external withPerm(ADMIN) {
+ require(_amount > 0, "Amount cannot be zero");
+ require(_amount <= unassignedTokens, "Amount is greater than unassigned tokens");
+ uint256 amount = unassignedTokens;
+ unassignedTokens = 0;
+ require(ISecurityToken(securityToken).transfer(treasuryWallet, amount), "Transfer failed");
+ emit SendToTreasury(amount, msg.sender);
+ }
+
+ /**
+ * @notice Pushes available tokens to the beneficiary's address
+ * @param _beneficiary Address of the beneficiary who will receive tokens
+ */
+ function pushAvailableTokens(address _beneficiary) public withPerm(ADMIN) {
+ _sendTokens(_beneficiary);
+ }
+
+ /**
+ * @notice Used to withdraw available tokens by beneficiary
+ */
+ function pullAvailableTokens() external {
+ _sendTokens(msg.sender);
+ }
+
+ /**
+ * @notice Adds template that can be used for creating schedule
+ * @param _name Name of the template will be created
+ * @param _numberOfTokens Number of tokens that should be assigned to schedule
+ * @param _duration Duration of the vesting schedule
+ * @param _frequency Frequency of the vesting schedule
+ */
+ function addTemplate(bytes32 _name, uint256 _numberOfTokens, uint256 _duration, uint256 _frequency) external withPerm(ADMIN) {
+ _addTemplate(_name, _numberOfTokens, _duration, _frequency);
+ }
+
+ function _addTemplate(bytes32 _name, uint256 _numberOfTokens, uint256 _duration, uint256 _frequency) internal {
+ require(_name != bytes32(0), "Invalid name");
+ require(!_isTemplateExists(_name), "Already exists");
+ _validateTemplate(_numberOfTokens, _duration, _frequency);
+ templateNames.push(_name);
+ templates[_name] = Template(_numberOfTokens, _duration, _frequency, templateNames.length - 1);
+ emit AddTemplate(_name, _numberOfTokens, _duration, _frequency);
+ }
+
+ /**
+ * @notice Removes template with a given name
+ * @param _name Name of the template that will be removed
+ */
+ function removeTemplate(bytes32 _name) external withPerm(ADMIN) {
+ require(_isTemplateExists(_name), "Template not found");
+ require(templateToUsers[_name].length == 0, "Template is used");
+ uint256 index = templates[_name].index;
+ if (index != templateNames.length - 1) {
+ templateNames[index] = templateNames[templateNames.length - 1];
+ templates[templateNames[index]].index = index;
+ }
+ templateNames.length--;
+ // delete template data
+ delete templates[_name];
+ emit RemoveTemplate(_name);
+ }
+
+ /**
+ * @notice Returns count of the templates those can be used for creating schedule
+ * @return Count of the templates
+ */
+ function getTemplateCount() external view returns(uint256) {
+ return templateNames.length;
+ }
+
+ /**
+ * @notice Gets the list of the template names those can be used for creating schedule
+ * @return bytes32 Array of all template names were created
+ */
+ function getAllTemplateNames() external view returns(bytes32[]) {
+ return templateNames;
+ }
+
+ /**
+ * @notice Adds vesting schedules for each of the beneficiary's address
+ * @param _beneficiary Address of the beneficiary for whom it is scheduled
+ * @param _templateName Name of the template that will be created
+ * @param _numberOfTokens Total number of tokens for created schedule
+ * @param _duration Duration of the created vesting schedule
+ * @param _frequency Frequency of the created vesting schedule
+ * @param _startTime Start time of the created vesting schedule
+ */
+ function addSchedule(
+ address _beneficiary,
+ bytes32 _templateName,
+ uint256 _numberOfTokens,
+ uint256 _duration,
+ uint256 _frequency,
+ uint256 _startTime
+ )
+ external
+ withPerm(ADMIN)
+ {
+ _addSchedule(_beneficiary, _templateName, _numberOfTokens, _duration, _frequency, _startTime);
+ }
+
+ function _addSchedule(
+ address _beneficiary,
+ bytes32 _templateName,
+ uint256 _numberOfTokens,
+ uint256 _duration,
+ uint256 _frequency,
+ uint256 _startTime
+ )
+ internal
+ {
+ _addTemplate(_templateName, _numberOfTokens, _duration, _frequency);
+ _addScheduleFromTemplate(_beneficiary, _templateName, _startTime);
+ }
+
+ /**
+ * @notice Adds vesting schedules from template for the beneficiary
+ * @param _beneficiary Address of the beneficiary for whom it is scheduled
+ * @param _templateName Name of the exists template
+ * @param _startTime Start time of the created vesting schedule
+ */
+ function addScheduleFromTemplate(address _beneficiary, bytes32 _templateName, uint256 _startTime) external withPerm(ADMIN) {
+ _addScheduleFromTemplate(_beneficiary, _templateName, _startTime);
+ }
+
+ function _addScheduleFromTemplate(address _beneficiary, bytes32 _templateName, uint256 _startTime) internal {
+ require(_beneficiary != address(0), "Invalid address");
+ require(_isTemplateExists(_templateName), "Template not found");
+ uint256 index = userToTemplateIndex[_beneficiary][_templateName];
+ require(
+ schedules[_beneficiary].length == 0 ||
+ schedules[_beneficiary][index].templateName != _templateName,
+ "Already added"
+ );
+ require(_startTime >= now, "Date in the past");
+ uint256 numberOfTokens = templates[_templateName].numberOfTokens;
+ if (numberOfTokens > unassignedTokens) {
+ _depositTokens(numberOfTokens.sub(unassignedTokens));
+ }
+ unassignedTokens = unassignedTokens.sub(numberOfTokens);
+ if (!beneficiaryAdded[_beneficiary]) {
+ beneficiaries.push(_beneficiary);
+ beneficiaryAdded[_beneficiary] = true;
+ }
+ schedules[_beneficiary].push(Schedule(_templateName, 0, _startTime));
+ userToTemplates[_beneficiary].push(_templateName);
+ userToTemplateIndex[_beneficiary][_templateName] = schedules[_beneficiary].length - 1;
+ templateToUsers[_templateName].push(_beneficiary);
+ templateToUserIndex[_templateName][_beneficiary] = templateToUsers[_templateName].length - 1;
+ emit AddSchedule(_beneficiary, _templateName, _startTime);
+ }
+
+ /**
+ * @notice Modifies vesting schedules for each of the beneficiary
+ * @param _beneficiary Address of the beneficiary for whom it is modified
+ * @param _templateName Name of the template was used for schedule creation
+ * @param _startTime Start time of the created vesting schedule
+ */
+ function modifySchedule(address _beneficiary, bytes32 _templateName, uint256 _startTime) public withPerm(ADMIN) {
+ _modifySchedule(_beneficiary, _templateName, _startTime);
+ }
+
+ function _modifySchedule(address _beneficiary, bytes32 _templateName, uint256 _startTime) internal {
+ _checkSchedule(_beneficiary, _templateName);
+ require(_startTime > now, "Date in the past");
+ uint256 index = userToTemplateIndex[_beneficiary][_templateName];
+ Schedule storage schedule = schedules[_beneficiary][index];
+ /*solium-disable-next-line security/no-block-members*/
+ require(now < schedule.startTime, "Schedule started");
+ schedule.startTime = _startTime;
+ emit ModifySchedule(_beneficiary, _templateName, _startTime);
+ }
+
+ /**
+ * @notice Revokes vesting schedule with given template name for given beneficiary
+ * @param _beneficiary Address of the beneficiary for whom it is revoked
+ * @param _templateName Name of the template was used for schedule creation
+ */
+ function revokeSchedule(address _beneficiary, bytes32 _templateName) external withPerm(ADMIN) {
+ _checkSchedule(_beneficiary, _templateName);
+ uint256 index = userToTemplateIndex[_beneficiary][_templateName];
+ _sendTokensPerSchedule(_beneficiary, index);
+ uint256 releasedTokens = _getReleasedTokens(_beneficiary, index);
+ unassignedTokens = unassignedTokens.add(templates[_templateName].numberOfTokens.sub(releasedTokens));
+ _deleteUserToTemplates(_beneficiary, _templateName);
+ _deleteTemplateToUsers(_beneficiary, _templateName);
+ emit RevokeSchedule(_beneficiary, _templateName);
+ }
+
+ function _deleteUserToTemplates(address _beneficiary, bytes32 _templateName) internal {
+ uint256 index = userToTemplateIndex[_beneficiary][_templateName];
+ Schedule[] storage userSchedules = schedules[_beneficiary];
+ if (index != userSchedules.length - 1) {
+ userSchedules[index] = userSchedules[userSchedules.length - 1];
+ userToTemplates[_beneficiary][index] = userToTemplates[_beneficiary][userToTemplates[_beneficiary].length - 1];
+ userToTemplateIndex[_beneficiary][userSchedules[index].templateName] = index;
+ }
+ userSchedules.length--;
+ userToTemplates[_beneficiary].length--;
+ delete userToTemplateIndex[_beneficiary][_templateName];
+ }
+
+ function _deleteTemplateToUsers(address _beneficiary, bytes32 _templateName) internal {
+ uint256 templateIndex = templateToUserIndex[_templateName][_beneficiary];
+ if (templateIndex != templateToUsers[_templateName].length - 1) {
+ templateToUsers[_templateName][templateIndex] = templateToUsers[_templateName][templateToUsers[_templateName].length - 1];
+ templateToUserIndex[_templateName][templateToUsers[_templateName][templateIndex]] = templateIndex;
+ }
+ templateToUsers[_templateName].length--;
+ delete templateToUserIndex[_templateName][_beneficiary];
+ }
+
+ /**
+ * @notice Revokes all vesting schedules for given beneficiary's address
+ * @param _beneficiary Address of the beneficiary for whom all schedules will be revoked
+ */
+ function revokeAllSchedules(address _beneficiary) public withPerm(ADMIN) {
+ _revokeAllSchedules(_beneficiary);
+ }
+
+ function _revokeAllSchedules(address _beneficiary) internal {
+ require(_beneficiary != address(0), "Invalid address");
+ _sendTokens(_beneficiary);
+ Schedule[] storage userSchedules = schedules[_beneficiary];
+ for (uint256 i = 0; i < userSchedules.length; i++) {
+ uint256 releasedTokens = _getReleasedTokens(_beneficiary, i);
+ Template memory template = templates[userSchedules[i].templateName];
+ unassignedTokens = unassignedTokens.add(template.numberOfTokens.sub(releasedTokens));
+ delete userToTemplateIndex[_beneficiary][userSchedules[i].templateName];
+ _deleteTemplateToUsers(_beneficiary, userSchedules[i].templateName);
+ }
+ delete schedules[_beneficiary];
+ delete userToTemplates[_beneficiary];
+ emit RevokeAllSchedules(_beneficiary);
+ }
+
+ /**
+ * @notice Returns beneficiary's schedule created using template name
+ * @param _beneficiary Address of the beneficiary who will receive tokens
+ * @param _templateName Name of the template was used for schedule creation
+ * @return beneficiary's schedule data (numberOfTokens, duration, frequency, startTime, claimedTokens, State)
+ */
+ function getSchedule(address _beneficiary, bytes32 _templateName) external view returns(uint256, uint256, uint256, uint256, uint256, State) {
+ _checkSchedule(_beneficiary, _templateName);
+ uint256 index = userToTemplateIndex[_beneficiary][_templateName];
+ Schedule memory schedule = schedules[_beneficiary][index];
+ return (
+ templates[schedule.templateName].numberOfTokens,
+ templates[schedule.templateName].duration,
+ templates[schedule.templateName].frequency,
+ schedule.startTime,
+ schedule.claimedTokens,
+ _getScheduleState(_beneficiary, _templateName)
+ );
+ }
+
+ function _getScheduleState(address _beneficiary, bytes32 _templateName) internal view returns(State) {
+ _checkSchedule(_beneficiary, _templateName);
+ uint256 index = userToTemplateIndex[_beneficiary][_templateName];
+ Schedule memory schedule = schedules[_beneficiary][index];
+ if (now < schedule.startTime) {
+ return State.CREATED;
+ } else if (now > schedule.startTime && now < schedule.startTime.add(templates[_templateName].duration)) {
+ return State.STARTED;
+ } else {
+ return State.COMPLETED;
+ }
+ }
+
+ /**
+ * @notice Returns list of the template names for given beneficiary's address
+ * @param _beneficiary Address of the beneficiary
+ * @return List of the template names that were used for schedule creation
+ */
+ function getTemplateNames(address _beneficiary) external view returns(bytes32[]) {
+ require(_beneficiary != address(0), "Invalid address");
+ return userToTemplates[_beneficiary];
+ }
+
+ /**
+ * @notice Returns count of the schedules were created for given beneficiary
+ * @param _beneficiary Address of the beneficiary
+ * @return Count of beneficiary's schedules
+ */
+ function getScheduleCount(address _beneficiary) external view returns(uint256) {
+ require(_beneficiary != address(0), "Invalid address");
+ return schedules[_beneficiary].length;
+ }
+
+ function _getAvailableTokens(address _beneficiary, uint256 _index) internal view returns(uint256) {
+ Schedule memory schedule = schedules[_beneficiary][_index];
+ uint256 releasedTokens = _getReleasedTokens(_beneficiary, _index);
+ return releasedTokens.sub(schedule.claimedTokens);
+ }
+
+ function _getReleasedTokens(address _beneficiary, uint256 _index) internal view returns(uint256) {
+ Schedule memory schedule = schedules[_beneficiary][_index];
+ Template memory template = templates[schedule.templateName];
+ /*solium-disable-next-line security/no-block-members*/
+ if (now > schedule.startTime) {
+ uint256 periodCount = template.duration.div(template.frequency);
+ /*solium-disable-next-line security/no-block-members*/
+ uint256 periodNumber = (now.sub(schedule.startTime)).div(template.frequency);
+ if (periodNumber > periodCount) {
+ periodNumber = periodCount;
+ }
+ return template.numberOfTokens.mul(periodNumber).div(periodCount);
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * @notice Used to bulk send available tokens for each of the beneficiaries
+ * @param _fromIndex Start index of array of beneficiary's addresses
+ * @param _toIndex End index of array of beneficiary's addresses
+ */
+ function pushAvailableTokensMulti(uint256 _fromIndex, uint256 _toIndex) external withPerm(ADMIN) {
+ require(_toIndex <= beneficiaries.length - 1, "Array out of bound");
+ for (uint256 i = _fromIndex; i <= _toIndex; i++) {
+ if (schedules[beneficiaries[i]].length !=0)
+ pushAvailableTokens(beneficiaries[i]);
+ }
+ }
+
+ /**
+ * @notice Used to bulk add vesting schedules for each of beneficiary
+ * @param _beneficiaries Array of the beneficiary's addresses
+ * @param _templateNames Array of the template names
+ * @param _numberOfTokens Array of number of tokens should be assigned to schedules
+ * @param _durations Array of the vesting duration
+ * @param _frequencies Array of the vesting frequency
+ * @param _startTimes Array of the vesting start time
+ */
+ function addScheduleMulti(
+ address[] _beneficiaries,
+ bytes32[] _templateNames,
+ uint256[] _numberOfTokens,
+ uint256[] _durations,
+ uint256[] _frequencies,
+ uint256[] _startTimes
+ )
+ public
+ withPerm(ADMIN)
+ {
+ require(
+ _beneficiaries.length == _templateNames.length && /*solium-disable-line operator-whitespace*/
+ _beneficiaries.length == _numberOfTokens.length && /*solium-disable-line operator-whitespace*/
+ _beneficiaries.length == _durations.length && /*solium-disable-line operator-whitespace*/
+ _beneficiaries.length == _frequencies.length && /*solium-disable-line operator-whitespace*/
+ _beneficiaries.length == _startTimes.length,
+ "Arrays sizes mismatch"
+ );
+ for (uint256 i = 0; i < _beneficiaries.length; i++) {
+ _addSchedule(_beneficiaries[i], _templateNames[i], _numberOfTokens[i], _durations[i], _frequencies[i], _startTimes[i]);
+ }
+ }
+
+ /**
+ * @notice Used to bulk add vesting schedules from template for each of the beneficiary
+ * @param _beneficiaries Array of beneficiary's addresses
+ * @param _templateNames Array of the template names were used for schedule creation
+ * @param _startTimes Array of the vesting start time
+ */
+ function addScheduleFromTemplateMulti(address[] _beneficiaries, bytes32[] _templateNames, uint256[] _startTimes) external withPerm(ADMIN) {
+ require(_beneficiaries.length == _templateNames.length && _beneficiaries.length == _startTimes.length, "Arrays sizes mismatch");
+ for (uint256 i = 0; i < _beneficiaries.length; i++) {
+ _addScheduleFromTemplate(_beneficiaries[i], _templateNames[i], _startTimes[i]);
+ }
+ }
+
+ /**
+ * @notice Used to bulk revoke vesting schedules for each of the beneficiaries
+ * @param _beneficiaries Array of the beneficiary's addresses
+ */
+ function revokeSchedulesMulti(address[] _beneficiaries) external withPerm(ADMIN) {
+ for (uint256 i = 0; i < _beneficiaries.length; i++) {
+ _revokeAllSchedules(_beneficiaries[i]);
+ }
+ }
+
+ /**
+ * @notice Used to bulk modify vesting schedules for each of the beneficiaries
+ * @param _beneficiaries Array of the beneficiary's addresses
+ * @param _templateNames Array of the template names
+ * @param _startTimes Array of the vesting start time
+ */
+ function modifyScheduleMulti(
+ address[] _beneficiaries,
+ bytes32[] _templateNames,
+ uint256[] _startTimes
+ )
+ public
+ withPerm(ADMIN)
+ {
+ require(
+ _beneficiaries.length == _templateNames.length && /*solium-disable-line operator-whitespace*/
+ _beneficiaries.length == _startTimes.length,
+ "Arrays sizes mismatch"
+ );
+ for (uint256 i = 0; i < _beneficiaries.length; i++) {
+ _modifySchedule(_beneficiaries[i], _templateNames[i], _startTimes[i]);
+ }
+ }
+
+ function _checkSchedule(address _beneficiary, bytes32 _templateName) internal view {
+ require(_beneficiary != address(0), "Invalid address");
+ uint256 index = userToTemplateIndex[_beneficiary][_templateName];
+ require(
+ index < schedules[_beneficiary].length &&
+ schedules[_beneficiary][index].templateName == _templateName,
+ "Schedule not found"
+ );
+ }
+
+ function _isTemplateExists(bytes32 _name) internal view returns(bool) {
+ return templates[_name].numberOfTokens > 0;
+ }
+
+ function _validateTemplate(uint256 _numberOfTokens, uint256 _duration, uint256 _frequency) internal view {
+ require(_numberOfTokens > 0, "Zero amount");
+ require(_duration % _frequency == 0, "Invalid frequency");
+ uint256 periodCount = _duration.div(_frequency);
+ require(_numberOfTokens % periodCount == 0);
+ uint256 amountPerPeriod = _numberOfTokens.div(periodCount);
+ require(amountPerPeriod % ISecurityToken(securityToken).granularity() == 0, "Invalid granularity");
+ }
+
+ function _sendTokens(address _beneficiary) internal {
+ for (uint256 i = 0; i < schedules[_beneficiary].length; i++) {
+ _sendTokensPerSchedule(_beneficiary, i);
+ }
+ }
+
+ function _sendTokensPerSchedule(address _beneficiary, uint256 _index) internal {
+ uint256 amount = _getAvailableTokens(_beneficiary, _index);
+ if (amount > 0) {
+ schedules[_beneficiary][_index].claimedTokens = schedules[_beneficiary][_index].claimedTokens.add(amount);
+ require(ISecurityToken(securityToken).transfer(_beneficiary, amount), "Transfer failed");
+ emit SendTokens(_beneficiary, amount);
+ }
+ }
+
+ /**
+ * @notice Return the permissions flag that are associated with VestingEscrowWallet
+ */
+ function getPermissions() public view returns(bytes32[]) {
+ bytes32[] memory allPermissions = new bytes32[](1);
+ allPermissions[0] = ADMIN;
+ return allPermissions;
+ }
+
+}
diff --git a/contracts/modules/Wallet/VestingEscrowWalletFactory.sol b/contracts/modules/Wallet/VestingEscrowWalletFactory.sol
new file mode 100644
index 000000000..238d571ea
--- /dev/null
+++ b/contracts/modules/Wallet/VestingEscrowWalletFactory.sol
@@ -0,0 +1,76 @@
+pragma solidity ^0.4.24;
+
+import "../../proxy/VestingEscrowWalletProxy.sol";
+import "../../interfaces/IBoot.sol";
+import "../ModuleFactory.sol";
+import "../../libraries/Util.sol";
+
+/**
+ * @title Factory for deploying VestingEscrowWallet module
+ */
+contract VestingEscrowWalletFactory is ModuleFactory {
+
+ address public logicContract;
+ /**
+ * @notice Constructor
+ * @param _polyAddress Address of the polytoken
+ */
+ constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public
+ ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost)
+ {
+ require(_logicContract != address(0), "Invalid address");
+ version = "1.0.0";
+ name = "VestingEscrowWallet";
+ title = "Vesting Escrow Wallet";
+ description = "Manage vesting schedules to employees / affiliates";
+ compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0));
+ compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0));
+ logicContract = _logicContract;
+ }
+
+ /**
+ * @notice Used to launch the Module with the help of factory
+ * _data Data used for the intialization of the module factory variables
+ * @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 due to insufficent Allowance provided");
+ }
+ VestingEscrowWalletProxy vestingEscrowWallet = new VestingEscrowWalletProxy(msg.sender, address(polyToken), logicContract);
+ //Checks that _data is valid (not calling anything it shouldn't)
+ require(Util.getSig(_data) == IBoot(vestingEscrowWallet).getInitFunction(), "Invalid data");
+ /*solium-disable-next-line security/no-low-level-calls*/
+ require(address(vestingEscrowWallet).call(_data), "Unsuccessfull call");
+ /*solium-disable-next-line security/no-block-members*/
+ emit GenerateModuleFromFactory(address(vestingEscrowWallet), getName(), address(this), msg.sender, setupCost, now);
+ return address(vestingEscrowWallet);
+ }
+
+ /**
+ * @notice Type of the Module factory
+ */
+ function getTypes() external view returns(uint8[]) {
+ uint8[] memory res = new uint8[](1);
+ res[0] = 6;
+ return res;
+ }
+
+ /**
+ * @notice Returns the instructions associated with the module
+ */
+ function getInstructions() external view returns(string) {
+ /*solium-disable-next-line max-len*/
+ return "Issuer can deposit tokens to the contract and create the vesting schedule for the given address (Affiliate/Employee). These address can withdraw tokens according to there vesting schedule.";
+ }
+
+ /**
+ * @notice Get the tags related to the module factory
+ */
+ function getTags() external view returns(bytes32[]) {
+ bytes32[] memory availableTags = new bytes32[](2);
+ availableTags[0] = "Vested";
+ availableTags[1] = "Escrow Wallet";
+ return availableTags;
+ }
+}
diff --git a/contracts/modules/Wallet/VestingEscrowWalletStorage.sol b/contracts/modules/Wallet/VestingEscrowWalletStorage.sol
new file mode 100644
index 000000000..af40d32bf
--- /dev/null
+++ b/contracts/modules/Wallet/VestingEscrowWalletStorage.sol
@@ -0,0 +1,54 @@
+pragma solidity ^0.4.24;
+
+/**
+ * @title Wallet for core vesting escrow functionality
+ */
+contract VestingEscrowWalletStorage {
+
+ struct Schedule {
+ // Name of the template
+ bytes32 templateName;
+ // Tokens that were already claimed
+ uint256 claimedTokens;
+ // Start time of the schedule
+ uint256 startTime;
+ }
+
+ struct Template {
+ // Total amount of tokens
+ uint256 numberOfTokens;
+ // Schedule duration (How long the schedule will last)
+ uint256 duration;
+ // Schedule frequency (It is a cliff time period)
+ uint256 frequency;
+ // Index of the template in an array template names
+ uint256 index;
+ }
+
+ // Number of tokens that are hold by the `this` contract but are unassigned to any schedule
+ uint256 public unassignedTokens;
+ // Address of the Treasury wallet. All of the unassigned token will transfer to that address.
+ address public treasuryWallet;
+ // List of all beneficiaries who have the schedules running/completed/created
+ address[] public beneficiaries;
+ // Flag whether beneficiary has been already added or not
+ mapping(address => bool) internal beneficiaryAdded;
+
+ // Holds schedules array corresponds to the affiliate/employee address
+ mapping(address => Schedule[]) public schedules;
+ // Holds template names array corresponds to the affiliate/employee address
+ mapping(address => bytes32[]) internal userToTemplates;
+ // Mapping use to store the indexes for different template names for a user.
+ // affiliate/employee address => template name => index
+ mapping(address => mapping(bytes32 => uint256)) internal userToTemplateIndex;
+ // Holds affiliate/employee addresses coressponds to the template name
+ mapping(bytes32 => address[]) internal templateToUsers;
+ // Mapping use to store the indexes for different users for a template.
+ // template name => affiliate/employee address => index
+ mapping(bytes32 => mapping(address => uint256)) internal templateToUserIndex;
+ // Store the template details corresponds to the template name
+ mapping(bytes32 => Template) templates;
+
+ // List of all template names
+ bytes32[] public templateNames;
+}
\ No newline at end of file
diff --git a/contracts/proxy/VestingEscrowWalletProxy.sol b/contracts/proxy/VestingEscrowWalletProxy.sol
new file mode 100644
index 000000000..0138e0402
--- /dev/null
+++ b/contracts/proxy/VestingEscrowWalletProxy.sol
@@ -0,0 +1,27 @@
+pragma solidity ^0.4.24;
+
+import "../modules/Wallet/VestingEscrowWalletStorage.sol";
+import "./OwnedProxy.sol";
+import "../Pausable.sol";
+import "../modules/ModuleStorage.sol";
+ /**
+ * @title Escrow wallet module for vesting functionality
+ */
+contract VestingEscrowWalletProxy is VestingEscrowWalletStorage, ModuleStorage, Pausable, OwnedProxy {
+ /**
+ * @notice Constructor
+ * @param _securityToken Address of the security token
+ * @param _polyAddress Address of the polytoken
+ * @param _implementation representing the address of the new implementation to be set
+ */
+ constructor (address _securityToken, address _polyAddress, address _implementation)
+ public
+ ModuleStorage(_securityToken, _polyAddress)
+ {
+ require(
+ _implementation != address(0),
+ "Implementation address should not be 0x"
+ );
+ __implementation = _implementation;
+ }
+ }
\ No newline at end of file
diff --git a/docs/permissions_list.md b/docs/permissions_list.md
index f75e9389c..4a4438d5f 100644
--- a/docs/permissions_list.md
+++ b/docs/permissions_list.md
@@ -264,7 +264,60 @@
removeTransferLimitInPercentageMulti |
+
+
+
+ Wallet |
+ VestingEscrowWallet |
+ changeTreasuryWallet() |
+ onlyOwner |
+
+
+ depositTokens() |
+ withPerm(ADMIN) |
+
+
+ sendToTreasury() |
+
+
+ pushAvailableTokens() |
+
+
+ addTemplate() |
+
+
+ removeTemplate() |
+
+
+ addSchedule() |
+
+
+ addScheduleFromTemplate() |
+
+ modifySchedule() |
+
+
+ revokeSchedule() |
+
+
+ revokeAllSchedules() |
+
+
+ pushAvailableTokensMulti() |
+
+
+ addScheduleMulti() |
+
+
+ addScheduleFromTemplateMulti() |
+
+
+ revokeSchedulesMulti() |
+
+
+ modifyScheduleMulti() |
+
diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js
index 0a5c79341..4beb28252 100644
--- a/migrations/2_deploy_contracts.js
+++ b/migrations/2_deploy_contracts.js
@@ -9,6 +9,8 @@ const EtherDividendCheckpointLogic = artifacts.require('./EtherDividendCheckpoin
const ERC20DividendCheckpointLogic = artifacts.require('./ERC20DividendCheckpoint.sol')
const EtherDividendCheckpointFactory = artifacts.require('./EtherDividendCheckpointFactory.sol')
const ERC20DividendCheckpointFactory = artifacts.require('./ERC20DividendCheckpointFactory.sol')
+const VestingEscrowWalletFactory = artifacts.require('./VestingEscrowWalletFactory.sol');
+const VestingEscrowWalletLogic = artifacts.require('./VestingEscrowWallet.sol');
const ModuleRegistry = artifacts.require('./ModuleRegistry.sol');
const ModuleRegistryProxy = artifacts.require('./ModuleRegistryProxy.sol');
const ManualApprovalTransferManagerFactory = artifacts.require('./ManualApprovalTransferManagerFactory.sol')
@@ -154,17 +156,25 @@ module.exports = function (deployer, network, accounts) {
// manager attach with the securityToken contract at the time of deployment)
return deployer.deploy(GeneralTransferManagerLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
}).then(() => {
- // B) Deploy the GeneralTransferManagerLogic Contract (Factory used to generate the GeneralTransferManager contract and this
+ // B) Deploy the ERC20DividendCheckpointLogic Contract (Factory used to generate the ERC20DividendCheckpoint contract and this
// manager attach with the securityToken contract at the time of deployment)
return deployer.deploy(ERC20DividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
}).then(() => {
- // B) Deploy the GeneralTransferManagerLogic Contract (Factory used to generate the GeneralTransferManager contract and this
+ // B) Deploy the EtherDividendCheckpointLogic Contract (Factory used to generate the EtherDividendCheckpoint contract and this
// manager attach with the securityToken contract at the time of deployment)
return deployer.deploy(EtherDividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
+ }).then(() => {
+ // B) Deploy the VestingEscrowWalletLogic Contract (Factory used to generate the VestingEscrowWallet contract and this
+ // manager attach with the securityToken contract at the time of deployment)
+ return deployer.deploy(VestingEscrowWalletLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
}).then(() => {
// B) Deploy the USDTieredSTOLogic Contract (Factory used to generate the USDTieredSTO contract and this
// manager attach with the securityToken contract at the time of deployment)
return deployer.deploy(USDTieredSTOLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
+ }).then(() => {
+ // B) Deploy the VestingEscrowWalletFactory Contract (Factory used to generate the VestingEscrowWallet contract and this
+ // manager attach with the securityToken contract at the time of deployment)
+ return deployer.deploy(VestingEscrowWalletFactory, PolyToken, 0, 0, 0, VestingEscrowWalletLogic.address, {from: PolymathAccount});
}).then(() => {
// B) Deploy the GeneralTransferManagerFactory Contract (Factory used to generate the GeneralTransferManager contract and this
// manager attach with the securityToken contract at the time of deployment)
@@ -220,6 +230,10 @@ module.exports = function (deployer, network, accounts) {
// D) Register the PercentageTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level.
// So any securityToken can use that factory to generate the PercentageTransferManager contract.
return moduleRegistry.registerModule(PercentageTransferManagerFactory.address, {from: PolymathAccount});
+ }).then(() => {
+ // D) Register the VestingEscrowWalletFactory in the ModuleRegistry to make the factory available at the protocol level.
+ // So any securityToken can use that factory to generate the VestingEscrowWallet contract.
+ return moduleRegistry.registerModule(VestingEscrowWalletFactory.address, {from: PolymathAccount});
}).then(() => {
// D) Register the CountTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level.
// So any securityToken can use that factory to generate the CountTransferManager contract.
@@ -279,6 +293,11 @@ module.exports = function (deployer, network, accounts) {
// contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only.
// Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts.
return moduleRegistry.verifyModule(ManualApprovalTransferManagerFactory.address, true, {from: PolymathAccount});
+ }).then(() => {
+ // F) Once the VestingEscrowWalletFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken
+ // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only.
+ // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts.
+ return moduleRegistry.verifyModule(VestingEscrowWalletFactory.address, true, {from: PolymathAccount});
}).then(() => {
// M) Deploy the CappedSTOFactory (Use to generate the CappedSTO contract which will used to collect the funds ).
return deployer.deploy(CappedSTOFactory, PolyToken, cappedSTOSetupCost, 0, 0, {from: PolymathAccount})
@@ -337,6 +356,8 @@ module.exports = function (deployer, network, accounts) {
ERC20DividendCheckpointLogic: ${ERC20DividendCheckpointLogic.address}
EtherDividendCheckpointFactory: ${EtherDividendCheckpointFactory.address}
ERC20DividendCheckpointFactory: ${ERC20DividendCheckpointFactory.address}
+ VestingEscrowWalletFactory: ${VestingEscrowWalletFactory.address}
+ VestingEscrowWalletLogic: ${VestingEscrowWalletLogic.address}
---------------------------------------------------------------------------------
`);
console.log('\n');
diff --git a/test/helpers/createInstances.js b/test/helpers/createInstances.js
index 77c7a5e5d..a6f495984 100644
--- a/test/helpers/createInstances.js
+++ b/test/helpers/createInstances.js
@@ -32,6 +32,8 @@ const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol");
const DummySTOFactory = artifacts.require("./DummySTOFactory.sol");
const MockBurnFactory = artifacts.require("./MockBurnFactory.sol");
const MockWrongTypeFactory = artifacts.require("./MockWrongTypeFactory.sol");
+const VestingEscrowWalletFactory = artifacts.require("./VestingEscrowWalletFactory.sol");
+const VestingEscrowWallet = artifacts.require("./VestingEscrowWallet.sol");
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port
@@ -54,6 +56,7 @@ let I_ERC20DividendCheckpointFactory;
let I_GeneralPermissionManagerFactory;
let I_GeneralTransferManagerLogic;
let I_GeneralTransferManagerFactory;
+let I_VestingEscrowWalletFactory;
let I_GeneralTransferManager;
let I_ModuleRegistryProxy;
let I_PreSaleSTOFactory;
@@ -68,6 +71,7 @@ let I_STFactory;
let I_USDTieredSTOLogic;
let I_PolymathRegistry;
let I_SecurityTokenRegistryProxy;
+let I_VestingEscrowWalletLogic;
let I_STRProxied;
let I_MRProxied;
@@ -104,7 +108,7 @@ export async function setUpPolymathNetwork(account_polymath, token_owner) {
}
-async function deployPolyRegistryAndPolyToken(account_polymath, token_owner) {
+export async function deployPolyRegistryAndPolyToken(account_polymath, token_owner) {
// Step 0: Deploy the PolymathRegistry
I_PolymathRegistry = await PolymathRegistry.new({ from: account_polymath });
@@ -404,6 +408,19 @@ export async function deployRedemptionAndVerifyed(accountPolymath, MRProxyInstan
return new Array(I_TrackedRedemptionFactory);
}
+export async function deployVestingEscrowWalletAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) {
+ I_VestingEscrowWalletLogic = await VestingEscrowWallet.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath });
+ I_VestingEscrowWalletFactory = await VestingEscrowWalletFactory.new(polyToken, setupCost, 0, 0, I_VestingEscrowWalletLogic.address, { from: accountPolymath });
+
+ assert.notEqual(
+ I_VestingEscrowWalletFactory.address.valueOf(),
+ "0x0000000000000000000000000000000000000000",
+ "VestingEscrowWalletFactory contract was not deployed"
+ );
+
+ await registerAndVerifyByMR(I_VestingEscrowWalletFactory.address, accountPolymath, MRProxyInstance);
+ return new Array(I_VestingEscrowWalletFactory);
+}
export async function deployMockRedemptionAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) {
I_MockBurnFactory = await MockBurnFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath });
@@ -431,8 +448,6 @@ export async function deployMockWrongTypeRedemptionAndVerifyed(accountPolymath,
return new Array(I_MockWrongTypeBurnFactory);
}
-
-
/// Helper function
function mergeReturn(returnData) {
let returnArray = new Array();
diff --git a/test/helpers/exceptions.js b/test/helpers/exceptions.js
index 22c05be07..ea0327af8 100644
--- a/test/helpers/exceptions.js
+++ b/test/helpers/exceptions.js
@@ -25,6 +25,9 @@ module.exports = {
catchRevert: async function(promise) {
await tryCatch(promise, "revert");
},
+ catchPermission: async function(promise) {
+ await tryCatch(promise, "revert Permission check failed");
+ },
catchOutOfGas: async function(promise) {
await tryCatch(promise, "out of gas");
},
diff --git a/test/z_vesting_escrow_wallet.js b/test/z_vesting_escrow_wallet.js
new file mode 100644
index 000000000..d15acddd1
--- /dev/null
+++ b/test/z_vesting_escrow_wallet.js
@@ -0,0 +1,1197 @@
+import {deployGPMAndVerifyed, deployVestingEscrowWalletAndVerifyed, setUpPolymathNetwork} from "./helpers/createInstances";
+import latestTime from "./helpers/latestTime";
+import {duration as durationUtil, latestBlock, promisifyLogWatch} from "./helpers/utils";
+import {catchRevert} from "./helpers/exceptions";
+import {increaseTime} from "./helpers/time";
+import {encodeModuleCall} from "./helpers/encodeCall";
+
+const SecurityToken = artifacts.require('./SecurityToken.sol');
+const GeneralTransferManager = artifacts.require('./GeneralTransferManager');
+const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager");
+const VestingEscrowWallet = artifacts.require('./VestingEscrowWallet.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('VestingEscrowWallet', accounts => {
+
+ const CREATED = 0;
+ const STARTED = 1;
+ const COMPLETED = 2;
+
+ // Accounts Variable declaration
+ let account_polymath;
+ let token_owner;
+ let wallet_admin;
+ let account_beneficiary1;
+ let account_beneficiary2;
+ let account_beneficiary3;
+
+ let beneficiaries;
+
+ let message = "Transaction Should Fail!";
+
+ // Contract Instance Declaration
+ let I_SecurityTokenRegistryProxy;
+ let I_GeneralPermissionManagerFactory;
+ let I_GeneralTransferManagerFactory;
+ let I_VestingEscrowWalletFactory;
+ let I_GeneralPermissionManager;
+ let I_VestingEscrowWallet;
+ 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";
+ const delegateDetails = "Hello I am legit delegate";
+
+ // 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];
+ token_owner = accounts[1];
+ wallet_admin = accounts[2];
+
+ account_beneficiary1 = accounts[6];
+ account_beneficiary2 = accounts[7];
+ account_beneficiary3 = accounts[8];
+
+ beneficiaries = [
+ account_beneficiary1,
+ account_beneficiary2,
+ account_beneficiary3
+ ];
+
+ // 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 GeneralDelegateManagerFactory
+ [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0);
+
+ // STEP 3: Deploy the VestingEscrowWallet
+ [I_VestingEscrowWalletFactory] = await deployVestingEscrowWalletAndVerifyed(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}
+ GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address}
+
+ I_VestingEscrowWalletFactory: ${I_VestingEscrowWalletFactory.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 successfully attach the General permission manager factory with the security token", async () => {
+ 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, ""),
+ "GeneralPermissionManager",
+ "GeneralPermissionManagerFactory module was not added"
+ );
+ I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module);
+ });
+
+ it("Should successfully attach the VestingEscrowWallet with the security token", async () => {
+ let bytesData = encodeModuleCall(
+ ["address"],
+ [token_owner]
+ );
+
+ await I_SecurityToken.changeGranularity(1, {from: token_owner});
+ const tx = await I_SecurityToken.addModule(I_VestingEscrowWalletFactory.address, bytesData, 0, 0, { from: token_owner });
+
+ assert.equal(tx.logs[2].args._types[0].toNumber(), 6, "VestingEscrowWallet doesn't get deployed");
+ assert.equal(
+ web3.utils.toAscii(tx.logs[2].args._name)
+ .replace(/\u0000/g, ''),
+ "VestingEscrowWallet",
+ "VestingEscrowWallet module was not added"
+ );
+ I_VestingEscrowWallet = VestingEscrowWallet.at(tx.logs[2].args._module);
+ });
+
+ it("Should Buy the tokens for token_owner", async() => {
+ // Add the Investor in to the whitelist
+ let tx = await I_GeneralTransferManager.modifyWhitelist(
+ token_owner,
+ latestTime(),
+ latestTime(),
+ latestTime() + durationUtil.days(10),
+ true,
+ {
+ from: token_owner,
+ gas: 6000000
+ });
+
+ assert.equal(tx.logs[0].args._investor.toLowerCase(), token_owner.toLowerCase(), "Failed in adding the token_owner in whitelist");
+
+ // Mint some tokens
+ await I_SecurityToken.mint(token_owner, web3.utils.toWei('1', 'ether'), { from: token_owner });
+
+ assert.equal(
+ (await I_SecurityToken.balanceOf(token_owner)).toNumber(),
+ web3.utils.toWei('1', 'ether')
+ );
+
+ });
+
+ it("Should whitelist investors", async() => {
+ // Add the Investor in to the whitelist
+ let tx = await I_GeneralTransferManager.modifyWhitelistMulti(
+ [I_VestingEscrowWallet.address, account_beneficiary1, account_beneficiary2, account_beneficiary3],
+ [latestTime(), latestTime(), latestTime(), latestTime()],
+ [latestTime(), latestTime(), latestTime(), latestTime()],
+ [latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10)],
+ [true, true, true, true],
+ {
+ from: token_owner,
+ gas: 6000000
+ });
+
+ assert.equal(tx.logs[0].args._investor, I_VestingEscrowWallet.address);
+ assert.equal(tx.logs[1].args._investor, account_beneficiary1);
+ assert.equal(tx.logs[2].args._investor, account_beneficiary2);
+ assert.equal(tx.logs[3].args._investor, account_beneficiary3);
+ });
+
+ it("Should successfully add the delegate", async() => {
+ let tx = await I_GeneralPermissionManager.addDelegate(wallet_admin, delegateDetails, { from: token_owner});
+ assert.equal(tx.logs[0].args._delegate, wallet_admin);
+ });
+
+ it("Should provide the permission", async() => {
+ let tx = await I_GeneralPermissionManager.changePermission(
+ wallet_admin,
+ I_VestingEscrowWallet.address,
+ "ADMIN",
+ true,
+ {from: token_owner}
+ );
+ assert.equal(tx.logs[0].args._delegate, wallet_admin);
+ });
+
+ it("Should get the permission", async () => {
+ let perm = await I_VestingEscrowWallet.getPermissions.call();
+ assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ""), "ADMIN");
+ });
+
+ it("Should get the tags of the factory", async () => {
+ let tags = await I_VestingEscrowWalletFactory.getTags.call();
+ assert.equal(tags.length, 2);
+ assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ""), "Vested");
+ assert.equal(web3.utils.toAscii(tags[1]).replace(/\u0000/g, ""), "Escrow Wallet");
+ });
+
+ it("Should get the instructions of the factory", async () => {
+ assert.equal(
+ (await I_VestingEscrowWalletFactory.getInstructions.call()).replace(/\u0000/g, ""),
+ "Issuer can deposit tokens to the contract and create the vesting schedule for the given address (Affiliate/Employee). These address can withdraw tokens according to there vesting schedule."
+ );
+ });
+
+ });
+
+ describe("Depositing and withdrawing tokens", async () => {
+
+ it("Should not be able to change treasury wallet -- fail because address is invalid", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.changeTreasuryWallet(0, {from: token_owner})
+ );
+ });
+
+ it("Should not be able to deposit -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.changeTreasuryWallet(account_beneficiary1, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should change treasury wallet", async () => {
+ const tx = await I_VestingEscrowWallet.changeTreasuryWallet(account_beneficiary1, {from: token_owner});
+
+ assert.equal(tx.logs[0].args._newWallet, account_beneficiary1);
+ assert.equal(tx.logs[0].args._oldWallet, token_owner);
+ let treasuryWallet = await I_VestingEscrowWallet.treasuryWallet.call();
+ assert.equal(treasuryWallet, account_beneficiary1);
+
+ await I_VestingEscrowWallet.changeTreasuryWallet(token_owner, {from: token_owner});
+ });
+
+ it("Should fail to deposit zero amount of tokens", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.depositTokens(0, {from: token_owner})
+ );
+ });
+
+ it("Should not be able to deposit -- fail because of permissions check", async () => {
+ let numberOfTokens = 25000;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner });
+ await catchRevert(
+ I_VestingEscrowWallet.depositTokens(25000, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should deposit tokens for new vesting schedules", async () => {
+ let numberOfTokens = 25000;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner });
+ const tx = await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner});
+
+ assert.equal(tx.logs[0].args._numberOfTokens, numberOfTokens);
+
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ assert.equal(unassignedTokens, numberOfTokens);
+
+ let balance = await I_SecurityToken.balanceOf.call(I_VestingEscrowWallet.address);
+ assert.equal(balance.toNumber(), numberOfTokens);
+ });
+
+ it("Should not be able to withdraw tokens to a treasury -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.sendToTreasury(10, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should not be able to withdraw tokens to a treasury -- fail because of zero amount", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.sendToTreasury(0, {from: wallet_admin})
+ );
+ });
+
+ it("Should not be able to withdraw tokens to a treasury -- fail because amount is greater than unassigned tokens", async () => {
+ let numberOfTokens = 25000 * 2;
+ await catchRevert(
+ I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_admin})
+ );
+ });
+
+ it("Should withdraw tokens to a treasury", async () => {
+ let numberOfTokens = 25000;
+ const tx = await I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_admin});
+
+ assert.equal(tx.logs[0].args._numberOfTokens, numberOfTokens);
+
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ assert.equal(unassignedTokens, 0);
+
+ let balance = await I_SecurityToken.balanceOf.call(I_VestingEscrowWallet.address);
+ assert.equal(balance.toNumber(), 0);
+ });
+
+ it("Should not be able to push available tokens -- fail because of permissions check", async () => {
+ let templateName = "template-01";
+ let numberOfTokens = 75000;
+ let duration = durationUtil.seconds(30);
+ let frequency = durationUtil.seconds(10);
+ let timeShift = durationUtil.seconds(100);
+ let startTime = latestTime() + timeShift;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner });
+ await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin});
+ await increaseTime(timeShift + frequency);
+
+ await catchRevert(
+ I_VestingEscrowWallet.pushAvailableTokens(account_beneficiary3, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should not be able to remove template -- fail because template is used", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.removeTemplate("template-01", {from: wallet_admin})
+ );
+ });
+
+ it("Should push available tokens to the beneficiary address", async () => {
+ let numberOfTokens = 75000;
+ const tx = await I_VestingEscrowWallet.pushAvailableTokens(account_beneficiary3, {from: wallet_admin});
+ assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3);
+ assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens / 3);
+
+ let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3);
+ assert.equal(balance.toNumber(), numberOfTokens / 3);
+
+ await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3});
+ });
+
+ it("Should fail to modify vesting schedule -- fail because schedule already started", async () => {
+ let templateName = "template-01";
+ let startTime = latestTime() + 100;
+ await catchRevert(
+ I_VestingEscrowWallet.modifySchedule(account_beneficiary3, templateName, startTime, {from: wallet_admin})
+ );
+
+ await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin});
+ await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin});
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+ });
+
+ it("Should fail to modify vesting schedule -- fail because date in the past", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.modifySchedule(account_beneficiary3, "template-01", latestTime() - 1000, {from: wallet_admin})
+ );
+ });
+
+ it("Should withdraw available tokens to the beneficiary address", async () => {
+ let templateName = "template-02";
+ let numberOfTokens = 33000;
+ let duration = durationUtil.seconds(30);
+ let frequency = durationUtil.seconds(10);
+ let timeShift = durationUtil.seconds(100);
+ let startTime = latestTime() + timeShift;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner });
+ await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin});
+ await increaseTime(timeShift + frequency * 3);
+
+ const tx = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3});
+ assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3);
+ assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens);
+
+ let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3);
+ assert.equal(balance.toNumber(), numberOfTokens);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary3, templateName);
+ checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, COMPLETED);
+
+ await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3});
+ await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin});
+ await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin});
+ });
+
+ it("Should withdraw available tokens 2 times by 3 schedules to the beneficiary address", async () => {
+ let schedules = [
+ {
+ templateName: "template-1-01",
+ numberOfTokens: 100000,
+ duration: durationUtil.minutes(4),
+ frequency: durationUtil.minutes(1)
+ },
+ {
+ templateName: "template-1-02",
+ numberOfTokens: 30000,
+ duration: durationUtil.minutes(6),
+ frequency: durationUtil.minutes(1)
+ },
+ {
+ templateName: "template-1-03",
+ numberOfTokens: 2000,
+ duration: durationUtil.minutes(10),
+ frequency: durationUtil.minutes(1)
+ }
+ ];
+
+ let totalNumberOfTokens = getTotalNumberOfTokens(schedules);
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner});
+ for (let i = 0; i < schedules.length; i++) {
+ let templateName = schedules[i].templateName;
+ let numberOfTokens = schedules[i].numberOfTokens;
+ let duration = schedules[i].duration;
+ let frequency = schedules[i].frequency;
+ let startTime = latestTime() + durationUtil.seconds(100);
+ await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin});
+ }
+ let stepCount = 6;
+ await increaseTime(durationUtil.minutes(stepCount) + durationUtil.seconds(100));
+
+ let numberOfTokens = 100000 + (30000 / 6 * stepCount) + (2000 / 10 * stepCount);
+ const tx = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3});
+
+ assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3);
+ assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), 100000);
+ assert.equal(tx.logs[1].args._beneficiary, account_beneficiary3);
+ assert.equal(tx.logs[1].args._numberOfTokens.toNumber(), 30000 / 6 * stepCount);
+ assert.equal(tx.logs[2].args._beneficiary, account_beneficiary3);
+ assert.equal(tx.logs[2].args._numberOfTokens.toNumber(), 2000 / 10 * stepCount);
+
+ let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3);
+ assert.equal(balance.toNumber(), numberOfTokens);
+
+ stepCount = 4;
+ await increaseTime(durationUtil.minutes(stepCount) + durationUtil.seconds(100));
+
+ const tx2 = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3});
+ assert.equal(tx2.logs[0].args._beneficiary, account_beneficiary3);
+ assert.equal(tx2.logs[0].args._numberOfTokens.toNumber(), 2000 / 10 * stepCount);
+
+ balance = await I_SecurityToken.balanceOf.call(account_beneficiary3);
+ assert.equal(balance.toNumber(), totalNumberOfTokens);
+
+ await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3});
+ await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin});
+ for (let i = 0; i < schedules.length; i++) {
+ await I_VestingEscrowWallet.removeTemplate(schedules[i].templateName, {from: wallet_admin});
+ }
+ });
+
+ });
+
+ describe("Adding, modifying and revoking vesting schedule", async () => {
+
+ let schedules = [
+ {
+ templateName: "template-2-01",
+ numberOfTokens: 100000,
+ duration: durationUtil.years(4),
+ frequency: durationUtil.years(1),
+ startTime: latestTime() + durationUtil.days(1)
+ },
+ {
+ templateName: "template-2-02",
+ numberOfTokens: 30000,
+ duration: durationUtil.weeks(6),
+ frequency: durationUtil.weeks(1),
+ startTime: latestTime() + durationUtil.days(2)
+ },
+ {
+ templateName: "template-2-03",
+ numberOfTokens: 2000,
+ duration: durationUtil.days(10),
+ frequency: durationUtil.days(2),
+ startTime: latestTime() + durationUtil.days(3)
+ }
+ ];
+
+ it("Should fail to add vesting schedule to the beneficiary address -- fail because address in invalid", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addSchedule(0, "template-2-01", 100000, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin})
+ );
+ });
+
+ it("Should fail to add vesting schedule to the beneficiary address -- fail because start date in the past", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 100000, 4, 1, latestTime() - durationUtil.days(1), {from: wallet_admin})
+ );
+ });
+
+ it("Should fail to add vesting schedule to the beneficiary address -- fail because number of tokens is 0", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 0, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin})
+ );
+ });
+
+ it("Should fail to add vesting schedule to the beneficiary address -- fail because duration can't be divided entirely by frequency", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 100000, 4, 3, latestTime() + durationUtil.days(1), {from: wallet_admin})
+ );
+ });
+
+ it("Should fail to add vesting schedule to the beneficiary address -- fail because number of tokens can't be divided entirely by period count", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 5, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin})
+ );
+ });
+
+ it("Should fail to get vesting schedule -- fail because address is invalid", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.getSchedule(0, "template-2-01")
+ );
+ });
+
+ it("Should fail to get vesting schedule -- fail because schedule not found", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.getSchedule(account_beneficiary1, "template-2-01")
+ );
+ });
+
+ it("Should fail to get count of vesting schedule -- fail because address is invalid", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.getScheduleCount(0)
+ );
+ });
+
+ it("Should not be able to add schedule -- fail because of permissions check", async () => {
+ let templateName = schedules[0].templateName;
+ let numberOfTokens = schedules[0].numberOfTokens;
+ let duration = schedules[0].duration;
+ let frequency = schedules[0].frequency;
+ let startTime = schedules[0].startTime;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner});
+ await catchRevert(
+ I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: account_beneficiary1})
+ );
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+ });
+
+ it("Should add vesting schedule to the beneficiary address", async () => {
+ let templateName = schedules[0].templateName;
+ let numberOfTokens = schedules[0].numberOfTokens;
+ let duration = schedules[0].duration;
+ let frequency = schedules[0].frequency;
+ let startTime = schedules[0].startTime;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner});
+ const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin});
+
+ checkTemplateLog(tx.logs[0], templateName, numberOfTokens, duration, frequency);
+ checkScheduleLog(tx.logs[1], account_beneficiary1, templateName, startTime);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1);
+ assert.equal(scheduleCount, 1);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, templateName);
+ checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED);
+
+ let templates = await I_VestingEscrowWallet.getTemplateNames.call(account_beneficiary1);
+ assert.equal(web3.utils.hexToUtf8(templates[0]), templateName);
+ });
+
+ it("Should add vesting schedule without depositing to the beneficiary address", async () => {
+ let templateName = "template-2-01-2";
+ let numberOfTokens = schedules[0].numberOfTokens;
+ let duration = schedules[0].duration;
+ let frequency = schedules[0].frequency;
+ let startTime = schedules[0].startTime;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner});
+ const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: token_owner});
+
+ checkTemplateLog(tx.logs[0], templateName, numberOfTokens, duration, frequency);
+ assert.equal(tx.logs[1].args._numberOfTokens, numberOfTokens);
+ checkScheduleLog(tx.logs[2], account_beneficiary1, templateName, startTime);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1);
+ assert.equal(scheduleCount, 2);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, templateName);
+ checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED);
+
+ await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin});
+ await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin});
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+ });
+
+ it("Should fail to modify vesting schedule -- fail because schedule not found", async () => {
+ let templateName = "template-2-03";
+ let startTime = schedules[0].startTime;
+ await catchRevert(
+ I_VestingEscrowWallet.modifySchedule(account_beneficiary1, templateName, startTime, {from: wallet_admin})
+ );
+ });
+
+ it("Should not be able to modify schedule -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.modifySchedule(account_beneficiary1, "template-2-01", latestTime() + 100, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should modify vesting schedule for the beneficiary's address", async () => {
+ let templateName = "template-2-01";
+ let numberOfTokens = schedules[0].numberOfTokens;
+ let duration = schedules[0].duration;
+ let frequency = schedules[0].frequency;
+ let startTime = schedules[1].startTime;
+ const tx = await I_VestingEscrowWallet.modifySchedule(account_beneficiary1, templateName, startTime, {from: wallet_admin});
+
+ checkScheduleLog(tx.logs[0], account_beneficiary1, templateName, startTime);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1);
+ assert.equal(scheduleCount.toNumber(), 1);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, "template-2-01");
+ checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED);
+ });
+
+ it("Should not be able to revoke schedule -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, "template-2-01", {from: account_beneficiary1})
+ );
+ });
+
+ it("Should revoke vesting schedule from the beneficiary address", async () => {
+ let templateName = "template-2-01";
+ const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin});
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+
+ assert.equal(tx.logs[0].args._beneficiary, account_beneficiary1);
+ assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._templateName), templateName);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1);
+ assert.equal(scheduleCount, 0);
+
+ await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin})
+ });
+
+ it("Should fail to revoke vesting schedule -- fail because address is invalid", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.revokeSchedule(0, "template-2-01", {from: wallet_admin})
+ );
+ });
+
+ it("Should fail to revoke vesting schedule -- fail because schedule not found", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, "template-2-02", {from: wallet_admin})
+ );
+ });
+
+ it("Should fail to revoke vesting schedules -- fail because address is invalid", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.revokeAllSchedules(0, {from: wallet_admin})
+ );
+ });
+
+ it("Should add 3 vesting schedules to the beneficiary address", async () => {
+ let totalNumberOfTokens = getTotalNumberOfTokens(schedules);
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner});
+ for (let i = 0; i < schedules.length; i++) {
+ let templateName = schedules[i].templateName;
+ let numberOfTokens = schedules[i].numberOfTokens;
+ let duration = schedules[i].duration;
+ let frequency = schedules[i].frequency;
+ let startTime = schedules[i].startTime;
+ const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary2, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin});
+
+ checkTemplateLog(tx.logs[0], templateName, numberOfTokens, duration, frequency);
+ checkScheduleLog(tx.logs[1], account_beneficiary2, templateName, startTime);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary2);
+ assert.equal(scheduleCount, i + 1);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary2, templateName);
+ checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED);
+ }
+ });
+
+ it("Should not be able to revoke schedules -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary1, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should revoke 1 of 3 vesting schedule from the beneficiary address", async () => {
+ let templateName = schedules[1].templateName;
+ const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary2, templateName, {from: wallet_admin});
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+
+ assert.equal(tx.logs[0].args._beneficiary, account_beneficiary2);
+ assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._templateName), templateName);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary2);
+ assert.equal(scheduleCount, 2);
+ });
+
+ it("Should revoke 2 vesting schedules from the beneficiary address", async () => {
+ const tx = await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary2, {from: wallet_admin});
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+
+ assert.equal(tx.logs[0].args._beneficiary, account_beneficiary2);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary2);
+ assert.equal(scheduleCount, 0);
+ });
+
+ it("Should push available tokens during revoking vesting schedule", async () => {
+ let schedules = [
+ {
+ templateName: "template-3-01",
+ numberOfTokens: 100000,
+ duration: durationUtil.minutes(4),
+ frequency: durationUtil.minutes(1)
+ },
+ {
+ templateName: "template-3-02",
+ numberOfTokens: 30000,
+ duration: durationUtil.minutes(6),
+ frequency: durationUtil.minutes(1)
+ },
+ {
+ templateName: "template-3-03",
+ numberOfTokens: 2000,
+ duration: durationUtil.minutes(10),
+ frequency: durationUtil.minutes(1)
+ }
+ ];
+
+ let totalNumberOfTokens = getTotalNumberOfTokens(schedules);
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner});
+ for (let i = 0; i < schedules.length; i++) {
+ let templateName = schedules[i].templateName;
+ let numberOfTokens = schedules[i].numberOfTokens;
+ let duration = schedules[i].duration;
+ let frequency = schedules[i].frequency;
+ let startTime = latestTime() + durationUtil.seconds(100);
+ await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin});
+ }
+ let stepCount = 3;
+ await increaseTime(durationUtil.minutes(stepCount) + durationUtil.seconds(100));
+
+ const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary3, "template-3-01", {from: wallet_admin});
+ assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3);
+ assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), 100000 / 4 * stepCount);
+
+ let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3);
+ assert.equal(balance.toNumber(), 100000 / 4 * stepCount);
+
+ stepCount = 7;
+ await increaseTime(durationUtil.minutes(stepCount));
+
+ const tx2 = await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin});
+ assert.equal(tx2.logs[0].args._beneficiary, account_beneficiary3);
+ assert.equal(tx2.logs[0].args._numberOfTokens.toNumber(), 2000);
+ assert.equal(tx2.logs[1].args._beneficiary, account_beneficiary3);
+ assert.equal(tx2.logs[1].args._numberOfTokens.toNumber(), 30000);
+
+ for (let i = 0; i < schedules.length; i++) {
+ await I_VestingEscrowWallet.removeTemplate(schedules[i].templateName, {from: wallet_admin});
+ }
+
+ balance = await I_SecurityToken.balanceOf.call(account_beneficiary3);
+ assert.equal(balance.toNumber(), totalNumberOfTokens - 100000 / 4);
+
+ await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3});
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+ });
+
+ });
+
+ describe("Adding, using and removing templates", async () => {
+
+ let schedules = [
+ {
+ templateName: "template-4-01",
+ numberOfTokens: 100000,
+ duration: durationUtil.years(4),
+ frequency: durationUtil.years(1),
+ startTime: latestTime() + durationUtil.days(1)
+ },
+ {
+ templateName: "template-4-02",
+ numberOfTokens: 30000,
+ duration: durationUtil.weeks(6),
+ frequency: durationUtil.weeks(1),
+ startTime: latestTime() + durationUtil.days(2)
+ },
+ {
+ templateName: "template-4-03",
+ numberOfTokens: 2000,
+ duration: durationUtil.days(10),
+ frequency: durationUtil.days(2),
+ startTime: latestTime() + durationUtil.days(3)
+ }
+ ];
+
+ it("Should not be able to add template -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addTemplate("template-4-01", 25000, 4, 1, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should not be able to add template -- fail because of invalid name", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addTemplate("", 25000, 4, 1, {from: wallet_admin})
+ );
+ });
+
+ it("Should add 3 Templates", async () => {
+ let oldTemplateCount = await I_VestingEscrowWallet.getTemplateCount.call();
+ for (let i = 0; i < schedules.length; i++) {
+ let templateName = schedules[i].templateName;
+ let numberOfTokens = schedules[i].numberOfTokens;
+ let duration = schedules[i].duration;
+ let frequency = schedules[i].frequency;
+ const tx = await I_VestingEscrowWallet.addTemplate(templateName, numberOfTokens, duration, frequency, {from: wallet_admin});
+
+ assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._name), templateName);
+ assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens);
+ assert.equal(tx.logs[0].args._duration.toNumber(), duration);
+ assert.equal(tx.logs[0].args._frequency.toNumber(), frequency);
+ }
+ let templateNames = await I_VestingEscrowWallet.getAllTemplateNames.call();
+
+ for (let i = 0, j = oldTemplateCount; i < schedules.length; i++, j++) {
+ assert.equal(web3.utils.hexToUtf8(templateNames[j]), schedules[i].templateName);
+ }
+ });
+
+ it("Should not be able to add template -- fail because template already exists", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addTemplate("template-4-01", 25000, 4, 1, {from: wallet_admin})
+ );
+ });
+
+ it("Should not be able to remove template -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.removeTemplate("template-4-02", {from: account_beneficiary1})
+ );
+ });
+
+ it("Should not be able to remove template -- fail because template not found", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.removeTemplate("template-444-02", {from: wallet_admin})
+ );
+ });
+
+ it("Should remove template", async () => {
+ const tx = await I_VestingEscrowWallet.removeTemplate("template-4-02", {from: wallet_admin});
+
+ assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._name), "template-4-02");
+ });
+
+ it("Should fail to add vesting schedule from template -- fail because template not found", async () => {
+ let startTime = schedules[2].startTime;
+ await catchRevert(
+ I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-4-02", startTime, {from: wallet_admin})
+ );
+ });
+
+ it("Should not be able to add schedule from template -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-4-01", latestTime(), {from: account_beneficiary1})
+ );
+ });
+
+ it("Should not be able to add vesting schedule from template -- fail because template not found", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-777", latestTime() + 100, {from: wallet_admin})
+ );
+ });
+
+ it("Should add vesting schedule from template", async () => {
+ let templateName = schedules[2].templateName;
+ let numberOfTokens = schedules[2].numberOfTokens;
+ let duration = schedules[2].duration;
+ let frequency = schedules[2].frequency;
+ let startTime = schedules[2].startTime;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner });
+ await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner});
+ const tx = await I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, templateName, startTime, {from: wallet_admin});
+
+ checkScheduleLog(tx.logs[0], account_beneficiary1, templateName, startTime);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1);
+ assert.equal(scheduleCount, 1);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, templateName);
+ checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED);
+
+ await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin});
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+ });
+
+ it("Should not be able to add vesting schedule from template -- fail because template already added", async () => {
+ let templateName = schedules[2].templateName;
+ await catchRevert(
+ I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, templateName, latestTime() + 100, {from: wallet_admin})
+ );
+ });
+
+ it("Should fail to remove template", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.removeTemplate("template-4-02", {from: wallet_admin})
+ );
+ });
+
+ it("Should remove 2 Templates", async () => {
+ let templateCount = await I_VestingEscrowWallet.getTemplateCount.call({from: wallet_admin});
+
+ await I_VestingEscrowWallet.removeTemplate("template-4-01", {from: wallet_admin});
+ await I_VestingEscrowWallet.removeTemplate("template-4-03", {from: wallet_admin});
+
+ let templateCountAfterRemoving = await I_VestingEscrowWallet.getTemplateCount.call({from: wallet_admin});
+ assert.equal(templateCount - templateCountAfterRemoving, 2);
+ });
+
+ });
+
+ describe("Tests for multi operations", async () => {
+
+ let templateNames = ["template-5-01", "template-5-02", "template-5-03"];
+
+ it("Should not be able to add schedules to the beneficiaries -- fail because of permissions check", async () => {
+ let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100];
+ await catchRevert(
+ I_VestingEscrowWallet.addScheduleMulti(beneficiaries, templateNames, [10000, 10000, 10000], [4, 4, 4], [1, 1, 1], startTimes, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should not be able to add schedules to the beneficiaries -- fail because of arrays sizes mismatch", async () => {
+ let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100];
+ let totalNumberOfTokens = 60000;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner});
+ await catchRevert(
+ I_VestingEscrowWallet.addScheduleMulti(beneficiaries, templateNames, [20000, 30000, 10000], [4, 4], [1, 1, 1], startTimes, {from: wallet_admin})
+ );
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+ });
+
+ it("Should add schedules for 3 beneficiaries", async () => {
+ let numberOfTokens = [15000, 15000, 15000];
+ let durations = [durationUtil.seconds(50), durationUtil.seconds(50), durationUtil.seconds(50)];
+ let frequencies = [durationUtil.seconds(10), durationUtil.seconds(10), durationUtil.seconds(10)];
+ let timeShift = durationUtil.seconds(100);
+ let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift];
+
+ let totalNumberOfTokens = 60000;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner});
+
+ let tx = await I_VestingEscrowWallet.addScheduleMulti(beneficiaries, templateNames, numberOfTokens, durations, frequencies, startTimes, {from: wallet_admin});
+
+ for (let i = 0; i < beneficiaries.length; i++) {
+ let templateName = templateNames[i];
+ let beneficiary = beneficiaries[i];
+ checkTemplateLog(tx.logs[i* 2], templateName, numberOfTokens[i], durations[i], frequencies[i]);
+ checkScheduleLog(tx.logs[i * 2 + 1], beneficiary, templateName, startTimes[i]);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(beneficiary);
+ assert.equal(scheduleCount, 1);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(beneficiary, templateName);
+ checkSchedule(schedule, numberOfTokens[i], durations[i], frequencies[i], startTimes[i], CREATED);
+ }
+ });
+
+ it("Should not be able modify vesting schedule for 3 beneficiary's addresses -- fail because of arrays sizes mismatch", async () => {
+ let timeShift = durationUtil.seconds(100);
+ let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift];
+
+ await catchRevert(
+ I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, ["template-5-01"], startTimes, {from: wallet_admin})
+ );
+ });
+
+ it("Should not be able to modify schedules for the beneficiaries -- fail because of permissions check", async () => {
+ let timeShift = durationUtil.seconds(100);
+ let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift];
+
+ await catchRevert(
+ I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, templateNames, startTimes, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should modify vesting schedule for 3 beneficiary's addresses", async () => {
+ let numberOfTokens = [15000, 15000, 15000];
+ let durations = [durationUtil.seconds(50), durationUtil.seconds(50), durationUtil.seconds(50)];
+ let frequencies = [durationUtil.seconds(10), durationUtil.seconds(10), durationUtil.seconds(10)];
+ let timeShift = durationUtil.seconds(100);
+ let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift];
+
+ const tx = await I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, templateNames, startTimes, {from: wallet_admin});
+ await increaseTime(timeShift + frequencies[0]);
+
+ for (let i = 0; i < beneficiaries.length; i++) {
+ let log = tx.logs[i];
+ let beneficiary = beneficiaries[i];
+ checkScheduleLog(log, beneficiary, templateNames[i], startTimes[i]);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(beneficiary);
+ assert.equal(scheduleCount, 1);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(beneficiary, templateNames[i]);
+ checkSchedule(schedule, numberOfTokens[i], durations[i], frequencies[i], startTimes[i], STARTED);
+ }
+ });
+
+ it("Should not be able to send available tokens to the beneficiaries addresses -- fail because of array size", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.pushAvailableTokensMulti(0, 3, {from: wallet_admin})
+ );
+ });
+
+ it("Should not be able to send available tokens to the beneficiaries -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.pushAvailableTokensMulti(0, 2, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should send available tokens to the beneficiaries addresses", async () => {
+ const tx = await I_VestingEscrowWallet.pushAvailableTokensMulti(0, 2, {from: wallet_admin});
+
+ for (let i = 0; i < beneficiaries.length; i++) {
+ let log = tx.logs[i];
+ let beneficiary = beneficiaries[i];
+ assert.equal(log.args._numberOfTokens.toNumber(), 3000);
+
+ let balance = await I_SecurityToken.balanceOf.call(beneficiary);
+ assert.equal(balance.toNumber(), 3000);
+
+ await I_SecurityToken.transfer(token_owner, balance, {from: beneficiary});
+ await I_VestingEscrowWallet.revokeAllSchedules(beneficiary, {from: wallet_admin});
+ await I_VestingEscrowWallet.removeTemplate(templateNames[i], {from: wallet_admin});
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+ }
+ });
+
+ it("Should not be able to add schedules from template to the beneficiaries -- fail because of permissions check", async () => {
+ let templateName = "template-6-01";
+ let numberOfTokens = 18000;
+ let duration = durationUtil.weeks(3);
+ let frequency = durationUtil.weeks(1);
+ let templateNames = [templateName, templateName, templateName];
+ let startTimes = [latestTime() + durationUtil.seconds(100), latestTime() + durationUtil.seconds(100), latestTime() + durationUtil.seconds(100)];
+
+ let totalNumberOfTokens = numberOfTokens * 3;
+ await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner});
+ await I_VestingEscrowWallet.addTemplate(templateName, numberOfTokens, duration, frequency, {from: wallet_admin});
+
+ await catchRevert(
+ I_VestingEscrowWallet.addScheduleFromTemplateMulti(beneficiaries, templateNames, startTimes, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should add schedules from template for 3 beneficiaries", async () => {
+ let templateName = "template-6-01";
+ let numberOfTokens = 18000;
+ let duration = durationUtil.weeks(3);
+ let frequency = durationUtil.weeks(1);
+ let templateNames = [templateName, templateName, templateName];
+ let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100];
+
+ let tx = await I_VestingEscrowWallet.addScheduleFromTemplateMulti(beneficiaries, templateNames, startTimes, {from: wallet_admin});
+ for (let i = 0; i < beneficiaries.length; i++) {
+ let log = tx.logs[i];
+ let beneficiary = beneficiaries[i];
+ checkScheduleLog(log, beneficiary, templateName, startTimes[i]);
+
+ let schedule = await I_VestingEscrowWallet.getSchedule.call(beneficiary, templateName);
+ checkSchedule(schedule, numberOfTokens, duration, frequency, startTimes[i], CREATED);
+ }
+ });
+
+ it("Should not be able to revoke schedules of the beneficiaries -- fail because of permissions check", async () => {
+ await catchRevert(
+ I_VestingEscrowWallet.revokeSchedulesMulti(beneficiaries, {from: account_beneficiary1})
+ );
+ });
+
+ it("Should revoke vesting schedule from the 3 beneficiary's addresses", async () => {
+ const tx = await I_VestingEscrowWallet.revokeSchedulesMulti(beneficiaries, {from: wallet_admin});
+
+ for (let i = 0; i < beneficiaries.length; i++) {
+ let log = tx.logs[i];
+ let beneficiary = beneficiaries[i];
+ assert.equal(log.args._beneficiary, beneficiary);
+
+ let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(beneficiary);
+ assert.equal(scheduleCount, 0);
+ }
+
+ let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call();
+ await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin});
+ });
+
+ });
+
+});
+
+function checkTemplateLog(log, templateName, numberOfTokens, duration, frequency) {
+ assert.equal(web3.utils.hexToUtf8(log.args._name), templateName);
+ assert.equal(log.args._numberOfTokens.toNumber(), numberOfTokens);
+ assert.equal(log.args._duration.toNumber(), duration);
+ assert.equal(log.args._frequency.toNumber(), frequency);
+}
+
+function checkScheduleLog(log, beneficiary, templateName, startTime) {
+ assert.equal(log.args._beneficiary, beneficiary);
+ assert.equal(web3.utils.hexToUtf8(log.args._templateName), templateName);
+ assert.equal(log.args._startTime.toNumber(), startTime);
+}
+
+function checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, state) {
+ assert.equal(schedule[0].toNumber(), numberOfTokens);
+ assert.equal(schedule[1].toNumber(), duration);
+ assert.equal(schedule[2].toNumber(), frequency);
+ assert.equal(schedule[3].toNumber(), startTime);
+ assert.equal(schedule[5].toNumber(), state);
+}
+
+function getTotalNumberOfTokens(schedules) {
+ let numberOfTokens = 0;
+ for (let i = 0; i < schedules.length; i++) {
+ numberOfTokens += schedules[i].numberOfTokens;
+ }
+ return numberOfTokens;
+}