diff --git a/CHANGELOG.md b/CHANGELOG.md index 57f78ee85..ecd6cb743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,10 @@ All notable changes to this project will be documented in this file. * `takeFee()` new function introduced to extract the POLY token from the factory. It only be called by the owner of the factory. * Added ability for issuer to provide a signed piece of data to allow investors to whitelist themselves. * `_securityTokenAddress` get indexed in the `LogNewSecurityToken` event. -* Now each investor have its `expiryTime` for the KYC. After the expiryTime limit reached, investor will not abe to use transfer related functions. +* Now each investor have its `expiryTime` for the KYC. After the expiryTime limit reached, investor will not abe to use transfer related functions. +* Transfers of tokens gets paused at the level of all TM as well as on the ST level. To facilitate this 3 functions get added namely +`pause()`, `unpause()`,`freezeTransfers()`. All 3 functions are called by the issuer of the securityToken only. +* Security token has got a new feature of burning the tokens, To use this feature user need to call the `burn()` function but before that issuer need to deploy the `TokenBurner` contract and set its address into the SecurityToken contract using the function `setTokenBurner()`. ## Remove diff --git a/contracts/helpers/TokenBurner.sol b/contracts/helpers/TokenBurner.sol new file mode 100644 index 000000000..dba8313df --- /dev/null +++ b/contracts/helpers/TokenBurner.sol @@ -0,0 +1,21 @@ +pragma solidity ^0.4.23; + +import "../interfaces/ISecurityToken.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; + +contract TokenBurner { + + address public securityToken; + + constructor (address _securityToken) { + securityToken = _securityToken; + } + + function burn(address _burner, uint256 _value) public returns(bool) { + require(msg.sender == securityToken); + // Add the schematics for the burner( token holder) that backing the burning of the securities + return true; + } + + +} \ No newline at end of file diff --git a/contracts/interfaces/IST20.sol b/contracts/interfaces/IST20.sol index de367d14d..18f34f13f 100644 --- a/contracts/interfaces/IST20.sol +++ b/contracts/interfaces/IST20.sol @@ -9,6 +9,9 @@ contract IST20 { //transfer, transferFrom must respect use respect the result of verifyTransfer function verifyTransfer(address _from, address _to, uint256 _amount) public view returns (bool success); - //used to create tokens + // used to create tokens function mint(address _investor, uint256 _amount) public returns (bool success); + + // used to burn the tokens + function burn(uint256 _value) public; } diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index 8d486d081..a711f8e15 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -11,7 +11,7 @@ import "../interfaces/IST20.sol"; import "../modules/TransferManager/ITransferManager.sol"; import "../modules/PermissionManager/IPermissionManager.sol"; import "../interfaces/ISecurityTokenRegistry.sol"; - +import "../helpers/TokenBurner.sol"; /** * @title SecurityToken @@ -25,6 +25,10 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { using SafeMath for uint256; bytes32 public securityTokenVersion = "0.0.1"; + + // Reference to token burner contract + TokenBurner public tokenBurner; + // Use to halt all the transactions bool public freeze = false; // Reference to the POLY token. @@ -53,10 +57,12 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { uint256 _timestamp ); + event LogUpdateTokenDetails(bytes32 _oldDetails, bytes32 _newDetails); event LogGranularityChanged(uint256 _oldGranularity, uint256 _newGranularity); event LogModuleRemoved(uint8 indexed _type, address _module, uint256 _timestamp); event LogModuleBudgetChanged(uint8 indexed _moduleType, address _module, uint256 _budget); - event Mint(address indexed to, uint256 amount); + event Minted(address indexed to, uint256 amount); + event Burnt(address indexed _burner, uint256 _value); event LogFreezeTransfers(bool _freeze, uint256 _timestamp); //if _fallback is true, then we only allow the module if it is set, if it is not set we only allow the owner @@ -216,6 +222,15 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { emit LogModuleBudgetChanged(_moduleType, modules[_moduleType][_moduleIndex].moduleAddress, _budget); } + /** + * @dev change the tokenDetails + */ + function updateTokenDetails(bytes32 _newTokenDetails) public onlyOwner { + bytes32 _oldTokenDetails = tokenDetails; + tokenDetails = _newTokenDetails; + emit LogUpdateTokenDetails(_oldTokenDetails, tokenDetails); + } + /** * @dev allows owner to change token granularity */ @@ -283,7 +298,7 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { require(verifyTransfer(address(0), _investor, _amount), "Transfer is not valid"); totalSupply_ = totalSupply_.add(_amount); balances[_investor] = balances[_investor].add(_amount); - emit Mint(_investor, _amount); + emit Minted(_investor, _amount); emit Transfer(address(0), _investor, _amount); return true; } @@ -302,4 +317,21 @@ contract SecurityToken is ISecurityToken, StandardToken, DetailedERC20 { } } } + + function setTokenBurner(address _tokenBurner) public onlyOwner { + tokenBurner = TokenBurner(_tokenBurner); + } + + function burn(uint256 _value) checkGranularity(_value) public { + require(tokenBurner != address(0), "Token Burner contract address is not set yet"); + require(_value <= balances[msg.sender], "Value should no be greater than the balance of msg.sender"); + // 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[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); + } } diff --git a/test/security_token.js b/test/security_token.js index 4f4255532..2fd0ddc8f 100644 --- a/test/security_token.js +++ b/test/security_token.js @@ -15,6 +15,7 @@ const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); const PolyToken = artifacts.require('./PolyToken.sol'); const PolyTokenFaucet = artifacts.require('./helpers/contracts/PolyTokenFaucet.sol'); +const TokenBurner = artifacts.require('./TokenBurner.sol'); const Web3 = require('web3'); const BigNumber = require('bignumber.js'); @@ -56,6 +57,7 @@ contract('SecurityToken', accounts => { let I_SecurityToken; let I_CappedSTO; let I_PolyToken; + let I_TokenBurner; // SecurityToken Details (Launched ST on the behalf of the issuer) const swarmHash = "dagwrgwgvwergwrvwrg"; @@ -684,6 +686,32 @@ contract('SecurityToken', accounts => { console.log(await I_SecurityToken.balanceOf(account_investor1)); 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() => { + // Deploy the token burner contract + I_TokenBurner = await TokenBurner.new(I_SecurityToken.address, { from: token_owner }); + + let errorThrown = false; + try { + await I_SecurityToken.burn(web3.utils.toWei('1', 'ether'),{ from: account_temp }); + } catch(error) { + console.log('failed in calling burn function because token burner contract is not set'); + errorThrown = true; + ensureException(error); + } + assert.ok(errorThrown, message); + }); + + it("Should burn the tokens", 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 tx = await I_SecurityToken.burn(web3.utils.toWei('1', 'ether'),{ from: account_temp }); + assert.equal(tx.logs[0].args._value, web3.utils.toWei('1', 'ether')); + }); }); }); \ No newline at end of file