Skip to content

Commit

Permalink
Refresh / Upgrade tokens (#632)
Browse files Browse the repository at this point in the history
* Added refresh token function

* Reduced STR size

* Added test cases for refreshToken

* reuse function

* Added refresh token event
  • Loading branch information
maxsam4 authored and adamdossa committed Apr 11, 2019
1 parent 87aa859 commit 2cad781
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 42 deletions.
134 changes: 92 additions & 42 deletions contracts/SecurityTokenRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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");
_;
}
}

/**
Expand Down Expand Up @@ -182,7 +195,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy {
address _owner,
address _getterContract
)
external
public
payable
{
require(!getBoolValue(INITIALIZE),"Initialized");
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -428,23 +441,23 @@ 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);
}

/**
* @notice Transfers the ownership of the ticker
* @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);
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -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
);
}

/**
Expand All @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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));
Expand All @@ -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);
}

Expand All @@ -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);
Expand All @@ -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);
}

Expand All @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions contracts/interfaces/ISecurityToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
60 changes: 60 additions & 0 deletions test/n_security_token_registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 2cad781

Please sign in to comment.