Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Track non-zero account holders #95

Merged
merged 10 commits into from
May 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ All notable changes to this project will be documented in this file.

## Changed

* Modified IST20 interface to inherit from ERC20 interfaces
* Modified verifyTransfer function in TransferManagers to return a Result enumeration instead of boolean
* Fixed `capReached()` which was checking fundsRaised agains cap, but the cap is expressed in tokens.
* Constant key variables migrated from the securityToken to ISecurityToken contract.
* Solidity compiler version get changed to 0.4.23 that leads to the addition of reason string in the `require` and `revert` statements.
* Solidity compiler version get changed to 0.4.23 that leads to the addition of reason string in the `require` and `revert` statements.
* zeppelin-solidity package version and name get changed to openzeppelin-solidity v1.9.0.

## Added

* added CountTransferManager to restrict the total number of token holders
* added PercentageTransferManager to restrict the total percentage of tokens held by any single holder
* generateSecurityToken in SecurityTokenRegistry takes an additional parameter specifying whether the token is divisible.
* IModule contract takes the polyToken contract address as the constructor argument to wrapping all the factories with the polyToken contract address.
* `takeFee()` new function introduced to extract the POLY token from the factory. It only be called by the owner of the factory.
* Added ability for issuer to provide a signed piece of data to allow investors to whitelist themselves.
* Added ability for issuer to provide a signed piece of data to allow investors to whitelist themselves.
* `_securityTokenAddress` get indexed in the `LogNewSecurityToken` event.
* Now each investor have its `expiryTime` for the KYC. After the expiryTime limit reached, investor will not abe to use transfer related functions.

Expand Down
4 changes: 3 additions & 1 deletion contracts/interfaces/IST20.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pragma solidity ^0.4.23;

import "openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
import "openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol";

