From 7b8d10d230ed24b1f534b4473e2f5b65c67c1783 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Mon, 30 Apr 2018 13:49:02 +0100 Subject: [PATCH 1/3] Add granularity to tokens --- contracts/interfaces/ISecurityToken.sol | 1 + contracts/tokens/STVersionProxy001.sol | 1 + contracts/tokens/STVersionProxy002.sol | 1 + contracts/tokens/SecurityToken.sol | 21 +++++++++++++++++++-- contracts/tokens/SecurityTokenV2.sol | 2 ++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/ISecurityToken.sol b/contracts/interfaces/ISecurityToken.sol index f853d856c..4be441c7f 100644 --- a/contracts/interfaces/ISecurityToken.sol +++ b/contracts/interfaces/ISecurityToken.sol @@ -9,6 +9,7 @@ contract ISecurityToken is IST20, Ownable { uint8 public constant PERMISSIONMANAGER_KEY = 1; uint8 public constant TRANSFERMANAGER_KEY = 2; uint8 public constant STO_KEY = 3; + uint256 public granularity; //TODO: Factor out more stuff here function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool); diff --git a/contracts/tokens/STVersionProxy001.sol b/contracts/tokens/STVersionProxy001.sol index 59c5ffd15..933937f05 100644 --- a/contracts/tokens/STVersionProxy001.sol +++ b/contracts/tokens/STVersionProxy001.sol @@ -25,6 +25,7 @@ contract STVersionProxy001 is ISTProxy { _name, _symbol, _decimals, + uint256(10)**_decimals, _tokenDetails, msg.sender ); diff --git a/contracts/tokens/STVersionProxy002.sol b/contracts/tokens/STVersionProxy002.sol index fdd944c8a..a79abb79b 100644 --- a/contracts/tokens/STVersionProxy002.sol +++ b/contracts/tokens/STVersionProxy002.sol @@ -23,6 +23,7 @@ contract STVersionProxy002 is ISTProxy { _name, _symbol, _decimals, + uint256(10)**_decimals, _tokenDetails, msg.sender ); diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index d4c883da8..eb64ee450 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -52,6 +52,7 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { uint256 _timestamp ); + event LogGranularityChanged(uint256 _oldGranularity, uint256 _newGranularity); 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); @@ -71,10 +72,16 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { _; } + modifier checkGranularity(uint256 _amount) { + require(_amount.div(granularity).mul(granularity) == _amount, "Unable to modify token balances at this granularity"); + _; + } + constructor ( string _name, string _symbol, uint8 _decimals, + uint256 _granularity, bytes32 _tokenDetails, address _securityTokenRegistry ) @@ -85,6 +92,7 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { moduleRegistry = ISecurityTokenRegistry(_securityTokenRegistry).moduleRegistry(); polyToken = ERC20(ISecurityTokenRegistry(_securityTokenRegistry).polyAddress()); tokenDetails = _tokenDetails; + granularity = _granularity; } function addModule( @@ -206,6 +214,15 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { emit LogModuleBudgetChanged(_moduleType, modules[_moduleType][_moduleIndex].moduleAddress, _budget); } + /** + * @dev allows owner to change token granularity + */ + function changeGranularity(uint256 _granularity) public onlyOwner { + require(_granularity != 0, "Granularity can not be 0"); + emit LogGranularityChanged(granularity, _granularity); + granularity = _granularity; + } + /** * @dev Overloaded version of the transfer function */ @@ -224,7 +241,7 @@ 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) public view returns (bool success) { + function verifyTransfer(address _from, address _to, uint256 _amount) public view checkGranularity(_amount) returns (bool success) { if (modules[TRANSFERMANAGER_KEY].length == 0) { return true; } @@ -240,7 +257,7 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { * @dev mints new tokens and assigns them to the target _investor. * Can only be called by the STO attached to the token (Or by the ST owner if there's no STO attached yet) */ - function mint(address _investor, uint256 _amount) public onlyModule(STO_KEY, true) returns (bool success) { + function mint(address _investor, uint256 _amount) public onlyModule(STO_KEY, true) checkGranularity(_amount) returns (bool success) { require(verifyTransfer(address(0), _investor, _amount), "Transfer is not valid"); totalSupply_ = totalSupply_.add(_amount); balances[_investor] = balances[_investor].add(_amount); diff --git a/contracts/tokens/SecurityTokenV2.sol b/contracts/tokens/SecurityTokenV2.sol index ce5a33b29..3f978ffde 100644 --- a/contracts/tokens/SecurityTokenV2.sol +++ b/contracts/tokens/SecurityTokenV2.sol @@ -14,6 +14,7 @@ contract SecurityTokenV2 is SecurityToken { string _name, string _symbol, uint8 _decimals, + uint256 _granularity, bytes32 _tokenDetails, address _securityTokenRegistry ) @@ -22,6 +23,7 @@ contract SecurityTokenV2 is SecurityToken { _name, _symbol, _decimals, + _granularity, _tokenDetails, _securityTokenRegistry) { From 0adc5912ae5b4a0257f89be8660bf9614694f857 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Mon, 30 Apr 2018 14:02:11 +0100 Subject: [PATCH 2/3] Add some test cases --- test/capped_sto.js | 32 ++++++++++++++++++++++++++++++++ test/security_token.js | 20 ++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/test/capped_sto.js b/test/capped_sto.js index 33e426373..89f6408b3 100644 --- a/test/capped_sto.js +++ b/test/capped_sto.js @@ -389,6 +389,22 @@ contract('CappedSTO', accounts => { assert.ok(errorThrown, message); }); + it("Should buy the tokens -- Failed due to wrong granularity", async () => { + let errorThrown = false; + try { + await web3.eth.sendTransaction({ + from: account_investor1, + to: I_CappedSTO.address, + value: web3.utils.toWei('0.1111', 'ether') + }); + } catch(error) { + console.log(`Failed due to wrong purchase granularity`); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + it("Should Buy the tokens", async() => { balanceOfReceiver = await web3.eth.getBalance(account_fundsReceiver); // Add the Investor in to the whitelist @@ -448,6 +464,22 @@ contract('CappedSTO', accounts => { TokenPurchase.stopWatching(); }); + it("Should buy the tokens -- Failed due to wrong granularity", async () => { + let errorThrown = false; + try { + await web3.eth.sendTransaction({ + from: account_investor1, + to: I_CappedSTO.address, + value: web3.utils.toWei('0.1111', 'ether') + }); + } catch(error) { + console.log(`Failed due to wrong purchase granularity`); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async() => { let tx = await I_GeneralTransferManager.modifyWhitelist( account_investor2, diff --git a/test/security_token.js b/test/security_token.js index 60cac0924..5406882b6 100644 --- a/test/security_token.js +++ b/test/security_token.js @@ -453,6 +453,26 @@ contract('SecurityToken', accounts => { assert.isTrue(tx.logs[0].args._allowAllTransfers, "AllowTransfer variable is not successfully updated"); }); + + it("Should fail to send tokens with the wrong granularity", async() => { + let errorThrown = false; + try { + await I_SecurityToken.transfer(accounts[7], Math.pow(10, 17), { from : account_investor1}); + } catch (error) { + console.log('Failed due to incorrect token granularity - expected'); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); + + it("Should adjust granularity", async() => { + let errorThrown = false; + await I_SecurityToken.changeGranularity(Math.pow(10, 17), {from: token_owner }); + await I_SecurityToken.transfer(accounts[7], Math.pow(10, 17), { from : account_investor1}); + await I_SecurityToken.transfer(account_investor1, Math.pow(10, 17), { from : accounts[7]}); + }); + it("Should transfer from whitelist investor to non-whitelist investor in first tx and in 2nd tx non-whitelist to non-whitelist transfer", async() => { await I_SecurityToken.transfer(accounts[7], (10 * Math.pow(10, 18)), { from : account_investor1}); From d6901135cf3b27ad7bfb8204a1b87f224664d909 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Wed, 2 May 2018 11:03:30 -0400 Subject: [PATCH 3/3] Add parameter to setup granularity --- CHANGELOG.md | 1 + contracts/SecurityTokenRegistry.sol | 5 +++-- contracts/interfaces/ISTProxy.sol | 2 +- contracts/interfaces/ISecurityTokenRegistry.sol | 4 ++-- contracts/tokens/STVersionProxy001.sol | 4 ++-- contracts/tokens/STVersionProxy002.sol | 4 ++-- test/Issuance.js | 2 +- test/capped_sto.js | 4 ++-- test/exchange_transfer_manager.js | 2 +- test/general_transfer_manager.js | 2 +- test/module_registry.js | 2 +- test/security_token.js | 2 +- test/security_token_registry.js | 6 +++--- 13 files changed, 21 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa47cc71..c837b89ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. ## Added +* generateSecurityToken in SecurityTokenRegistry takes an additional parameter specifying whether the token is divisible. * 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. diff --git a/contracts/SecurityTokenRegistry.sol b/contracts/SecurityTokenRegistry.sol index 579716c44..8f4236e54 100644 --- a/contracts/SecurityTokenRegistry.sol +++ b/contracts/SecurityTokenRegistry.sol @@ -39,7 +39,7 @@ contract SecurityTokenRegistry is Ownable, ISecurityTokenRegistry, Util { * @param _decimals Decimals value for token * @param _tokenDetails off-chain details of the token */ - function generateSecurityToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails) public { + function generateSecurityToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails, bool _divisible) public { require(bytes(_name).length > 0 && bytes(_symbol).length > 0, "Name and Symbol string length should be greater than 0"); require(ITickerRegistry(tickerRegistry).checkValidity(_symbol, msg.sender, _name), "Trying to use non-valid symbol"); string memory symbol = upper(_symbol); @@ -48,7 +48,8 @@ contract SecurityTokenRegistry is Ownable, ISecurityTokenRegistry, Util { symbol, _decimals, _tokenDetails, - msg.sender + msg.sender, + _divisible ); securityTokens[newSecurityTokenAddress] = SecurityTokenData(symbol, _tokenDetails); diff --git a/contracts/interfaces/ISTProxy.sol b/contracts/interfaces/ISTProxy.sol index cdc96710b..a707d8dcc 100644 --- a/contracts/interfaces/ISTProxy.sol +++ b/contracts/interfaces/ISTProxy.sol @@ -3,6 +3,6 @@ pragma solidity ^0.4.23; contract ISTProxy { - function deployToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails, address _issuer) + function deployToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails, address _issuer, bool _divisible) public returns (address); } diff --git a/contracts/interfaces/ISecurityTokenRegistry.sol b/contracts/interfaces/ISecurityTokenRegistry.sol index b1627aae6..c606efbbe 100644 --- a/contracts/interfaces/ISecurityTokenRegistry.sol +++ b/contracts/interfaces/ISecurityTokenRegistry.sol @@ -23,12 +23,12 @@ contract ISecurityTokenRegistry { /** * @dev Creates a new Security Token and saves it to the registry - * @param _name Name of the token + * @param _name Name of the token * @param _symbol Ticker symbol of the security token * @param _decimals Decimals value for token * @param _tokenDetails off-chain details of the token */ - function generateSecurityToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails) public; + function generateSecurityToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails, bool _divisible) public; function setProtocolVersion(address _stVersionProxyAddress, bytes32 _version) public; diff --git a/contracts/tokens/STVersionProxy001.sol b/contracts/tokens/STVersionProxy001.sol index 933937f05..67fa39d9a 100644 --- a/contracts/tokens/STVersionProxy001.sol +++ b/contracts/tokens/STVersionProxy001.sol @@ -19,13 +19,13 @@ contract STVersionProxy001 is ISTProxy { * @dev deploys the token and adds default modules like permission manager and transfer manager. * Future versions of the proxy can attach different modules or pass some other paramters. */ - function deployToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails, address _issuer) + function deployToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails, address _issuer, bool _divisible) public returns (address) { address newSecurityTokenAddress = new SecurityToken( _name, _symbol, _decimals, - uint256(10)**_decimals, + _divisible ? 1 : uint256(10)**_decimals, _tokenDetails, msg.sender ); diff --git a/contracts/tokens/STVersionProxy002.sol b/contracts/tokens/STVersionProxy002.sol index a79abb79b..3e94172d7 100644 --- a/contracts/tokens/STVersionProxy002.sol +++ b/contracts/tokens/STVersionProxy002.sol @@ -16,14 +16,14 @@ contract STVersionProxy002 is ISTProxy { transferManagerFactory = _transferManagerFactory; } - function deployToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails, address _issuer) + function deployToken(string _name, string _symbol, uint8 _decimals, bytes32 _tokenDetails, address _issuer, bool _divisible) public returns (address) { address newSecurityTokenAddress = new SecurityTokenV2( _name, _symbol, _decimals, - uint256(10)**_decimals, + _divisible ? 1 : uint256(10)**_decimals, _tokenDetails, msg.sender ); diff --git a/test/Issuance.js b/test/Issuance.js index 2e2f0bbe6..cde9bd489 100644 --- a/test/Issuance.js +++ b/test/Issuance.js @@ -234,7 +234,7 @@ contract('Issuance', accounts => { }); it("POLYMATH: 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: account_polymath }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, decimals, tokenDetails, false, { from: account_polymath }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); diff --git a/test/capped_sto.js b/test/capped_sto.js index 89f6408b3..d17857856 100644 --- a/test/capped_sto.js +++ b/test/capped_sto.js @@ -236,7 +236,7 @@ contract('CappedSTO', accounts => { }); 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 }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, decimals, tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); @@ -572,7 +572,7 @@ contract('CappedSTO', accounts => { }); it("POLY: Should generate the new security token with the same symbol as registered above", async () => { - let tx = await I_SecurityTokenRegistry.generateSecurityToken(P_name, P_symbol, P_decimals, P_tokenDetails, { from: token_owner }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(P_name, P_symbol, P_decimals, P_tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, P_symbol, "SecurityToken doesn't get deployed"); diff --git a/test/exchange_transfer_manager.js b/test/exchange_transfer_manager.js index 43e880526..b87be9b58 100644 --- a/test/exchange_transfer_manager.js +++ b/test/exchange_transfer_manager.js @@ -235,7 +235,7 @@ contract('ExchangeTransferManager', accounts => { }); 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 }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, decimals, tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); diff --git a/test/general_transfer_manager.js b/test/general_transfer_manager.js index 10134e3db..a8793f9d0 100644 --- a/test/general_transfer_manager.js +++ b/test/general_transfer_manager.js @@ -228,7 +228,7 @@ contract('GeneralTransferManager', accounts => { }); 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 }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, decimals, tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); diff --git a/test/module_registry.js b/test/module_registry.js index c307367ca..eafd8fa28 100644 --- a/test/module_registry.js +++ b/test/module_registry.js @@ -372,7 +372,7 @@ contract('ModuleRegistry', accounts => { }); 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 }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, decimals, tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); diff --git a/test/security_token.js b/test/security_token.js index 5406882b6..a2747d257 100644 --- a/test/security_token.js +++ b/test/security_token.js @@ -239,7 +239,7 @@ contract('SecurityToken', accounts => { }); 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 }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, decimals, tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); diff --git a/test/security_token_registry.js b/test/security_token_registry.js index a46338796..fad292b35 100644 --- a/test/security_token_registry.js +++ b/test/security_token_registry.js @@ -233,7 +233,7 @@ contract('SecurityTokenRegistry', accounts => { }); 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 }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name, symbol, decimals, tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken doesn't get deployed"); @@ -281,7 +281,7 @@ contract('SecurityTokenRegistry', accounts => { }); it("Should generate the new security token with version 2", async() => { - let tx = await I_SecurityTokenRegistry.generateSecurityToken(name2, symbol2, decimals, tokenDetails, { from: token_owner }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name2, symbol2, decimals, tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, symbol2, "SecurityToken doesn't get deployed"); @@ -329,7 +329,7 @@ contract('SecurityTokenRegistry', accounts => { }); it("Should generate the new security token with version 3", async() => { - let tx = await I_SecurityTokenRegistry.generateSecurityToken(name2, "DET3", decimals, tokenDetails, { from: token_owner }); + let tx = await I_SecurityTokenRegistry.generateSecurityToken(name2, "DET3", decimals, tokenDetails, false, { from: token_owner }); // Verify the successful generation of the security token assert.equal(tx.logs[1].args._ticker, "DET3", "SecurityToken doesn't get deployed");