diff --git a/CHANGELOG.md b/CHANGELOG.md index 436ff769f..31681f579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ All notable changes to this project will be documented in this file. * IModule contract takes the polyToken contract address as the constructor argument to wrapping all the factories with the polyToken contract address. * `takeFee()` new function introduced to extract the POLY token from the factory. It only be called by the owner of the factory. - +* Added ability for issuer to provide a signed piece of data to allow investors to whitelist themselves. *** @@ -22,7 +22,7 @@ All notable changes to this project will be documented in this file. ## Added * Add `emit` keyword to emit the events. -* Two new variable is added at the time of registeration of ticker. `swarmHash` represents the off-chain data storage location on IPFS and `owner` It reperesent the ethereum address of the owner. +* Two new variable is added at the time of registration of ticker. `swarmHash` represents the off-chain data storage location on IPFS and `owner` It reperesent the ethereum address of the owner. * `LogRegisterTicker` emits two more variable called `_swarmHash` and `_owner`. * Two events are added in `GeneralPermissionManager` contract to facilitate the notifications for the UI end. __`LogChangePermission`__ :Emit when permissions to a delegate get changed. diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol index 6702452cb..a3325e2e5 100644 --- a/contracts/Migrations.sol +++ b/contracts/Migrations.sol @@ -4,7 +4,7 @@ pragma solidity ^0.4.23; contract Migrations { address public owner; - + uint public lastCompletedMigration; modifier restricted() { @@ -12,7 +12,7 @@ contract Migrations { _; } - constructor() public { + function Migrations() public { owner = msg.sender; } diff --git a/contracts/interfaces/IModule.sol b/contracts/interfaces/IModule.sol index 842518460..c0c744943 100644 --- a/contracts/interfaces/IModule.sol +++ b/contracts/interfaces/IModule.sol @@ -27,29 +27,29 @@ contract IModule { modifier withPerm(bytes32 _perm) { bool isOwner = msg.sender == ISecurityToken(securityToken).owner(); bool isFactory = msg.sender == factory; - require(isOwner||isFactory||ISecurityToken(securityToken).checkPermission(msg.sender, address(this), _perm)); + require(isOwner||isFactory||ISecurityToken(securityToken).checkPermission(msg.sender, address(this), _perm), "Permission check failed"); _; } modifier onlyOwner { - require(msg.sender == ISecurityToken(securityToken).owner()); + require(msg.sender == ISecurityToken(securityToken).owner(), "Sender is not owner"); _; } modifier onlyFactory { - require(msg.sender == factory); + require(msg.sender == factory, "Sender is not factory"); _; } modifier onlyFactoryOwner { - require(msg.sender == IModuleFactory(factory).owner()); + require(msg.sender == IModuleFactory(factory).owner(), "Sender is not factory owner"); _; } function getPermissions() public view returns(bytes32[]); function takeFee(uint256 _amount) public withPerm(FEE_ADMIN) returns(bool) { - require(polyToken.transferFrom(address(this), IModuleFactory(factory).owner(), _amount)); + require(polyToken.transferFrom(address(this), IModuleFactory(factory).owner(), _amount), "Unable to take fee"); return true; } } diff --git a/contracts/modules/PermissionManager/GeneralPermissionManager.sol b/contracts/modules/PermissionManager/GeneralPermissionManager.sol index 4e9c420c0..451111444 100644 --- a/contracts/modules/PermissionManager/GeneralPermissionManager.sol +++ b/contracts/modules/PermissionManager/GeneralPermissionManager.sol @@ -80,7 +80,7 @@ contract GeneralPermissionManager is IPermissionManager { withPerm(CHANGE_PERMISSION) returns(bool) { - require(delegateDetails[_delegate] != bytes32(0)); + require(delegateDetails[_delegate] != bytes32(0), "Delegate details not set"); perms[_module][_delegate][_perm] = _valid; emit LogChangePermission(_delegate, _module, _perm, _valid, now); return true; diff --git a/contracts/modules/TransferManager/GeneralTransferManager.sol b/contracts/modules/TransferManager/GeneralTransferManager.sol index d2c9f0f5f..fd42ab8b7 100644 --- a/contracts/modules/TransferManager/GeneralTransferManager.sol +++ b/contracts/modules/TransferManager/GeneralTransferManager.sol @@ -18,6 +18,9 @@ contract GeneralTransferManager is ITransferManager { //Address from which issuances come address public issuanceAddress = address(0); + //Address which can sign whitelist changes + address public signingAddress = address(0); + bytes32 public constant WHITELIST = "WHITELIST"; bytes32 public constant FLAGS = "FLAGS"; @@ -42,6 +45,7 @@ contract GeneralTransferManager is ITransferManager { event LogAllowAllTransfers(bool _allowAllTransfers); event LogAllowAllWhitelistTransfers(bool _allowAllWhitelistTransfers); event LogAllowAllWhitelistIssuances(bool _allowAllWhitelistIssuances); + event LogChangeSigningAddress(address _signingAddress); event LogModifyWhitelist( address _investor, @@ -66,6 +70,11 @@ contract GeneralTransferManager is ITransferManager { emit LogChangeIssuanceAddress(_issuanceAddress); } + function changeSigningAddress(address _signingAddress) public withPerm(FLAGS) { + signingAddress = _signingAddress; + emit LogChangeSigningAddress(_signingAddress); + } + function changeAllowAllTransfers(bool _allowAllTransfers) public withPerm(FLAGS) { allowAllTransfers = _allowAllTransfers; emit LogAllowAllTransfers(_allowAllTransfers); @@ -123,13 +132,41 @@ contract GeneralTransferManager is ITransferManager { uint256[] _fromTimes, uint256[] _toTimes ) public withPerm(WHITELIST) { - require(_investors.length == _fromTimes.length); - require(_fromTimes.length == _toTimes.length); + require(_investors.length == _fromTimes.length, "Mismatched input lengths"); + require(_fromTimes.length == _toTimes.length, "Mismatched input lengths"); for (uint256 i = 0; i < _investors.length; i++) { modifyWhitelist(_investors[i], _fromTimes[i], _toTimes[i]); } } + /** + * @dev adds or removes addresses from the whitelist - can be called by anyone with a valid signature + * @param _investor is the address to whitelist + * @param _fromTime is the moment when the sale lockup period ends and the investor can freely sell his tokens + * @param _toTime is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others + * @param _validFrom is the time that this signature is valid from + * @param _validTo is the time that this signature is valid until + * @param _v issuer signature + * @param _r issuer signature + * @param _s issuer signature + */ + function modifyWhitelistSigned(address _investor, uint256 _fromTime, uint256 _toTime, uint256 _validFrom, uint256 _validTo, uint8 _v, bytes32 _r, bytes32 _s) public { + require(_validFrom <= now, "ValidFrom is too early"); + require(_validTo >= now, "ValidTo is too late"); + bytes32 hash = keccak256(this, _investor, _fromTime, _toTime, _validFrom, _validTo); + checkSig(hash, _v, _r, _s); + //Passing a _time == 0 into this function, is equivalent to removing the _investor from the whitelist + whitelist[_investor] = TimeRestriction(_fromTime, _toTime); + emit LogModifyWhitelist(_investor, now, msg.sender, _fromTime, _toTime); + } + + function checkSig(bytes32 _hash, uint8 _v, bytes32 _r, bytes32 _s) internal view { + //Check that the signature is valid + //sig should be signing - _investor, _fromTime & _toTime and be signed by the issuer address + address signer = ecrecover(keccak256("\x19Ethereum Signed Message:\n32", _hash), _v, _r, _s); + require(signer == ISecurityToken(securityToken).owner() || signer == signingAddress, "Incorrect signer"); + } + function getPermissions() public view returns(bytes32[]) { bytes32[] memory allPermissions = new bytes32[](2); allPermissions[0] = WHITELIST; diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index 0621f9a0f..d4c883da8 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -64,9 +64,9 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { isModuleType = isModuleType || (modules[_moduleType][i].moduleAddress == msg.sender); } if (_fallback && !isModuleType) { - require(msg.sender == owner); + require(msg.sender == owner, "Sender is not owner"); } else { - require(isModuleType); + require(isModuleType, "Sender is not correct module type"); } _; } @@ -200,9 +200,9 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { * @dev allows owner to approve more POLY to one of the modules */ function changeModuleBudget(uint8 _moduleType, uint8 _moduleIndex, uint256 _budget) public onlyOwner { - require(_moduleType != 0); - require(_moduleIndex < modules[_moduleType].length); - require(polyToken.approve(modules[_moduleType][_moduleIndex].moduleAddress, _budget)); + require(_moduleType != 0, "Module type cannot be zero"); + require(_moduleIndex < modules[_moduleType].length, "Incorrrect module index"); + require(polyToken.approve(modules[_moduleType][_moduleIndex].moduleAddress, _budget), "Insufficient balance to approve"); emit LogModuleBudgetChanged(_moduleType, modules[_moduleType][_moduleIndex].moduleAddress, _budget); } diff --git a/package-lock.json b/package-lock.json index d75e553fe..2a4c072fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2973,6 +2973,61 @@ } } }, + "ethers": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-3.0.15.tgz", + "integrity": "sha512-d/tiMUavaeaY2GFqjpgfPzT46cEc0cilP3hnlTXR3LR/HR5Qrhv4PfdgW3gxBlR5aBTtUeM/lo8z8ph3JdtFhQ==", + "requires": { + "aes-js": "3.0.0", + "bn.js": "4.11.6", + "elliptic": "6.3.3", + "hash.js": "1.1.3", + "inherits": "2.0.1", + "js-sha3": "0.5.7", + "scrypt-js": "2.0.3", + "setimmediate": "1.0.4", + "uuid": "2.0.1", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=" + }, + "elliptic": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", + "integrity": "sha1-VILZZG1UvLif19mU/J4ulWiHbj8=", + "requires": { + "bn.js": "4.11.6", + "brorand": "1.1.0", + "hash.js": "1.1.3", + "inherits": "2.0.1" + } + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=" + }, + "setimmediate": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", + "integrity": "sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48=" + }, + "uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=" + } + } + }, "ethjs-abi": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz", @@ -7560,6 +7615,11 @@ "nan": "2.9.2" } }, + "scrypt-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.3.tgz", + "integrity": "sha1-uwBAvgMEPamgEqLOqfyfhSz8h9Q=" + }, "scrypt.js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/scrypt.js/-/scrypt.js-0.2.0.tgz", @@ -8654,14 +8714,14 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, "truffle": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.5.tgz", - "integrity": "sha512-6sOVFQ0xNbb52MMWf0nHxv0FiXWPTV+OIbq1B0+I5F3sIS8JJ7pM1+o7chbs+oO/CLqbbC6ggXJqFWzIWaiaQg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.7.tgz", + "integrity": "sha512-fe6BIcD9xo6iIJvV1m6ZhOk56kmB8k38kdoWOKYnPPw7ZUUSupgojeTb2K5e+4qIpIHvEvmET4yLUjSGR+hvwA==", "dev": true, "requires": { "mocha": "3.5.3", "original-require": "1.0.1", - "solc": "0.4.21" + "solc": "0.4.23" }, "dependencies": { "camelcase": { @@ -8801,9 +8861,9 @@ } }, "solc": { - "version": "0.4.21", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.21.tgz", - "integrity": "sha512-8lJmimVjOG9AJOQRWS2ph4rSctPMsPGZ4H360HLs5iI+euUlt7iAvUxSLeFZZzwk0kas4Qta7HmlMXNU3yYwhw==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.23.tgz", + "integrity": "sha512-AT7anLHY6uIRg2It6N0UlCHeZ7YeecIkUhnlirrCgCPCUevtnoN48BxvgigN/4jJTRljv5oFhAJtI6gvHzT5DQ==", "dev": true, "requires": { "fs-extra": "0.30.0", diff --git a/package.json b/package.json index b33ccd3f0..69485407c 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "babel-preset-stage-3": "6.24.1", "babel-register": "6.26.0", "bignumber.js": "^6.0.0", + "ethers": "^3.0.15", "readline-sync": "^1.4.9", "truffle-contract": "^3.0.4", "truffle-hdwallet-provider-privkey": "^0.1.0", @@ -68,7 +69,7 @@ "ganache-cli": "^6.1.0", "sol-merger": "^0.1.2", "solium": "^1.1.6", - "truffle": "^4.1.5", + "truffle": "^4.1.7", "truffle-wallet-provider": "0.0.5" } } diff --git a/test/general_transfer_manager.js b/test/general_transfer_manager.js new file mode 100644 index 000000000..10134e3db --- /dev/null +++ b/test/general_transfer_manager.js @@ -0,0 +1,482 @@ +import latestTime from './helpers/latestTime'; +import { duration, ensureException } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } 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('./STVersionProxy001.sol'); +const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol'); +const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const ExchangeTransferManager = artifacts.require('./ExchangeTransferManager'); +const PolyTokenFaucet = artifacts.require('./helpers/contracts/PolyTokenFaucet.sol'); + +import {signData} from './helpers/signData'; +import { pk } from './helpers/testprivateKey'; + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('GeneralTransferManager', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let token_owner_pk; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + + let message = "Transaction Should Fail!"; + + // 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 swarmHash = "dagwrgwgvwergwrvwrg"; + 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]; + + token_owner = account_issuer; + token_owner_pk = pk.account_1; + + account_investor1 = accounts[8]; + account_investor2 = accounts[9]; + + + // ----------- POLYMATH NETWORK Configuration ------------ + + // Step 0: Deploy the Polytoken Contract + I_PolyToken = await PolyTokenFaucet.new(); + + // 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 2: Deploy the GeneralTransferManagerFactory + + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, {from:account_polymath}); + + assert.notEqual( + I_GeneralTransferManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManagerFactory contract was not deployed" + ); + + // STEP 3: Deploy the GeneralDelegateManagerFactory + + I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(I_PolyToken.address, {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(I_PolyToken.address, {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 }); + + // 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); + + assert.notEqual( + I_STVersion.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "STVersion contract was not deployed", + ); + + // 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(token_owner, symbol, contact, swarmHash, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._symbol, symbol.toUpperCase()); + }); + + 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.toUpperCase(), "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 GeneralTransferManager module get added successfully or not + assert.equal(log.args._type.toNumber(), 2); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); + 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", + ); + + }); + + it("Should successfully attach the STO factory with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_DummySTOFactory.address, bytesSTO, 0, 0, true, { 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 using on-chain whitelist", async() => { + + it("Should buy the tokens -- Failed due to investor is not in the whitelist", async () => { + let errorThrown = false; + 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`); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); + + 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.toLowerCase(), account_investor1.toLowerCase(), "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("Buy tokens using off-chain whitelist", async() => { + + it("Should buy the tokens -- Failed due to investor is not in the whitelist", async () => { + let errorThrown = false; + try { + 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`); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); + + it("Should buy the tokens -- Failed due to incorrect signature input", async() => { + // Add the Investor in to the whitelist + //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk + let validFrom = latestTime(); + let validTo = latestTime() + (60 * 60); + const sig = signData(account_investor2, account_investor2, fromTime, toTime, validFrom, validTo, token_owner_pk); + + const r = `0x${sig.r.toString('hex')}`; + const s = `0x${sig.s.toString('hex')}`; + const v = sig.v; + let errorThrown = false; + + try { + let tx = await I_GeneralTransferManager.modifyWhitelistSigned( + account_investor2, + fromTime, + toTime, + validFrom, + validTo, + v, + r, + s, + { + from: account_investor2, + gas: 500000 + }); + } catch(error) { + console.log(`Failed because incorrect sig data`); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + + }); + + it("Should buy the tokens -- Failed due to incorrect signature timing", async() => { + // Add the Investor in to the whitelist + //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk + let validFrom = latestTime() - 100; + let validTo = latestTime() - 1; + const sig = signData(I_GeneralTransferManager.address, account_investor2, fromTime, toTime, validFrom, validTo, token_owner_pk); + + const r = `0x${sig.r.toString('hex')}`; + const s = `0x${sig.s.toString('hex')}`; + const v = sig.v; + + let errorThrown = false; + try { + let tx = await I_GeneralTransferManager.modifyWhitelistSigned( + account_investor2, + fromTime, + toTime, + validFrom, + validTo, + v, + r, + s, + { + from: account_investor2, + gas: 500000 + }); + } catch(error) { + console.log(`Failed because incorrect sig data`); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + + }); + + it("Should buy the tokens -- Failed due to incorrect signature signer", async() => { + // Add the Investor in to the whitelist + //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk + let validFrom = latestTime(); + let validTo = latestTime() + (60 * 60); + + const sig = signData(account_investor2, account_investor2, fromTime, toTime, validFrom, validTo, '2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200'); + + const r = `0x${sig.r.toString('hex')}`; + const s = `0x${sig.s.toString('hex')}`; + const v = sig.v; + let errorThrown = false; + + try { + let tx = await I_GeneralTransferManager.modifyWhitelistSigned( + account_investor2, + fromTime, + toTime, + validFrom, + validTo, + v, + r, + s, + { + from: account_investor2, + gas: 500000 + }); + } catch(error) { + console.log(`Failed because incorrect sig data`); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + + }); + + it("Should Buy the tokens", async() => { + // Add the Investor in to the whitelist + //tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk + let validFrom = latestTime(); + let validTo = latestTime() + (60 * 60); + const sig = signData(I_GeneralTransferManager.address, account_investor2, fromTime, toTime, validFrom, validTo, token_owner_pk); + + const r = `0x${sig.r.toString('hex')}`; + const s = `0x${sig.s.toString('hex')}`; + const v = sig.v; + + let tx = await I_GeneralTransferManager.modifyWhitelistSigned( + account_investor2, + fromTime, + toTime, + validFrom, + validTo, + v, + r, + s, + { + from: account_investor2, + gas: 500000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Jump time + await increaseTime(5000); + + // Mint some tokens + await I_DummySTO.generateTokens(account_investor2, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + + }); + + }); + +}); diff --git a/test/helpers/signData.js b/test/helpers/signData.js new file mode 100644 index 000000000..138759d5e --- /dev/null +++ b/test/helpers/signData.js @@ -0,0 +1,23 @@ +const ethers = require('ethers'); +const utils = ethers.utils; +const ethUtil = require('ethereumjs-util'); + +//this, _investor, _fromTime, _toTime, _validTo +function signData(tmAddress, investorAddress, fromTime, toTime, validFrom, validTo, pk) { + let packedData = utils.solidityKeccak256( + [ "address", "address", "uint256", "uint256", "uint256", "uint256" ], + [ tmAddress, investorAddress, fromTime, toTime, validFrom, validTo ] + ).slice(2); + packedData = new Buffer(packedData, 'hex'); + packedData = Buffer.concat([ + new Buffer(`\x19Ethereum Signed Message:\n${packedData.length.toString()}`), + packedData]); + packedData = web3.sha3(`0x${packedData.toString('hex')}`, { encoding: 'hex' }); + return ethUtil.ecsign( + new Buffer(packedData.slice(2), 'hex'), + new Buffer(pk, 'hex')); +} + +module.exports = { + signData +} diff --git a/test/helpers/testprivateKey.js b/test/helpers/testprivateKey.js new file mode 100644 index 000000000..16e464cd1 --- /dev/null +++ b/test/helpers/testprivateKey.js @@ -0,0 +1,6 @@ +export const pk = { + account_0 :'2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200', + account_1 :'2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201' +} + +export default { pk };