From e70b072ef9ee295d8d4e9eb6068e548f69fa637d Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Thu, 27 Sep 2018 22:33:27 +0100 Subject: [PATCH 1/9] Update burn functions --- contracts/interfaces/ISecurityToken.sol | 23 +++-- contracts/tokens/SecurityToken.sol | 109 ++++++++++++++---------- 2 files changed, 77 insertions(+), 55 deletions(-) diff --git a/contracts/interfaces/ISecurityToken.sol b/contracts/interfaces/ISecurityToken.sol index 33e38201c..c36f45270 100644 --- a/contracts/interfaces/ISecurityToken.sol +++ b/contracts/interfaces/ISecurityToken.sol @@ -19,15 +19,15 @@ interface ISecurityToken { event Approval(address indexed owner, address indexed spender, uint256 value); //transfer, transferFrom must respect use respect the result of verifyTransfer - function verifyTransfer(address _from, address _to, uint256 _amount) external returns (bool success); + function verifyTransfer(address _from, address _to, uint256 _value) external returns (bool success); /** * @notice 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) * @param _investor address the tokens will be minted to - * @param _amount is the amount of tokens that will be minted to the investor + * @param _value is the amount of tokens that will be minted to the investor */ - function mint(address _investor, uint256 _amount) external returns (bool success); + function mint(address _investor, uint256 _value) external returns (bool success); /** * @notice Burn function used to burn the securityToken @@ -35,7 +35,14 @@ interface ISecurityToken { */ function burn(uint256 _value) external returns (bool success); - event Minted(address indexed to, uint256 amount); + /** + * @notice Burn function used to burn the securityToken on behalf of someone else + * @param _from Address for whom to burn tokens + * @param _value No. of token that get burned + */ + function burnFrom(address _from, uint256 _value) external returns (bool success); + + event Minted(address indexed _to, uint256 _value); event Burnt(address indexed _burner, uint256 _value); // Permissions this to a Permission module, which has a key of 1 @@ -118,9 +125,9 @@ interface ISecurityToken { /** * @notice allows the owner to withdraw unspent POLY stored by them on the ST. * @dev Owner can transfer POLY to the ST which will be used to pay for modules that require a POLY fee. - * @param _amount amount of POLY to withdraw + * @param _value amount of POLY to withdraw */ - function withdrawPoly(uint256 _amount) external; + function withdrawPoly(uint256 _value) external; /** * @notice allows owner to approve more POLY to one of the modules @@ -168,10 +175,10 @@ interface ISecurityToken { * @notice mints new tokens and assigns them to the target investors. * Can only be called by the STO attached to the token or by the Issuer (Security Token contract owner) * @param _investors A list of addresses to whom the minted tokens will be delivered - * @param _amounts A list of the amount of tokens to mint to corresponding addresses from _investor[] list + * @param _values A list of the amount of tokens to mint to corresponding addresses from _investor[] list * @return success */ - function mintMulti(address[] _investors, uint256[] _amounts) external returns (bool success); + function mintMulti(address[] _investors, uint256[] _values) external returns (bool success); /** * @notice used to set the token Burner address. It can only be called by the owner diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index f8d91c389..0b85e6762 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -132,12 +132,12 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr // Change the STR address in the event of a upgrade event ChangeSTRAddress(address indexed _oldAddress, address indexed _newAddress); // Events to log minting and burning - event Minted(address indexed to, uint256 amount); + event Minted(address indexed _to, uint256 _value); event Burnt(address indexed _burner, uint256 _value); // Events to log controller actions event SetController(address indexed _oldController, address indexed _newController); - event ForceTransfer(address indexed _controller, address indexed _from, address indexed _to, uint256 _amount, bool _verifyTransfer, bytes _data); + event ForceTransfer(address indexed _controller, address indexed _from, address indexed _to, uint256 _value, bool _verifyTransfer, bytes _data); event DisableController(uint256 _timestamp); function isModule(address _module, uint8 _type) internal view returns (bool) { @@ -163,8 +163,8 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr } } - modifier checkGranularity(uint256 _amount) { - require(_amount % granularity == 0, "Unable to modify token balances at this granularity"); + modifier checkGranularity(uint256 _value) { + require(_value % granularity == 0, "Unable to modify token balances at this granularity"); _; } @@ -360,10 +360,10 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr /** * @notice allows the owner to withdraw unspent POLY stored by them on the ST. * @dev Owner can transfer POLY to the ST which will be used to pay for modules that require a POLY fee. - * @param _amount amount of POLY to withdraw + * @param _value amount of POLY to withdraw */ - function withdrawPoly(uint256 _amount) external onlyOwner { - require(ERC20(polyToken).transfer(owner, _amount), "In-sufficient balance"); + function withdrawPoly(uint256 _value) external onlyOwner { + require(ERC20(polyToken).transfer(owner, _value), "In-sufficient balance"); } /** @@ -540,13 +540,17 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _value value of transfer * @return bool success */ - function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { + function transferFrom(address _from, address _to, uint256 _value) public returns(bool) { + require(_updateTransfer(_from, _to, _value), "Transfer is not valid"); + require(super.transferFrom(_from, _to, _value)); + return true; + } + + function _updateTransfer(address _from, address _to, uint256 _value) internal returns(bool) { _adjustInvestorCount(_from, _to, _value); - require(_verifyTransfer(_from, _to, _value, true), "Transfer is not valid"); _adjustBalanceCheckpoints(_from); _adjustBalanceCheckpoints(_to); - require(super.transferFrom(_from, _to, _value)); - return true; + return _verifyTransfer(_from, _to, _value, true); } /** @@ -554,11 +558,11 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @dev TransferManager module has a key of 2 * @param _from sender of transfer * @param _to receiver of transfer - * @param _amount value of transfer + * @param _value value of transfer * @param _isTransfer whether transfer is being executed * @return bool */ - function _verifyTransfer(address _from, address _to, uint256 _amount, bool _isTransfer) internal checkGranularity(_amount) returns (bool) { + function _verifyTransfer(address _from, address _to, uint256 _value, bool _isTransfer) internal checkGranularity(_value) returns (bool) { if (!transfersFrozen) { if (modules[TRANSFERMANAGER_KEY].length == 0) { return true; @@ -572,7 +576,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr module = modules[TRANSFERMANAGER_KEY][i]; if (!modulesToData[module].isArchived) { unarchived = true; - ITransferManager.Result valid = ITransferManager(module).verifyTransfer(_from, _to, _amount, _isTransfer); + ITransferManager.Result valid = ITransferManager(module).verifyTransfer(_from, _to, _value, _isTransfer); if (valid == ITransferManager.Result.INVALID) { isInvalid = true; } @@ -595,11 +599,11 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @dev TransferManager module has a key of 2 * @param _from sender of transfer * @param _to receiver of transfer - * @param _amount value of transfer + * @param _value value of transfer * @return bool */ - function verifyTransfer(address _from, address _to, uint256 _amount) public returns (bool) { - return _verifyTransfer(_from, _to, _amount, false); + function verifyTransfer(address _from, address _to, uint256 _value) public returns (bool) { + return _verifyTransfer(_from, _to, _value, false); } /** @@ -615,19 +619,19 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @notice mints new tokens and assigns them to the target _investor. * @dev Can only be called by the issuer or STO attached to the token * @param _investor Address where the minted tokens will be delivered - * @param _amount Number of tokens be minted + * @param _value Number of tokens be minted * @return success */ - function mint(address _investor, uint256 _amount) public onlyModuleOrOwner(STO_KEY) checkGranularity(_amount) isMintingAllowed() returns (bool success) { + function mint(address _investor, uint256 _value) public onlyModuleOrOwner(STO_KEY) checkGranularity(_value) isMintingAllowed() returns (bool success) { require(_investor != address(0), "Investor address should not be 0x"); - _adjustInvestorCount(address(0), _investor, _amount); - require(_verifyTransfer(address(0), _investor, _amount, true), "Transfer is not valid"); + _adjustInvestorCount(address(0), _investor, _value); + require(_verifyTransfer(address(0), _investor, _value, true), "Transfer is not valid"); _adjustBalanceCheckpoints(_investor); _adjustTotalSupplyCheckpoints(); - totalSupply_ = totalSupply_.add(_amount); - balances[_investor] = balances[_investor].add(_amount); - emit Minted(_investor, _amount); - emit Transfer(address(0), _investor, _amount); + totalSupply_ = totalSupply_.add(_value); + balances[_investor] = balances[_investor].add(_value); + emit Minted(_investor, _value); + emit Transfer(address(0), _investor, _value); return true; } @@ -635,13 +639,13 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @notice mints new tokens and assigns them to the target _investor. * @dev Can only be called by the issuer or STO attached to the token. * @param _investors A list of addresses to whom the minted tokens will be dilivered - * @param _amounts A list of number of tokens get minted and transfer to corresponding address of the investor from _investor[] list + * @param _values A list of number of tokens get minted and transfer to corresponding address of the investor from _investor[] list * @return success */ - function mintMulti(address[] _investors, uint256[] _amounts) external onlyModuleOrOwner(STO_KEY) returns (bool success) { - require(_investors.length == _amounts.length, "Mis-match in the length of the arrays"); + function mintMulti(address[] _investors, uint256[] _values) external onlyModuleOrOwner(STO_KEY) returns (bool success) { + require(_investors.length == _values.length, "Mis-match in the length of the arrays"); for (uint256 i = 0; i < _investors.length; i++) { - mint(_investors[i], _amounts[i]); + mint(_investors[i], _values[i]); } return true; } @@ -675,25 +679,40 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr tokenBurner = ITokenBurner(_tokenBurner); } + function _burn(address _from, uint256 _value) internal returns (bool) { + require(tokenBurner != address(0), "Token Burner contract address is not set yet"); + require(_value <= balances[_from], "Value too high"); + _updateTransfer(_from, address(0), _value); + + // no need to require value <= totalSupply, since that would imply the + // sender's balance is greater than the totalSupply, which *should* be an assertion failure + balances[_from] = balances[_from].sub(_value); + totalSupply_ = totalSupply_.sub(_value); + require(tokenBurner.burn(_from, _value), "Token burner failed"); + emit Burnt(_from, _value); + emit Transfer(_from, address(0), _value); + return true; + } + /** * @notice Burn function used to burn the securityToken * @param _value No. of token that get burned */ function burn(uint256 _value) checkGranularity(_value) public returns (bool) { - _adjustInvestorCount(msg.sender, address(0), _value); - require(tokenBurner != address(0), "Token Burner contract address is not set yet"); - require(_verifyTransfer(msg.sender, address(0), _value, true), "Transfer is not valid"); - require(_value <= balances[msg.sender], "Value should no be greater than the balance of msg.sender"); - _adjustBalanceCheckpoints(msg.sender); - _adjustTotalSupplyCheckpoints(); - // no need to require value <= totalSupply, since that would imply the - // sender's balance is greater than the totalSupply, which *should* be an assertion failure + require(_burn(msg.sender, _value)); + return true; + } - balances[msg.sender] = balances[msg.sender].sub(_value); - require(tokenBurner.burn(msg.sender, _value), "Token burner process is not validated"); - totalSupply_ = totalSupply_.sub(_value); - emit Burnt(msg.sender, _value); - emit Transfer(msg.sender, address(0), _value); + /** + * @notice Burn function used to burn the securityToken on behalf of someone else + * @param _from Address for whom to burn tokens + * @param _value No. of token that get burned + */ + function burnFrom(address _from, uint256 _value) checkGranularity(_value) public returns (bool) { + require(_value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + require(_burn(_from, _value)); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); return true; } @@ -797,13 +816,9 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _data data attached to the transfer by controller to emit in event */ function forceTransfer(address _from, address _to, uint256 _value, bytes _data) public onlyController returns(bool) { - _adjustInvestorCount(_from, _to, _value); - bool verified = _verifyTransfer(_from, _to, _value, true); - _adjustBalanceCheckpoints(_from); - _adjustBalanceCheckpoints(_to); - require(_to != address(0)); require(_value <= balances[_from]); + bool verified = _updateTransfer(_from, _to, _value); balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); From 4d6d16954077ab07b5d7e2e17e5033bf567b923f Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Fri, 28 Sep 2018 11:25:32 +0100 Subject: [PATCH 2/9] Updates --- contracts/tokens/SecurityToken.sol | 98 +++++++++++++++--------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index 0b85e6762..7a62360a0 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -8,7 +8,6 @@ import "../interfaces/IModuleRegistry.sol"; import "../interfaces/IFeatureRegistry.sol"; import "../modules/TransferManager/ITransferManager.sol"; import "../modules/PermissionManager/IPermissionManager.sol"; -import "../interfaces/ITokenBurner.sol"; import "../RegistryUpdater.sol"; import "../libraries/Util.sol"; import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; @@ -42,8 +41,10 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr uint8 public constant PERMISSIONMANAGER_KEY = 1; uint8 public constant TRANSFERMANAGER_KEY = 2; - uint8 public constant STO_KEY = 3; + uint8 public constant MINT_KEY = 3; uint8 public constant CHECKPOINT_KEY = 4; + uint8 public constant BURN_KEY = 5; + uint256 public granularity; // Value of current checkpoint @@ -55,9 +56,6 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr // List of token holders address[] public investors; - // Reference to token burner contract - ITokenBurner public tokenBurner; - // Use to temporarily halt all transactions bool public transfersFrozen; @@ -133,11 +131,12 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr event ChangeSTRAddress(address indexed _oldAddress, address indexed _newAddress); // Events to log minting and burning event Minted(address indexed _to, uint256 _value); - event Burnt(address indexed _burner, uint256 _value); + event Burnt(address indexed _from, uint256 _value); // Events to log controller actions event SetController(address indexed _oldController, address indexed _newController); event ForceTransfer(address indexed _controller, address indexed _from, address indexed _to, uint256 _value, bool _verifyTransfer, bytes _data); + event ForceBurn(address indexed _controller, address indexed _from, uint256 _value, bytes _data); event DisableController(uint256 _timestamp); function isModule(address _module, uint8 _type) internal view returns (bool) { @@ -622,7 +621,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _value Number of tokens be minted * @return success */ - function mint(address _investor, uint256 _value) public onlyModuleOrOwner(STO_KEY) checkGranularity(_value) isMintingAllowed() returns (bool success) { + function mint(address _investor, uint256 _value) public onlyModuleOrOwner(MINT_KEY) checkGranularity(_value) isMintingAllowed() returns (bool success) { require(_investor != address(0), "Investor address should not be 0x"); _adjustInvestorCount(address(0), _investor, _value); require(_verifyTransfer(address(0), _investor, _value, true), "Transfer is not valid"); @@ -642,7 +641,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _values A list of number of tokens get minted and transfer to corresponding address of the investor from _investor[] list * @return success */ - function mintMulti(address[] _investors, uint256[] _values) external onlyModuleOrOwner(STO_KEY) returns (bool success) { + function mintMulti(address[] _investors, uint256[] _values) external onlyModuleOrOwner(MINT_KEY) returns (bool success) { require(_investors.length == _values.length, "Mis-match in the length of the arrays"); for (uint256 i = 0; i < _investors.length; i++) { mint(_investors[i], _values[i]); @@ -650,45 +649,11 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr return true; } - /** - * @notice Validate permissions with PermissionManager if it exists, If no Permission return false - * @dev Note that IModule withPerm will allow ST owner all permissions anyway - * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions) - * @param _delegate address of delegate - * @param _module address of PermissionManager module - * @param _perm the permissions - * @return success - */ - function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool) { - if (modules[PERMISSIONMANAGER_KEY].length == 0) { - return false; - } - - for (uint8 i = 0; i < modules[PERMISSIONMANAGER_KEY].length; i++) { - if (IPermissionManager(modules[PERMISSIONMANAGER_KEY][i]).checkPermission(_delegate, _module, _perm)) { - return true; - } - } - } - - /** - * @notice used to set the token Burner address. It only be called by the owner - * @param _tokenBurner Address of the token burner contract - */ - function setTokenBurner(address _tokenBurner) external onlyOwner { - tokenBurner = ITokenBurner(_tokenBurner); - } - function _burn(address _from, uint256 _value) internal returns (bool) { - require(tokenBurner != address(0), "Token Burner contract address is not set yet"); require(_value <= balances[_from], "Value too high"); - _updateTransfer(_from, address(0), _value); - - // no need to require value <= totalSupply, since that would imply the - // sender's balance is greater than the totalSupply, which *should* be an assertion failure + require(_updateTransfer(_from, address(0), _value), "Burn is not valid"); balances[_from] = balances[_from].sub(_value); totalSupply_ = totalSupply_.sub(_value); - require(tokenBurner.burn(_from, _value), "Token burner failed"); emit Burnt(_from, _value); emit Transfer(_from, address(0), _value); return true; @@ -696,9 +661,9 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr /** * @notice Burn function used to burn the securityToken - * @param _value No. of token that get burned + * @param _value No. of tokens that get burned */ - function burn(uint256 _value) checkGranularity(_value) public returns (bool) { + function burn(uint256 _value) checkGranularity(_value) onlyModule(BURN_KEY) public returns (bool) { require(_burn(msg.sender, _value)); return true; } @@ -706,10 +671,9 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr /** * @notice Burn function used to burn the securityToken on behalf of someone else * @param _from Address for whom to burn tokens - * @param _value No. of token that get burned + * @param _value No. of tokens that get burned */ - function burnFrom(address _from, uint256 _value) checkGranularity(_value) public returns (bool) { - require(_value <= balances[_from]); + function burnFrom(address _from, uint256 _value) checkGranularity(_value) onlyModule(BURN_KEY) public returns (bool) { require(_value <= allowed[_from][msg.sender]); require(_burn(_from, _value)); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); @@ -727,6 +691,27 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr return currentCheckpointId; } + /** + * @notice Validate permissions with PermissionManager if it exists, If no Permission return false + * @dev Note that IModule withPerm will allow ST owner all permissions anyway + * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions) + * @param _delegate address of delegate + * @param _module address of PermissionManager module + * @param _perm the permissions + * @return success + */ + function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool) { + if (modules[PERMISSIONMANAGER_KEY].length == 0) { + return false; + } + + for (uint8 i = 0; i < modules[PERMISSIONMANAGER_KEY].length; i++) { + if (IPermissionManager(modules[PERMISSIONMANAGER_KEY][i]).checkPermission(_delegate, _module, _perm)) { + return true; + } + } + } + /** * @notice Queries totalSupply as of a defined checkpoint * @param _checkpointId Checkpoint ID to query @@ -827,6 +812,23 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr return true; } + /** + * @notice Use by a controller to execute a foced burn + * @param _from address from which to take tokens + * @param _value amount of tokens to transfer + * @param _data data attached to the transfer by controller to emit in event + */ + function forceBurn(address _from, uint256 _value, bytes _data) public onlyController returns(bool) { + require(_value <= balances[_from], "Value too high"); + bool verified = _updateTransfer(_from, address(0), _value); + balances[_from] = balances[_from].sub(_value); + totalSupply_ = totalSupply_.sub(_value); + emit ForceBurn(msg.sender, _from, _value, _data); + emit Burnt(_from, _value); + emit Transfer(_from, address(0), _value); + return true; + } + /** * @notice Use to get the version of the securityToken */ From 3d75dda6810ac754eb7a7a0b1d79e58f6eb945a3 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Fri, 28 Sep 2018 11:56:58 +0100 Subject: [PATCH 3/9] Add an example redemption module --- contracts/modules/Burn/IBurn.sol | 8 ++ contracts/modules/Burn/TrackedRedemption.sol | 52 +++++++++ .../modules/Burn/TrackedRedemptionFactory.sol | 100 ++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 contracts/modules/Burn/IBurn.sol create mode 100644 contracts/modules/Burn/TrackedRedemption.sol create mode 100644 contracts/modules/Burn/TrackedRedemptionFactory.sol diff --git a/contracts/modules/Burn/IBurn.sol b/contracts/modules/Burn/IBurn.sol new file mode 100644 index 000000000..3af836f40 --- /dev/null +++ b/contracts/modules/Burn/IBurn.sol @@ -0,0 +1,8 @@ +pragma solidity ^0.4.24; + +/** + * @title Interface to be implemented by all checkpoint modules + */ +interface IBurn { + +} diff --git a/contracts/modules/Burn/TrackedRedemption.sol b/contracts/modules/Burn/TrackedRedemption.sol new file mode 100644 index 000000000..f69d8a86a --- /dev/null +++ b/contracts/modules/Burn/TrackedRedemption.sol @@ -0,0 +1,52 @@ +pragma solidity ^0.4.24; + +import "./IBurn.sol"; +import "../Module.sol"; +import "../../interfaces/ISecurityToken.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +/** + * @title Burn module for burning tokens and keeping track of burnt amounts + */ +contract TrackedRedemption is IBurn, Module { + using SafeMath for uint256; + + mapping (address => uint256) redeemedTokens; + + event Redeemed(address _investor, uint256 _value, uint256 _timestamp); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + */ + constructor (address _securityToken, address _polyAddress) public + Module(_securityToken, _polyAddress) + { + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() public pure returns (bytes4) { + return bytes4(0); + } + + /** + * @notice Redeem tokens and track redemptions + * @param _value The number of tokens to redeem + */ + function redeemTokens(uint256 _value) public { + require(ISecurityToken(securityToken).burnFrom(msg.sender, _value), "Unable to redeem tokens"); + redeemedTokens[msg.sender] = redeemedTokens[msg.sender].add(_value); + emit Redeemed(msg.sender, _value, now); + } + + /** + * @notice Return the permissions flag that are associated with CountTransferManager + */ + function getPermissions() public view returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](0); + return allPermissions; + } +} diff --git a/contracts/modules/Burn/TrackedRedemptionFactory.sol b/contracts/modules/Burn/TrackedRedemptionFactory.sol new file mode 100644 index 000000000..94003286b --- /dev/null +++ b/contracts/modules/Burn/TrackedRedemptionFactory.sol @@ -0,0 +1,100 @@ +pragma solidity ^0.4.24; + +import "./TrackedRedemption.sol"; +import "../ModuleFactory.sol"; + +/** + * @title Factory for deploying GeneralTransferManager module + */ +contract TrackedRedemptionFactory is ModuleFactory { + + /** + * @notice Constructor + * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of module + * @param _usageCost Usage cost of module + * @param _subscriptionCost Monthly cost of module + */ + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public + ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + { + version = "1.0.0"; + name = "TrackedRedemption"; + title = "Tracked Redemption"; + description = "Track token redemptions"; + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + } + + /** + * @notice used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes /* _data */) external returns(address) { + if (setupCost > 0) + require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom because of sufficent Allowance is not provided"); + address trackedRedemption = new TrackedRedemption(msg.sender, address(polyToken)); + emit GenerateModuleFromFactory(address(trackedRedemption), getName(), address(this), msg.sender, setupCost, now); + return address(trackedRedemption); + } + + /** + * @notice Type of the Module factory + */ + function getType() public view returns(uint8) { + return 5; + } + + /** + * @notice Get the name of the Module + */ + function getName() public view returns(bytes32) { + return name; + } + + /** + * @notice Get the description of the Module + */ + function getDescription() public view returns(string) { + return description; + } + + /** + * @notice Get the version of the Module + */ + function getVersion() external view returns(string) { + return version; + } + + /** + * @notice Get the title of the Module + */ + function getTitle() public view returns(string) { + return title; + } + + /** + * @notice Get the setup cost of the module + */ + function getSetupCost() external view returns (uint256) { + return setupCost; + } + + /** + * @notice Get the Instructions that helped to used the module + */ + function getInstructions() public view returns(string) { + return "Allows an investor to redeem security tokens which are tracked by this module"; + } + + /** + * @notice Get the tags related to the module factory + */ + function getTags() public view returns(bytes32[]) { + bytes32[] memory availableTags = new bytes32[](2); + availableTags[0] = "Redemption"; + availableTags[1] = "Tracked"; + return availableTags; + } + +} From 0384c3c4a6eaad8402c65541cedbdb3f4aace033 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Sun, 30 Sep 2018 23:52:18 +0100 Subject: [PATCH 4/9] Update tests --- contracts/libraries/KindMath.sol | 53 ++++++++++ contracts/tokens/SecurityToken.sol | 4 +- test/o_security_token.js | 152 +++++++++++++---------------- 3 files changed, 123 insertions(+), 86 deletions(-) create mode 100644 contracts/libraries/KindMath.sol diff --git a/contracts/libraries/KindMath.sol b/contracts/libraries/KindMath.sol new file mode 100644 index 000000000..92d77362e --- /dev/null +++ b/contracts/libraries/KindMath.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.4.24; + +// Copied from OpenZeppelin and modified to be friendlier + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library KindMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { + // Gas optimization: this is cheaper than requireing 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + + c = a * b; + require(c / a == b, "mul overflow"); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // require(b > 0); // Solidity automatically throws when dividing by 0 + // uint256 c = a / b; + // require(a == b * c + a % b); // There is no case in which this doesn't hold + return a / b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a, "sub overflow"); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = a + b; + require(c >= a, "add overflow"); + return c; + } +} diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index 7a62360a0..e83724ecb 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -136,7 +136,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr // Events to log controller actions event SetController(address indexed _oldController, address indexed _newController); event ForceTransfer(address indexed _controller, address indexed _from, address indexed _to, uint256 _value, bool _verifyTransfer, bytes _data); - event ForceBurn(address indexed _controller, address indexed _from, uint256 _value, bytes _data); + event ForceBurn(address indexed _controller, address indexed _from, uint256 _value, bool _verifyTransfer, bytes _data); event DisableController(uint256 _timestamp); function isModule(address _module, uint8 _type) internal view returns (bool) { @@ -823,7 +823,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr bool verified = _updateTransfer(_from, address(0), _value); balances[_from] = balances[_from].sub(_value); totalSupply_ = totalSupply_.sub(_value); - emit ForceBurn(msg.sender, _from, _value, _data); + emit ForceBurn(msg.sender, _from, _value, verified, _data); emit Burnt(_from, _value); emit Transfer(_from, address(0), _value); return true; diff --git a/test/o_security_token.js b/test/o_security_token.js index 8e4223f7d..f1a905154 100644 --- a/test/o_security_token.js +++ b/test/o_security_token.js @@ -139,7 +139,7 @@ contract('SecurityToken', accounts => { }); // STEP 3: Deploy the ModuleRegistry - + I_ModuleRegistry = await ModuleRegistry.new({from:account_polymath}); // Step 3 (b): Deploy the proxy and attach the implementation contract to it I_ModuleRegistryProxy = await ModuleRegistryProxy.new({from:account_polymath}); @@ -1106,36 +1106,6 @@ contract('SecurityToken', accounts => { await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), {from: account_temp}); }); - it("Should fail to call the burn the tokens because token burner contract is not set", async() => { - let errorThrown = false; - try { - await I_SecurityToken.burn(web3.utils.toWei('1', 'ether'),{ from: account_temp }); - } catch(error) { - console.log(' tx revert -> Token burner contract is not set'.grey); - errorThrown = true; - ensureException(error); - } - assert.ok(errorThrown, message); - }); - - it("Should fail to call the burn the tokens because TM does not allow it", async ()=> { - // Deploy the token burner contract - I_TokenBurner = await TokenBurner.new(I_SecurityToken.address, { from: token_owner }); - - await I_SecurityToken.setTokenBurner(I_TokenBurner.address, { from: token_owner }); - assert.equal(await I_SecurityToken.tokenBurner.call(), I_TokenBurner.address); - let errorThrown = false; - try { - await I_SecurityToken.burn(web3.utils.toWei('1', 'ether'),{ from: account_temp }); - } catch(error) { - console.log(' tx revert -> Token burner contract is not set'.grey); - errorThrown = true; - ensureException(error); - } - assert.ok(errorThrown, message); - - }); - it("Should check that the list of investors is correct", async ()=> { // Hardcode list of expected accounts based on transfers above let investorsLength = await I_SecurityToken.getInvestorsLength(); @@ -1148,27 +1118,76 @@ contract('SecurityToken', accounts => { assert.equal(investor, expectedAccounts[i]); } }); + it("Should fail to set controller status because msg.sender not owner", async() => { + let errorThrown = false; + try { + await I_SecurityToken.setController(account_controller, {from: account_controller}); + } catch (error) { + console.log(` tx revert -> msg.sender not owner`.grey); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); - it("Should burn the tokens", async ()=> { - let errorThrown = false; - await I_GeneralTransferManager.changeAllowAllBurnTransfers(true, {from : token_owner}); - let currentInvestorCount = await I_SecurityToken.investorCount(); - let currentBalance = await I_SecurityToken.balanceOf(account_temp); - try { - let tx = await I_SecurityToken.burn(currentBalance + web3.utils.toWei("500", "ether"), { from: account_temp }); - } catch(error) { - console.log(` tx revert -> value is greater than its current balance`.grey); - errorThrown = true; - ensureException(error); - } - assert.ok(errorThrown, message); - }); + it("Should successfully set controller", async() => { + let tx1 = await I_SecurityToken.setController(account_controller, {from: token_owner}); + + // check event + assert.equal(address_zero, tx1.logs[0].args._oldController, "Event not emitted as expected"); + assert.equal(account_controller, tx1.logs[0].args._newController, "Event not emitted as expected"); + + let tx2 = await I_SecurityToken.setController(address_zero, {from: token_owner}); + + // check event + assert.equal(account_controller, tx2.logs[0].args._oldController, "Event not emitted as expected"); + assert.equal(address_zero, tx2.logs[0].args._newController, "Event not emitted as expected"); + + let tx3 = await I_SecurityToken.setController(account_controller, {from: token_owner}); + + // check event + assert.equal(address_zero, tx3.logs[0].args._oldController, "Event not emitted as expected"); + assert.equal(account_controller, tx3.logs[0].args._newController, "Event not emitted as expected"); + + // check status + let controller = await I_SecurityToken.controller.call(); + assert.equal(account_controller, controller, "Status not set correctly"); + }); + + it("Should force burn the tokens - value too high", async ()=> { + let errorThrown = false; + await I_GeneralTransferManager.changeAllowAllBurnTransfers(true, {from : token_owner}); + let currentInvestorCount = await I_SecurityToken.investorCount(); + let currentBalance = await I_SecurityToken.balanceOf(account_temp); + try { + let tx = await I_SecurityToken.forceBurn(account_temp, currentBalance + web3.utils.toWei("500", "ether"), "", { from: account_controller }); + } catch(error) { + console.log(` tx revert -> value is greater than its current balance`.grey); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); + it("Should force burn the tokens - wrong caller", async ()=> { + let errorThrown = false; + await I_GeneralTransferManager.changeAllowAllBurnTransfers(true, {from : token_owner}); + let currentInvestorCount = await I_SecurityToken.investorCount(); + let currentBalance = await I_SecurityToken.balanceOf(account_temp); + try { + let tx = await I_SecurityToken.forceBurn(account_temp, currentBalance, "", { from: token_owner }); + } catch(error) { + console.log(` tx revert -> not owner`.grey); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); it("Should burn the tokens", async ()=> { let currentInvestorCount = await I_SecurityToken.investorCount(); let currentBalance = await I_SecurityToken.balanceOf(account_temp); // console.log(currentInvestorCount.toString(), currentBalance.toString()); - let tx = await I_SecurityToken.burn(currentBalance, { from: account_temp }); + let tx = await I_SecurityToken.forceBurn(account_temp, currentBalance, "", { from: account_controller }); // console.log(tx.logs[0].args._value.toNumber(), currentBalance.toNumber()); assert.equal(tx.logs[0].args._value.toNumber(), currentBalance.toNumber()); let newInvestorCount = await I_SecurityToken.investorCount(); @@ -1243,42 +1262,6 @@ contract('SecurityToken', accounts => { describe("Force Transfer", async() => { - it("Should fail to set controller status because msg.sender not owner", async() => { - let errorThrown = false; - try { - await I_SecurityToken.setController(account_controller, {from: account_controller}); - } catch (error) { - console.log(` tx revert -> msg.sender not owner`.grey); - errorThrown = true; - ensureException(error); - } - assert.ok(errorThrown, message); - }); - - it("Should successfully set controller", async() => { - let tx1 = await I_SecurityToken.setController(account_controller, {from: token_owner}); - - // check event - assert.equal(address_zero, tx1.logs[0].args._oldController, "Event not emitted as expected"); - assert.equal(account_controller, tx1.logs[0].args._newController, "Event not emitted as expected"); - - let tx2 = await I_SecurityToken.setController(address_zero, {from: token_owner}); - - // check event - assert.equal(account_controller, tx2.logs[0].args._oldController, "Event not emitted as expected"); - assert.equal(address_zero, tx2.logs[0].args._newController, "Event not emitted as expected"); - - let tx3 = await I_SecurityToken.setController(account_controller, {from: token_owner}); - - // check event - assert.equal(address_zero, tx3.logs[0].args._oldController, "Event not emitted as expected"); - assert.equal(account_controller, tx3.logs[0].args._newController, "Event not emitted as expected"); - - // check status - let controller = await I_SecurityToken.controller.call(); - assert.equal(account_controller, controller, "Status not set correctly"); - }); - it("Should fail to forceTransfer because not approved controller", async() => { let errorThrown1 = false; try { @@ -1332,11 +1315,12 @@ contract('SecurityToken', accounts => { assert.equal(start_investorCount.add(1).toNumber(), end_investorCount.toNumber(), "Investor count not changed"); assert.equal(start_balInv1.sub(web3.utils.toWei("10", "ether")).toNumber(), end_balInv1.toNumber(), "Investor balance not changed"); assert.equal(start_balInv2.add(web3.utils.toWei("10", "ether")).toNumber(), end_balInv2.toNumber(), "Investor balance not changed"); - + console.log(tx.logs[0].args); + console.log(tx.logs[1].args); assert.equal(account_controller, tx.logs[0].args._controller, "Event not emitted as expected"); assert.equal(account_investor1, tx.logs[0].args._from, "Event not emitted as expected"); assert.equal(account_investor2, tx.logs[0].args._to, "Event not emitted as expected"); - assert.equal(web3.utils.toWei("10", "ether"), tx.logs[0].args._amount, "Event not emitted as expected"); + assert.equal(web3.utils.toWei("10", "ether"), tx.logs[0].args._value, "Event not emitted as expected"); console.log(tx.logs[0].args._verifyTransfer); assert.equal(false, tx.logs[0].args._verifyTransfer, "Event not emitted as expected"); assert.equal("reason", web3.utils.hexToUtf8(tx.logs[0].args._data), "Event not emitted as expected"); From 9d547ec67b6f5a6c9a3a48608898f875b915652f Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Mon, 1 Oct 2018 00:07:00 +0100 Subject: [PATCH 5/9] Add module test cases --- test/v_tracked_redemptions.js | 378 ++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 test/v_tracked_redemptions.js diff --git a/test/v_tracked_redemptions.js b/test/v_tracked_redemptions.js new file mode 100644 index 000000000..0f767214f --- /dev/null +++ b/test/v_tracked_redemptions.js @@ -0,0 +1,378 @@ +import latestTime from './helpers/latestTime'; +import { duration, ensureException, promisifyLogWatch, latestBlock } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { encodeProxyCall } from './helpers/encodeCall'; + +const PolymathRegistry = artifacts.require('./PolymathRegistry.sol') +const ModuleRegistry = artifacts.require('./ModuleRegistry.sol'); +const ModuleRegistryProxy = artifacts.require('./ModuleRegistryProxy.sol'); +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const SecurityTokenRegistry = artifacts.require('./SecurityTokenRegistry.sol'); +const SecurityTokenRegistryProxy = artifacts.require('./SecurityTokenRegistryProxy.sol'); +const FeatureRegistry = artifacts.require('./FeatureRegistry.sol'); +const STFactory = artifacts.require('./STFactory.sol'); +const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol'); +const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const TrackedRedemptionFactory = artifacts.require('./TrackedRedemptionFactory.sol'); +const TrackedRedemption = artifacts.require('./TrackedRedemption'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const PolyTokenFaucet = artifacts.require('./PolyTokenFaucet.sol'); + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('TrackedRedemption', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + let account_temp; + + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let I_SecurityTokenRegistryProxy; + let I_GeneralTransferManagerFactory; + let I_TrackedRedemptionFactory; + let I_GeneralPermissionManager; + let I_TrackedRedemption; + let I_GeneralTransferManager; + let I_ExchangeTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_STRProxied; + let I_STFactory; + let I_SecurityToken; + let I_PolyToken; + let I_MRProxied; + let I_PolymathRegistry; + + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + let snapId; + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + const checkpointKey = 4; + const burnKey = 5; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + const STRProxyParameters = ['address', 'address', 'uint256', 'uint256', 'address', 'address']; + const MRProxyParameters = ['address', 'address']; + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + + account_investor1 = accounts[6]; + account_investor2 = accounts[7]; + account_investor3 = accounts[8]; + account_investor4 = accounts[9]; + account_temp = accounts[2]; + + // ----------- POLYMATH NETWORK Configuration ------------ + + // Step 0: Deploy the PolymathRegistry + I_PolymathRegistry = await PolymathRegistry.new({from: account_polymath}); + + // Step 1: Deploy the token Faucet and Mint tokens for token_owner + I_PolyToken = await PolyTokenFaucet.new(); + await I_PolyToken.getTokens((10000 * Math.pow(10, 18)), token_owner); + + // Step 2: Deploy the FeatureRegistry + + I_FeatureRegistry = await FeatureRegistry.new( + I_PolymathRegistry.address, + { + from: account_polymath + }); + + // STEP 3: Deploy the ModuleRegistry + + I_ModuleRegistry = await ModuleRegistry.new({from:account_polymath}); + // Step 3 (b): Deploy the proxy and attach the implementation contract to it + I_ModuleRegistryProxy = await ModuleRegistryProxy.new({from:account_polymath}); + let bytesMRProxy = encodeProxyCall(MRProxyParameters, [I_PolymathRegistry.address, account_polymath]); + await I_ModuleRegistryProxy.upgradeToAndCall("1.0.0", I_ModuleRegistry.address, bytesMRProxy, {from: account_polymath}); + I_MRProxied = await ModuleRegistry.at(I_ModuleRegistryProxy.address); + + // STEP 4: Deploy the GeneralTransferManagerFactory + + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_GeneralTransferManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManagerFactory contract was not deployed" + ); + + // STEP 5: Deploy the GeneralDelegateManagerFactory + + I_GeneralPermissionManagerFactory = await GeneralPermissionManagerFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + + assert.notEqual( + I_GeneralPermissionManagerFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralDelegateManagerFactory contract was not deployed" + ); + + // STEP 4: Deploy the TrackedRedemption + I_TrackedRedemptionFactory = await TrackedRedemptionFactory.new(I_PolyToken.address, 0, 0, 0, {from:account_polymath}); + assert.notEqual( + I_TrackedRedemptionFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "TrackedRedemptionFactory contract was not deployed" + ); + + // STEP 5: Register the Modules with the ModuleRegistry contract + + // (A) : Register the GeneralTransferManagerFactory + await I_MRProxied.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + + // (B) : Register the GeneralDelegateManagerFactory + await I_MRProxied.registerModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); + + // (C) : Register the TrackedRedemptionFactory + await I_MRProxied.registerModule(I_TrackedRedemptionFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_TrackedRedemptionFactory.address, true, { from: account_polymath }); + + // Step 6: Deploy the STFactory contract + + I_STFactory = await STFactory.new(I_GeneralTransferManagerFactory.address, {from : account_polymath }); + + assert.notEqual( + I_STFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "STFactory contract was not deployed", + ); + + // Step 7: Deploy the SecurityTokenRegistry contract + + I_SecurityTokenRegistry = await SecurityTokenRegistry.new({from: account_polymath }); + + assert.notEqual( + I_SecurityTokenRegistry.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "SecurityTokenRegistry contract was not deployed", + ); + + // Step 8: Deploy the proxy and attach the implementation contract to it. + I_SecurityTokenRegistryProxy = await SecurityTokenRegistryProxy.new({from: account_polymath}); + let bytesProxy = encodeProxyCall(STRProxyParameters, [I_PolymathRegistry.address, I_STFactory.address, initRegFee, initRegFee, I_PolyToken.address, account_polymath]); + await I_SecurityTokenRegistryProxy.upgradeToAndCall("1.0.0", I_SecurityTokenRegistry.address, bytesProxy, {from: account_polymath}); + I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); + + // Step 9: update the registries addresses from the PolymathRegistry contract + await I_PolymathRegistry.changeAddress("PolyToken", I_PolyToken.address, {from: account_polymath}) + await I_PolymathRegistry.changeAddress("ModuleRegistry", I_ModuleRegistryProxy.address, {from: account_polymath}); + await I_PolymathRegistry.changeAddress("FeatureRegistry", I_FeatureRegistry.address, {from: account_polymath}); + await I_PolymathRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistryProxy.address, {from: account_polymath}); + await I_MRProxied.updateFromRegistry({from: account_polymath}); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${PolymathRegistry.address} + SecurityTokenRegistryProxy: ${SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${SecurityTokenRegistry.address} + ModuleRegistry: ${ModuleRegistry.address} + ModuleRegistryProxy: ${ModuleRegistryProxy.address} + FeatureRegistry: ${FeatureRegistry.address} + + STFactory: ${STFactory.address} + GeneralTransferManagerFactory: ${GeneralTransferManagerFactory.address} + GeneralPermissionManagerFactory: ${GeneralPermissionManagerFactory.address} + + TrackedRedemptionFactory: ${I_TrackedRedemptionFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async() => { + + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner, gas: 85000000 }); + + // 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 log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + + // 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" + ); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = await I_SecurityToken.modules(2, 0); + I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + + }); + + it("Should successfully attach the TrackedRedemption with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_TrackedRedemptionFactory.address, "", 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._type.toNumber(), burnKey, "TrackedRedemption doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "TrackedRedemption", + "TrackedRedemption module was not added" + ); + I_TrackedRedemption = TrackedRedemption.at(tx.logs[2].args._module); + }); + }); + + describe("Make Redemptions", async() => { + + it("Buy some tokens for account_investor1 (1 ETH)", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(30), + true, + { + 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_SecurityToken.mint(account_investor1, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + }); + + it("Buy some tokens for account_investor2 (2 ETH)", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(30), + true, + { + from: account_issuer, + gas: 500000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("Redeem some tokens - fail insufficient allowance", async() => { + await I_GeneralTransferManager.changeAllowAllBurnTransfers(true, {from : token_owner}); + + let errorThrown = false; + try { + let tx = await I_TrackedRedemption.redeemTokens(web3.utils.toWei('1', 'ether'), {from: account_investor1}); + } catch(error) { + console.log(` tx -> failed insufficent allowance`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Redeem some tokens", async() => { + await I_SecurityToken.approve(I_TrackedRedemption.address, web3.utils.toWei('1', 'ether'), {from: account_investor1}); + let tx = await I_TrackedRedemption.redeemTokens(web3.utils.toWei('1', 'ether'), {from: account_investor1}); + console.log(JSON.stringify(tx.logs)); + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Mismatch address"); + assert.equal(tx.logs[0].args._value, web3.utils.toWei('1', 'ether'), "Wrong value"); + }); + + it("Get the init data", async() => { + let tx = await I_TrackedRedemption.getInitFunction.call(); + assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ''),0); + }); + + it("Should get the listed permissions", async() => { + let tx = await I_TrackedRedemption.getPermissions.call(); + assert.equal(tx.length,0); + }); + + describe("Test cases for the TrackedRedemptionFactory", async() => { + it("should get the exact details of the factory", async() => { + assert.equal((await I_TrackedRedemptionFactory.setupCost.call()).toNumber(), 0); + assert.equal(await I_TrackedRedemptionFactory.getType.call(), 5); + assert.equal(web3.utils.toAscii(await I_TrackedRedemptionFactory.getName.call()) + .replace(/\u0000/g, ''), + "TrackedRedemption", + "Wrong Module added"); + assert.equal(await I_TrackedRedemptionFactory.getDescription.call(), + "Track token redemptions", + "Wrong Module added"); + assert.equal(await I_TrackedRedemptionFactory.getTitle.call(), + "Tracked Redemption", + "Wrong Module added"); + assert.equal(await I_TrackedRedemptionFactory.getInstructions.call(), + "Allows an investor to redeem security tokens which are tracked by this module", + "Wrong Module added"); + let tags = await I_TrackedRedemptionFactory.getTags.call(); + assert.equal(tags.length, 2); + + }); + }); + + }); + +}); From 712ec474295c5f6cf1abf00ae71c2e6c450b80d5 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Mon, 1 Oct 2018 15:12:50 +0100 Subject: [PATCH 6/9] Fixes based on Satyam feedback --- contracts/tokens/SecurityToken.sol | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index 219467ece..11eeec231 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -525,10 +525,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @return bool success */ function transfer(address _to, uint256 _value) public returns (bool success) { - _adjustInvestorCount(msg.sender, _to, _value); - require(_verifyTransfer(msg.sender, _to, _value, true), "Transfer is not valid"); - _adjustBalanceCheckpoints(msg.sender); - _adjustBalanceCheckpoints(_to); + require(_updateTransfer(msg.sender, _to, _value), "Transfer is not valid"); require(super.transfer(_to, _value)); return true; } @@ -653,6 +650,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr function _burn(address _from, uint256 _value) internal returns (bool) { require(_value <= balances[_from], "Value too high"); require(_updateTransfer(_from, address(0), _value), "Burn is not valid"); + _adjustTotalSupplyCheckpoints(); balances[_from] = balances[_from].sub(_value); totalSupply_ = totalSupply_.sub(_value); emit Burnt(_from, _value); @@ -676,8 +674,8 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr */ function burnFrom(address _from, uint256 _value) checkGranularity(_value) onlyModule(BURN_KEY) public returns (bool) { require(_value <= allowed[_from][msg.sender]); - require(_burn(_from, _value)); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + require(_burn(_from, _value)); return true; } @@ -713,7 +711,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr } } } - + /** * @notice Gets list of times that checkpoints were created * @return List of checkpoint times @@ -831,6 +829,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr function forceBurn(address _from, uint256 _value, bytes _data) public onlyController returns(bool) { require(_value <= balances[_from], "Value too high"); bool verified = _updateTransfer(_from, address(0), _value); + _adjustTotalSupplyCheckpoints(); balances[_from] = balances[_from].sub(_value); totalSupply_ = totalSupply_.sub(_value); emit ForceBurn(msg.sender, _from, _value, verified, _data); From d348ff8d1ce1b04e916799b84b3847996a79a6a9 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Mon, 1 Oct 2018 15:20:39 +0100 Subject: [PATCH 7/9] Modify require text --- contracts/tokens/SecurityToken.sol | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index 11eeec231..c3d8ca8fb 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -164,12 +164,12 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr } modifier checkGranularity(uint256 _value) { - require(_value % granularity == 0, "Unable to modify token balances at this granularity"); + require(_value % granularity == 0, "Incorrect granularity"); _; } modifier isMintingAllowed() { - require(!mintingFrozen, "Minting is permanently frozen"); + require(!mintingFrozen, "Minting is frozen"); _; } @@ -182,8 +182,8 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @notice Revert if called by account which is not a controller */ modifier onlyController() { - require(msg.sender == controller); - require(!controllerDisabled); + require(msg.sender == controller, "Caller not controller"); + require(!controllerDisabled, "Controller disabled"); _; } @@ -249,14 +249,14 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr uint8 moduleType = moduleFactory.getType(); /* require(modules[moduleType].length < MAX_MODULES, "Limit of MAX MODULES is reached"); */ uint256 moduleCost = moduleFactory.getSetupCost(); - require(moduleCost <= _maxCost, "Max Cost is always be greater than module cost"); + require(moduleCost <= _maxCost, "Module cost too high"); //Approve fee for module - require(ERC20(polyToken).approve(_moduleFactory, moduleCost), "Not able to approve the module cost"); + require(ERC20(polyToken).approve(_moduleFactory, moduleCost), "Insufficient funds for cost"); //Creates instance of module from factory address module = moduleFactory.deploy(_data); require(modulesToData[module].module == address(0), "Module already exists"); //Approve ongoing budget - require(ERC20(polyToken).approve(module, _budget), "Not able to approve the budget"); + require(ERC20(polyToken).approve(module, _budget), "Insufficient funds for budget"); //Add to SecurityToken module map bytes32 moduleName = moduleFactory.getName(); modulesToData[module] = ModuleData(moduleName, module, _moduleFactory, false, moduleType, modules[moduleType].length, names[moduleName].length); @@ -271,7 +271,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _module address of module to archive */ function archiveModule(address _module) external onlyOwner { - require(!modulesToData[_module].isArchived, "Module not unarchived"); + require(!modulesToData[_module].isArchived, "Module already archived"); require(modulesToData[_module].module != address(0), "Module missing"); emit ModuleArchived(modulesToData[_module].moduleType, _module, now); modulesToData[_module].isArchived = true; @@ -282,7 +282,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _module address of module to unarchive */ function unarchiveModule(address _module) external onlyOwner { - require(modulesToData[_module].isArchived, "Module not archived"); + require(modulesToData[_module].isArchived, "Module already unarchived"); emit ModuleUnarchived(modulesToData[_module].moduleType, _module, now); modulesToData[_module].isArchived = false; } @@ -363,7 +363,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _value amount of POLY to withdraw */ function withdrawPoly(uint256 _value) external onlyOwner { - require(ERC20(polyToken).transfer(owner, _value), "In-sufficient balance"); + require(ERC20(polyToken).transfer(owner, _value), "Insufficient balance"); } /** @@ -640,7 +640,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @return success */ function mintMulti(address[] _investors, uint256[] _values) external onlyModuleOrOwner(MINT_KEY) returns (bool success) { - require(_investors.length == _values.length, "Mis-match in the length of the arrays"); + require(_investors.length == _values.length, "Incorrect inputs"); for (uint256 i = 0; i < _investors.length; i++) { mint(_investors[i], _values[i]); } @@ -663,7 +663,7 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _value No. of tokens that get burned */ function burn(uint256 _value) checkGranularity(_value) onlyModule(BURN_KEY) public returns (bool) { - require(_burn(msg.sender, _value)); + require(_burn(msg.sender, _value), "Invalid burn"); return true; } @@ -673,9 +673,9 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr * @param _value No. of tokens that get burned */ function burnFrom(address _from, uint256 _value) checkGranularity(_value) onlyModule(BURN_KEY) public returns (bool) { - require(_value <= allowed[_from][msg.sender]); + require(_value <= allowed[_from][msg.sender], "Value too high"); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); - require(_burn(_from, _value)); + require(_burn(_from, _value), "Invalid burn"); return true; } From c8195a5645bb871e73cb98af07b477ea90c0bec6 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 2 Oct 2018 09:58:52 +0100 Subject: [PATCH 8/9] Update title --- contracts/libraries/KindMath.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/libraries/KindMath.sol b/contracts/libraries/KindMath.sol index 92d77362e..a9cce4546 100644 --- a/contracts/libraries/KindMath.sol +++ b/contracts/libraries/KindMath.sol @@ -3,7 +3,7 @@ pragma solidity ^0.4.24; // Copied from OpenZeppelin and modified to be friendlier /** - * @title SafeMath + * @title KindMath * @dev Math operations with safety checks that throw on error */ library KindMath { From 840fb8f6ae5158e283f260e64a07c9d62b59e562 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 2 Oct 2018 10:23:48 +0100 Subject: [PATCH 9/9] Fix test case based on registerModule change --- test/v_tracked_redemptions.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/test/v_tracked_redemptions.js b/test/v_tracked_redemptions.js index 0f767214f..fe03a8614 100644 --- a/test/v_tracked_redemptions.js +++ b/test/v_tracked_redemptions.js @@ -148,19 +148,6 @@ contract('TrackedRedemption', accounts => { "TrackedRedemptionFactory contract was not deployed" ); - // STEP 5: Register the Modules with the ModuleRegistry contract - - // (A) : Register the GeneralTransferManagerFactory - await I_MRProxied.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); - - // (B) : Register the GeneralDelegateManagerFactory - await I_MRProxied.registerModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); - - // (C) : Register the TrackedRedemptionFactory - await I_MRProxied.registerModule(I_TrackedRedemptionFactory.address, { from: account_polymath }); - await I_MRProxied.verifyModule(I_TrackedRedemptionFactory.address, true, { from: account_polymath }); // Step 6: Deploy the STFactory contract @@ -195,6 +182,20 @@ contract('TrackedRedemption', accounts => { await I_PolymathRegistry.changeAddress("SecurityTokenRegistry", I_SecurityTokenRegistryProxy.address, {from: account_polymath}); await I_MRProxied.updateFromRegistry({from: account_polymath}); + // STEP 5: Register the Modules with the ModuleRegistry contract + + // (A) : Register the GeneralTransferManagerFactory + await I_MRProxied.registerModule(I_GeneralTransferManagerFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralTransferManagerFactory.address, true, { from: account_polymath }); + + // (B) : Register the GeneralDelegateManagerFactory + await I_MRProxied.registerModule(I_GeneralPermissionManagerFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_GeneralPermissionManagerFactory.address, true, { from: account_polymath }); + + // (C) : Register the TrackedRedemptionFactory + await I_MRProxied.registerModule(I_TrackedRedemptionFactory.address, { from: account_polymath }); + await I_MRProxied.verifyModule(I_TrackedRedemptionFactory.address, true, { from: account_polymath }); + // Printing all the contract addresses console.log(` --------------------- Polymath Network Smart Contracts: ---------------------