diff --git a/contracts/ModuleRegistry.sol b/contracts/ModuleRegistry.sol index b494bf872..1e117a961 100644 --- a/contracts/ModuleRegistry.sol +++ b/contracts/ModuleRegistry.sol @@ -3,6 +3,7 @@ pragma solidity ^0.4.18; import './interfaces/IModuleRegistry.sol'; import './interfaces/IModuleFactory.sol'; import './interfaces/ISecurityToken.sol'; +import './interfaces/ISecurityTokenRegistry.sol'; import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; /** @@ -18,12 +19,16 @@ contract ModuleRegistry is IModuleRegistry, Ownable { mapping (uint8 => address[]) public moduleList; mapping (address => bool) public verified; + address public securityTokenRegistry; + event LogModuleUsed(address indexed _moduleFactory, address indexed _securityToken); event LogModuleRegistered(address indexed _moduleFactory, address indexed _owner); event LogModuleVerified(address indexed _moduleFactory, bool _verified); //Checks that module is correctly configured in registry function useModule(address _moduleFactory) external { + //msg.sender must be a security token - below will throw if not + ISecurityTokenRegistry(securityTokenRegistry).getSecurityTokenData(msg.sender); require(registry[_moduleFactory] != 0); //To use a module, either it must be verified, or owned by the ST owner require(verified[_moduleFactory] || (IModuleFactory(_moduleFactory).owner() == ISecurityToken(msg.sender).owner())); @@ -31,6 +36,12 @@ contract ModuleRegistry is IModuleRegistry, Ownable { LogModuleUsed(_moduleFactory, msg.sender); } + //Sets the securityTokenRegistry so that moduleRegistry can validate security tokens are genuine + function setTokenRegistry(address _securityTokenRegistry) public onlyOwner { + require(_securityTokenRegistry != address(0)); + securityTokenRegistry = _securityTokenRegistry; + } + /** * @dev Called by Polymath to register new modules for SecurityToken to use * @param _moduleFactory is the address of the module factory to be registered diff --git a/contracts/interfaces/IModule.sol b/contracts/interfaces/IModule.sol index 97231ff90..c6e1e2467 100644 --- a/contracts/interfaces/IModule.sol +++ b/contracts/interfaces/IModule.sol @@ -1,6 +1,7 @@ pragma solidity ^0.4.18; import './ISecurityToken.sol'; +import './IModuleFactory.sol'; //Simple interface that any module contracts should implement contract IModule { @@ -34,5 +35,10 @@ contract IModule { _; } + modifier onlyFactoryOwner { + require(msg.sender == IModuleFactory(factory).owner()); + _; + } + function permissions() public returns(bytes32[]); } diff --git a/contracts/modules/TransferManager/ExchangeTransferManager.sol b/contracts/modules/TransferManager/ExchangeTransferManager.sol new file mode 100644 index 000000000..eb21c0ec1 --- /dev/null +++ b/contracts/modules/TransferManager/ExchangeTransferManager.sol @@ -0,0 +1,60 @@ +pragma solidity ^0.4.18; + +import './ITransferManager.sol'; + +///////////////////// +// Module permissions +///////////////////// +// Factory Owner +// modifyWhitelist X +// modifyWhitelistMulti X + +contract ExchangeTransferManager is ITransferManager { + + address public exchange; + mapping (address => bool) public whitelist; + + event LogModifyWhitelist(address _investor, uint256 _dateAdded, address _addedBy); + + function ExchangeTransferManager(address _securityToken) + IModule(_securityToken) + public + { + } + + function configure(address _exchange) public onlyFactory { + exchange = _exchange; + } + + function getInitFunction() public returns(bytes4) { + return bytes4(keccak256("configure(address)")); + } + + function verifyTransfer(address _from, address _to, uint256 _amount) view external returns(bool) { + //Transfer must be from / to the exchange + require((_from == exchange) || (_to == exchange)); + return getExchangePermission(_from) || getExchangePermission(_to); + } + + function getExchangePermission(address _investor) view internal returns (bool) { + //This function could implement more complex logic, e.g. calling out to another contract maintained by the exchange to get list of allowed users + return whitelist[_investor]; + } + + function modifyWhitelist(address _investor, bool _valid) public onlyFactoryOwner { + whitelist[_investor] = _valid; + LogModifyWhitelist(_investor, now, msg.sender); + } + + function modifyWhitelistMulti(address[] _investors, bool[] _valids) public onlyFactoryOwner { + require(_investors.length == _valids.length); + for (uint256 i = 0; i < _investors.length; i++) { + modifyWhitelist(_investors[i], _valids[i]); + } + } + + function permissions() public returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](0); + return allPermissions; + } +} diff --git a/contracts/modules/TransferManager/ExchangeTransferManagerFactory.sol b/contracts/modules/TransferManager/ExchangeTransferManagerFactory.sol new file mode 100644 index 000000000..685a51481 --- /dev/null +++ b/contracts/modules/TransferManager/ExchangeTransferManagerFactory.sol @@ -0,0 +1,37 @@ +pragma solidity ^0.4.18; + +import './ExchangeTransferManager.sol'; +import '../../interfaces/IModuleFactory.sol'; + +contract ExchangeTransferManagerFactory is IModuleFactory { + + function deploy(bytes _data) external returns(address) { + //polyToken.transferFrom(msg.sender, owner, getCost()); + ExchangeTransferManager exchangeTransferManager = new ExchangeTransferManager(msg.sender); + require(getSig(_data) == exchangeTransferManager.getInitFunction()); + require(address(exchangeTransferManager).call(_data)); + return address(exchangeTransferManager); + + } + + function getCost() view external returns(uint256) { + return 0; + } + + function getType() view external returns(uint8) { + return 2; + } + + function getName() view external returns(bytes32) { + return "ExchangeTransferManager"; + } + + function getDescription() view external returns(string) { + return "Manage transfers within an exchange"; + } + + function getTitle() view external returns(string) { + return "Exchange Transfer Manager"; + } + +} diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index 37c6b493d..2502650d9 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -38,18 +38,27 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { // TransferManager has a key of 2 // STO has a key of 3 // Other modules TBD - mapping (uint8 => ModuleData) public modules; + // Module list should be order agnostic! + mapping (uint8 => ModuleData[]) public modules; + + uint8 public MAX_MODULES = 10; event LogModuleAdded(uint8 indexed _type, bytes32 _name, address _moduleFactory, address _module, uint256 _moduleCost, uint256 _budget, uint256 _timestamp); + event LogModuleRemoved(uint8 indexed _type, address _module, uint256 _timestamp); event LogModuleBudgetChanged(uint8 indexed _moduleType, address _module, uint256 _budget); event Mint(address indexed to, uint256 amount); //if _fallback is true, then we only allow the module if it is set, if it is not set we only allow the owner - modifier onlyModule(uint8 _i, bool _fallback) { - if (_fallback && (address(0) == modules[_i].moduleAddress)) { + modifier onlyModule(uint8 _moduleType, bool _fallback) { + //Loop over all modules of type _moduleType + bool isModuleType = false; + for (uint8 i = 0; i < modules[_moduleType].length; i++) { + isModuleType = isModuleType || (modules[_moduleType][i].moduleAddress == msg.sender); + } + if (_fallback && !isModuleType) { require(msg.sender == owner); } else { - require(msg.sender == modules[_i].moduleAddress); + require(isModuleType); } _; } @@ -71,8 +80,7 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { //owner = _owner; } - function addModule(address _moduleFactory, bytes _data, uint256 _maxCost, uint256 _budget, bool _replaceable) external { - require(msg.sender == owner); + function addModule(address _moduleFactory, bytes _data, uint256 _maxCost, uint256 _budget, bool _replaceable) external onlyOwner { _addModule(_moduleFactory, _data, _maxCost, _budget, _replaceable); } @@ -85,7 +93,9 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { * @param _maxCost max amount of POLY willing to pay to module. (WIP) * @param _replaceable whether or not the module is supposed to be replaceable */ - //You are only ever allowed one instance, for a given module type + //You are allowed to add a new moduleType if: + // - there is no existing module of that type yet added + // - the last member of the module list is replacable function _addModule(address _moduleFactory, bytes _data, uint256 _maxCost, uint256 _budget, bool _replaceable) internal { //Check that module exists in registry - will throw otherwise IModuleRegistry(moduleRegistry).useModule(_moduleFactory); @@ -93,8 +103,8 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { uint256 moduleCost = moduleFactory.getCost(); require(moduleCost <= _maxCost); //Check that this module has not already been set as non-replaceable - if (modules[moduleFactory.getType()].moduleAddress != address(0)) { - require(modules[moduleFactory.getType()].replaceable); + if (modules[moduleFactory.getType()].length != 0) { + require(modules[moduleFactory.getType()][modules[moduleFactory.getType()].length - 1].replaceable); } //Approve fee for module require(polyToken.approve(_moduleFactory, moduleCost)); @@ -103,20 +113,30 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { //Approve ongoing budget require(polyToken.approve(module, _budget)); //Add to SecurityToken module map - modules[moduleFactory.getType()] = ModuleData(moduleFactory.getName(), module, _replaceable); + modules[moduleFactory.getType()].push(ModuleData(moduleFactory.getName(), module, _replaceable)); //Emit log event LogModuleAdded(moduleFactory.getType(), moduleFactory.getName(), _moduleFactory, module, moduleCost, _budget, now); } + function removeModule(uint8 _moduleType, uint8 _moduleIndex) external onlyOwner { + require(_moduleIndex < modules[_moduleType].length); + require(modules[_moduleType][_moduleIndex].moduleAddress != address(0)); + require(modules[_moduleType][_moduleIndex].replaceable); + //Take the last member of the list, and replace _moduleIndex with this, then shorten the list by one + LogModuleRemoved(_moduleType, modules[_moduleType][_moduleIndex].moduleAddress, now); + modules[_moduleType][_moduleIndex] = modules[_moduleType][modules[_moduleType].length - 1]; + modules[_moduleType].length = modules[_moduleType].length - 1; + } + function withdrawPoly(uint256 _amount) public onlyOwner { require(polyToken.transfer(owner, _amount)); } - function changeModuleBudget(uint8 _moduleType, uint256 _budget) public onlyOwner { + function changeModuleBudget(uint8 _moduleType, uint8 _moduleIndex, uint256 _budget) public onlyOwner { require(_moduleType != 0); - require(modules[_moduleType].moduleAddress != address(0)); - require(polyToken.approve(modules[_moduleType].moduleAddress, _budget)); - LogModuleBudgetChanged(_moduleType, modules[_moduleType].moduleAddress, _budget); + require(_moduleIndex < modules[_moduleType].length); + require(polyToken.approve(modules[_moduleType][_moduleType].moduleAddress, _budget)); + LogModuleBudgetChanged(_moduleType, modules[_moduleType][_moduleType].moduleAddress, _budget); } /** @@ -138,10 +158,15 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { // Permissions this to a TransferManager module, which has a key of 2 // If no TransferManager return true function verifyTransfer(address _from, address _to, uint256 _amount) view public returns (bool success) { - if (modules[2].moduleAddress == address(0)) { + if (modules[2].length == 0) { return true; } - return ITransferManager(modules[2].moduleAddress).verifyTransfer(_from, _to, _amount); + for (uint8 i = 0; i < modules[2].length; i++) { + if (ITransferManager(modules[2][i].moduleAddress).verifyTransfer(_from, _to, _amount)) { + return true; + } + } + return false; } // Only STO module can call this, has a key of 3 @@ -163,10 +188,17 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { // If no Permission return false - note that IModule withPerm will allow ST owner all permissions anyway // this allows individual modules to override this logic if needed (to not allow ST owner all permissions) function checkPermission(address _delegate, address _module, bytes32 _perm) view public returns(bool) { - if (modules[1].moduleAddress == address(0)) { + + if (modules[1].length == 0) { return false; } - return IPermissionManager(modules[1].moduleAddress).checkPermission(_delegate, _module, _perm); + + for (uint8 i = 0; i < modules[1].length; i++) { + if (IPermissionManager(modules[1][i].moduleAddress).checkPermission(_delegate, _module, _perm)) { + return true; + } + } + } } diff --git a/docs/multiple_transfer_managers.md b/docs/multiple_transfer_managers.md new file mode 100644 index 000000000..b19797358 --- /dev/null +++ b/docs/multiple_transfer_managers.md @@ -0,0 +1,53 @@ +# Multiple Transfer Managers + +We consider the two most common types of exchanges: + - a CEX where the Issuer is willing to cede some control to the CEX for authorising users + - a DEX where the Issuer wants to maintain full control over who can trade tokens + +We also consider the case where an Issuer may want to have multiple KYC providers + +## CEX + +The Issuer and the CEX reach an agreement that the CEX is allowed to authorise users to transfer and receive tokens from the CEX, following an appropriate authorisation process for the user. + +The Issuer and the CEX should agree the set of rules which an investor must meet in order to become authorised. + +If these rules depend on the current `GeneralTransferManager` state (e.g. whether a user is already on the `GeneralTransferManager` or not, and / or their time based restrictions) then the `ExchangeTransferManager` may need to be passed the `GeneralTransferManager` in its `initFunction` so that it can appropriately enforce these rules when adding users to the `ExchangeTransferManager` whitelist by referencing the state of the `GeneralTransferManager`. + +The Issuer adds the CEX's `ExchangeTransferManager` to its token as a `TransferManager` module. + +The logic for verifying transfers in the `ExchangeTransferManager` will likely depend on the specific exchange, but as an example we consider the following agreement / rules: + - the CEX is allowed to authorise transfers between itself and users (representing deposits / withdrawals) + - in other words, the CEX TransferManager will only verify transfers between the CEX wallet address and its authorised users. + +As users are authorised by the CEX, the CEX adds the users address to the whitelist on the `ExchangeTransferManager` instance. + +If the user wants to deposit funds to the exchange, then a `ST.transfer(userAddress, exchangeAddress, amount)` is triggered. Since the user has been added to the `ExchangeTransferManager`, this transfer will be deemed valid by the `SecurityToken`. + +If the user wants to withdraw funds from the exchange, then a `ST.transfer(exchangeAddress, userAddress, amount)` is triggered. Since the user has been added to the `ExchangeTransferManager`, this transfer will be deemed valid by the `SecurityToken`. + +The CEX should ensure that before allowing a user to purchase a particular token on its exchange, that the user is authorised to do so, and has been added to the corresponding `SecurityToken`'s `ExchangeTransferManager`. This avoids the problem where a user may buy a token on an exchange, but then not be able to subsequently withdraw it. i.e. it should call `verifyTransfer(exchangeAddress, userAddress, amount)` and only allow the trade if this returns `true`. + +### Notes + +It is possible for the CEX to maintain it's own contract which lists all of its authorised users across all security tokens. Its `ExchangeTransferManager` can then reference this global list, possibly enforcing additional criteria on users metadata (e.g. their jurisdiction and so on). This would allow it to easily offer `ExchangeTransferManager`'s for its exchange to multiple Issuers at a very low additional cost to itself (since the `ExchangeTransferManager` is generic and just references the exchanges own contract where it maintains a global list of authorised users). Obviously the Issuer would need to agree to this approach. + +It may be that the exchange doesn't have a single exchange address (i.e. each user has their own wallet) which would make the implementation harder. This logic would then need to be determined on a case by case basis, but the exchange should be able to write their own TransferManager with appropriate rules and add it to the Polymath ecosystem. + +## DEX + +It may be that the Issuer does not want to delegate any responsibility to the DEX to authorise users. + +In this case, the following is a straightforward approach: + - DEX contract is added to the `GeneralTransferManager` by the Issuer (probably with no time restrictions). + - Before allowing a user to deposit to the DEX, the DEX calls `verifyTransfer(DEXAddress, userAddress, amount)` and only allows the trade if this returns `true`. This ensures that the user would be able to withdraw any deposited tokens if they decide to. + - Before allowing a user to buy tokens on the DEX, the DEX calls `verifyTransfer(DEXAddress, userAddress, amount)` and only allows the trade if this returns `true`. This ensures that the user would be able to withdraw any deposited tokens if they decide to. + - The check to `verifyTransfer(userAddress, DEXAddress, amount)` is implicit in the deposit transfer, but an exchange could also call this explicitly if they wanted to, and for example only show SecurityTokens to the user for which this returns `true`. + +With this approach, users can only transfer tokens to the exchange if they are on the `GeneralTransferManager` with a `fromTime` in the past, and users can only purchase tokens on the exchange if they are on the `GeneralTransferManager` with a `toTime` in the past. + +## Multiple KYC Providers + +An issuer may want to have multiple KYC providers. In this case it is straightforward to add multiple TransferManagers to their SecurityToken, one per KYC provider, and each KYC provider can operate independently (with the full set of authorised users being users who are authorised by any single KYC provider). + +The Issuer could subsequently remove one of the KYC providers TransferManagers from their SecurityToken if they were unhappy with the service (although they would not get any POLY paid for the service refunded). diff --git a/package-lock.json b/package-lock.json index 0c318d56e..e6d1f36fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "polymath-core_v2", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/test/ExchangeTransferManager.js b/test/ExchangeTransferManager.js new file mode 100644 index 000000000..7a196c368 --- /dev/null +++ b/test/ExchangeTransferManager.js @@ -0,0 +1,388 @@ +import latestTime from './helpers/latestTime'; +import { duration, ensureException } from './helpers/utils'; +import { increaseTime } from './helpers/time'; + +const DummySTOFactory = artifacts.require('./DummySTOFactory.sol'); +const DummySTO = artifacts.require('./DummySTO.sol'); +const ModuleRegistry = artifacts.require('./ModuleRegistry.sol'); +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const SecurityTokenRegistry = artifacts.require('./SecurityTokenRegistry.sol'); +const TickerRegistry = artifacts.require("./TickerRegistry.sol"); +const STVersion = artifacts.require('./STVersionProxy_001.sol'); +const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol'); +const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol'); +const ExchangeTransferManagerFactory = artifacts.require('./ExchangeTransferManagerFactory.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const ExchangeTransferManager = artifacts.require('./ExchangeTransferManager'); +const PolyToken = artifacts.require('./PolyToken.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('ExchangeTransferManager', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_investor1; + let account_issuer; + let token_owner; + let account_investor2; + let account_exchange; + // investor Details + let fromTime = latestTime(); + let toTime = latestTime() + duration.days(15); + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let I_GeneralTransferManagerFactory; + let I_ExchangeTransferManagerFactory; + let I_GeneralPermissionManager; + let I_GeneralTransferManager; + let I_ExchangeTransferManager; + let I_ModuleRegistry; + let I_TickerRegistry; + let I_SecurityTokenRegistry; + let I_DummySTOFactory; + let I_STVersion; + let I_SecurityToken; + let I_DummySTO; + let I_PolyToken; + + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + // Dummy STO details + const startTime = latestTime() + duration.seconds(5000); // Start time will be 5000 seconds more than the latest time + const endTime = startTime + duration.days(30); // Add 30 days more + const cap = web3.utils.toWei('10', 'ether'); + const someString = "A string which is not used"; + + let bytesSTO = web3.eth.abi.encodeFunctionCall({ + name: 'configure', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_startTime' + },{ + type: 'uint256', + name: '_endTime' + },{ + type: 'uint256', + name: '_cap' + },{ + type: 'string', + name: '_someString' + } + ] + }, [startTime, endTime, cap, someString]); + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + account_investor1 = accounts[2]; + account_investor2 = accounts[3]; + account_exchange = accounts[4]; + token_owner = account_issuer; + + // ----------- POLYMATH NETWORK Configuration ------------ + + // STEP 1: Deploy the ModuleRegistry + + I_ModuleRegistry = await ModuleRegistry.new({from:account_polymath}); + + assert.notEqual( + I_ModuleRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "ModuleRegistry contract was not deployed" + ); + + // STEP 2a: Deploy the GeneralTransferManagerFactory + + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new({from:account_polymath}); + + assert.notEqual( + I_GeneralTransferManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManagerFactory contract was not deployed" + ); + + // STEP 2b: Deploy the ExchangeTransferManagerFactory + + I_ExchangeTransferManagerFactory = await ExchangeTransferManagerFactory.new({from:account_polymath}); + + assert.notEqual( + I_ExchangeTransferManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "ExchangeTransferManagerFactory contract was not deployed" + ); + + // STEP 3: Deploy the GeneralDelegateManagerFactory + + I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new({from:account_polymath}); + + assert.notEqual( + I_GeneralPermissionManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralDelegateManagerFactory contract was not deployed" + ); + + // STEP 4: Deploy the DummySTOFactory + + I_DummySTOFactory = await DummySTOFactory.new({from:account_polymath}); + + assert.notEqual( + I_DummySTOFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "DummySTOFactory contract was not deployed" + ); + + // STEP 5: Register the Modules with the ModuleRegistry contract + + // (A) : Register the GeneralTransferManagerFactory + await I_ModuleRegistry.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); + await I_ModuleRegistry.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + + // (B) : Register the GeneralDelegateManagerFactory + await I_ModuleRegistry.registerModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); + await I_ModuleRegistry.verifyModule(I_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); + + // (C) : Register the STOFactory + await I_ModuleRegistry.registerModule(I_DummySTOFactory.address, { from: account_polymath }); + await I_ModuleRegistry.verifyModule(I_DummySTOFactory.address, true, { from: account_polymath }); + + // (D) : Register the ExchangeTransferManager + await I_ModuleRegistry.registerModule(I_ExchangeTransferManagerFactory.address, { from: account_polymath }); + await I_ModuleRegistry.verifyModule(I_ExchangeTransferManagerFactory.address, true, { from: account_polymath }); + + // Step 6: Deploy the TickerRegistry + + I_TickerRegistry = await TickerRegistry.new({ from: account_polymath }); + + assert.notEqual( + I_TickerRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "TickerRegistry contract was not deployed", + ); + + // Step 7: Deploy the STversionProxy contract + + I_STVersion = await STVersion.new(I_GeneralTransferManagerFactory.address, I_GeneralPermissionManagerFactory.address); + + assert.notEqual( + I_STVersion.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "STVersion contract was not deployed", + ); + + // Step ANY: Deploy the Polytoken Contract + I_PolyToken = await PolyToken.new(); + + // Step 8: Deploy the SecurityTokenRegistry + + I_SecurityTokenRegistry = await SecurityTokenRegistry.new( + I_PolyToken.address, + I_ModuleRegistry.address, + I_TickerRegistry.address, + I_STVersion.address, + { + from: account_polymath + }); + + assert.notEqual( + I_SecurityTokenRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "SecurityTokenRegistry contract was not deployed", + ); + + // Step 8: Set the STR in TickerRegistry & ModuleRegistry + await I_TickerRegistry.setTokenRegistry(I_SecurityTokenRegistry.address, {from: account_polymath}); + await I_ModuleRegistry.setTokenRegistry(I_SecurityTokenRegistry.address, {from: account_polymath}); + + + // Printing all the contract addresses + console.log(`\nPolymath Network Smart Contracts Deployed:\n + ModuleRegistry: ${I_ModuleRegistry.address}\n + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address}\n + GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address}\n + DummySTOFactory: ${I_DummySTOFactory.address}\n + TickerRegistry: ${I_TickerRegistry.address}\n + STVersionProxy_001: ${I_STVersion.address}\n + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address}\n + `); + }); + + describe("Generate the SecurityToken", async() => { + + it("Should register the ticker before the generation of the security token", async () => { + let tx = await I_TickerRegistry.registerTicker(symbol, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._symbol, symbol); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, decimals, tokenDetails, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const LogAddModule = await I_SecurityToken.allEvents(); + const log = await new Promise(function(resolve, reject) { + LogAddModule.watch(function(error, log){ resolve(log);}); + }); + + // Verify that GeneralPermissionManager module get added successfully or not + assert.equal(log.args._type.toNumber(), 1); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralPermissionManager" + ); + LogAddModule.stopWatching(); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = await I_SecurityToken.modules(2, 0); + I_GeneralTransferManager = GeneralTransferManager.at(moduleData[1]); + + assert.notEqual( + I_GeneralTransferManager.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManager contract was not deployed", + ); + + moduleData = await I_SecurityToken.modules(1, 0); + I_GeneralPermissionManager = GeneralPermissionManager.at(moduleData[1]); + + assert.notEqual( + I_GeneralPermissionManager.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralDelegateManager contract was not deployed", + ); + }); + + it("Should successfully attach the STO factory with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_DummySTOFactory.address, bytesSTO, 0, 0, false, { from: token_owner }); + assert.equal(tx.logs[2].args._type.toNumber(), stoKey, "DummySTO doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "DummySTO", + "DummySTOFactory module was not added" + ); + I_DummySTO = DummySTO.at(tx.logs[2].args._module); + }); + }); + + describe("Buy tokens", async() => { + + it("Should buy the tokens -- Failed due to investor is not in the whitelist", async () => { + try { + await I_DummySTO.generateTokens(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); + } catch(error) { + console.log(`Failed because investor isn't present in the whitelist`); + ensureException(error); + } + }); + + it("Should Buy the tokens", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor1, + fromTime, + toTime, + { + from: account_issuer, + gas: 500000 + }); + + assert.equal(tx.logs[0].args._investor, account_investor1, "Failed in adding the investor in whitelist"); + + // Jump time + await increaseTime(5000); + + // Mint some tokens + await I_DummySTO.generateTokens(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + }); + + }); + + describe("Add ExchangeTransferManager", async() => { + + it("Should attach ExchangeTransferManager", async () => { + let bytesExchange = web3.eth.abi.encodeFunctionCall({ + name: 'configure', + type: 'function', + inputs: [{ + type: 'address', + name: '_exchange' + } + ] + }, [account_exchange]); + + const tx = await I_SecurityToken.addModule(I_ExchangeTransferManagerFactory.address, bytesExchange, 0, 0, true, { from: token_owner }); + assert.equal(tx.logs[2].args._type.toNumber(), transferManagerKey, "ExchangeTransferManager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "ExchangeTransferManager", + "ExchangeTransferManager module was not added" + ); + I_ExchangeTransferManager = ExchangeTransferManager.at(tx.logs[2].args._module); + }); + + it("Existing investor should still be able to receive tokens", async() => { + + await I_DummySTO.generateTokens(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("New investor should not be able to transfer tokens", async() => { + try { + await I_SecurityToken.transfer(account_exchange, web3.utils.toWei('1', 'ether'), {from: account_investor1}); + // await I_DummySTO.generateTokens(account_investor2, web3.utils.toWei('1', 'ether'), { from: token_owner }); + } catch(error) { + console.log(`Failed because investor isn't present in the whitelist`); + ensureException(error); + } + }); + + it("Add new investor to exchange whitelist", async() => { + + await I_ExchangeTransferManager.modifyWhitelist(account_investor1, true, {from: account_polymath}); + + await I_SecurityToken.transfer(account_exchange, web3.utils.toWei('1', 'ether'), {from: account_investor1}); + + assert.equal( + (await I_SecurityToken.balanceOf(account_exchange)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + + }); + + + }); +});