Skip to content

Commit

Permalink
Merge pull request #422 from PolymathNetwork/Vesting-Escrow-Wallet
Browse files Browse the repository at this point in the history
Vesting escrow wallet
  • Loading branch information
VictorVicente authored Jan 3, 2019
2 parents c85c599 + 0ecdf79 commit ab3d94b
Show file tree
Hide file tree
Showing 10 changed files with 2,028 additions and 5 deletions.
19 changes: 19 additions & 0 deletions contracts/modules/Wallet/IWallet.sol
Original file line number Diff line number Diff line change
@@ -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();
}
}
558 changes: 558 additions & 0 deletions contracts/modules/Wallet/VestingEscrowWallet.sol

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions contracts/modules/Wallet/VestingEscrowWalletFactory.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
54 changes: 54 additions & 0 deletions contracts/modules/Wallet/VestingEscrowWalletStorage.sol
Original file line number Diff line number Diff line change
@@ -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;
}
27 changes: 27 additions & 0 deletions contracts/proxy/VestingEscrowWalletProxy.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
53 changes: 53 additions & 0 deletions docs/permissions_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,60 @@
</tr>
<tr>
<td> removeTransferLimitInPercentageMulti </td>
</tr>
<tr></tr>
<tr>
<td rowspan=16>Wallet</td>
<td rowspan=16>VestingEscrowWallet</td>
<td>changeTreasuryWallet()</td>
<td>onlyOwner</td>
</tr>
<tr>
<td>depositTokens()</td>
<td rowspan=15>withPerm(ADMIN)</td>
</tr>
<tr>
<td>sendToTreasury()</td>
</tr>
<tr>
<td>pushAvailableTokens()</td>
</tr>
<tr>
<td>addTemplate()</td>
</tr>
<tr>
<td>removeTemplate()</td>
</tr>
<tr>
<td>addSchedule()</td>
</tr>
<tr>
<td>addScheduleFromTemplate()</td>
</tr>
<tr>
<td>modifySchedule()</td>
</tr>
<tr>
<td>revokeSchedule()</td>
</tr>
<tr>
<td>revokeAllSchedules()</td>
</tr>
<tr>
<td>pushAvailableTokensMulti()</td>
</tr>
<tr>
<td>addScheduleMulti()</td>
</tr>
<tr>
<td>addScheduleFromTemplateMulti()</td>
</tr>
<tr>
<td>revokeSchedulesMulti()</td>
</tr>
<tr>
<td>modifyScheduleMulti()</td>
</tr>
</tbody>
</table>

Expand Down
25 changes: 23 additions & 2 deletions migrations/2_deploy_contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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})
Expand Down Expand Up @@ -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');
Expand Down
21 changes: 18 additions & 3 deletions test/helpers/createInstances.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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 });

Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -431,8 +448,6 @@ export async function deployMockWrongTypeRedemptionAndVerifyed(accountPolymath,
return new Array(I_MockWrongTypeBurnFactory);
}



/// Helper function
function mergeReturn(returnData) {
let returnArray = new Array();
Expand Down
3 changes: 3 additions & 0 deletions test/helpers/exceptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
},
Expand Down
Loading

0 comments on commit ab3d94b

Please sign in to comment.