contract IST20 {
contract IST20 is StandardToken, DetailedERC20 {

// off-chain hash
bytes32 public tokenDetails;
Expand Down
2 changes: 2 additions & 0 deletions contracts/interfaces/ISecurityToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ contract ISecurityToken is IST20, Ownable {
uint8 public constant TRANSFERMANAGER_KEY = 2;
uint8 public constant STO_KEY = 3;
uint256 public granularity;
// Total number of non-zero token holders
uint256 public investorCount;

//TODO: Factor out more stuff here
function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool);
Expand Down
49 changes: 49 additions & 0 deletions contracts/modules/TransferManager/CountTransferManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
pragma solidity ^0.4.23;

import "./ITransferManager.sol";

contract CountTransferManager is ITransferManager {

uint256 public maxHolderCount;

event LogModifyHolderCount(uint256 _oldHolderCount, uint256 _newHolderCount);

constructor (address _securityToken, address _polyAddress)
public
IModule(_securityToken, _polyAddress)
{
}

function verifyTransfer(address /* _from */, address _to, uint256 /* _amount */) public view returns(Result) {
if (!paused) {
if (maxHolderCount < ISecurityToken(securityToken).investorCount()) {
// Allow trannsfers to existing maxHolders
if (ISecurityToken(securityToken).balanceOf(_to) != 0) {
return Result.VALID;
}
return Result.INVALID;
}
return Result.NA;
}
return Result.NA;
}

function configure(uint256 _maxHolderCount) public onlyFactory {
maxHolderCount = _maxHolderCount;
}

function getInitFunction() public returns(bytes4) {
return bytes4(keccak256("configure(uint256)"));
}

function changeHolderCount(uint256 _maxHolderCount) public onlyOwner {
emit LogModifyHolderCount(maxHolderCount, _maxHolderCount);
maxHolderCount = _maxHolderCount;
}

function getPermissions() public view returns(bytes32[]) {
bytes32[] memory allPermissions = new bytes32[](0);
return allPermissions;
}

}
55 changes: 55 additions & 0 deletions contracts/modules/TransferManager/CountTransferManagerFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
pragma solidity ^0.4.23;

import "./CountTransferManager.sol";
import "../../interfaces/IModuleFactory.sol";


contract CountTransferManagerFactory is IModuleFactory {

constructor (address _polyAddress) public
IModuleFactory(_polyAddress)
{

}

function deploy(bytes _data) external returns(address) {
if(getCost() > 0)
require(polyToken.transferFrom(msg.sender, owner, getCost()), "Failed transferFrom because of sufficent Allowance is not provided");
CountTransferManager countTransferManager = new CountTransferManager(msg.sender, address(polyToken));
require(getSig(_data) == countTransferManager.getInitFunction(), "Provided data is not valid");
require(address(countTransferManager).call(_data), "Un-successfull call");
return address(countTransferManager);

}

function getCost() public view returns(uint256) {
return 0;
}

function getType() public view returns(uint8) {
return 2;
}

function getName() public view returns(bytes32) {
return "CountTransferManager";
}

function getDescription() public view returns(string) {
return "Restrict the number of investors";
}

function getTitle() public view returns(string) {
return "Count Transfer Manager";
}

function getInstructions() public view returns(string) {
return "Allows an issuer to restrict the total number of non-zero token holders";
}

function getTags() public view returns(bytes32[]) {
bytes32[] memory availableTags = new bytes32[](2);
availableTags[0] = "Count";
availableTags[1] = "Transfer Restriction";
return availableTags;
}
}
11 changes: 6 additions & 5 deletions contracts/modules/TransferManager/ExchangeTransferManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ contract ExchangeTransferManager is ITransferManager {
{
}

function verifyTransfer(address _from, address _to, uint256 /*_amount*/) public view returns(bool) {
function verifyTransfer(address _from, address _to, uint256 /*_amount*/) public view returns(Result) {
if (!paused) {
if (_from == exchange) {
return getExchangePermission(_to);
if (_from == exchange) {
return (getExchangePermission(_to) ? Result.VALID : Result.NA);
} else if (_to == exchange) {
return getExchangePermission(_from);
return (getExchangePermission(_from) ? Result.VALID : Result.NA);
}
}
return false;

return Result.NA;
}

function configure(address _exchange) public onlyFactory {
Expand Down
16 changes: 8 additions & 8 deletions contracts/modules/TransferManager/GeneralTransferManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,24 +100,24 @@ contract GeneralTransferManager is ITransferManager {
* b) Seller's sale lockup period is over
* c) Buyer's purchase lockup is over
*/
function verifyTransfer(address _from, address _to, uint256 /*_amount*/) public view returns(bool) {
function verifyTransfer(address _from, address _to, uint256 /*_amount*/) public view returns(Result) {
if (!paused) {
if (allowAllTransfers) {
//All transfers allowed, regardless of whitelist
return true;
//All transfers allowed, regardless of whitelist
return Result.VALID;
}
if (allowAllWhitelistTransfers) {
//Anyone on the whitelist can transfer, regardless of block number
return (onWhitelist(_to) && onWhitelist(_from));
return (onWhitelist(_to) && onWhitelist(_from)) ? Result.VALID : Result.NA;
}
if (allowAllWhitelistIssuances && _from == issuanceAddress) {
return onWhitelist(_to);
return onWhitelist(_to) ? Result.VALID : Result.NA;
}
//Anyone on the whitelist can transfer provided the blocknumber is large enough
return ((onWhitelist(_from) && whitelist[_from].fromTime <= now) &&
(onWhitelist(_to) && whitelist[_to].toTime <= now));
(onWhitelist(_to) && whitelist[_to].toTime <= now)) ? Result.VALID : Result.NA;
}
return false;
return Result.NA;
}

/**
Expand Down Expand Up @@ -194,7 +194,7 @@ contract GeneralTransferManager is ITransferManager {
}

function onWhitelist(address _investor) internal view returns(bool) {
return (((whitelist[_investor].fromTime != 0) || (whitelist[_investor].toTime != 0)) &&
return (((whitelist[_investor].fromTime != 0) || (whitelist[_investor].toTime != 0)) &&
(whitelist[_investor].expiryTime >= now));
}

Expand Down
10 changes: 8 additions & 2 deletions contracts/modules/TransferManager/ITransferManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import "../../interfaces/IModule.sol";

contract ITransferManager is IModule {

//If verifyTransfer returns:
// INVALID, then the transfer should not be allowed regardless of other TM results
// NA, then the result from this TM is ignored
// VALID, then the transfer is valid for this TM
enum Result {INVALID, NA, VALID}

function verifyTransfer(address _from, address _to, uint256 _amount) public view returns(Result);

event Pause(uint256 _timestammp);
event Unpause(uint256 _timestamp);

Expand All @@ -28,6 +36,4 @@ contract ITransferManager is IModule {
emit Unpause(now);
}

function verifyTransfer(address _from, address _to, uint256 _amount) public view returns(bool);

}
47 changes: 47 additions & 0 deletions contracts/modules/TransferManager/PercentageTransferManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pragma solidity ^0.4.23;

import "./ITransferManager.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";

contract PercentageTransferManager is ITransferManager {
using SafeMath for uint256;

uint256 public maxHolderPercentage; // percentage multiplied by 10**16 - e.g. 20% is 20 * 10**16

event LogModifyHolderPercentage(uint256 _oldHolderPercentage, uint256 _newHolderPercentage);

constructor (address _securityToken, address _polyAddress)
public
IModule(_securityToken, _polyAddress)
{
}

function verifyTransfer(address /* _from */, address _to, uint256 _amount) public view returns(Result) {
if (!paused) {
uint256 newBalance = ISecurityToken(securityToken).balanceOf(_to).add(_amount);
if (newBalance.mul(10**18).div(ISecurityToken(securityToken).totalSupply()) > maxHolderPercentage) {
return Result.INVALID;
}
}
return Result.NA;
}

function configure(uint256 _maxHolderPercentage) public onlyFactory {
maxHolderPercentage = _maxHolderPercentage;
}

function getInitFunction() public returns(bytes4) {
return bytes4(keccak256("configure(uint256)"));
}

function changeHolderPercentage(uint256 _maxHolderPercentage) public onlyOwner {
emit LogModifyHolderPercentage(maxHolderPercentage, _maxHolderPercentage);
maxHolderPercentage = _maxHolderPercentage;
}

function getPermissions() public view returns(bytes32[]) {
bytes32[] memory allPermissions = new bytes32[](0);
return allPermissions;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
pragma solidity ^0.4.23;

import "./PercentageTransferManager.sol";
import "../../interfaces/IModuleFactory.sol";


contract PercentageTransferManagerFactory is IModuleFactory {

constructor (address _polyAddress) public
IModuleFactory(_polyAddress)
{

}

function deploy(bytes _data) external returns(address) {
if(getCost() > 0)
require(polyToken.transferFrom(msg.sender, owner, getCost()), "Failed transferFrom because of sufficent Allowance is not provided");
PercentageTransferManager percentageTransferManager = new PercentageTransferManager(msg.sender, address(polyToken));
require(getSig(_data) == percentageTransferManager.getInitFunction(), "Provided data is not valid");
require(address(percentageTransferManager).call(_data), "Un-successfull call");
return address(percentageTransferManager);

}

function getCost() public view returns(uint256) {
return 0;
}

function getType() public view returns(uint8) {
return 2;
}

function getName() public view returns(bytes32) {
return "PercentageTransferManager";
}

function getDescription() public view returns(string) {
return "Restrict the number of investors";
}

function getTitle() public view returns(string) {
return "Percentage Transfer Manager";
}

function getInstructions() public view returns(string) {
return "Allows an issuer to restrict the total number of non-zero token holders";
}

function getTags() public view returns(bytes32[]) {
bytes32[] memory availableTags = new bytes32[](2);
availableTags[0] = "Percentage";
availableTags[1] = "Transfer Restriction";
return availableTags;
}
}
Loading