Skip to content

Refresh / Upgrade tokens #632

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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