Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ All notable changes to this project will be documented in this file.
* Added `getTokensSoldByTier` to return sold (not minted during finalisation) tokens in each tier to USDTSTO.
* Removed individual mappings for tier data removed in UDSTSTO.

# GeneralTransferManager
* Add an Offset that can be used to move all from & to dates forwards or backwards by a fixed offset.
* Add `address[] public investors` to record a list of all addresses that have been added to the whitelist (`getInvestors`)
* Fix for when `allowAllWhitelistIssuances` is FALSE
* Make GTM a Proxy based implementation to reduce deployment gas costs

##Changed
* `getAllModulesAndPermsFromTypes()` does not take securityToken address as a parameter anymore.

Expand Down
20 changes: 5 additions & 15 deletions contracts/modules/Module.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,23 @@ pragma solidity ^0.4.24;

import "../interfaces/IModule.sol";
import "../interfaces/ISecurityToken.sol";
import "../interfaces/IERC20.sol";
import "./ModuleStorage.sol";
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";

/**
* @title Interface that any module contract should implement
* @notice Contract is abstract
*/
contract Module is IModule {

address public factory;

address public securityToken;

bytes32 public constant FEE_ADMIN = "FEE_ADMIN";

IERC20 public polyToken;
contract Module is IModule, ModuleStorage {

/**
* @notice Constructor
* @param _securityToken Address of the security token
* @param _polyAddress Address of the polytoken
*/
constructor (address _securityToken, address _polyAddress) public {
require(_securityToken != address(0) && _polyAddress != address(0), "Invalid address");
securityToken = _securityToken;
factory = msg.sender;
polyToken = IERC20(_polyAddress);
constructor (address _securityToken, address _polyAddress) public
ModuleStorage(_securityToken, _polyAddress)
{
}

//Allows owner, factory or permissioned delegate
Expand Down
30 changes: 30 additions & 0 deletions contracts/modules/ModuleStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pragma solidity ^0.4.24;

import "../interfaces/IERC20.sol";

/**
* @title Storage for Module contract
* @notice Contract is abstract
*/
contract ModuleStorage {

/**
* @notice Constructor
* @param _securityToken Address of the security token
* @param _polyAddress Address of the polytoken
*/
constructor (address _securityToken, address _polyAddress) public {
securityToken = _securityToken;
factory = msg.sender;
polyToken = IERC20(_polyAddress);
}

address public factory;

address public securityToken;

bytes32 public constant FEE_ADMIN = "FEE_ADMIN";

IERC20 public polyToken;

}
150 changes: 85 additions & 65 deletions contracts/modules/TransferManager/GeneralTransferManager.sol
Original file line number Diff line number Diff line change
@@ -1,47 +1,16 @@
pragma solidity ^0.4.24;

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

/**
* @title Transfer Manager module for core transfer validation functionality
*/
contract GeneralTransferManager is ITransferManager {
contract GeneralTransferManager is GeneralTransferManagerStorage, ITransferManager {

using SafeMath for uint256;

//Address from which issuances come
address public issuanceAddress = address(0);

//Address which can sign whitelist changes
address public signingAddress = address(0);

bytes32 public constant WHITELIST = "WHITELIST";
bytes32 public constant FLAGS = "FLAGS";

//from and to timestamps that an investor can send / receive tokens respectively
struct TimeRestriction {
uint256 fromTime;
uint256 toTime;
uint256 expiryTime;
bool canBuyFromSTO;
}

// An address can only send / receive tokens once their corresponding uint256 > block.number
// (unless allowAllTransfers == true or allowAllWhitelistTransfers == true)
mapping (address => TimeRestriction) public whitelist;
// Map of used nonces by customer
mapping(address => mapping(uint256 => bool)) public nonceMap;

//If true, there are no transfer restrictions, for any addresses
bool public allowAllTransfers = false;
//If true, time lock is ignored for transfers (address must still be on whitelist)
bool public allowAllWhitelistTransfers = false;
//If true, time lock is ignored for issuances (address must still be on whitelist)
bool public allowAllWhitelistIssuances = true;
//If true, time lock is ignored for burn transactions
bool public allowAllBurnTransfers = false;

// Emit when Issuance address get changed
event ChangeIssuanceAddress(address _issuanceAddress);
// Emit when there is change in the flag variable called allowAllTransfers
Expand All @@ -55,14 +24,16 @@ contract GeneralTransferManager is ITransferManager {
// Emit when there is change in the flag variable called signingAddress
event ChangeSigningAddress(address _signingAddress);
// Emit when investor details get modified related to their whitelisting
event OffsetModified(uint64 _time, uint8 _isForward);

event ModifyWhitelist(
address _investor,
uint256 _dateAdded,
address _addedBy,
uint256 _fromTime,
uint256 _toTime,
uint256 _expiryTime,
bool _canBuyFromSTO
uint64 _fromTime,
uint64 _toTime,
uint64 _expiryTime,
uint8 _canBuyFromSTO
);

/**
Expand All @@ -76,6 +47,12 @@ contract GeneralTransferManager is ITransferManager {
{
}

function modifyOffset(uint64 _time, uint8 _isForward) public withPerm(FLAGS) {
offset.time = _time;
offset.isForward = _isForward;
emit OffsetModified(_time, _isForward);
}

/**
* @notice This function returns the signature of configure function
*/
Expand Down Expand Up @@ -168,16 +145,19 @@ contract GeneralTransferManager is ITransferManager {
//Anyone on the whitelist can transfer, regardless of time
return (_onWhitelist(_to) && _onWhitelist(_from)) ? Result.VALID : Result.NA;
}
if (_from == issuanceAddress && (whitelist[_to].canBuyFromSTO == 0) && _isSTOAttached()) {
return Result.NA;
}
if (allowAllWhitelistIssuances && _from == issuanceAddress) {
if (!whitelist[_to].canBuyFromSTO && _isSTOAttached()) {
return Result.NA;
}
return _onWhitelist(_to) ? Result.VALID : Result.NA;
}
if (_from == issuanceAddress) {
return (_onWhitelist(_to) && _adjustTimes(whitelist[_to].toTime) <= uint64(now)) ? Result.VALID : Result.NA;
}
//Anyone on the whitelist can transfer provided the blocknumber is large enough
/*solium-disable-next-line security/no-block-members*/
return ((_onWhitelist(_from) && whitelist[_from].fromTime <= now) &&
(_onWhitelist(_to) && whitelist[_to].toTime <= now)) ? Result.VALID : Result.NA; /*solium-disable-line security/no-block-members*/
return ((_onWhitelist(_from) && _adjustTimes(whitelist[_from].fromTime) <= uint64(now)) &&
(_onWhitelist(_to) && _adjustTimes(whitelist[_to].toTime) <= uint64(now))) ? Result.VALID : Result.NA; /*solium-disable-line security/no-block-members*/
}
return Result.NA;
}
Expand All @@ -192,16 +172,38 @@ contract GeneralTransferManager is ITransferManager {
*/
function modifyWhitelist(
address _investor,
uint256 _fromTime,
uint256 _toTime,
uint256 _expiryTime,
bool _canBuyFromSTO
uint64 _fromTime,
uint64 _toTime,
uint64 _expiryTime,
uint8 _canBuyFromSTO
)
public
withPerm(WHITELIST)
{
//Passing a _time == 0 into this function, is equivalent to removing the _investor from the whitelist
whitelist[_investor] = TimeRestriction(_fromTime, _toTime, _expiryTime, _canBuyFromSTO);
_modifyWhitelist(_investor, _fromTime, _toTime, _expiryTime, _canBuyFromSTO);
}

/**
* @notice Adds or removes addresses from the whitelist.
* @param _investor is the address to whitelist
* @param _fromTime is the moment when the sale lockup period ends and the investor can freely sell his tokens
* @param _toTime is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others
* @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC
* @param _canBuyFromSTO is used to know whether the investor is restricted investor or not.
*/
function _modifyWhitelist(
address _investor,
uint64 _fromTime,
uint64 _toTime,
uint64 _expiryTime,
uint8 _canBuyFromSTO
)
internal
{
if (whitelist[_investor].added == uint8(0)) {
investors.push(_investor);
}
whitelist[_investor] = TimeRestriction(_fromTime, _toTime, _expiryTime, _canBuyFromSTO, uint8(1));
/*solium-disable-next-line security/no-block-members*/
emit ModifyWhitelist(_investor, now, msg.sender, _fromTime, _toTime, _expiryTime, _canBuyFromSTO);
}
Expand All @@ -216,17 +218,17 @@ contract GeneralTransferManager is ITransferManager {
*/
function modifyWhitelistMulti(
address[] _investors,
uint256[] _fromTimes,
uint256[] _toTimes,
uint256[] _expiryTimes,
bool[] _canBuyFromSTO
uint64[] _fromTimes,
uint64[] _toTimes,
uint64[] _expiryTimes,
uint8[] _canBuyFromSTO
) public withPerm(WHITELIST) {
require(_investors.length == _fromTimes.length, "Mismatched input lengths");
require(_fromTimes.length == _toTimes.length, "Mismatched input lengths");
require(_toTimes.length == _expiryTimes.length, "Mismatched input lengths");
require(_canBuyFromSTO.length == _toTimes.length, "Mismatched input length");
for (uint256 i = 0; i < _investors.length; i++) {
modifyWhitelist(_investors[i], _fromTimes[i], _toTimes[i], _expiryTimes[i], _canBuyFromSTO[i]);
_modifyWhitelist(_investors[i], _fromTimes[i], _toTimes[i], _expiryTimes[i], _canBuyFromSTO[i]);
}
}

Expand All @@ -246,29 +248,31 @@ contract GeneralTransferManager is ITransferManager {
*/
function modifyWhitelistSigned(
address _investor,
uint256 _fromTime,
uint256 _toTime,
uint256 _expiryTime,
bool _canBuyFromSTO,
uint256 _validFrom,
uint256 _validTo,
uint64 _fromTime,
uint64 _toTime,
uint64 _expiryTime,
uint8 _canBuyFromSTO,
uint64 _validFrom,
uint64 _validTo,
uint256 _nonce,
uint8 _v,
bytes32 _r,
bytes32 _s
) public {
/*solium-disable-next-line security/no-block-members*/
require(_validFrom <= now, "ValidFrom is too early");
require(_validFrom <= uint64(now), "ValidFrom is too early");
/*solium-disable-next-line security/no-block-members*/
require(_validTo >= now, "ValidTo is too late");
require(_validTo >= uint64(now), "ValidTo is too late");
require(!nonceMap[_investor][_nonce], "Already used signature");
nonceMap[_investor][_nonce] = true;
bytes32 hash = keccak256(
abi.encodePacked(this, _investor, _fromTime, _toTime, _expiryTime, _canBuyFromSTO, _validFrom, _validTo, _nonce)
);
_checkSig(hash, _v, _r, _s);
//Passing a _time == 0 into this function, is equivalent to removing the _investor from the whitelist
whitelist[_investor] = TimeRestriction(_fromTime, _toTime, _expiryTime, _canBuyFromSTO);
if (whitelist[_investor].added == uint8(0)) {
investors.push(_investor);
}
whitelist[_investor] = TimeRestriction(_fromTime, _toTime, _expiryTime, _canBuyFromSTO, uint8(1));
/*solium-disable-next-line security/no-block-members*/
emit ModifyWhitelist(_investor, now, msg.sender, _fromTime, _toTime, _expiryTime, _canBuyFromSTO);
}
Expand All @@ -289,8 +293,7 @@ contract GeneralTransferManager is ITransferManager {
* @param _investor Address of the investor
*/
function _onWhitelist(address _investor) internal view returns(bool) {
return (((whitelist[_investor].fromTime != 0) || (whitelist[_investor].toTime != 0)) &&
(whitelist[_investor].expiryTime >= now)); /*solium-disable-line security/no-block-members*/
return (whitelist[_investor].expiryTime >= uint64(now)); /*solium-disable-line security/no-block-members*/
}

/**
Expand All @@ -301,6 +304,23 @@ contract GeneralTransferManager is ITransferManager {
return attached;
}

function _adjustTimes(uint64 _time) internal view returns(uint64) {
if (offset.isForward != 0) {
require(_time + offset.time > _time);
return _time + offset.time;
} else {
require(_time >= offset.time);
return _time - offset.time;
}
}

/**
* @dev Returns list of all investors
*/
function getInvestors() external view returns(address[]) {
return investors;
}

/**
* @notice Return the permissions flag that are associated with general trnasfer manager
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
pragma solidity ^0.4.24;

import "./GeneralTransferManager.sol";
import "../../proxy/GeneralTransferManagerProxy.sol";
import "../ModuleFactory.sol";

/**
* @title Factory for deploying GeneralTransferManager module
*/
contract GeneralTransferManagerFactory is ModuleFactory {

address public logicContract;

/**
* @notice Constructor
* @param _polyAddress Address of the polytoken
*/
constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public
constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public
ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost)
{
version = "1.0.0";
Expand All @@ -21,6 +23,7 @@ contract GeneralTransferManagerFactory is ModuleFactory {
description = "Manage transfers using a time based whitelist";
compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0));
compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0));
logicContract = _logicContract;
}


Expand All @@ -31,7 +34,7 @@ contract GeneralTransferManagerFactory is ModuleFactory {
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 generalTransferManager = new GeneralTransferManager(msg.sender, address(polyToken));
address generalTransferManager = new GeneralTransferManagerProxy(msg.sender, address(polyToken), logicContract);
/*solium-disable-next-line security/no-block-members*/
emit GenerateModuleFromFactory(address(generalTransferManager), getName(), address(this), msg.sender, setupCost, now);
return address(generalTransferManager);
Expand Down
Loading