From 213b318984176aedecc31e624c88c13cad913f45 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Tue, 2 Oct 2018 10:40:21 +0530 Subject: [PATCH 1/6] Added name to dividend struct --- .../modules/Checkpoint/DividendCheckpoint.sol | 453 +++++++++--------- .../Checkpoint/ERC20DividendCheckpoint.sol | 362 ++++++++------ .../ERC20DividendCheckpointFactory.sol | 200 ++++---- .../Checkpoint/EtherDividendCheckpoint.sol | 329 +++++++------ .../EtherDividendCheckpointFactory.sol | 200 ++++---- contracts/modules/Checkpoint/ICheckpoint.sol | 16 +- test/e_erc20_dividends.js | 26 +- test/f_ether_dividends.js | 24 +- 8 files changed, 849 insertions(+), 761 deletions(-) diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol index d751e02fe..3842bf5df 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -1,226 +1,227 @@ -pragma solidity ^0.4.24; - -import "./ICheckpoint.sol"; -import "../Module.sol"; -import "../../interfaces/ISecurityToken.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -import "openzeppelin-solidity/contracts/math/Math.sol"; - -/** - * @title Checkpoint module for issuing ether dividends - * @dev abstract contract - */ -contract DividendCheckpoint is ICheckpoint, Module { - using SafeMath for uint256; - - uint256 public EXCLUDED_ADDRESS_LIMIT = 50; - bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; - - struct Dividend { - uint256 checkpointId; - uint256 created; // Time at which the dividend was created - uint256 maturity; // Time after which dividend can be claimed - set to 0 to bypass - uint256 expiry; // Time until which dividend can be claimed - after this time any remaining amount can be withdrawn by issuer - set to very high value to bypass - uint256 amount; // Dividend amount in WEI - uint256 claimedAmount; // Amount of dividend claimed so far - uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) - bool reclaimed; // True if expiry has passed and issuer has reclaimed remaining dividend - uint256 dividendWithheld; - uint256 dividendWithheldReclaimed; - mapping (address => bool) claimed; // List of addresses which have claimed dividend - mapping (address => bool) dividendExcluded; // List of addresses which cannot claim dividends - } - - // List of all dividends - Dividend[] public dividends; - - // List of addresses which cannot claim dividends - address[] public excluded; - - // Mapping from address to withholding tax as a percentage * 10**16 - mapping (address => uint256) public withholdingTax; - - // Total amount of ETH withheld per investor - mapping (address => uint256) public investorWithheld; - - event SetDefaultExcludedAddresses(address[] _excluded, uint256 _timestamp); - event SetWithholding(address[] _investors, uint256[] _withholding, uint256 _timestamp); - event SetWithholdingFixed(address[] _investors, uint256 _withholding, uint256 _timestamp); - - modifier validDividendIndex(uint256 _dividendIndex) { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - require(now >= dividends[_dividendIndex].maturity, "Dividend maturity is in the future"); - require(now < dividends[_dividendIndex].expiry, "Dividend expiry is in the past"); - require(!dividends[_dividendIndex].reclaimed, "Dividend has been reclaimed by issuer"); - _; - } - - /** - * @notice Init function i.e generalise function to maintain the structure of the module contract - * @return bytes4 - */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(0); - } - - /** - * @notice Return the default excluded addresses - * @return List of excluded addresses - */ - function getDefaultExcluded() external view returns (address[]) { - return excluded; - } - - /** - * @notice Function to clear and set list of excluded addresses used for future dividends - * @param _excluded addresses of investor - */ - function setDefaultExcluded(address[] _excluded) public onlyOwner { - require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses"); - excluded = _excluded; - emit SetDefaultExcludedAddresses(excluded, now); - } - - /** - * @notice Function to set withholding tax rates for investors - * @param _investors addresses of investor - * @param _withholding withholding tax for individual investors (multiplied by 10**16) - */ - function setWithholding(address[] _investors, uint256[] _withholding) public onlyOwner { - require(_investors.length == _withholding.length, "Mismatched input lengths"); - emit SetWithholding(_investors, _withholding, now); - for (uint256 i = 0; i < _investors.length; i++) { - require(_withholding[i] <= 10**18, "Incorrect withholding tax"); - withholdingTax[_investors[i]] = _withholding[i]; - } - } - - /** - * @notice Function to set withholding tax rates for investors - * @param _investors addresses of investor - * @param _withholding withholding tax for all investors (multiplied by 10**16) - */ - function setWithholdingFixed(address[] _investors, uint256 _withholding) public onlyOwner { - require(_withholding <= 10**18, "Incorrect withholding tax"); - emit SetWithholdingFixed(_investors, _withholding, now); - for (uint256 i = 0; i < _investors.length; i++) { - withholdingTax[_investors[i]] = _withholding; - } - } - - /** - * @notice Issuer can push dividends to provided addresses - * @param _dividendIndex Dividend to push - * @param _payees Addresses to which to push the dividend - */ - function pushDividendPaymentToAddresses(uint256 _dividendIndex, address[] _payees) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { - Dividend storage dividend = dividends[_dividendIndex]; - for (uint256 i = 0; i < _payees.length; i++) { - if ((!dividend.claimed[_payees[i]]) && (!dividend.dividendExcluded[_payees[i]])) { - _payDividend(_payees[i], dividend, _dividendIndex); - } - } - } - - /** - * @notice Issuer can push dividends using the investor list from the security token - * @param _dividendIndex Dividend to push - * @param _start Index in investor list at which to start pushing dividends - * @param _iterations Number of addresses to push dividends for - */ - function pushDividendPayment(uint256 _dividendIndex, uint256 _start, uint256 _iterations) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { - Dividend storage dividend = dividends[_dividendIndex]; - uint256 numberInvestors = ISecurityToken(securityToken).getInvestorsLength(); - for (uint256 i = _start; i < Math.min256(numberInvestors, _start.add(_iterations)); i++) { - address payee = ISecurityToken(securityToken).investors(i); - if ((!dividend.claimed[payee]) && (!dividend.dividendExcluded[payee])) { - _payDividend(payee, dividend, _dividendIndex); - } - } - } - - /** - * @notice Investors can pull their own dividends - * @param _dividendIndex Dividend to pull - */ - function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) - { - Dividend storage dividend = dividends[_dividendIndex]; - require(!dividend.claimed[msg.sender], "Dividend already claimed by msg.sender"); - require(!dividend.dividendExcluded[msg.sender], "msg.sender excluded from Dividend"); - _payDividend(msg.sender, dividend, _dividendIndex); - } - - /** - * @notice Internal function for paying dividends - * @param _payee address of investor - * @param _dividend storage with previously issued dividends - * @param _dividendIndex Dividend to pay - */ - function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal; - - /** - * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends - * @param _dividendIndex Dividend to reclaim - */ - function reclaimDividend(uint256 _dividendIndex) external; - - /** - * @notice Calculate amount of dividends claimable - * @param _dividendIndex Dividend to calculate - * @param _payee Affected investor address - * @return claim, withheld amounts - */ - function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - Dividend storage dividend = dividends[_dividendIndex]; - if (dividend.claimed[_payee] || dividend.dividendExcluded[_payee]) { - return (0, 0); - } - uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); - uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); - uint256 withheld = claim.mul(withholdingTax[_payee]).div(uint256(10**18)); - return (claim, withheld); - } - - /** - * @notice Get the index according to the checkpoint id - * @param _checkpointId Checkpoint id to query - * @return uint256[] - */ - function getDividendIndex(uint256 _checkpointId) public view returns(uint256[]) { - uint256 counter = 0; - for(uint256 i = 0; i < dividends.length; i++) { - if (dividends[i].checkpointId == _checkpointId) { - counter++; - } - } - - uint256[] memory index = new uint256[](counter); - counter = 0; - for(uint256 j = 0; j < dividends.length; j++) { - if (dividends[j].checkpointId == _checkpointId) { - index[counter] = j; - counter++; - } - } - return index; - } - - /** - * @notice Allows issuer to withdraw withheld tax - * @param _dividendIndex Dividend to withdraw from - */ - function withdrawWithholding(uint256 _dividendIndex) external; - - /** - * @notice Return the permissions flag that are associated with STO - * @return bytes32 array - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = DISTRIBUTE; - return allPermissions; - } - -} +pragma solidity ^0.4.24; + +import "./ICheckpoint.sol"; +import "../Module.sol"; +import "../../interfaces/ISecurityToken.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/math/Math.sol"; + +/** + * @title Checkpoint module for issuing ether dividends + * @dev abstract contract + */ +contract DividendCheckpoint is ICheckpoint, Module { + using SafeMath for uint256; + + uint256 public EXCLUDED_ADDRESS_LIMIT = 50; + bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; + + struct Dividend { + uint256 checkpointId; + uint256 created; // Time at which the dividend was created + uint256 maturity; // Time after which dividend can be claimed - set to 0 to bypass + uint256 expiry; // Time until which dividend can be claimed - after this time any remaining amount can be withdrawn by issuer - set to very high value to bypass + uint256 amount; // Dividend amount in WEI + uint256 claimedAmount; // Amount of dividend claimed so far + uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) + bool reclaimed; // True if expiry has passed and issuer has reclaimed remaining dividend + uint256 dividendWithheld; + uint256 dividendWithheldReclaimed; + mapping (address => bool) claimed; // List of addresses which have claimed dividend + mapping (address => bool) dividendExcluded; // List of addresses which cannot claim dividends + string name; // Name/title - used for identification + } + + // List of all dividends + Dividend[] public dividends; + + // List of addresses which cannot claim dividends + address[] public excluded; + + // Mapping from address to withholding tax as a percentage * 10**16 + mapping (address => uint256) public withholdingTax; + + // Total amount of ETH withheld per investor + mapping (address => uint256) public investorWithheld; + + event SetDefaultExcludedAddresses(address[] _excluded, uint256 _timestamp); + event SetWithholding(address[] _investors, uint256[] _withholding, uint256 _timestamp); + event SetWithholdingFixed(address[] _investors, uint256 _withholding, uint256 _timestamp); + + modifier validDividendIndex(uint256 _dividendIndex) { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + require(now >= dividends[_dividendIndex].maturity, "Dividend maturity is in the future"); + require(now < dividends[_dividendIndex].expiry, "Dividend expiry is in the past"); + require(!dividends[_dividendIndex].reclaimed, "Dividend has been reclaimed by issuer"); + _; + } + + /** + * @notice Init function i.e generalise function to maintain the structure of the module contract + * @return bytes4 + */ + function getInitFunction() public pure returns (bytes4) { + return bytes4(0); + } + + /** + * @notice Return the default excluded addresses + * @return List of excluded addresses + */ + function getDefaultExcluded() external view returns (address[]) { + return excluded; + } + + /** + * @notice Function to clear and set list of excluded addresses used for future dividends + * @param _excluded addresses of investor + */ + function setDefaultExcluded(address[] _excluded) public onlyOwner { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses"); + excluded = _excluded; + emit SetDefaultExcludedAddresses(excluded, now); + } + + /** + * @notice Function to set withholding tax rates for investors + * @param _investors addresses of investor + * @param _withholding withholding tax for individual investors (multiplied by 10**16) + */ + function setWithholding(address[] _investors, uint256[] _withholding) public onlyOwner { + require(_investors.length == _withholding.length, "Mismatched input lengths"); + emit SetWithholding(_investors, _withholding, now); + for (uint256 i = 0; i < _investors.length; i++) { + require(_withholding[i] <= 10**18, "Incorrect withholding tax"); + withholdingTax[_investors[i]] = _withholding[i]; + } + } + + /** + * @notice Function to set withholding tax rates for investors + * @param _investors addresses of investor + * @param _withholding withholding tax for all investors (multiplied by 10**16) + */ + function setWithholdingFixed(address[] _investors, uint256 _withholding) public onlyOwner { + require(_withholding <= 10**18, "Incorrect withholding tax"); + emit SetWithholdingFixed(_investors, _withholding, now); + for (uint256 i = 0; i < _investors.length; i++) { + withholdingTax[_investors[i]] = _withholding; + } + } + + /** + * @notice Issuer can push dividends to provided addresses + * @param _dividendIndex Dividend to push + * @param _payees Addresses to which to push the dividend + */ + function pushDividendPaymentToAddresses(uint256 _dividendIndex, address[] _payees) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { + Dividend storage dividend = dividends[_dividendIndex]; + for (uint256 i = 0; i < _payees.length; i++) { + if ((!dividend.claimed[_payees[i]]) && (!dividend.dividendExcluded[_payees[i]])) { + _payDividend(_payees[i], dividend, _dividendIndex); + } + } + } + + /** + * @notice Issuer can push dividends using the investor list from the security token + * @param _dividendIndex Dividend to push + * @param _start Index in investor list at which to start pushing dividends + * @param _iterations Number of addresses to push dividends for + */ + function pushDividendPayment(uint256 _dividendIndex, uint256 _start, uint256 _iterations) public withPerm(DISTRIBUTE) validDividendIndex(_dividendIndex) { + Dividend storage dividend = dividends[_dividendIndex]; + uint256 numberInvestors = ISecurityToken(securityToken).getInvestorsLength(); + for (uint256 i = _start; i < Math.min256(numberInvestors, _start.add(_iterations)); i++) { + address payee = ISecurityToken(securityToken).investors(i); + if ((!dividend.claimed[payee]) && (!dividend.dividendExcluded[payee])) { + _payDividend(payee, dividend, _dividendIndex); + } + } + } + + /** + * @notice Investors can pull their own dividends + * @param _dividendIndex Dividend to pull + */ + function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) + { + Dividend storage dividend = dividends[_dividendIndex]; + require(!dividend.claimed[msg.sender], "Dividend already claimed by msg.sender"); + require(!dividend.dividendExcluded[msg.sender], "msg.sender excluded from Dividend"); + _payDividend(msg.sender, dividend, _dividendIndex); + } + + /** + * @notice Internal function for paying dividends + * @param _payee address of investor + * @param _dividend storage with previously issued dividends + * @param _dividendIndex Dividend to pay + */ + function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal; + + /** + * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends + * @param _dividendIndex Dividend to reclaim + */ + function reclaimDividend(uint256 _dividendIndex) external; + + /** + * @notice Calculate amount of dividends claimable + * @param _dividendIndex Dividend to calculate + * @param _payee Affected investor address + * @return claim, withheld amounts + */ + function calculateDividend(uint256 _dividendIndex, address _payee) public view returns(uint256, uint256) { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + Dividend storage dividend = dividends[_dividendIndex]; + if (dividend.claimed[_payee] || dividend.dividendExcluded[_payee]) { + return (0, 0); + } + uint256 balance = ISecurityToken(securityToken).balanceOfAt(_payee, dividend.checkpointId); + uint256 claim = balance.mul(dividend.amount).div(dividend.totalSupply); + uint256 withheld = claim.mul(withholdingTax[_payee]).div(uint256(10**18)); + return (claim, withheld); + } + + /** + * @notice Get the index according to the checkpoint id + * @param _checkpointId Checkpoint id to query + * @return uint256[] + */ + function getDividendIndex(uint256 _checkpointId) public view returns(uint256[]) { + uint256 counter = 0; + for(uint256 i = 0; i < dividends.length; i++) { + if (dividends[i].checkpointId == _checkpointId) { + counter++; + } + } + + uint256[] memory index = new uint256[](counter); + counter = 0; + for(uint256 j = 0; j < dividends.length; j++) { + if (dividends[j].checkpointId == _checkpointId) { + index[counter] = j; + counter++; + } + } + return index; + } + + /** + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from + */ + function withdrawWithholding(uint256 _dividendIndex) external; + + /** + * @notice Return the permissions flag that are associated with STO + * @return bytes32 array + */ + function getPermissions() public view returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = DISTRIBUTE; + return allPermissions; + } + +} diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index ad61b24a8..703a81128 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -1,157 +1,205 @@ -pragma solidity ^0.4.24; - -import "./DividendCheckpoint.sol"; -import "../../interfaces/IERC20.sol"; - -/** - * @title Checkpoint module for issuing ERC20 dividends - */ -contract ERC20DividendCheckpoint is DividendCheckpoint { - using SafeMath for uint256; - - // Mapping to token address for each dividend - mapping (uint256 => address) public dividendTokens; - - event ERC20DividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, address indexed _token, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex); - event ERC20DividendClaimed(address indexed _payee, uint256 _dividendIndex, address indexed _token, uint256 _amount, uint256 _withheld); - event ERC20DividendReclaimed(address indexed _claimer, uint256 _dividendIndex, address indexed _token, uint256 _claimedAmount); - event ERC20DividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, address indexed _token, uint256 _withheldAmount); - - /** - * @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 Creates a dividend and checkpoint for the dividend - * @param _maturity Time from which dividend can be paid - * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer - * @param _token Address of ERC20 token in which dividend is to be denominated - * @param _amount Amount of specified token for dividend - */ - function createDividend(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount) external onlyOwner { - createDividendWithExclusions(_maturity, _expiry, _token, _amount, excluded); - } - - /** - * @notice Creates a dividend with a provided checkpoint - * @param _maturity Time from which dividend can be paid - * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer - * @param _token Address of ERC20 token in which dividend is to be denominated - * @param _amount Amount of specified token for dividend - * @param _checkpointId Checkpoint id from which to create dividends - */ - function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId) external onlyOwner { - createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, excluded); - } - - /** - * @notice Creates a dividend and checkpoint for the dividend - * @param _maturity Time from which dividend can be paid - * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer - * @param _token Address of ERC20 token in which dividend is to be denominated - * @param _amount Amount of specified token for dividend - * @param _excluded List of addresses to exclude - */ - function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, address[] _excluded) public onlyOwner { - uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, checkpointId, _excluded); - } - - /** - * @notice Creates a dividend with a provided checkpoint - * @param _maturity Time from which dividend can be paid - * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer - * @param _token Address of ERC20 token in which dividend is to be denominated - * @param _amount Amount of specified token for dividend - * @param _checkpointId Checkpoint id from which to create dividends - * @param _excluded List of addresses to exclude - */ - function createDividendWithCheckpointAndExclusions(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId, address[] _excluded) public onlyOwner { - require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); - require(_expiry > _maturity, "Expiry is before maturity"); - require(_expiry > now, "Expiry is in the past"); - require(_amount > 0, "No dividend sent"); - require(_token != address(0), "0x not valid token"); - require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId(), "Invalid checkpoint"); - require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "Unable to transfer tokens for dividend"); - uint256 dividendIndex = dividends.length; - uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); - uint256 excludedSupply = 0; - for (uint256 i = 0; i < _excluded.length; i++) { - excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[i], _checkpointId)); - } - dividends.push( - Dividend( - _checkpointId, - now, - _maturity, - _expiry, - _amount, - 0, - currentSupply.sub(excludedSupply), - false, - 0, - 0 - ) - ); - for (uint256 j = 0; j < _excluded.length; j++) { - dividends[dividends.length - 1].dividendExcluded[_excluded[j]] = true; - } - dividendTokens[dividendIndex] = _token; - emit ERC20DividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex); - } - - /** - * @notice Internal function for paying dividends - * @param _payee address of investor - * @param _dividend storage with previously issued dividends - * @param _dividendIndex Dividend to pay - */ - function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { - (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); - _dividend.claimed[_payee] = true; - _dividend.claimedAmount = claim.add(_dividend.claimedAmount); - uint256 claimAfterWithheld = claim.sub(withheld); - if (claimAfterWithheld > 0) { - require(IERC20(dividendTokens[_dividendIndex]).transfer(_payee, claimAfterWithheld), "Unable to transfer tokens"); - _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); - investorWithheld[_payee] = investorWithheld[_payee].add(withheld); - emit ERC20DividendClaimed(_payee, _dividendIndex, dividendTokens[_dividendIndex], claim, withheld); - } - } - - /** - * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends - * @param _dividendIndex Dividend to reclaim - */ - function reclaimDividend(uint256 _dividendIndex) external onlyOwner { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future"); - require(!dividends[_dividendIndex].reclaimed, "Dividend already claimed"); - dividends[_dividendIndex].reclaimed = true; - Dividend storage dividend = dividends[_dividendIndex]; - uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); - require(IERC20(dividendTokens[_dividendIndex]).transfer(msg.sender, remainingAmount), "Unable to transfer tokens"); - emit ERC20DividendReclaimed(msg.sender, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount); - } - - /** - * @notice Allows issuer to withdraw withheld tax - * @param _dividendIndex Dividend to withdraw from - */ - function withdrawWithholding(uint256 _dividendIndex) external onlyOwner { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - Dividend storage dividend = dividends[_dividendIndex]; - uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); - dividend.dividendWithheldReclaimed = dividend.dividendWithheld; - require(IERC20(dividendTokens[_dividendIndex]).transfer(msg.sender, remainingWithheld), "Unable to transfer tokens"); - emit ERC20DividendWithholdingWithdrawn(msg.sender, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld); - } - -} +pragma solidity ^0.4.24; + +import "./DividendCheckpoint.sol"; +import "../../interfaces/IERC20.sol"; + +/** + * @title Checkpoint module for issuing ERC20 dividends + */ +contract ERC20DividendCheckpoint is DividendCheckpoint { + using SafeMath for uint256; + + // Mapping to token address for each dividend + mapping (uint256 => address) public dividendTokens; + + event ERC20DividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, address indexed _token, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex, string _name); + event ERC20DividendClaimed(address indexed _payee, uint256 _dividendIndex, address indexed _token, uint256 _amount, uint256 _withheld); + event ERC20DividendReclaimed(address indexed _claimer, uint256 _dividendIndex, address indexed _token, uint256 _claimedAmount); + event ERC20DividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, address indexed _token, uint256 _withheldAmount); + + /** + * @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 Creates a dividend and checkpoint for the dividend + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _token Address of ERC20 token in which dividend is to be denominated + * @param _amount Amount of specified token for dividend + * @param _name name/title for identification + */ + function createDividend(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, string _name) external onlyOwner { + createDividendWithExclusions(_maturity, _expiry, _token, _amount, excluded, _name); + } + + /** + * @notice Creates a dividend with a provided checkpoint + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _token Address of ERC20 token in which dividend is to be denominated + * @param _amount Amount of specified token for dividend + * @param _checkpointId Checkpoint id from which to create dividends + * @param _name name/title for identification + */ + function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId, string _name) external onlyOwner { + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, excluded, _name); + } + + /** + * @notice Creates a dividend and checkpoint for the dividend + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _token Address of ERC20 token in which dividend is to be denominated + * @param _amount Amount of specified token for dividend + * @param _excluded List of addresses to exclude + * @param _name name/title for identification + */ + function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, address[] _excluded, string _name) public onlyOwner { + uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, checkpointId, _excluded, _name); + } + + /** + * @notice Creates a dividend with a provided checkpoint + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _token Address of ERC20 token in which dividend is to be denominated + * @param _amount Amount of specified token for dividend + * @param _checkpointId Checkpoint id from which to create dividends + * @param _excluded List of addresses to exclude + * @param _name name/title for identification + */ + function createDividendWithCheckpointAndExclusions( + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + uint256 _checkpointId, + address[] _excluded, + string _name + ) + public + onlyOwner + { + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, _excluded, _name); + } + + /** + * @notice Creates a dividend with a provided checkpoint + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _token Address of ERC20 token in which dividend is to be denominated + * @param _amount Amount of specified token for dividend + * @param _checkpointId Checkpoint id from which to create dividends + * @param _excluded List of addresses to exclude + * @param _name name/title for identification + */ + function _createDividendWithCheckpointAndExclusions( + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + uint256 _checkpointId, + address[] _excluded, + string _name + ) + internal + { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); + require(_expiry > _maturity, "Expiry is before maturity"); + require(_expiry > now, "Expiry is in the past"); + require(_amount > 0, "No dividend sent"); + require(_token != address(0), "0x not valid token"); + require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId(), "Invalid checkpoint"); + require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "Unable to transfer tokens for dividend"); + uint256 dividendIndex = dividends.length; + uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); + uint256 excludedSupply = 0; + for (uint256 i = 0; i < _excluded.length; i++) { + excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[i], _checkpointId)); + } + dividends.push( + Dividend( + _checkpointId, + now, + _maturity, + _expiry, + _amount, + 0, + currentSupply.sub(excludedSupply), + false, + 0, + 0, + _name + ) + ); + for (uint256 j = 0; j < _excluded.length; j++) { + dividends[dividends.length - 1].dividendExcluded[_excluded[j]] = true; + } + dividendTokens[dividendIndex] = _token; + _CallERC20DividendDepositedEvent(_checkpointId, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); + } + + /** + * @notice emits the ERC20DividendDeposited event. + * Seperated into a different function to work around the Stack too deep error + */ + function _CallERC20DividendDepositedEvent(uint256 _checkpointId, uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 currentSupply, uint256 dividendIndex, string _name) internal { + emit ERC20DividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); + } + + /** + * @notice Internal function for paying dividends + * @param _payee address of investor + * @param _dividend storage with previously issued dividends + * @param _dividendIndex Dividend to pay + */ + function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { + (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); + _dividend.claimed[_payee] = true; + _dividend.claimedAmount = claim.add(_dividend.claimedAmount); + uint256 claimAfterWithheld = claim.sub(withheld); + if (claimAfterWithheld > 0) { + require(IERC20(dividendTokens[_dividendIndex]).transfer(_payee, claimAfterWithheld), "Unable to transfer tokens"); + _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); + investorWithheld[_payee] = investorWithheld[_payee].add(withheld); + emit ERC20DividendClaimed(_payee, _dividendIndex, dividendTokens[_dividendIndex], claim, withheld); + } + } + + /** + * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends + * @param _dividendIndex Dividend to reclaim + */ + function reclaimDividend(uint256 _dividendIndex) external onlyOwner { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future"); + require(!dividends[_dividendIndex].reclaimed, "Dividend already claimed"); + dividends[_dividendIndex].reclaimed = true; + Dividend storage dividend = dividends[_dividendIndex]; + uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); + require(IERC20(dividendTokens[_dividendIndex]).transfer(msg.sender, remainingAmount), "Unable to transfer tokens"); + emit ERC20DividendReclaimed(msg.sender, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount); + } + + /** + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from + */ + function withdrawWithholding(uint256 _dividendIndex) external onlyOwner { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + Dividend storage dividend = dividends[_dividendIndex]; + uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); + dividend.dividendWithheldReclaimed = dividend.dividendWithheld; + require(IERC20(dividendTokens[_dividendIndex]).transfer(msg.sender, remainingWithheld), "Unable to transfer tokens"); + emit ERC20DividendWithholdingWithdrawn(msg.sender, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld); + } + +} diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol index d789ad5e3..f71f601c6 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol @@ -1,100 +1,100 @@ -pragma solidity ^0.4.24; - -import "./ERC20DividendCheckpoint.sol"; -import "../ModuleFactory.sol"; - -/** - * @title Factory for deploying ERC20DividendCheckpoint module - */ -contract ERC20DividendCheckpointFactory is ModuleFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - * @param _setupCost Setup cost of the module - * @param _usageCost Usage cost of the module - * @param _subscriptionCost Subscription cost of the module - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "1.0.0"; - name = "ERC20DividendCheckpoint"; - title = "ERC20 Dividend Checkpoint"; - description = "Create ERC20 dividends for token holders at a specific checkpoint"; - 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 erc20DividendCheckpoint = new ERC20DividendCheckpoint(msg.sender, address(polyToken)); - emit GenerateModuleFromFactory(erc20DividendCheckpoint, getName(), address(this), msg.sender, setupCost, now); - return erc20DividendCheckpoint; - } - - /** - * @notice Type of the Module factory - */ - function getType() public view returns(uint8) { - return 4; - } - - /** - * @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 title of the Module - */ - function getTitle() public view returns(string) { - return title; - } - - /** - * @notice Get the version of the Module - */ - function getVersion() external view returns(string) { - return version; - } - - /** - * @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 "Create a ERC20 dividend which will be paid out to token holders proportional to their balances at the point the dividend is created"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() public view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](3); - availableTags[0] = "ERC20"; - availableTags[1] = "Dividend"; - availableTags[2] = "Checkpoint"; - return availableTags; - } -} +pragma solidity ^0.4.24; + +import "./ERC20DividendCheckpoint.sol"; +import "../ModuleFactory.sol"; + +/** + * @title Factory for deploying ERC20DividendCheckpoint module + */ +contract ERC20DividendCheckpointFactory is ModuleFactory { + + /** + * @notice Constructor + * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of the module + * @param _usageCost Usage cost of the module + * @param _subscriptionCost Subscription cost of the module + */ + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public + ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + { + version = "1.0.0"; + name = "ERC20DividendCheckpoint"; + title = "ERC20 Dividend Checkpoint"; + description = "Create ERC20 dividends for token holders at a specific checkpoint"; + 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 erc20DividendCheckpoint = new ERC20DividendCheckpoint(msg.sender, address(polyToken)); + emit GenerateModuleFromFactory(erc20DividendCheckpoint, getName(), address(this), msg.sender, setupCost, now); + return erc20DividendCheckpoint; + } + + /** + * @notice Type of the Module factory + */ + function getType() public view returns(uint8) { + return 4; + } + + /** + * @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 title of the Module + */ + function getTitle() public view returns(string) { + return title; + } + + /** + * @notice Get the version of the Module + */ + function getVersion() external view returns(string) { + return version; + } + + /** + * @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 "Create a ERC20 dividend which will be paid out to token holders proportional to their balances at the point the dividend is created"; + } + + /** + * @notice Get the tags related to the module factory + */ + function getTags() public view returns(bytes32[]) { + bytes32[] memory availableTags = new bytes32[](3); + availableTags[0] = "ERC20"; + availableTags[1] = "Dividend"; + availableTags[2] = "Checkpoint"; + return availableTags; + } +} diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index 1d17e1abb..0a20ee7a3 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -1,147 +1,182 @@ -pragma solidity ^0.4.24; - -import "./DividendCheckpoint.sol"; - -/** - * @title Checkpoint module for issuing ether dividends - */ -contract EtherDividendCheckpoint is DividendCheckpoint { - using SafeMath for uint256; - - event EtherDividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex); - event EtherDividendClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); - event EtherDividendReclaimed(address indexed _claimer, uint256 _dividendIndex, uint256 _claimedAmount); - event EtherDividendClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); - event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, uint256 _withheldAmount); - - /** - * @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 Creates a dividend and checkpoint for the dividend, using global list of excluded addresses - * @param _maturity Time from which dividend can be paid - * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer - */ - function createDividend(uint256 _maturity, uint256 _expiry) payable external onlyOwner { - createDividendWithExclusions(_maturity, _expiry, excluded); - } - - /** - * @notice Creates a dividend with a provided checkpoint, using global list of excluded addresses - * @param _maturity Time from which dividend can be paid - * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer - * @param _checkpointId Id of the checkpoint from which to issue dividend - */ - function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId) payable external onlyOwner { - createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, excluded); - } - - /** - * @notice Creates a dividend and checkpoint for the dividend, specifying explicit excluded addresses - * @param _maturity Time from which dividend can be paid - * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer - * @param _excluded List of addresses to exclude - */ - function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address[] _excluded) payable public onlyOwner { - uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - createDividendWithCheckpointAndExclusions(_maturity, _expiry, checkpointId, _excluded); - } - - /** - * @notice Creates a dividend with a provided checkpoint, specifying explicit excluded addresses - * @param _maturity Time from which dividend can be paid - * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer - * @param _checkpointId Id of the checkpoint from which to issue dividend - * @param _excluded List of addresses to exclude - */ - function createDividendWithCheckpointAndExclusions(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, address[] _excluded) payable public onlyOwner { - require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); - require(_expiry > _maturity, "Expiry is before maturity"); - require(_expiry > now, "Expiry is in the past"); - require(msg.value > 0, "No dividend sent"); - require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId()); - uint256 dividendIndex = dividends.length; - uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); - uint256 excludedSupply = 0; - for (uint256 i = 0; i < _excluded.length; i++) { - excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[i], _checkpointId)); - } - dividends.push( - Dividend( - _checkpointId, - now, - _maturity, - _expiry, - msg.value, - 0, - currentSupply.sub(excludedSupply), - false, - 0, - 0 - ) - ); - for (uint256 j = 0; j < _excluded.length; j++) { - dividends[dividends.length - 1].dividendExcluded[_excluded[j]] = true; - } - emit EtherDividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex); - } - - /** - * @notice Internal function for paying dividends - * @param _payee address of investor - * @param _dividend storage with previously issued dividends - * @param _dividendIndex Dividend to pay - */ - function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { - (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); - _dividend.claimed[_payee] = true; - uint256 claimAfterWithheld = claim.sub(withheld); - if (claimAfterWithheld > 0) { - if (_payee.send(claimAfterWithheld)) { - _dividend.claimedAmount = _dividend.claimedAmount.add(claim); - _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); - investorWithheld[_payee] = investorWithheld[_payee].add(withheld); - emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); - } else { - _dividend.claimed[_payee] = false; - emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); - } - } - } - - /** - * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends - * @param _dividendIndex Dividend to reclaim - */ - function reclaimDividend(uint256 _dividendIndex) external onlyOwner { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future"); - require(!dividends[_dividendIndex].reclaimed, "Dividend already claimed"); - Dividend storage dividend = dividends[_dividendIndex]; - dividend.reclaimed = true; - uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); - msg.sender.transfer(remainingAmount); - emit EtherDividendReclaimed(msg.sender, _dividendIndex, remainingAmount); - } - - /** - * @notice Allows issuer to withdraw withheld tax - * @param _dividendIndex Dividend to withdraw from - */ - function withdrawWithholding(uint256 _dividendIndex) external onlyOwner { - require(_dividendIndex < dividends.length, "Incorrect dividend index"); - Dividend storage dividend = dividends[_dividendIndex]; - uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); - dividend.dividendWithheldReclaimed = dividend.dividendWithheld; - msg.sender.transfer(remainingWithheld); - emit EtherDividendWithholdingWithdrawn(msg.sender, _dividendIndex, remainingWithheld); - } - -} +pragma solidity ^0.4.24; + +import "./DividendCheckpoint.sol"; + +/** + * @title Checkpoint module for issuing ether dividends + */ +contract EtherDividendCheckpoint is DividendCheckpoint { + using SafeMath for uint256; + + event EtherDividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex, string _name); + event EtherDividendClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendReclaimed(address indexed _claimer, uint256 _dividendIndex, uint256 _claimedAmount); + event EtherDividendClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, uint256 _withheldAmount); + + /** + * @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 Creates a dividend and checkpoint for the dividend, using global list of excluded addresses + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _name name/title for identification + */ + function createDividend(uint256 _maturity, uint256 _expiry, string _name) payable external onlyOwner { + createDividendWithExclusions(_maturity, _expiry, excluded, _name); + } + + /** + * @notice Creates a dividend with a provided checkpoint, using global list of excluded addresses + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _checkpointId Id of the checkpoint from which to issue dividend + * @param _name name/title for identification + */ + function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, string _name) payable external onlyOwner { + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, excluded, _name); + } + + /** + * @notice Creates a dividend and checkpoint for the dividend, specifying explicit excluded addresses + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _excluded List of addresses to exclude + * @param _name name/title for identification + */ + function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address[] _excluded, string _name) payable public onlyOwner { + uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, checkpointId, _excluded, _name); + } + + /** + * @notice Creates a dividend with a provided checkpoint, specifying explicit excluded addresses + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _checkpointId Id of the checkpoint from which to issue dividend + * @param _excluded List of addresses to exclude + * @param _name name/title for identification + */ + function createDividendWithCheckpointAndExclusions( + uint256 _maturity, + uint256 _expiry, + uint256 _checkpointId, + address[] _excluded, + string _name + ) + payable + public + onlyOwner + { + _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, _excluded, _name); + } + + /** + * @notice Creates a dividend with a provided checkpoint, specifying explicit excluded addresses + * @param _maturity Time from which dividend can be paid + * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer + * @param _checkpointId Id of the checkpoint from which to issue dividend + * @param _excluded List of addresses to exclude + * @param _name name/title for identification + */ + function _createDividendWithCheckpointAndExclusions( + uint256 _maturity, + uint256 _expiry, + uint256 _checkpointId, + address[] _excluded, + string _name + ) + internal + { + require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); + require(_expiry > _maturity, "Expiry is before maturity"); + require(_expiry > now, "Expiry is in the past"); + require(msg.value > 0, "No dividend sent"); + require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId()); + uint256 dividendIndex = dividends.length; + uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); + uint256 excludedSupply = 0; + for (uint256 i = 0; i < _excluded.length; i++) { + excludedSupply = excludedSupply.add(ISecurityToken(securityToken).balanceOfAt(_excluded[i], _checkpointId)); + } + dividends.push( + Dividend( + _checkpointId, + now, + _maturity, + _expiry, + msg.value, + 0, + currentSupply.sub(excludedSupply), + false, + 0, + 0, + _name + ) + ); + for (uint256 j = 0; j < _excluded.length; j++) { + dividends[dividends.length - 1].dividendExcluded[_excluded[j]] = true; + } + emit EtherDividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, msg.value, currentSupply, dividendIndex, _name); + } + + /** + * @notice Internal function for paying dividends + * @param _payee address of investor + * @param _dividend storage with previously issued dividends + * @param _dividendIndex Dividend to pay + */ + function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { + (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); + _dividend.claimed[_payee] = true; + uint256 claimAfterWithheld = claim.sub(withheld); + if (claimAfterWithheld > 0) { + if (_payee.send(claimAfterWithheld)) { + _dividend.claimedAmount = _dividend.claimedAmount.add(claim); + _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); + investorWithheld[_payee] = investorWithheld[_payee].add(withheld); + emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); + } else { + _dividend.claimed[_payee] = false; + emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); + } + } + } + + /** + * @notice Issuer can reclaim remaining unclaimed dividend amounts, for expired dividends + * @param _dividendIndex Dividend to reclaim + */ + function reclaimDividend(uint256 _dividendIndex) external onlyOwner { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + require(now >= dividends[_dividendIndex].expiry, "Dividend expiry is in the future"); + require(!dividends[_dividendIndex].reclaimed, "Dividend already claimed"); + Dividend storage dividend = dividends[_dividendIndex]; + dividend.reclaimed = true; + uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); + msg.sender.transfer(remainingAmount); + emit EtherDividendReclaimed(msg.sender, _dividendIndex, remainingAmount); + } + + /** + * @notice Allows issuer to withdraw withheld tax + * @param _dividendIndex Dividend to withdraw from + */ + function withdrawWithholding(uint256 _dividendIndex) external onlyOwner { + require(_dividendIndex < dividends.length, "Incorrect dividend index"); + Dividend storage dividend = dividends[_dividendIndex]; + uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); + dividend.dividendWithheldReclaimed = dividend.dividendWithheld; + msg.sender.transfer(remainingWithheld); + emit EtherDividendWithholdingWithdrawn(msg.sender, _dividendIndex, remainingWithheld); + } + +} diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol b/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol index 146691d82..d1e8ac50b 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol @@ -1,100 +1,100 @@ -pragma solidity ^0.4.24; - -import "./EtherDividendCheckpoint.sol"; -import "../ModuleFactory.sol"; - -/** - * @title Factory for deploying EtherDividendCheckpoint module - */ -contract EtherDividendCheckpointFactory is ModuleFactory { - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - * @param _setupCost Setup cost of the module - * @param _usageCost Usage cost of the module - * @param _subscriptionCost Subscription cost of the module - */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "1.0.0"; - name = "EtherDividendCheckpoint"; - title = "Ether Dividend Checkpoint"; - description = "Create ETH dividends for token holders at a specific checkpoint"; - 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 ethDividendCheckpoint = new EtherDividendCheckpoint(msg.sender, address(polyToken)); - emit GenerateModuleFromFactory(ethDividendCheckpoint, getName(), address(this), msg.sender, setupCost, now); - return ethDividendCheckpoint; - } - - /** - * @notice Type of the Module factory - */ - function getType() public view returns(uint8) { - return 4; - } - - /** - * @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 title of the Module - */ - function getTitle() public view returns(string) { - return title; - } - - /** - * @notice Get the version of the Module - */ - function getVersion() external view returns(string) { - return version; - } - - /** - * @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 "Create a dividend which will be paid out to token holders proportional to their balances at the point the dividend is created"; - } - - /** - * @notice Get the tags related to the module factory - */ - function getTags() public view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](3); - availableTags[0] = "ETH"; - availableTags[1] = "Checkpoint"; - availableTags[2] = "Dividend"; - return availableTags; - } -} +pragma solidity ^0.4.24; + +import "./EtherDividendCheckpoint.sol"; +import "../ModuleFactory.sol"; + +/** + * @title Factory for deploying EtherDividendCheckpoint module + */ +contract EtherDividendCheckpointFactory is ModuleFactory { + + /** + * @notice Constructor + * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of the module + * @param _usageCost Usage cost of the module + * @param _subscriptionCost Subscription cost of the module + */ + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public + ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + { + version = "1.0.0"; + name = "EtherDividendCheckpoint"; + title = "Ether Dividend Checkpoint"; + description = "Create ETH dividends for token holders at a specific checkpoint"; + 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 ethDividendCheckpoint = new EtherDividendCheckpoint(msg.sender, address(polyToken)); + emit GenerateModuleFromFactory(ethDividendCheckpoint, getName(), address(this), msg.sender, setupCost, now); + return ethDividendCheckpoint; + } + + /** + * @notice Type of the Module factory + */ + function getType() public view returns(uint8) { + return 4; + } + + /** + * @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 title of the Module + */ + function getTitle() public view returns(string) { + return title; + } + + /** + * @notice Get the version of the Module + */ + function getVersion() external view returns(string) { + return version; + } + + /** + * @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 "Create a dividend which will be paid out to token holders proportional to their balances at the point the dividend is created"; + } + + /** + * @notice Get the tags related to the module factory + */ + function getTags() public view returns(bytes32[]) { + bytes32[] memory availableTags = new bytes32[](3); + availableTags[0] = "ETH"; + availableTags[1] = "Checkpoint"; + availableTags[2] = "Dividend"; + return availableTags; + } +} diff --git a/contracts/modules/Checkpoint/ICheckpoint.sol b/contracts/modules/Checkpoint/ICheckpoint.sol index 214093131..3e567ba7b 100644 --- a/contracts/modules/Checkpoint/ICheckpoint.sol +++ b/contracts/modules/Checkpoint/ICheckpoint.sol @@ -1,8 +1,8 @@ -pragma solidity ^0.4.24; - -/** - * @title Interface to be implemented by all checkpoint modules - */ -interface ICheckpoint { - -} +pragma solidity ^0.4.24; + +/** + * @title Interface to be implemented by all checkpoint modules + */ +interface ICheckpoint { + +} diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index 395afbec2..af265518c 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -41,6 +41,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiryTime = toTime + duration.days(15); let message = "Transaction Should Fail!"; + let dividendName = "TestDividend"; // Contract Instance Declaration let I_GeneralPermissionManagerFactory; @@ -364,7 +365,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because allowance = 0`.grey); ensureException(error); @@ -379,7 +380,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() - duration.days(10); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -393,7 +394,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(2); let expiry = latestTime() - duration.days(1); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -407,7 +408,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, 0, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, 0, web3.utils.toWei('1.5', 'ether'), dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because token address is 0x`.grey); ensureException(error); @@ -421,7 +422,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, 0, {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, 0, dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because amount < 0`.grey); ensureException(error); @@ -434,8 +435,9 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), dividendName, {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); + assert.equal(tx.logs[0].args._name.toString(), dividendName, "Dividend name incorrect in event"); }); it("Investor 1 transfers his token balance to investor 2", async() => { @@ -524,7 +526,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), dividendName, {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); }); @@ -589,7 +591,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('11', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('11', 'ether'), {from: token_owner}); - let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('10', 'ether'), {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('10', 'ether'), dividendName, {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); }); @@ -676,7 +678,7 @@ contract('ERC20DividendCheckpoint', accounts => { console.log((await I_SecurityToken.currentCheckpointId()).toNumber()); await I_PolyToken.getTokens(web3.utils.toWei('20', 'ether'), token_owner); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because allowance is not provided`.grey); ensureException(error); @@ -709,7 +711,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(5); let expiry = latestTime() - duration.days(2); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -723,7 +725,7 @@ contract('ERC20DividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(2); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 5, {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 5, dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because checkpoint id > current checkpoint`.grey); ensureException(error); @@ -741,7 +743,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('11', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('11', 'ether'), {from: token_owner}); - let tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, I_PolyToken.address, web3.utils.toWei('10', 'ether'), 4, [account_investor1], {from: token_owner}); + let tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, I_PolyToken.address, web3.utils.toWei('10', 'ether'), 4, [account_investor1], dividendName, {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); }); diff --git a/test/f_ether_dividends.js b/test/f_ether_dividends.js index 00a8852a2..5cab95fce 100644 --- a/test/f_ether_dividends.js +++ b/test/f_ether_dividends.js @@ -41,6 +41,7 @@ contract('EtherDividendCheckpoint', accounts => { let expiryTime = toTime + duration.days(15); let message = "Transaction Should Fail!"; + let dividendName = "TestDividend"; // Contract Instance Declaration let I_GeneralPermissionManagerFactory; @@ -364,7 +365,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because msg.value = 0`.grey); ensureException(error); @@ -378,7 +379,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() - duration.days(10); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -408,8 +409,9 @@ contract('EtherDividendCheckpoint', accounts => { it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); + assert.equal(tx.logs[0].args._name.toString(), dividendName, "Dividend name incorrect in event"); }); it("Investor 1 transfers his token balance to investor 2", async() => { @@ -510,7 +512,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 2"); }); @@ -576,7 +578,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create another new dividend", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 3"); }); @@ -662,7 +664,7 @@ contract('EtherDividendCheckpoint', accounts => { let expiry = latestTime() + duration.days(2); let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: 0}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, dividendName, {from: token_owner, value: 0}); } catch(error) { console.log(` tx -> failed because msg.value is 0`.grey); ensureException(error); @@ -676,7 +678,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() - duration.days(10); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, dividendName, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); @@ -690,7 +692,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(5); let expiry = latestTime() - duration.days(2); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 4, dividendName, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); @@ -704,7 +706,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(2); try { - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, 5, dividendName, {from: token_owner, value: web3.utils.toWei('11', 'ether')}); } catch(error) { console.log(` tx -> failed because checkpoint id > current checkpoint`.grey); ensureException(error); @@ -717,7 +719,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); let tx = await I_SecurityToken.createCheckpoint({from: token_owner}); - tx = await I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, 4, [account_investor1], {from: token_owner, value: web3.utils.toWei('10', 'ether')}); + tx = await I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions(maturity, expiry, 4, [account_investor1], dividendName, {from: token_owner, value: web3.utils.toWei('10', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 4"); }); @@ -890,7 +892,7 @@ contract('EtherDividendCheckpoint', accounts => { it("Create another new dividend", async() => { let maturity = latestTime(); let expiry = latestTime() + duration.days(10); - let tx = await I_EtherDividendCheckpoint.createDividendWithExclusions(maturity, expiry, [], {from: token_owner, value: web3.utils.toWei('12', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividendWithExclusions(maturity, expiry, [], dividendName, {from: token_owner, value: web3.utils.toWei('12', 'ether')}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 6, "Dividend should be created at checkpoint 6"); }); From 9b626d5531c74ea8933fdd840f61c702f1cb71d8 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Tue, 2 Oct 2018 10:57:33 +0530 Subject: [PATCH 2/6] test case typos fixed --- test/e_erc20_dividends.js | 2 +- test/f_ether_dividends.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index af265518c..3530a7fd0 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -695,7 +695,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiry = latestTime() - duration.days(10); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('20', 'ether'), {from: token_owner}); try { - tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, {from: token_owner}); + tx = await I_ERC20DividendCheckpoint.createDividendWithCheckpoint(maturity, expiry, I_PolyToken.address, web3.utils.toWei('20', 'ether'), 4, dividendName, {from: token_owner}); } catch(error) { console.log(` tx -> failed because maturity > expiry`.grey); ensureException(error); diff --git a/test/f_ether_dividends.js b/test/f_ether_dividends.js index 5cab95fce..cb3fad8f6 100644 --- a/test/f_ether_dividends.js +++ b/test/f_ether_dividends.js @@ -393,7 +393,7 @@ contract('EtherDividendCheckpoint', accounts => { let maturity = latestTime() - duration.days(2); let expiry = latestTime() - duration.days(1); try { - let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + let tx = await I_EtherDividendCheckpoint.createDividend(maturity, expiry, dividendName, {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); } catch(error) { console.log(` tx -> failed because now > expiry`.grey); ensureException(error); From 61056d85edea43c21d04e97be234250fb1a023ed Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Tue, 2 Oct 2018 16:25:23 +0530 Subject: [PATCH 3/6] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e0d50da..ca2181f6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. [__1.5.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __15-08-18__ ## Added +* Added `name` field to dividends struct in DividendCheckpoint. #295 * Added `getTagsByType`, `getTagsByTypeAndToken`, `getModulesByType`, `getModulesByTypeAndToken` to MR * Added `getTokensByOwner` to STR * Added withholding tax to ether & erc20 dividends From ec34f027a1b63230e2feeabba0747de7af603f06 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Tue, 2 Oct 2018 18:21:29 +0530 Subject: [PATCH 4/6] Renamed a function Renamed `_CallERC20DividendDepositedEvent` to `_emitERC20DividendDepositedEvent` --- contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index 703a81128..e89e0a0d1 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -144,14 +144,14 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { dividends[dividends.length - 1].dividendExcluded[_excluded[j]] = true; } dividendTokens[dividendIndex] = _token; - _CallERC20DividendDepositedEvent(_checkpointId, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); + _emitERC20DividendDepositedEvent(_checkpointId, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); } /** * @notice emits the ERC20DividendDeposited event. - * Seperated into a different function to work around the Stack too deep error + * Seperated into a different function as a workaround for stack too deep error */ - function _CallERC20DividendDepositedEvent(uint256 _checkpointId, uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 currentSupply, uint256 dividendIndex, string _name) internal { + function _emitERC20DividendDepositedEvent(uint256 _checkpointId, uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 currentSupply, uint256 dividendIndex, string _name) internal { emit ERC20DividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); } From 94495e31ea6839fbbbc948b67821aef79e817301 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Tue, 2 Oct 2018 20:02:19 +0530 Subject: [PATCH 5/6] Name changed from string to bytes32 --- .../modules/Checkpoint/DividendCheckpoint.sol | 2 +- .../Checkpoint/ERC20DividendCheckpoint.sol | 15 ++++---- .../Checkpoint/EtherDividendCheckpoint.sol | 13 +++---- test/e_erc20_dividends.js | 34 ++++++++++++++++++- test/f_ether_dividends.js | 16 ++++++++- 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol index 3842bf5df..fc70a9fde 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -29,7 +29,7 @@ contract DividendCheckpoint is ICheckpoint, Module { uint256 dividendWithheldReclaimed; mapping (address => bool) claimed; // List of addresses which have claimed dividend mapping (address => bool) dividendExcluded; // List of addresses which cannot claim dividends - string name; // Name/title - used for identification + bytes32 name; // Name/title - used for identification } // List of all dividends diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index e89e0a0d1..60d84a41b 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -12,7 +12,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { // Mapping to token address for each dividend mapping (uint256 => address) public dividendTokens; - event ERC20DividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, address indexed _token, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex, string _name); + event ERC20DividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, address indexed _token, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex, bytes32 indexed _name); event ERC20DividendClaimed(address indexed _payee, uint256 _dividendIndex, address indexed _token, uint256 _amount, uint256 _withheld); event ERC20DividendReclaimed(address indexed _claimer, uint256 _dividendIndex, address indexed _token, uint256 _claimedAmount); event ERC20DividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, address indexed _token, uint256 _withheldAmount); @@ -35,7 +35,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { * @param _amount Amount of specified token for dividend * @param _name name/title for identification */ - function createDividend(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, string _name) external onlyOwner { + function createDividend(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, bytes32 _name) external onlyOwner { createDividendWithExclusions(_maturity, _expiry, _token, _amount, excluded, _name); } @@ -48,7 +48,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { * @param _checkpointId Checkpoint id from which to create dividends * @param _name name/title for identification */ - function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId, string _name) external onlyOwner { + function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 _checkpointId, bytes32 _name) external onlyOwner { _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, excluded, _name); } @@ -61,7 +61,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { * @param _excluded List of addresses to exclude * @param _name name/title for identification */ - function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, address[] _excluded, string _name) public onlyOwner { + function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, address[] _excluded, bytes32 _name) public onlyOwner { uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, checkpointId, _excluded, _name); } @@ -83,7 +83,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { uint256 _amount, uint256 _checkpointId, address[] _excluded, - string _name + bytes32 _name ) public onlyOwner @@ -108,7 +108,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { uint256 _amount, uint256 _checkpointId, address[] _excluded, - string _name + bytes32 _name ) internal { @@ -119,6 +119,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { require(_token != address(0), "0x not valid token"); require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId(), "Invalid checkpoint"); require(IERC20(_token).transferFrom(msg.sender, address(this), _amount), "Unable to transfer tokens for dividend"); + require(_name[0] != 0); uint256 dividendIndex = dividends.length; uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); uint256 excludedSupply = 0; @@ -151,7 +152,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { * @notice emits the ERC20DividendDeposited event. * Seperated into a different function as a workaround for stack too deep error */ - function _emitERC20DividendDepositedEvent(uint256 _checkpointId, uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 currentSupply, uint256 dividendIndex, string _name) internal { + function _emitERC20DividendDepositedEvent(uint256 _checkpointId, uint256 _maturity, uint256 _expiry, address _token, uint256 _amount, uint256 currentSupply, uint256 dividendIndex, bytes32 _name) internal { emit ERC20DividendDeposited(msg.sender, _checkpointId, now, _maturity, _expiry, _token, _amount, currentSupply, dividendIndex, _name); } diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index 0a20ee7a3..d8ecb2e9a 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -8,7 +8,7 @@ import "./DividendCheckpoint.sol"; contract EtherDividendCheckpoint is DividendCheckpoint { using SafeMath for uint256; - event EtherDividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex, string _name); + event EtherDividendDeposited(address indexed _depositor, uint256 _checkpointId, uint256 _created, uint256 _maturity, uint256 _expiry, uint256 _amount, uint256 _totalSupply, uint256 _dividendIndex, bytes32 indexed _name); event EtherDividendClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); event EtherDividendReclaimed(address indexed _claimer, uint256 _dividendIndex, uint256 _claimedAmount); event EtherDividendClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); @@ -30,7 +30,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { * @param _expiry Time until dividend can no longer be paid, and can be reclaimed by issuer * @param _name name/title for identification */ - function createDividend(uint256 _maturity, uint256 _expiry, string _name) payable external onlyOwner { + function createDividend(uint256 _maturity, uint256 _expiry, bytes32 _name) payable external onlyOwner { createDividendWithExclusions(_maturity, _expiry, excluded, _name); } @@ -41,7 +41,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { * @param _checkpointId Id of the checkpoint from which to issue dividend * @param _name name/title for identification */ - function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, string _name) payable external onlyOwner { + function createDividendWithCheckpoint(uint256 _maturity, uint256 _expiry, uint256 _checkpointId, bytes32 _name) payable external onlyOwner { _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _checkpointId, excluded, _name); } @@ -52,7 +52,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { * @param _excluded List of addresses to exclude * @param _name name/title for identification */ - function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address[] _excluded, string _name) payable public onlyOwner { + function createDividendWithExclusions(uint256 _maturity, uint256 _expiry, address[] _excluded, bytes32 _name) payable public onlyOwner { uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); _createDividendWithCheckpointAndExclusions(_maturity, _expiry, checkpointId, _excluded, _name); } @@ -70,7 +70,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { uint256 _expiry, uint256 _checkpointId, address[] _excluded, - string _name + bytes32 _name ) payable public @@ -92,7 +92,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { uint256 _expiry, uint256 _checkpointId, address[] _excluded, - string _name + bytes32 _name ) internal { @@ -101,6 +101,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { require(_expiry > now, "Expiry is in the past"); require(msg.value > 0, "No dividend sent"); require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId()); + require(_name[0] != 0); uint256 dividendIndex = dividends.length; uint256 currentSupply = ISecurityToken(securityToken).totalSupplyAt(_checkpointId); uint256 excludedSupply = 0; diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index dd90435b2..1cf3eb020 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -41,7 +41,7 @@ contract('ERC20DividendCheckpoint', accounts => { let expiryTime = toTime + duration.days(15); let message = "Transaction Should Fail!"; - let dividendName = "TestDividend"; + let dividendName = "0x546573744469766964656e640000000000000000000000000000000000000000"; // Contract Instance Declaration let I_GeneralPermissionManagerFactory; @@ -521,11 +521,43 @@ contract('ERC20DividendCheckpoint', accounts => { ); }); + it("Should not allow to create dividend without name", async() => { + let maturity = latestTime() + duration.days(1); + let expiry = latestTime() + duration.days(10); + await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); + await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let errorThrown = false; + try { + await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), '', {from: token_owner}); + } catch(error) { + console.log(` tx -> failed because dividend name is empty`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); + let errorThrown = false; + try { + await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), '', {from: token_owner}); + } catch(error) { + console.log(` tx -> failed because dividend name is empty`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + + it("Create new dividend", async() => { + let maturity = latestTime() + duration.days(1); + let expiry = latestTime() + duration.days(10); + await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); + // approved in above test let tx = await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), dividendName, {from: token_owner}); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); }); diff --git a/test/f_ether_dividends.js b/test/f_ether_dividends.js index 661f87ac9..dec4be72f 100644 --- a/test/f_ether_dividends.js +++ b/test/f_ether_dividends.js @@ -41,7 +41,7 @@ contract('EtherDividendCheckpoint', accounts => { let expiryTime = toTime + duration.days(15); let message = "Transaction Should Fail!"; - let dividendName = "TestDividend"; + let dividendName = "0x546573744469766964656e640000000000000000000000000000000000000000"; // Contract Instance Declaration let I_GeneralPermissionManagerFactory; @@ -406,6 +406,20 @@ contract('EtherDividendCheckpoint', accounts => { await I_EtherDividendCheckpoint.setWithholdingFixed([account_investor2], BigNumber(20*10**16), {from: token_owner}); }); + it("Should fail in creating the dividend", async() => { + let errorThrown = false; + let maturity = latestTime() + duration.days(1); + let expiry = latestTime() + duration.days(10); + try { + await I_EtherDividendCheckpoint.createDividend(maturity, expiry, '', {from: token_owner, value: web3.utils.toWei('1.5', 'ether')}); + } catch(error) { + console.log(` tx -> failed because dividend name is empty`.grey); + ensureException(error); + errorThrown = true; + } + assert.ok(errorThrown, message); + }); + it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); From 842760359bf1621a65c9e66fc03ff2cde1dc9c61 Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Tue, 2 Oct 2018 20:12:26 +0530 Subject: [PATCH 6/6] Removed duplicate test case --- test/e_erc20_dividends.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index 1cf3eb020..cdd1fa98d 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -537,22 +537,6 @@ contract('ERC20DividendCheckpoint', accounts => { assert.ok(errorThrown, message); }); - it("Create new dividend", async() => { - let maturity = latestTime() + duration.days(1); - let expiry = latestTime() + duration.days(10); - await I_PolyToken.getTokens(web3.utils.toWei('1.5', 'ether'), token_owner); - await I_PolyToken.approve(I_ERC20DividendCheckpoint.address, web3.utils.toWei('1.5', 'ether'), {from: token_owner}); - let errorThrown = false; - try { - await I_ERC20DividendCheckpoint.createDividend(maturity, expiry, I_PolyToken.address, web3.utils.toWei('1.5', 'ether'), '', {from: token_owner}); - } catch(error) { - console.log(` tx -> failed because dividend name is empty`.grey); - ensureException(error); - errorThrown = true; - } - assert.ok(errorThrown, message); - }); - it("Create new dividend", async() => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10);