Skip to content

Commit

Permalink
Merge pull request #277 from PolymathNetwork/withholding_tax_on_divid…
Browse files Browse the repository at this point in the history
…ends

Withholding tax on dividends
  • Loading branch information
satyamakgec authored Sep 26, 2018
2 parents 706a464 + bb69c80 commit b955fa2
Show file tree
Hide file tree
Showing 12 changed files with 1,160 additions and 406 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ cache:
- node_modules
matrix:
fast_finish: true
before_install:
- echo -ne '\n' | sudo add-apt-repository ppa:ethereum/ethereum
- sudo apt-get -y update
- sudo apt-get -y install solc
before_script:
- truffle version
script:
- npm run test
- npm run docs
notifications:
slack:
secure: W4FZSabLrzF74f317hutolEHnlq2GBlQxU6b85L5XymrjgLEhlgE16c5Qz7Emoyt6le6PXL+sfG2ujJc3XYys/6hppgrHSAasuJnKCdQNpmMZ9BNyMs6WGkmB3enIf3K/FLXb26AQdwpQdIXuOeJUTf879u+YoiZV0eZH8d3+fsIOyovq9N6X5pKOpDM9iT8gGB4t7fie7xf51s+iUaHxyO9G7jDginZ4rBXHcU7mxCub9z+Z1H8+kCTnPWaF+KKVEXx4Z0nI3+urboD7E4OIP02LwrThQls2CppA3X0EoesTcdvj/HLErY/JvsXIFiFEEHZzB1Wi+k2TiOeLcYwEuHIVij+HPxxlJNX/j8uy01Uk8s4rd+0EhvfdKHJqUKqxH4YN2npcKfHEss7bU3y7dUinXQfYShW5ZewHdvc7pnnxBTfhvmdi64HdNrXAPq+s1rhciH7MmnU+tsm4lhrpr+FBuHzUMA9fOCr7b0SQytZEgWpiUls88gdbh3yG8TjyZxmZJGx09cwEP0q7VoH0UwFh7mIu5XmYdd5tWUhavTiO7YV8cUPn7MvwMsTltB3YBpF/fB26L7ka8zBhCsjm9prW6SVYU/dyO3m91VeZtO/zJFHRDA6Q58JGVW2rgzO39z193qC1EGRXqTie96VwAAtNg8+hRb+bI/CWDVzSPc=
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 withholding tax to ether & erc20 dividends
* Generalised MakerDAO oracle to allow different instances referencing different currencies
* Added DAI as a fundraising currency to USDTieredSTO
* `transferTickerOwnership()` function is introduced in `TickerRegistry` to transfer the ticker ownership after the registeration #191.
Expand Down
46 changes: 46 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Contributing Guidelines

## Do you have a question?

[Check out our Gitter](https://gitter.im/PolymathNetwork/Lobby)

See also frequently asked questions tagged with Polymath on Stack Exchange and Reddit.

# Please Report bugs!

Do not open an issue on Github if you think your discovered bug could be a security-relevant vulnerability. In the case of the discovery of high security venerabilities, pleasse email us the issue privately at team@polymath.network.

Otherwise, just create a new issue in our repository and follow the template below:

[Issue template to make things easier for you](https://github.com/PolymathNetwork/polymath-core/blob/master/.github/ISSUE_TEMPLATE/feature_request.md)


# Contribute!

If you would like to contribute to Polymath-core, please fork it, fix bugs or implement features, and propose a [pull request](https://github.com/PolymathNetwork/polymath-core/blob/master/PULL_REQUEST_TEMPLATE.md)

Please, refer to the Coding Guide on our [Developer Portal](https://developers.polymath.network/) for more details about hacking on Polymath.

# Contributing

When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.

Please note we have a code of conduct, please follow it in all your interactions with the project.

# Pull Request Process

[Template](https://github.com/PolymathNetwork/polymath-core/blob/master/PULL_REQUEST_TEMPLATE.md)

# Code Styleguide

The polymath-core repo follows the [Solidity style guide](https://solidity.readthedocs.io/en/v0.4.24/style-guide.html)

# Code of Conduct

[Community Standards](https://github.com/PolymathNetwork/polymath-core/blob/master/CODE_OF_CONDUCT.md)

In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community feel safe and respected.

# Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
214 changes: 214 additions & 0 deletions contracts/modules/Checkpoint/DividendCheckpoint.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
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 SetExcludedAddresses(address[] _excluded, 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 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");
for (uint256 i = 0; i < _investors.length; i++) {
require(_withholding[i] <= 10**18);
withholdingTax[_investors[i]] = _withholding[i];
}
}

/**
* @notice Function to clear and set list of excluded addresses used for future dividends
* @param _excluded addresses of investor
*/
function setExcluded(address[] _excluded) public onlyOwner {
require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many excluded addresses");
excluded = _excluded;
emit SetExcludedAddresses(excluded, now);
}

/**
* @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);
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 unit256
*/
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;
}

}
Loading

0 comments on commit b955fa2

Please sign in to comment.