diff --git a/contracts/SecurityTokenRegistry.sol b/contracts/SecurityTokenRegistry.sol index 7f2250ee0..66ab80347 100644 --- a/contracts/SecurityTokenRegistry.sol +++ b/contracts/SecurityTokenRegistry.sol @@ -5,6 +5,7 @@ import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "./interfaces/IOwnable.sol"; import "./interfaces/ISTFactory.sol"; import "./interfaces/ISecurityTokenRegistry.sol"; +import "./interfaces/ISecurityToken.sol"; import "./interfaces/IPolymathRegistry.sol"; import "./interfaces/IOracle.sol"; import "./storage/EternalStorage.sol"; @@ -121,6 +122,16 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { uint256 _usdFee, uint256 _polyFee ); + // Emit at when issuer refreshes exisiting token + event SecurityTokenRefreshed( + string _ticker, + string _name, + address indexed _securityTokenAddress, + address indexed _owner, + uint256 _addedAt, + address _registrant, + uint256 _protocolVersion + ); event ProtocolFactorySet(address indexed _STFactory, uint8 _major, uint8 _minor, uint8 _patch); event LatestVersionSet(uint8 _major, uint8 _minor, uint8 _patch); event ProtocolFactoryRemoved(address indexed _STFactory, uint8 _major, uint8 _minor, uint8 _patch); @@ -140,11 +151,13 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPausedOrOwner() { - if (msg.sender == owner()) _; - else { + _whenNotPausedOrOwner(); + _; + } + + function _whenNotPausedOrOwner() internal view { + if (msg.sender != owner()) require(!isPaused(), "Paused"); - _; - } } /** @@ -182,7 +195,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { address _owner, address _getterContract ) - external + public payable { require(!getBoolValue(INITIALIZE),"Initialized"); @@ -266,7 +279,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _ticker is unique token ticker * @param _tokenName is the name of the token */ - function registerTicker(address _owner, string calldata _ticker, string calldata _tokenName) external whenNotPausedOrOwner { + function registerTicker(address _owner, string memory _ticker, string memory _tokenName) public whenNotPausedOrOwner { require(_owner != address(0), "Bad address"); require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Bad ticker"); // Attempt to charge the reg fee if it is > 0 USD @@ -315,13 +328,13 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { */ function modifyTicker( address _owner, - string calldata _ticker, - string calldata _tokenName, + string memory _ticker, + string memory _tokenName, uint256 _registrationDate, uint256 _expiryDate, bool _status ) - external + public onlyOwner { require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Bad ticker"); @@ -367,7 +380,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Removes the ticker details, associated ownership & security token mapping * @param _ticker is the token ticker */ - function removeTicker(string calldata _ticker) external onlyOwner { + function removeTicker(string memory _ticker) public onlyOwner { string memory ticker = Util.upper(_ticker); address owner = _tickerOwner(ticker); require(owner != address(0), "Bad ticker"); @@ -428,15 +441,15 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { internal { bytes32 key = Encoder.getKey("registeredTickers_owner", _ticker); - if (getAddressValue(key) != _owner) set(key, _owner); + set(key, _owner); key = Encoder.getKey("registeredTickers_registrationDate", _ticker); - if (getUintValue(key) != _registrationDate) set(key, _registrationDate); + set(key, _registrationDate); key = Encoder.getKey("registeredTickers_expiryDate", _ticker); - if (getUintValue(key) != _expiryDate) set(key, _expiryDate); + set(key, _expiryDate); key = Encoder.getKey("registeredTickers_tokenName", _ticker); - if (Encoder.getKey(getStringValue(key)) != Encoder.getKey(_tokenName)) set(key, _tokenName); + set(key, _tokenName); key = Encoder.getKey("registeredTickers_status", _ticker); - if (getBoolValue(key) != _status) set(key, _status); + set(key, _status); } /** @@ -444,7 +457,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _newOwner is the address of the new owner of the ticker * @param _ticker is the ticker symbol */ - function transferTickerOwnership(address _newOwner, string calldata _ticker) external whenNotPausedOrOwner { + function transferTickerOwnership(address _newOwner, string memory _ticker) public whenNotPausedOrOwner { string memory ticker = Util.upper(_ticker); require(_newOwner != address(0), "Bad address"); bytes32 ownerKey = Encoder.getKey("registeredTickers_owner", ticker); @@ -479,7 +492,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Changes the expiry time for the token ticker. Only available to Polymath. * @param _newExpiry is the new expiry for newly generated tickers */ - function changeExpiryLimit(uint256 _newExpiry) external onlyOwner { + function changeExpiryLimit(uint256 _newExpiry) public onlyOwner { require(_newExpiry >= 1 days, "Bad dates"); emit ChangeExpiryLimit(getUintValue(EXPIRYLIMIT), _newExpiry); set(EXPIRYLIMIT, _newExpiry); @@ -501,14 +514,14 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * - if _protocolVersion == 0 then latest version of securityToken will be generated */ function generateSecurityToken( - string calldata _name, - string calldata _ticker, - string calldata _tokenDetails, + string memory _name, + string memory _ticker, + string memory _tokenDetails, bool _divisible, address _treasuryWallet, uint256 _protocolVersion ) - external + public whenNotPausedOrOwner { uint256 protocolVersion = _protocolVersion; @@ -525,7 +538,46 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { require(_tickerOwner(ticker) == msg.sender, "Not authorised"); /*solium-disable-next-line security/no-block-members*/ require(getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now, "Ticker expired"); - _deployToken(_name, ticker, _tokenDetails, msg.sender, _divisible, _treasuryWallet, protocolVersion); + (uint256 _usdFee, uint256 _polyFee) = _takeFee(STLAUNCHFEE); + address newSecurityTokenAddress = + _deployToken(_name, ticker, _tokenDetails, msg.sender, _divisible, _treasuryWallet, protocolVersion, _usdFee, _polyFee); + emit NewSecurityToken( + _ticker, _name, newSecurityTokenAddress, msg.sender, now, msg.sender, false, _usdFee, _polyFee, protocolVersion + ); + } + + /** + * @notice Deploys an instance of a new Security Token and replaces the old one in the registry + * This can be used to upgrade from version 2.0 of ST to 3.0 or in case something goes wrong with earlier ST + * @dev This function needs to be in STR 3.0. Defined public to avoid stack overflow + * @param _name is the name of the token + * @param _ticker is the ticker symbol of the security token + * @param _tokenDetails is the off-chain details of the token + * @param _divisible is whether or not the token is divisible + */ + function refreshSecurityToken( + string memory _name, + string memory _ticker, + string memory _tokenDetails, + bool _divisible, + address _treasuryWallet + ) + public whenNotPausedOrOwner returns (address) + { + require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Bad ticker"); + require(_treasuryWallet != address(0), "0x0 not allowed"); + string memory ticker = Util.upper(_ticker); + require(_tickerStatus(ticker), "not deployed"); + address st = getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker)); + address stOwner = IOwnable(st).owner(); + require(msg.sender == stOwner, "Unauthroized"); + require(ISecurityToken(st).transfersFrozen(), "Transfers not frozen"); + uint256 protocolVersion = getUintValue(Encoder.getKey("latestVersion")); + address newSecurityTokenAddress = + _deployToken(_name, ticker, _tokenDetails, stOwner, _divisible, _treasuryWallet, protocolVersion, 0, 0); + emit SecurityTokenRefreshed( + _ticker, _name, newSecurityTokenAddress, stOwner, now, stOwner, protocolVersion + ); } function _deployToken( @@ -535,12 +587,14 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { address _issuer, bool _divisible, address _wallet, - uint256 _protocolVersion + uint256 _protocolVersion, + uint256 _usdFee, + uint256 _polyFee ) internal + returns(address newSecurityTokenAddress) { - (uint256 _usdFee, uint256 _polyFee) = _takeFee(STLAUNCHFEE); - address newSecurityTokenAddress = ISTFactory(getAddressValue(Encoder.getKey("protocolVersionST", _protocolVersion))).deployToken( + newSecurityTokenAddress = ISTFactory(getAddressValue(Encoder.getKey("protocolVersionST", _protocolVersion))).deployToken( _name, _ticker, 18, @@ -554,10 +608,6 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { /*solium-disable-next-line security/no-block-members*/ _storeSecurityTokenData(newSecurityTokenAddress, _ticker, _tokenDetails, now); set(Encoder.getKey("tickerToSecurityToken", _ticker), newSecurityTokenAddress); - /*solium-disable-next-line security/no-block-members*/ - emit NewSecurityToken( - _ticker, _name, newSecurityTokenAddress, msg.sender, now, msg.sender, false, _usdFee, _polyFee, _protocolVersion - ); } /** @@ -570,14 +620,14 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _deployedAt is the timestamp at which the security token is deployed */ function modifySecurityToken( - string calldata _name, - string calldata _ticker, + string memory _name, + string memory _ticker, address _owner, address _securityToken, - string calldata _tokenDetails, + string memory _tokenDetails, uint256 _deployedAt ) - external + public onlyOwner { require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Bad data"); @@ -631,7 +681,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param _newOwner The address to transfer ownership to. */ - function transferOwnership(address _newOwner) external onlyOwner { + function transferOwnership(address _newOwner) public onlyOwner { require(_newOwner != address(0), "Bad address"); emit OwnershipTransferred(getAddressValue(OWNER), _newOwner); set(OWNER, _newOwner); @@ -659,7 +709,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Sets the ticker registration fee in USD tokens. Only Polymath. * @param _tickerRegFee is the registration fee in USD tokens (base 18 decimals) */ - function changeTickerRegistrationFee(uint256 _tickerRegFee) external onlyOwner { + function changeTickerRegistrationFee(uint256 _tickerRegFee) public onlyOwner { uint256 fee = getUintValue(TICKERREGFEE); require(fee != _tickerRegFee, "Bad fee"); _changeTickerRegistrationFee(fee, _tickerRegFee); @@ -674,7 +724,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Sets the ticker registration fee in USD tokens. Only Polymath. * @param _stLaunchFee is the registration fee in USD tokens (base 18 decimals) */ - function changeSecurityLaunchFee(uint256 _stLaunchFee) external onlyOwner { + function changeSecurityLaunchFee(uint256 _stLaunchFee) public onlyOwner { uint256 fee = getUintValue(STLAUNCHFEE); require(fee != _stLaunchFee, "Bad fee"); _changeSecurityLaunchFee(fee, _stLaunchFee); @@ -691,7 +741,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _stLaunchFee is the st generation fee (base 18 decimals) * @param _isFeeInPoly defines if the fee is in poly or usd */ - function changeFeesAmountAndCurrency(uint256 _tickerRegFee, uint256 _stLaunchFee, bool _isFeeInPoly) external onlyOwner { + function changeFeesAmountAndCurrency(uint256 _tickerRegFee, uint256 _stLaunchFee, bool _isFeeInPoly) public onlyOwner { uint256 tickerFee = getUintValue(TICKERREGFEE); uint256 stFee = getUintValue(STLAUNCHFEE); bool isOldFeesInPoly = getBoolValue(IS_FEE_IN_POLY); @@ -706,7 +756,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Reclaims all ERC20Basic compatible tokens * @param _tokenContract is the address of the token contract */ - function reclaimERC20(address _tokenContract) external onlyOwner { + function reclaimERC20(address _tokenContract) public onlyOwner { require(_tokenContract != address(0), "Bad address"); IERC20 token = IERC20(_tokenContract); uint256 balance = token.balanceOf(address(this)); @@ -722,7 +772,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _minor Minor version of the proxy. * @param _patch Patch version of the proxy */ - function setProtocolFactory(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) external onlyOwner { + function setProtocolFactory(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) public onlyOwner { _setProtocolFactory(_STFactoryAddress, _major, _minor, _patch); } @@ -740,7 +790,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _minor Minor version of the proxy. * @param _patch Patch version of the proxy */ - function removeProtocolFactory(uint8 _major, uint8 _minor, uint8 _patch) external onlyOwner { + function removeProtocolFactory(uint8 _major, uint8 _minor, uint8 _patch) public onlyOwner { uint24 _packedVersion = VersionUtils.pack(_major, _minor, _patch); require(getUintValue(Encoder.getKey("latestVersion")) != _packedVersion, "Cannot remove latestVersion"); emit ProtocolFactoryRemoved(getAddressValue(Encoder.getKey("protocolVersionST", _packedVersion)), _major, _minor, _patch); @@ -755,7 +805,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _minor Minor version of the proxy. * @param _patch Patch version of the proxy */ - function setLatestVersion(uint8 _major, uint8 _minor, uint8 _patch) external onlyOwner { + function setLatestVersion(uint8 _major, uint8 _minor, uint8 _patch) public onlyOwner { _setLatestVersion(_major, _minor, _patch); } @@ -770,7 +820,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Changes the PolyToken address. Only Polymath. * @param _newAddress is the address of the polytoken. */ - function updatePolyTokenAddress(address _newAddress) external onlyOwner { + function updatePolyTokenAddress(address _newAddress) public onlyOwner { require(_newAddress != address(0), "Bad address"); set(POLYTOKEN, _newAddress); } diff --git a/contracts/interfaces/ISecurityToken.sol b/contracts/interfaces/ISecurityToken.sol index 3406a6bbe..a3fe36773 100644 --- a/contracts/interfaces/ISecurityToken.sol +++ b/contracts/interfaces/ISecurityToken.sol @@ -463,4 +463,9 @@ interface ISecurityToken { * @return bool `true` signifies the minting is allowed. While `false` denotes the end of minting */ function isIssuable() external view returns (bool); + + /** + * @notice Returns if transfers are currently frozen or not + */ + function transfersFrozen() external view returns (bool); } diff --git a/test/n_security_token_registry.js b/test/n_security_token_registry.js index 40bb6910c..9abbec95f 100644 --- a/test/n_security_token_registry.js +++ b/test/n_security_token_registry.js @@ -1122,6 +1122,66 @@ contract("SecurityTokenRegistry", async (accounts) => { }); }); + describe("Test case for refreshing a security token", async () => { + it("Should fail if msg.sender is not ST owner", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("refreshedToken", symbol, "refreshedToken", true, token_owner, { + from: account_delegate + }) + ); + }); + + it("Should fail if ticker is not deployed", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("refreshedToken", "LOGLOG3", "refreshedToken", true, token_owner, { + from: token_owner + }) + ); + }); + + it("Should fail if name is 0 length", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("", symbol, "refreshedToken", true, token_owner, { + from: token_owner + }) + ); + }); + + it("Should fail if null treasurey wallet", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("refreshedToken", symbol, "refreshedToken", true, address_zero, { + from: token_owner + }) + ); + }); + + it("Should fail if transfers not frozen", async () => { + await catchRevert( + I_STRProxied.refreshSecurityToken("refreshedToken", symbol, "refreshedToken", true, token_owner, { + from: token_owner + }) + ); + }); + + it("Should refresh security token", async () => { + let snapid = await takeSnapshot(); + let oldStAddress = await I_Getter.getSecurityTokenAddress(symbol); + let oldSt = await SecurityToken.at(oldStAddress); + await oldSt.freezeTransfers({ from:token_owner }); + let tx = await I_STRProxied.refreshSecurityToken("refreshedToken", symbol, "refreshedToken", true, token_owner, { + from: token_owner + }); + assert.equal(tx.logs[1].args._ticker, symbol, "SecurityToken not deployed"); + let newStAddress = await I_Getter.getSecurityTokenAddress(symbol); + let securityTokenTmp = tx.logs[1].args._securityTokenAddress; + assert.equal(newStAddress, securityTokenTmp, "ST address not updated"); + let newST = await SecurityToken.at(newStAddress); + assert.notEqual(oldStAddress, newStAddress, "new ST not generated"); + assert.equal(await newST.name(), "refreshedToken", "ST not deployed properly"); + await revertToSnapshot(snapid); + }); + }); + describe("Test cases for getters", async () => { it("Should get the security token address", async () => { let address = await I_Getter.getSecurityTokenAddress.call(symbol);