Skip to content

Commit ab3d94b

Browse files
Merge pull request #422 from PolymathNetwork/Vesting-Escrow-Wallet
Vesting escrow wallet
2 parents c85c599 + 0ecdf79 commit ab3d94b

File tree

10 files changed

+2028
-5
lines changed

10 files changed

+2028
-5
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pragma solidity ^0.4.24;
2+
3+
import "../../Pausable.sol";
4+
import "../Module.sol";
5+
6+
/**
7+
* @title Interface to be implemented by all Wallet modules
8+
* @dev abstract contract
9+
*/
10+
contract IWallet is Module, Pausable {
11+
12+
function unpause() public onlyOwner {
13+
super._unpause();
14+
}
15+
16+
function pause() public onlyOwner {
17+
super._pause();
18+
}
19+
}

contracts/modules/Wallet/VestingEscrowWallet.sol

Lines changed: 558 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
pragma solidity ^0.4.24;
2+
3+
import "../../proxy/VestingEscrowWalletProxy.sol";
4+
import "../../interfaces/IBoot.sol";
5+
import "../ModuleFactory.sol";
6+
import "../../libraries/Util.sol";
7+
8+
/**
9+
* @title Factory for deploying VestingEscrowWallet module
10+
*/
11+
contract VestingEscrowWalletFactory is ModuleFactory {
12+
13+
address public logicContract;
14+
/**
15+
* @notice Constructor
16+
* @param _polyAddress Address of the polytoken
17+
*/
18+
constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public
19+
ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost)
20+
{
21+
require(_logicContract != address(0), "Invalid address");
22+
version = "1.0.0";
23+
name = "VestingEscrowWallet";
24+
title = "Vesting Escrow Wallet";
25+
description = "Manage vesting schedules to employees / affiliates";
26+
compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0));
27+
compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0));
28+
logicContract = _logicContract;
29+
}
30+
31+
/**
32+
* @notice Used to launch the Module with the help of factory
33+
* _data Data used for the intialization of the module factory variables
34+
* @return address Contract address of the Module
35+
*/
36+
function deploy(bytes _data) external returns(address) {
37+
if (setupCost > 0) {
38+
require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom due to insufficent Allowance provided");
39+
}
40+
VestingEscrowWalletProxy vestingEscrowWallet = new VestingEscrowWalletProxy(msg.sender, address(polyToken), logicContract);
41+
//Checks that _data is valid (not calling anything it shouldn't)
42+
require(Util.getSig(_data) == IBoot(vestingEscrowWallet).getInitFunction(), "Invalid data");
43+
/*solium-disable-next-line security/no-low-level-calls*/
44+
require(address(vestingEscrowWallet).call(_data), "Unsuccessfull call");
45+
/*solium-disable-next-line security/no-block-members*/
46+
emit GenerateModuleFromFactory(address(vestingEscrowWallet), getName(), address(this), msg.sender, setupCost, now);
47+
return address(vestingEscrowWallet);
48+
}
49+
50+
/**
51+
* @notice Type of the Module factory
52+
*/
53+
function getTypes() external view returns(uint8[]) {
54+
uint8[] memory res = new uint8[](1);
55+
res[0] = 6;
56+
return res;
57+
}
58+
59+
/**
60+
* @notice Returns the instructions associated with the module
61+
*/
62+
function getInstructions() external view returns(string) {
63+
/*solium-disable-next-line max-len*/
64+
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.";
65+
}
66+
67+
/**
68+
* @notice Get the tags related to the module factory
69+
*/
70+
function getTags() external view returns(bytes32[]) {
71+
bytes32[] memory availableTags = new bytes32[](2);
72+
availableTags[0] = "Vested";
73+
availableTags[1] = "Escrow Wallet";
74+
return availableTags;
75+
}
76+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
pragma solidity ^0.4.24;
2+
3+
/**
4+
* @title Wallet for core vesting escrow functionality
5+
*/
6+
contract VestingEscrowWalletStorage {
7+
8+
struct Schedule {
9+
// Name of the template
10+
bytes32 templateName;
11+
// Tokens that were already claimed
12+
uint256 claimedTokens;
13+
// Start time of the schedule
14+
uint256 startTime;
15+
}
16+
17+
struct Template {
18+
// Total amount of tokens
19+
uint256 numberOfTokens;
20+
// Schedule duration (How long the schedule will last)
21+
uint256 duration;
22+
// Schedule frequency (It is a cliff time period)
23+
uint256 frequency;
24+
// Index of the template in an array template names
25+
uint256 index;
26+
}
27+
28+
// Number of tokens that are hold by the `this` contract but are unassigned to any schedule
29+
uint256 public unassignedTokens;
30+
// Address of the Treasury wallet. All of the unassigned token will transfer to that address.
31+
address public treasuryWallet;
32+
// List of all beneficiaries who have the schedules running/completed/created
33+
address[] public beneficiaries;
34+
// Flag whether beneficiary has been already added or not
35+
mapping(address => bool) internal beneficiaryAdded;
36+
37+
// Holds schedules array corresponds to the affiliate/employee address
38+
mapping(address => Schedule[]) public schedules;
39+
// Holds template names array corresponds to the affiliate/employee address
40+
mapping(address => bytes32[]) internal userToTemplates;
41+
// Mapping use to store the indexes for different template names for a user.
42+
// affiliate/employee address => template name => index
43+
mapping(address => mapping(bytes32 => uint256)) internal userToTemplateIndex;
44+
// Holds affiliate/employee addresses coressponds to the template name
45+
mapping(bytes32 => address[]) internal templateToUsers;
46+
// Mapping use to store the indexes for different users for a template.
47+
// template name => affiliate/employee address => index
48+
mapping(bytes32 => mapping(address => uint256)) internal templateToUserIndex;
49+
// Store the template details corresponds to the template name
50+
mapping(bytes32 => Template) templates;
51+
52+
// List of all template names
53+
bytes32[] public templateNames;
54+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
pragma solidity ^0.4.24;
2+
3+
import "../modules/Wallet/VestingEscrowWalletStorage.sol";
4+
import "./OwnedProxy.sol";
5+
import "../Pausable.sol";
6+
import "../modules/ModuleStorage.sol";
7+
/**
8+
* @title Escrow wallet module for vesting functionality
9+
*/
10+
contract VestingEscrowWalletProxy is VestingEscrowWalletStorage, ModuleStorage, Pausable, OwnedProxy {
11+
/**
12+
* @notice Constructor
13+
* @param _securityToken Address of the security token
14+
* @param _polyAddress Address of the polytoken
15+
* @param _implementation representing the address of the new implementation to be set
16+
*/
17+
constructor (address _securityToken, address _polyAddress, address _implementation)
18+
public
19+
ModuleStorage(_securityToken, _polyAddress)
20+
{
21+
require(
22+
_implementation != address(0),
23+
"Implementation address should not be 0x"
24+
);
25+
__implementation = _implementation;
26+
}
27+
}

docs/permissions_list.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,60 @@
264264
</tr>
265265
<tr>
266266
<td> removeTransferLimitInPercentageMulti </td>
267+
</tr>
268+
<tr></tr>
269+
<tr>
270+
<td rowspan=16>Wallet</td>
271+
<td rowspan=16>VestingEscrowWallet</td>
272+
<td>changeTreasuryWallet()</td>
273+
<td>onlyOwner</td>
274+
</tr>
275+
<tr>
276+
<td>depositTokens()</td>
277+
<td rowspan=15>withPerm(ADMIN)</td>
278+
</tr>
279+
<tr>
280+
<td>sendToTreasury()</td>
281+
</tr>
282+
<tr>
283+
<td>pushAvailableTokens()</td>
284+
</tr>
285+
<tr>
286+
<td>addTemplate()</td>
287+
</tr>
288+
<tr>
289+
<td>removeTemplate()</td>
290+
</tr>
291+
<tr>
292+
<td>addSchedule()</td>
293+
</tr>
294+
<tr>
295+
<td>addScheduleFromTemplate()</td>
267296
</tr>
297+
<tr>
298+
<td>modifySchedule()</td>
299+
</tr>
300+
<tr>
301+
<td>revokeSchedule()</td>
302+
</tr>
303+
<tr>
304+
<td>revokeAllSchedules()</td>
305+
</tr>
306+
<tr>
307+
<td>pushAvailableTokensMulti()</td>
308+
</tr>
309+
<tr>
310+
<td>addScheduleMulti()</td>
311+
</tr>
312+
<tr>
313+
<td>addScheduleFromTemplateMulti()</td>
314+
</tr>
315+
<tr>
316+
<td>revokeSchedulesMulti()</td>
317+
</tr>
318+
<tr>
319+
<td>modifyScheduleMulti()</td>
320+
</tr>
268321
</tbody>
269322
</table>
270323

migrations/2_deploy_contracts.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const EtherDividendCheckpointLogic = artifacts.require('./EtherDividendCheckpoin
99
const ERC20DividendCheckpointLogic = artifacts.require('./ERC20DividendCheckpoint.sol')
1010
const EtherDividendCheckpointFactory = artifacts.require('./EtherDividendCheckpointFactory.sol')
1111
const ERC20DividendCheckpointFactory = artifacts.require('./ERC20DividendCheckpointFactory.sol')
12+
const VestingEscrowWalletFactory = artifacts.require('./VestingEscrowWalletFactory.sol');
13+
const VestingEscrowWalletLogic = artifacts.require('./VestingEscrowWallet.sol');
1214
const ModuleRegistry = artifacts.require('./ModuleRegistry.sol');
1315
const ModuleRegistryProxy = artifacts.require('./ModuleRegistryProxy.sol');
1416
const ManualApprovalTransferManagerFactory = artifacts.require('./ManualApprovalTransferManagerFactory.sol')
@@ -154,17 +156,25 @@ module.exports = function (deployer, network, accounts) {
154156
// manager attach with the securityToken contract at the time of deployment)
155157
return deployer.deploy(GeneralTransferManagerLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
156158
}).then(() => {
157-
// B) Deploy the GeneralTransferManagerLogic Contract (Factory used to generate the GeneralTransferManager contract and this
159+
// B) Deploy the ERC20DividendCheckpointLogic Contract (Factory used to generate the ERC20DividendCheckpoint contract and this
158160
// manager attach with the securityToken contract at the time of deployment)
159161
return deployer.deploy(ERC20DividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
160162
}).then(() => {
161-
// B) Deploy the GeneralTransferManagerLogic Contract (Factory used to generate the GeneralTransferManager contract and this
163+
// B) Deploy the EtherDividendCheckpointLogic Contract (Factory used to generate the EtherDividendCheckpoint contract and this
162164
// manager attach with the securityToken contract at the time of deployment)
163165
return deployer.deploy(EtherDividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
166+
}).then(() => {
167+
// B) Deploy the VestingEscrowWalletLogic Contract (Factory used to generate the VestingEscrowWallet contract and this
168+
// manager attach with the securityToken contract at the time of deployment)
169+
return deployer.deploy(VestingEscrowWalletLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
164170
}).then(() => {
165171
// B) Deploy the USDTieredSTOLogic Contract (Factory used to generate the USDTieredSTO contract and this
166172
// manager attach with the securityToken contract at the time of deployment)
167173
return deployer.deploy(USDTieredSTOLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount});
174+
}).then(() => {
175+
// B) Deploy the VestingEscrowWalletFactory Contract (Factory used to generate the VestingEscrowWallet contract and this
176+
// manager attach with the securityToken contract at the time of deployment)
177+
return deployer.deploy(VestingEscrowWalletFactory, PolyToken, 0, 0, 0, VestingEscrowWalletLogic.address, {from: PolymathAccount});
168178
}).then(() => {
169179
// B) Deploy the GeneralTransferManagerFactory Contract (Factory used to generate the GeneralTransferManager contract and this
170180
// manager attach with the securityToken contract at the time of deployment)
@@ -220,6 +230,10 @@ module.exports = function (deployer, network, accounts) {
220230
// D) Register the PercentageTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level.
221231
// So any securityToken can use that factory to generate the PercentageTransferManager contract.
222232
return moduleRegistry.registerModule(PercentageTransferManagerFactory.address, {from: PolymathAccount});
233+
}).then(() => {
234+
// D) Register the VestingEscrowWalletFactory in the ModuleRegistry to make the factory available at the protocol level.
235+
// So any securityToken can use that factory to generate the VestingEscrowWallet contract.
236+
return moduleRegistry.registerModule(VestingEscrowWalletFactory.address, {from: PolymathAccount});
223237
}).then(() => {
224238
// D) Register the CountTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level.
225239
// So any securityToken can use that factory to generate the CountTransferManager contract.
@@ -279,6 +293,11 @@ module.exports = function (deployer, network, accounts) {
279293
// contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only.
280294
// Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts.
281295
return moduleRegistry.verifyModule(ManualApprovalTransferManagerFactory.address, true, {from: PolymathAccount});
296+
}).then(() => {
297+
// F) Once the VestingEscrowWalletFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken
298+
// contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only.
299+
// Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts.
300+
return moduleRegistry.verifyModule(VestingEscrowWalletFactory.address, true, {from: PolymathAccount});
282301
}).then(() => {
283302
// M) Deploy the CappedSTOFactory (Use to generate the CappedSTO contract which will used to collect the funds ).
284303
return deployer.deploy(CappedSTOFactory, PolyToken, cappedSTOSetupCost, 0, 0, {from: PolymathAccount})
@@ -337,6 +356,8 @@ module.exports = function (deployer, network, accounts) {
337356
ERC20DividendCheckpointLogic: ${ERC20DividendCheckpointLogic.address}
338357
EtherDividendCheckpointFactory: ${EtherDividendCheckpointFactory.address}
339358
ERC20DividendCheckpointFactory: ${ERC20DividendCheckpointFactory.address}
359+
VestingEscrowWalletFactory: ${VestingEscrowWalletFactory.address}
360+
VestingEscrowWalletLogic: ${VestingEscrowWalletLogic.address}
340361
---------------------------------------------------------------------------------
341362
`);
342363
console.log('\n');

test/helpers/createInstances.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol");
3232
const DummySTOFactory = artifacts.require("./DummySTOFactory.sol");
3333
const MockBurnFactory = artifacts.require("./MockBurnFactory.sol");
3434
const MockWrongTypeFactory = artifacts.require("./MockWrongTypeFactory.sol");
35+
const VestingEscrowWalletFactory = artifacts.require("./VestingEscrowWalletFactory.sol");
36+
const VestingEscrowWallet = artifacts.require("./VestingEscrowWallet.sol");
3537

3638
const Web3 = require("web3");
3739
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port
@@ -54,6 +56,7 @@ let I_ERC20DividendCheckpointFactory;
5456
let I_GeneralPermissionManagerFactory;
5557
let I_GeneralTransferManagerLogic;
5658
let I_GeneralTransferManagerFactory;
59+
let I_VestingEscrowWalletFactory;
5760
let I_GeneralTransferManager;
5861
let I_ModuleRegistryProxy;
5962
let I_PreSaleSTOFactory;
@@ -68,6 +71,7 @@ let I_STFactory;
6871
let I_USDTieredSTOLogic;
6972
let I_PolymathRegistry;
7073
let I_SecurityTokenRegistryProxy;
74+
let I_VestingEscrowWalletLogic;
7175
let I_STRProxied;
7276
let I_MRProxied;
7377

@@ -104,7 +108,7 @@ export async function setUpPolymathNetwork(account_polymath, token_owner) {
104108
}
105109

106110

107-
async function deployPolyRegistryAndPolyToken(account_polymath, token_owner) {
111+
export async function deployPolyRegistryAndPolyToken(account_polymath, token_owner) {
108112
// Step 0: Deploy the PolymathRegistry
109113
I_PolymathRegistry = await PolymathRegistry.new({ from: account_polymath });
110114

@@ -404,6 +408,19 @@ export async function deployRedemptionAndVerifyed(accountPolymath, MRProxyInstan
404408
return new Array(I_TrackedRedemptionFactory);
405409
}
406410

411+
export async function deployVestingEscrowWalletAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) {
412+
I_VestingEscrowWalletLogic = await VestingEscrowWallet.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath });
413+
I_VestingEscrowWalletFactory = await VestingEscrowWalletFactory.new(polyToken, setupCost, 0, 0, I_VestingEscrowWalletLogic.address, { from: accountPolymath });
414+
415+
assert.notEqual(
416+
I_VestingEscrowWalletFactory.address.valueOf(),
417+
"0x0000000000000000000000000000000000000000",
418+
"VestingEscrowWalletFactory contract was not deployed"
419+
);
420+
421+
await registerAndVerifyByMR(I_VestingEscrowWalletFactory.address, accountPolymath, MRProxyInstance);
422+
return new Array(I_VestingEscrowWalletFactory);
423+
}
407424

408425
export async function deployMockRedemptionAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) {
409426
I_MockBurnFactory = await MockBurnFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath });
@@ -431,8 +448,6 @@ export async function deployMockWrongTypeRedemptionAndVerifyed(accountPolymath,
431448
return new Array(I_MockWrongTypeBurnFactory);
432449
}
433450

434-
435-
436451
/// Helper function
437452
function mergeReturn(returnData) {
438453
let returnArray = new Array();

test/helpers/exceptions.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ module.exports = {
2525
catchRevert: async function(promise) {
2626
await tryCatch(promise, "revert");
2727
},
28+
catchPermission: async function(promise) {
29+
await tryCatch(promise, "revert Permission check failed");
30+
},
2831
catchOutOfGas: async function(promise) {
2932
await tryCatch(promise, "out of gas");
3033
},

0 commit comments

Comments
 (0)