Created
September 5, 2018 09:46
-
-
Save kn9ts/f5ea8c043052ab6ec531f4f2ad884021 to your computer and use it in GitHub Desktop.
Demo security token MIMI deployed using Polymath. https://kovan.etherscan.io/address/0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC#code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.24; | |
/** | |
* @title Math | |
* @dev Assorted math operations | |
*/ | |
library Math { | |
function max64(uint64 a, uint64 b) internal pure returns (uint64) { | |
return a >= b ? a : b; | |
} | |
function min64(uint64 a, uint64 b) internal pure returns (uint64) { | |
return a < b ? a : b; | |
} | |
function max256(uint256 a, uint256 b) internal pure returns (uint256) { | |
return a >= b ? a : b; | |
} | |
function min256(uint256 a, uint256 b) internal pure returns (uint256) { | |
return a < b ? a : b; | |
} | |
} | |
/** | |
* @title ERC20Basic | |
* @dev Simpler version of ERC20 interface | |
* @dev see https://github.com/ethereum/EIPs/issues/179 | |
*/ | |
contract ERC20Basic { | |
function totalSupply() public view returns (uint256); | |
function balanceOf(address who) public view returns (uint256); | |
function transfer(address to, uint256 value) public returns (bool); | |
event Transfer(address indexed from, address indexed to, uint256 value); | |
} | |
/** | |
* @title ERC20 interface | |
* @dev see https://github.com/ethereum/EIPs/issues/20 | |
*/ | |
contract ERC20 is ERC20Basic { | |
function allowance(address owner, address spender) | |
public view returns (uint256); | |
function transferFrom(address from, address to, uint256 value) | |
public returns (bool); | |
function approve(address spender, uint256 value) public returns (bool); | |
event Approval( | |
address indexed owner, | |
address indexed spender, | |
uint256 value | |
); | |
} | |
contract IERC20 is ERC20 { | |
function decreaseApproval( | |
address _spender, | |
uint _subtractedValue | |
) | |
public | |
returns (bool); | |
function increaseApproval( | |
address _spender, | |
uint _addedValue | |
) | |
public | |
returns (bool); | |
} | |
/** | |
* @title SafeMath | |
* @dev Math operations with safety checks that throw on error | |
*/ | |
library SafeMath { | |
/** | |
* @dev Multiplies two numbers, throws on overflow. | |
*/ | |
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { | |
// Gas optimization: this is cheaper than asserting 'a' not being zero, but the | |
// benefit is lost if 'b' is also tested. | |
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 | |
if (a == 0) { | |
return 0; | |
} | |
c = a * b; | |
assert(c / a == b); | |
return c; | |
} | |
/** | |
* @dev Integer division of two numbers, truncating the quotient. | |
*/ | |
function div(uint256 a, uint256 b) internal pure returns (uint256) { | |
// assert(b > 0); // Solidity automatically throws when dividing by 0 | |
// uint256 c = a / b; | |
// assert(a == b * c + a % b); // There is no case in which this doesn't hold | |
return a / b; | |
} | |
/** | |
* @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). | |
*/ | |
function sub(uint256 a, uint256 b) internal pure returns (uint256) { | |
assert(b <= a); | |
return a - b; | |
} | |
/** | |
* @dev Adds two numbers, throws on overflow. | |
*/ | |
function add(uint256 a, uint256 b) internal pure returns (uint256 c) { | |
c = a + b; | |
assert(c >= a); | |
return c; | |
} | |
} | |
/** | |
* @title Basic token | |
* @dev Basic version of StandardToken, with no allowances. | |
*/ | |
contract BasicToken is ERC20Basic { | |
using SafeMath for uint256; | |
mapping(address => uint256) balances; | |
uint256 totalSupply_; | |
/** | |
* @dev total number of tokens in existence | |
*/ | |
function totalSupply() public view returns (uint256) { | |
return totalSupply_; | |
} | |
/** | |
* @dev transfer token for a specified address | |
* @param _to The address to transfer to. | |
* @param _value The amount to be transferred. | |
*/ | |
function transfer(address _to, uint256 _value) public returns (bool) { | |
require(_to != address(0)); | |
require(_value <= balances[msg.sender]); | |
balances[msg.sender] = balances[msg.sender].sub(_value); | |
balances[_to] = balances[_to].add(_value); | |
emit Transfer(msg.sender, _to, _value); | |
return true; | |
} | |
/** | |
* @dev Gets the balance of the specified address. | |
* @param _owner The address to query the the balance of. | |
* @return An uint256 representing the amount owned by the passed address. | |
*/ | |
function balanceOf(address _owner) public view returns (uint256) { | |
return balances[_owner]; | |
} | |
} | |
/** | |
* @title Standard ERC20 token | |
* | |
* @dev Implementation of the basic standard token. | |
* @dev https://github.com/ethereum/EIPs/issues/20 | |
* @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol | |
*/ | |
contract StandardToken is ERC20, BasicToken { | |
mapping (address => mapping (address => uint256)) internal allowed; | |
/** | |
* @dev Transfer tokens from one address to another | |
* @param _from address The address which you want to send tokens from | |
* @param _to address The address which you want to transfer to | |
* @param _value uint256 the amount of tokens to be transferred | |
*/ | |
function transferFrom( | |
address _from, | |
address _to, | |
uint256 _value | |
) | |
public | |
returns (bool) | |
{ | |
require(_to != address(0)); | |
require(_value <= balances[_from]); | |
require(_value <= allowed[_from][msg.sender]); | |
balances[_from] = balances[_from].sub(_value); | |
balances[_to] = balances[_to].add(_value); | |
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); | |
emit Transfer(_from, _to, _value); | |
return true; | |
} | |
/** | |
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. | |
* | |
* Beware that changing an allowance with this method brings the risk that someone may use both the old | |
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this | |
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: | |
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 | |
* @param _spender The address which will spend the funds. | |
* @param _value The amount of tokens to be spent. | |
*/ | |
function approve(address _spender, uint256 _value) public returns (bool) { | |
allowed[msg.sender][_spender] = _value; | |
emit Approval(msg.sender, _spender, _value); | |
return true; | |
} | |
/** | |
* @dev Function to check the amount of tokens that an owner allowed to a spender. | |
* @param _owner address The address which owns the funds. | |
* @param _spender address The address which will spend the funds. | |
* @return A uint256 specifying the amount of tokens still available for the spender. | |
*/ | |
function allowance( | |
address _owner, | |
address _spender | |
) | |
public | |
view | |
returns (uint256) | |
{ | |
return allowed[_owner][_spender]; | |
} | |
/** | |
* @dev Increase the amount of tokens that an owner allowed to a spender. | |
* | |
* approve should be called when allowed[_spender] == 0. To increment | |
* allowed value is better to use this function to avoid 2 calls (and wait until | |
* the first transaction is mined) | |
* From MonolithDAO Token.sol | |
* @param _spender The address which will spend the funds. | |
* @param _addedValue The amount of tokens to increase the allowance by. | |
*/ | |
function increaseApproval( | |
address _spender, | |
uint _addedValue | |
) | |
public | |
returns (bool) | |
{ | |
allowed[msg.sender][_spender] = ( | |
allowed[msg.sender][_spender].add(_addedValue)); | |
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); | |
return true; | |
} | |
/** | |
* @dev Decrease the amount of tokens that an owner allowed to a spender. | |
* | |
* approve should be called when allowed[_spender] == 0. To decrement | |
* allowed value is better to use this function to avoid 2 calls (and wait until | |
* the first transaction is mined) | |
* From MonolithDAO Token.sol | |
* @param _spender The address which will spend the funds. | |
* @param _subtractedValue The amount of tokens to decrease the allowance by. | |
*/ | |
function decreaseApproval( | |
address _spender, | |
uint _subtractedValue | |
) | |
public | |
returns (bool) | |
{ | |
uint oldValue = allowed[msg.sender][_spender]; | |
if (_subtractedValue > oldValue) { | |
allowed[msg.sender][_spender] = 0; | |
} else { | |
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); | |
} | |
emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); | |
return true; | |
} | |
} | |
/** | |
* @title DetailedERC20 token | |
* @dev The decimals are only for visualization purposes. | |
* All the operations are done using the smallest and indivisible token unit, | |
* just as on Ethereum all the operations are done in wei. | |
*/ | |
contract DetailedERC20 is ERC20 { | |
string public name; | |
string public symbol; | |
uint8 public decimals; | |
constructor(string _name, string _symbol, uint8 _decimals) public { | |
name = _name; | |
symbol = _symbol; | |
decimals = _decimals; | |
} | |
} | |
/** | |
* @title Interface for the ST20 token standard | |
*/ | |
contract IST20 is StandardToken, DetailedERC20 { | |
// off-chain hash | |
string public tokenDetails; | |
//transfer, transferFrom must respect use respect the result of verifyTransfer | |
function verifyTransfer(address _from, address _to, uint256 _amount) public returns (bool success); | |
/** | |
* @notice mints new tokens and assigns them to the target _investor. | |
* Can only be called by the STO attached to the token (Or by the ST owner if there's no STO attached yet) | |
*/ | |
function mint(address _investor, uint256 _amount) public returns (bool success); | |
/** | |
* @notice Burn function used to burn the securityToken | |
* @param _value No. of token that get burned | |
*/ | |
function burn(uint256 _value) public; | |
event Minted(address indexed to, uint256 amount); | |
event Burnt(address indexed _burner, uint256 _value); | |
} | |
/** | |
* @title Ownable | |
* @dev The Ownable contract has an owner address, and provides basic authorization control | |
* functions, this simplifies the implementation of "user permissions". | |
*/ | |
contract Ownable { | |
address public owner; | |
event OwnershipRenounced(address indexed previousOwner); | |
event OwnershipTransferred( | |
address indexed previousOwner, | |
address indexed newOwner | |
); | |
/** | |
* @dev The Ownable constructor sets the original `owner` of the contract to the sender | |
* account. | |
*/ | |
constructor() public { | |
owner = msg.sender; | |
} | |
/** | |
* @dev Throws if called by any account other than the owner. | |
*/ | |
modifier onlyOwner() { | |
require(msg.sender == owner); | |
_; | |
} | |
/** | |
* @dev Allows the current owner to relinquish control of the contract. | |
*/ | |
function renounceOwnership() public onlyOwner { | |
emit OwnershipRenounced(owner); | |
owner = address(0); | |
} | |
/** | |
* @dev Allows the current owner to transfer control of the contract to a newOwner. | |
* @param _newOwner The address to transfer ownership to. | |
*/ | |
function transferOwnership(address _newOwner) public onlyOwner { | |
_transferOwnership(_newOwner); | |
} | |
/** | |
* @dev Transfers control of the contract to a newOwner. | |
* @param _newOwner The address to transfer ownership to. | |
*/ | |
function _transferOwnership(address _newOwner) internal { | |
require(_newOwner != address(0)); | |
emit OwnershipTransferred(owner, _newOwner); | |
owner = _newOwner; | |
} | |
} | |
/** | |
* @title Interface for all security tokens | |
*/ | |
contract ISecurityToken is IST20, Ownable { | |
uint8 public constant PERMISSIONMANAGER_KEY = 1; | |
uint8 public constant TRANSFERMANAGER_KEY = 2; | |
uint8 public constant STO_KEY = 3; | |
uint8 public constant CHECKPOINT_KEY = 4; | |
uint256 public granularity; | |
// Value of current checkpoint | |
uint256 public currentCheckpointId; | |
// Total number of non-zero token holders | |
uint256 public investorCount; | |
// List of token holders | |
address[] public investors; | |
// Permissions this to a Permission module, which has a key of 1 | |
// If no Permission return false - note that IModule withPerm will allow ST owner all permissions anyway | |
// this allows individual modules to override this logic if needed (to not allow ST owner all permissions) | |
function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool); | |
/** | |
* @notice returns module list for a module type | |
* @param _moduleType is which type of module we are trying to remove | |
* @param _moduleIndex is the index of the module within the chosen type | |
*/ | |
function getModule(uint8 _moduleType, uint _moduleIndex) public view returns (bytes32, address); | |
/** | |
* @notice returns module list for a module name - will return first match | |
* @param _moduleType is which type of module we are trying to remove | |
* @param _name is the name of the module within the chosen type | |
*/ | |
function getModuleByName(uint8 _moduleType, bytes32 _name) public view returns (bytes32, address); | |
/** | |
* @notice Queries totalSupply as of a defined checkpoint | |
* @param _checkpointId Checkpoint ID to query as of | |
*/ | |
function totalSupplyAt(uint256 _checkpointId) public view returns(uint256); | |
/** | |
* @notice Queries balances as of a defined checkpoint | |
* @param _investor Investor to query balance for | |
* @param _checkpointId Checkpoint ID to query as of | |
*/ | |
function balanceOfAt(address _investor, uint256 _checkpointId) public view returns(uint256); | |
/** | |
* @notice Creates a checkpoint that can be used to query historical balances / totalSuppy | |
*/ | |
function createCheckpoint() public returns(uint256); | |
/** | |
* @notice gets length of investors array | |
* NB - this length may differ from investorCount if list has not been pruned of zero balance investors | |
* @return length | |
*/ | |
function getInvestorsLength() public view returns(uint256); | |
} | |
/** | |
* @title Interface that any module factory contract should implement | |
*/ | |
contract IModuleFactory is Ownable { | |
ERC20 public polyToken; | |
uint256 public setupCost; | |
uint256 public usageCost; | |
uint256 public monthlySubscriptionCost; | |
event LogChangeFactorySetupFee(uint256 _oldSetupcost, uint256 _newSetupCost, address _moduleFactory); | |
event LogChangeFactoryUsageFee(uint256 _oldUsageCost, uint256 _newUsageCost, address _moduleFactory); | |
event LogChangeFactorySubscriptionFee(uint256 _oldSubscriptionCost, uint256 _newMonthlySubscriptionCost, address _moduleFactory); | |
event LogGenerateModuleFromFactory(address _module, bytes32 indexed _moduleName, address indexed _moduleFactory, address _creator, uint256 _timestamp); | |
/** | |
* @notice Constructor | |
* @param _polyAddress Address of the polytoken | |
*/ | |
constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public { | |
polyToken = ERC20(_polyAddress); | |
setupCost = _setupCost; | |
usageCost = _usageCost; | |
monthlySubscriptionCost = _subscriptionCost; | |
} | |
//Should create an instance of the Module, or throw | |
function deploy(bytes _data) external returns(address); | |
/** | |
* @notice Type of the Module factory | |
*/ | |
function getType() public view returns(uint8); | |
/** | |
* @notice Get the name of the Module | |
*/ | |
function getName() public view returns(bytes32); | |
/** | |
* @notice Get the description of the Module | |
*/ | |
function getDescription() public view returns(string); | |
/** | |
* @notice Get the title of the Module | |
*/ | |
function getTitle() public view returns(string); | |
/** | |
* @notice Get the Instructions that helped to used the module | |
*/ | |
function getInstructions() public view returns (string); | |
/** | |
* @notice Get the tags related to the module factory | |
*/ | |
function getTags() public view returns (bytes32[]); | |
//Pull function sig from _data | |
function getSig(bytes _data) internal pure returns (bytes4 sig) { | |
uint len = _data.length < 4 ? _data.length : 4; | |
for (uint i = 0; i < len; i++) { | |
sig = bytes4(uint(sig) + uint(_data[i]) * (2 ** (8 * (len - 1 - i)))); | |
} | |
} | |
/** | |
* @notice used to change the fee of the setup cost | |
* @param _newSetupCost new setup cost | |
*/ | |
function changeFactorySetupFee(uint256 _newSetupCost) public onlyOwner { | |
emit LogChangeFactorySetupFee(setupCost, _newSetupCost, address(this)); | |
setupCost = _newSetupCost; | |
} | |
/** | |
* @notice used to change the fee of the usage cost | |
* @param _newUsageCost new usage cost | |
*/ | |
function changeFactoryUsageFee(uint256 _newUsageCost) public onlyOwner { | |
emit LogChangeFactoryUsageFee(usageCost, _newUsageCost, address(this)); | |
usageCost = _newUsageCost; | |
} | |
/** | |
* @notice used to change the fee of the subscription cost | |
* @param _newSubscriptionCost new subscription cost | |
*/ | |
function changeFactorySubscriptionFee(uint256 _newSubscriptionCost) public onlyOwner { | |
emit LogChangeFactorySubscriptionFee(monthlySubscriptionCost, _newSubscriptionCost, address(this)); | |
monthlySubscriptionCost = _newSubscriptionCost; | |
} | |
} | |
/** | |
* @title Interface that any module contract should implement | |
*/ | |
contract IModule { | |
address public factory; | |
address public securityToken; | |
bytes32 public constant FEE_ADMIN = "FEE_ADMIN"; | |
ERC20 public polyToken; | |
/** | |
* @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 = ERC20(_polyAddress); | |
} | |
/** | |
* @notice This function returns the signature of configure function | |
*/ | |
function getInitFunction() public pure returns (bytes4); | |
//Allows owner, factory or permissioned delegate | |
modifier withPerm(bytes32 _perm) { | |
bool isOwner = msg.sender == ISecurityToken(securityToken).owner(); | |
bool isFactory = msg.sender == factory; | |
require(isOwner||isFactory||ISecurityToken(securityToken).checkPermission(msg.sender, address(this), _perm), "Permission check failed"); | |
_; | |
} | |
modifier onlyOwner { | |
require(msg.sender == ISecurityToken(securityToken).owner(), "Sender is not owner"); | |
_; | |
} | |
modifier onlyFactory { | |
require(msg.sender == factory, "Sender is not factory"); | |
_; | |
} | |
modifier onlyFactoryOwner { | |
require(msg.sender == IModuleFactory(factory).owner(), "Sender is not factory owner"); | |
_; | |
} | |
/** | |
* @notice Return the permissions flag that are associated with Module | |
*/ | |
function getPermissions() public view returns(bytes32[]); | |
/** | |
* @notice used to withdraw the fee by the factory owner | |
*/ | |
function takeFee(uint256 _amount) public withPerm(FEE_ADMIN) returns(bool) { | |
require(polyToken.transferFrom(address(this), IModuleFactory(factory).owner(), _amount), "Unable to take fee"); | |
return true; | |
} | |
} | |
/** | |
* @title Interface for the polymath module registry contract | |
*/ | |
contract IModuleRegistry { | |
/** | |
* @notice Called by a security token to notify the registry it is using a module | |
* @param _moduleFactory is the address of the relevant module factory | |
*/ | |
function useModule(address _moduleFactory) external; | |
/** | |
* @notice Called by moduleFactory owner to register new modules for SecurityToken to use | |
* @param _moduleFactory is the address of the module factory to be registered | |
*/ | |
function registerModule(address _moduleFactory) external returns(bool); | |
/** | |
* @notice Use to get all the tags releated to the functionality of the Module Factory. | |
* @param _moduleType Type of module | |
*/ | |
function getTagByModuleType(uint8 _moduleType) public view returns(bytes32[]); | |
} | |
/** | |
* @title Utility contract to allow pausing and unpausing of certain functions | |
*/ | |
contract Pausable { | |
event Pause(uint256 _timestammp); | |
event Unpause(uint256 _timestamp); | |
bool public paused = false; | |
/** | |
* @notice Modifier to make a function callable only when the contract is not paused. | |
*/ | |
modifier whenNotPaused() { | |
require(!paused); | |
_; | |
} | |
/** | |
* @notice Modifier to make a function callable only when the contract is paused. | |
*/ | |
modifier whenPaused() { | |
require(paused); | |
_; | |
} | |
/** | |
* @notice called by the owner to pause, triggers stopped state | |
*/ | |
function _pause() internal { | |
require(!paused); | |
paused = true; | |
emit Pause(now); | |
} | |
/** | |
* @notice called by the owner to unpause, returns to normal state | |
*/ | |
function _unpause() internal { | |
require(paused); | |
paused = false; | |
emit Unpause(now); | |
} | |
} | |
/** | |
* @title Interface to be implemented by all Transfer Manager modules | |
*/ | |
contract ITransferManager is IModule, Pausable { | |
//If verifyTransfer returns: | |
// FORCE_VALID, the transaction will always be valid, regardless of other TM results | |
// INVALID, then the transfer should not be allowed regardless of other TM results | |
// VALID, then the transfer is valid for this TM | |
// NA, then the result from this TM is ignored | |
enum Result {INVALID, NA, VALID, FORCE_VALID} | |
function verifyTransfer(address _from, address _to, uint256 _amount, bool _isTransfer) public returns(Result); | |
function unpause() onlyOwner public { | |
super._unpause(); | |
} | |
function pause() onlyOwner public { | |
super._pause(); | |
} | |
} | |
/** | |
* @title Interface to be implemented by all permission manager modules | |
*/ | |
contract IPermissionManager is IModule { | |
function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool); | |
function changePermission(address _delegate, address _module, bytes32 _perm, bool _valid) public returns(bool); | |
function getDelegateDetails(address _delegate) public view returns(bytes32); | |
} | |
/** | |
* @title Interface for the token burner contract | |
*/ | |
interface ITokenBurner { | |
function burn(address _burner, uint256 _value ) external returns(bool); | |
} | |
/** | |
* @title Utility contract to allow owner to retreive any ERC20 sent to the contract | |
*/ | |
contract ReclaimTokens is Ownable { | |
/** | |
* @notice Reclaim all ERC20Basic compatible tokens | |
* @param _tokenContract The address of the token contract | |
*/ | |
function reclaimERC20(address _tokenContract) external onlyOwner { | |
require(_tokenContract != address(0)); | |
ERC20Basic token = ERC20Basic(_tokenContract); | |
uint256 balance = token.balanceOf(address(this)); | |
require(token.transfer(owner, balance)); | |
} | |
} | |
/** | |
* @title Core functionality for registry upgradability | |
*/ | |
contract PolymathRegistry is ReclaimTokens { | |
mapping (bytes32 => address) public storedAddresses; | |
event LogChangeAddress(string _nameKey, address indexed _oldAddress, address indexed _newAddress); | |
/** | |
* @notice Get the contract address | |
* @param _nameKey is the key for the contract address mapping | |
* @return address | |
*/ | |
function getAddress(string _nameKey) view public returns(address) { | |
bytes32 key = keccak256(bytes(_nameKey)); | |
require(storedAddresses[key] != address(0), "Invalid address key"); | |
return storedAddresses[key]; | |
} | |
/** | |
* @notice change the contract address | |
* @param _nameKey is the key for the contract address mapping | |
* @param _newAddress is the new contract address | |
*/ | |
function changeAddress(string _nameKey, address _newAddress) public onlyOwner { | |
bytes32 key = keccak256(bytes(_nameKey)); | |
emit LogChangeAddress(_nameKey, storedAddresses[key], _newAddress); | |
storedAddresses[key] = _newAddress; | |
} | |
} | |
contract RegistryUpdater is Ownable { | |
address public polymathRegistry; | |
address public moduleRegistry; | |
address public securityTokenRegistry; | |
address public tickerRegistry; | |
address public polyToken; | |
constructor (address _polymathRegistry) public { | |
require(_polymathRegistry != address(0)); | |
polymathRegistry = _polymathRegistry; | |
} | |
function updateFromRegistry() onlyOwner public { | |
moduleRegistry = PolymathRegistry(polymathRegistry).getAddress("ModuleRegistry"); | |
securityTokenRegistry = PolymathRegistry(polymathRegistry).getAddress("SecurityTokenRegistry"); | |
tickerRegistry = PolymathRegistry(polymathRegistry).getAddress("TickerRegistry"); | |
polyToken = PolymathRegistry(polymathRegistry).getAddress("PolyToken"); | |
} | |
} | |
/** | |
* @title Helps contracts guard agains reentrancy attacks. | |
* @author Remco Bloemen <remco@2π.com> | |
* @notice If you mark a function `nonReentrant`, you should also | |
* mark it `external`. | |
*/ | |
contract ReentrancyGuard { | |
/** | |
* @dev We use a single lock for the whole contract. | |
*/ | |
bool private reentrancyLock = false; | |
/** | |
* @dev Prevents a contract from calling itself, directly or indirectly. | |
* @notice If you mark a function `nonReentrant`, you should also | |
* mark it `external`. Calling one nonReentrant function from | |
* another is not supported. Instead, you can implement a | |
* `private` function doing the actual work, and a `external` | |
* wrapper marked as `nonReentrant`. | |
*/ | |
modifier nonReentrant() { | |
require(!reentrancyLock); | |
reentrancyLock = true; | |
_; | |
reentrancyLock = false; | |
} | |
} | |
/** | |
* @title Security Token contract | |
* @notice SecurityToken is an ERC20 token with added capabilities: | |
* @notice - Implements the ST-20 Interface | |
* @notice - Transfers are restricted | |
* @notice - Modules can be attached to it to control its behaviour | |
* @notice - ST should not be deployed directly, but rather the SecurityTokenRegistry should be used | |
*/ | |
contract SecurityToken is ISecurityToken, ReentrancyGuard, RegistryUpdater { | |
using SafeMath for uint256; | |
bytes32 public constant securityTokenVersion = "0.0.1"; | |
// Reference to token burner contract | |
ITokenBurner public tokenBurner; | |
// Use to halt all the transactions | |
bool public freeze = false; | |
struct ModuleData { | |
bytes32 name; | |
address moduleAddress; | |
} | |
// Structures to maintain checkpoints of balances for governance / dividends | |
struct Checkpoint { | |
uint256 checkpointId; | |
uint256 value; | |
} | |
mapping (address => Checkpoint[]) public checkpointBalances; | |
Checkpoint[] public checkpointTotalSupply; | |
bool public finishedIssuerMinting = false; | |
bool public finishedSTOMinting = false; | |
mapping (bytes4 => bool) transferFunctions; | |
// Module list should be order agnostic! | |
mapping (uint8 => ModuleData[]) public modules; | |
uint8 public constant MAX_MODULES = 20; | |
mapping (address => bool) public investorListed; | |
// Emit at the time when module get added | |
event LogModuleAdded( | |
uint8 indexed _type, | |
bytes32 _name, | |
address _moduleFactory, | |
address _module, | |
uint256 _moduleCost, | |
uint256 _budget, | |
uint256 _timestamp | |
); | |
// Emit when the token details get updated | |
event LogUpdateTokenDetails(string _oldDetails, string _newDetails); | |
// Emit when the granularity get changed | |
event LogGranularityChanged(uint256 _oldGranularity, uint256 _newGranularity); | |
// Emit when Module get removed from the securityToken | |
event LogModuleRemoved(uint8 indexed _type, address _module, uint256 _timestamp); | |
// Emit when the budget allocated to a module is changed | |
event LogModuleBudgetChanged(uint8 indexed _moduleType, address _module, uint256 _budget); | |
// Emit when all the transfers get freeze | |
event LogFreezeTransfers(bool _freeze, uint256 _timestamp); | |
// Emit when new checkpoint created | |
event LogCheckpointCreated(uint256 indexed _checkpointId, uint256 _timestamp); | |
// Emit when the minting get finished for the Issuer | |
event LogFinishMintingIssuer(uint256 _timestamp); | |
// Emit when the minting get finished for the STOs | |
event LogFinishMintingSTO(uint256 _timestamp); | |
// Change the STR address in the event of a upgrade | |
event LogChangeSTRAddress(address indexed _oldAddress, address indexed _newAddress); | |
// If _fallback is true, then for STO module type we only allow the module if it is set, if it is not set we only allow the owner | |
// for other _moduleType we allow both issuer and module. | |
modifier onlyModule(uint8 _moduleType, bool _fallback) { | |
//Loop over all modules of type _moduleType | |
bool isModuleType = false; | |
for (uint8 i = 0; i < modules[_moduleType].length; i++) { | |
isModuleType = isModuleType || (modules[_moduleType][i].moduleAddress == msg.sender); | |
} | |
if (_fallback && !isModuleType) { | |
if (_moduleType == STO_KEY) | |
require(modules[_moduleType].length == 0 && msg.sender == owner, "Sender is not owner or STO module is attached"); | |
else | |
require(msg.sender == owner, "Sender is not owner"); | |
} else { | |
require(isModuleType, "Sender is not correct module type"); | |
} | |
_; | |
} | |
modifier checkGranularity(uint256 _amount) { | |
require(_amount % granularity == 0, "Unable to modify token balances at this granularity"); | |
_; | |
} | |
// Checks whether the minting is allowed or not, check for the owner if owner is no the msg.sender then check | |
// for the finishedSTOMinting flag because only STOs and owner are allowed for minting | |
modifier isMintingAllowed() { | |
if (msg.sender == owner) { | |
require(!finishedIssuerMinting, "Minting is finished for Issuer"); | |
} else { | |
require(!finishedSTOMinting, "Minting is finished for STOs"); | |
} | |
_; | |
} | |
/** | |
* @notice Constructor | |
* @param _name Name of the SecurityToken | |
* @param _symbol Symbol of the Token | |
* @param _decimals Decimals for the securityToken | |
* @param _granularity granular level of the token | |
* @param _tokenDetails Details of the token that are stored off-chain (IPFS hash) | |
* @param _polymathRegistry Contract address of the polymath registry | |
*/ | |
constructor ( | |
string _name, | |
string _symbol, | |
uint8 _decimals, | |
uint256 _granularity, | |
string _tokenDetails, | |
address _polymathRegistry | |
) | |
public | |
DetailedERC20(_name, _symbol, _decimals) | |
RegistryUpdater(_polymathRegistry) | |
{ | |
//When it is created, the owner is the STR | |
updateFromRegistry(); | |
tokenDetails = _tokenDetails; | |
granularity = _granularity; | |
transferFunctions[bytes4(keccak256("transfer(address,uint256)"))] = true; | |
transferFunctions[bytes4(keccak256("transferFrom(address,address,uint256)"))] = true; | |
transferFunctions[bytes4(keccak256("mint(address,uint256)"))] = true; | |
transferFunctions[bytes4(keccak256("burn(uint256)"))] = true; | |
} | |
/** | |
* @notice Function used to attach the module in security token | |
* @param _moduleFactory Contract address of the module factory that needs to be attached | |
* @param _data Data used for the intialization of the module factory variables | |
* @param _maxCost Maximum cost of the Module factory | |
* @param _budget Budget of the Module factory | |
*/ | |
function addModule( | |
address _moduleFactory, | |
bytes _data, | |
uint256 _maxCost, | |
uint256 _budget | |
) external onlyOwner nonReentrant { | |
_addModule(_moduleFactory, _data, _maxCost, _budget); | |
} | |
/** | |
* @notice _addModule handles the attachment (or replacement) of modules for the ST | |
* @dev E.G.: On deployment (through the STR) ST gets a TransferManager module attached to it | |
* @dev to control restrictions on transfers. | |
* @dev You are allowed to add a new moduleType if: | |
* @dev - there is no existing module of that type yet added | |
* @dev - the last member of the module list is replacable | |
* @param _moduleFactory is the address of the module factory to be added | |
* @param _data is data packed into bytes used to further configure the module (See STO usage) | |
* @param _maxCost max amount of POLY willing to pay to module. (WIP) | |
*/ | |
function _addModule(address _moduleFactory, bytes _data, uint256 _maxCost, uint256 _budget) internal { | |
//Check that module exists in registry - will throw otherwise | |
IModuleRegistry(moduleRegistry).useModule(_moduleFactory); | |
IModuleFactory moduleFactory = IModuleFactory(_moduleFactory); | |
uint8 moduleType = moduleFactory.getType(); | |
require(modules[moduleType].length < MAX_MODULES, "Limit of MAX MODULES is reached"); | |
uint256 moduleCost = moduleFactory.setupCost(); | |
require(moduleCost <= _maxCost, "Max Cost is always be greater than module cost"); | |
//Approve fee for module | |
require(ERC20(polyToken).approve(_moduleFactory, moduleCost), "Not able to approve the module cost"); | |
//Creates instance of module from factory | |
address module = moduleFactory.deploy(_data); | |
//Approve ongoing budget | |
require(ERC20(polyToken).approve(module, _budget), "Not able to approve the budget"); | |
//Add to SecurityToken module map | |
bytes32 moduleName = moduleFactory.getName(); | |
modules[moduleType].push(ModuleData(moduleName, module)); | |
//Emit log event | |
emit LogModuleAdded(moduleType, moduleName, _moduleFactory, module, moduleCost, _budget, now); | |
} | |
/** | |
* @notice Removes a module attached to the SecurityToken | |
* @param _moduleType is which type of module we are trying to remove | |
* @param _moduleIndex is the index of the module within the chosen type | |
*/ | |
function removeModule(uint8 _moduleType, uint8 _moduleIndex) external onlyOwner { | |
require(_moduleIndex < modules[_moduleType].length, | |
"Module index doesn't exist as per the choosen module type"); | |
require(modules[_moduleType][_moduleIndex].moduleAddress != address(0), | |
"Module contract address should not be 0x"); | |
//Take the last member of the list, and replace _moduleIndex with this, then shorten the list by one | |
emit LogModuleRemoved(_moduleType, modules[_moduleType][_moduleIndex].moduleAddress, now); | |
modules[_moduleType][_moduleIndex] = modules[_moduleType][modules[_moduleType].length - 1]; | |
modules[_moduleType].length = modules[_moduleType].length - 1; | |
} | |
/** | |
* @notice Returns module list for a module type | |
* @param _moduleType is which type of module we are trying to get | |
* @param _moduleIndex is the index of the module within the chosen type | |
* @return bytes32 | |
* @return address | |
*/ | |
function getModule(uint8 _moduleType, uint _moduleIndex) public view returns (bytes32, address) { | |
if (modules[_moduleType].length > 0) { | |
return ( | |
modules[_moduleType][_moduleIndex].name, | |
modules[_moduleType][_moduleIndex].moduleAddress | |
); | |
} else { | |
return ("", address(0)); | |
} | |
} | |
/** | |
* @notice returns module list for a module name - will return first match | |
* @param _moduleType is which type of module we are trying to get | |
* @param _name is the name of the module within the chosen type | |
* @return bytes32 | |
* @return address | |
*/ | |
function getModuleByName(uint8 _moduleType, bytes32 _name) public view returns (bytes32, address) { | |
if (modules[_moduleType].length > 0) { | |
for (uint256 i = 0; i < modules[_moduleType].length; i++) { | |
if (modules[_moduleType][i].name == _name) { | |
return ( | |
modules[_moduleType][i].name, | |
modules[_moduleType][i].moduleAddress | |
); | |
} | |
} | |
return ("", address(0)); | |
} else { | |
return ("", address(0)); | |
} | |
} | |
/** | |
* @notice allows the owner to withdraw unspent POLY stored by them on the ST. | |
* @dev Owner can transfer POLY to the ST which will be used to pay for modules that require a POLY fee. | |
* @param _amount amount of POLY to withdraw | |
*/ | |
function withdrawPoly(uint256 _amount) public onlyOwner { | |
require(ERC20(polyToken).transfer(owner, _amount), "In-sufficient balance"); | |
} | |
/** | |
* @notice allows owner to approve more POLY to one of the modules | |
* @param _moduleType module type | |
* @param _moduleIndex module index | |
* @param _budget new budget | |
*/ | |
function changeModuleBudget(uint8 _moduleType, uint8 _moduleIndex, uint256 _budget) public onlyOwner { | |
require(_moduleType != 0, "Module type cannot be zero"); | |
require(_moduleIndex < modules[_moduleType].length, "Incorrrect module index"); | |
uint256 _currentAllowance = IERC20(polyToken).allowance(address(this), modules[_moduleType][_moduleIndex].moduleAddress); | |
if (_budget < _currentAllowance) { | |
require(IERC20(polyToken).decreaseApproval(modules[_moduleType][_moduleIndex].moduleAddress, _currentAllowance.sub(_budget)), "Insufficient balance to decreaseApproval"); | |
} else { | |
require(IERC20(polyToken).increaseApproval(modules[_moduleType][_moduleIndex].moduleAddress, _budget.sub(_currentAllowance)), "Insufficient balance to increaseApproval"); | |
} | |
emit LogModuleBudgetChanged(_moduleType, modules[_moduleType][_moduleIndex].moduleAddress, _budget); | |
} | |
/** | |
* @notice change the tokenDetails | |
* @param _newTokenDetails New token details | |
*/ | |
function updateTokenDetails(string _newTokenDetails) public onlyOwner { | |
emit LogUpdateTokenDetails(tokenDetails, _newTokenDetails); | |
tokenDetails = _newTokenDetails; | |
} | |
/** | |
* @notice allows owner to change token granularity | |
* @param _granularity granularity level of the token | |
*/ | |
function changeGranularity(uint256 _granularity) public onlyOwner { | |
require(_granularity != 0, "Granularity can not be 0"); | |
emit LogGranularityChanged(granularity, _granularity); | |
granularity = _granularity; | |
} | |
/** | |
* @notice keeps track of the number of non-zero token holders | |
* @param _from sender of transfer | |
* @param _to receiver of transfer | |
* @param _value value of transfer | |
*/ | |
function adjustInvestorCount(address _from, address _to, uint256 _value) internal { | |
if ((_value == 0) || (_from == _to)) { | |
return; | |
} | |
// Check whether receiver is a new token holder | |
if ((balanceOf(_to) == 0) && (_to != address(0))) { | |
investorCount = investorCount.add(1); | |
} | |
// Check whether sender is moving all of their tokens | |
if (_value == balanceOf(_from)) { | |
investorCount = investorCount.sub(1); | |
} | |
//Also adjust investor list | |
if (!investorListed[_to] && (_to != address(0))) { | |
investors.push(_to); | |
investorListed[_to] = true; | |
} | |
} | |
/** | |
* @notice removes addresses with zero balances from the investors list | |
* @param _start Index in investor list at which to start removing zero balances | |
* @param _iters Max number of iterations of the for loop | |
* NB - pruning this list will mean you may not be able to iterate over investors on-chain as of a historical checkpoint | |
*/ | |
function pruneInvestors(uint256 _start, uint256 _iters) public onlyOwner { | |
for (uint256 i = _start; i < Math.min256(_start.add(_iters), investors.length); i++) { | |
if ((i < investors.length) && (balanceOf(investors[i]) == 0)) { | |
investorListed[investors[i]] = false; | |
investors[i] = investors[investors.length - 1]; | |
investors.length--; | |
} | |
} | |
} | |
/** | |
* @notice gets length of investors array | |
* NB - this length may differ from investorCount if list has not been pruned of zero balance investors | |
* @return length | |
*/ | |
function getInvestorsLength() public view returns(uint256) { | |
return investors.length; | |
} | |
/** | |
* @notice freeze all the transfers | |
*/ | |
function freezeTransfers() public onlyOwner { | |
require(!freeze); | |
freeze = true; | |
emit LogFreezeTransfers(freeze, now); | |
} | |
/** | |
* @notice un-freeze all the transfers | |
*/ | |
function unfreezeTransfers() public onlyOwner { | |
require(freeze); | |
freeze = false; | |
emit LogFreezeTransfers(freeze, now); | |
} | |
/** | |
* @notice adjust totalsupply at checkpoint after minting or burning tokens | |
*/ | |
function adjustTotalSupplyCheckpoints() internal { | |
adjustCheckpoints(checkpointTotalSupply, totalSupply()); | |
} | |
/** | |
* @notice adjust token holder balance at checkpoint after a token transfer | |
* @param _investor address of the token holder affected | |
*/ | |
function adjustBalanceCheckpoints(address _investor) internal { | |
adjustCheckpoints(checkpointBalances[_investor], balanceOf(_investor)); | |
} | |
/** | |
* @notice store the changes to the checkpoint objects | |
* @param _checkpoints the affected checkpoint object array | |
* @param _newValue the new value that needs to be stored | |
*/ | |
function adjustCheckpoints(Checkpoint[] storage _checkpoints, uint256 _newValue) internal { | |
//No checkpoints set yet | |
if (currentCheckpointId == 0) { | |
return; | |
} | |
//No previous checkpoint data - add current balance against checkpoint | |
if (_checkpoints.length == 0) { | |
_checkpoints.push( | |
Checkpoint({ | |
checkpointId: currentCheckpointId, | |
value: _newValue | |
}) | |
); | |
return; | |
} | |
//No new checkpoints since last update | |
if (_checkpoints[_checkpoints.length - 1].checkpointId == currentCheckpointId) { | |
return; | |
} | |
//New checkpoint, so record balance | |
_checkpoints.push( | |
Checkpoint({ | |
checkpointId: currentCheckpointId, | |
value: _newValue | |
}) | |
); | |
} | |
/** | |
* @notice Overloaded version of the transfer function | |
* @param _to receiver of transfer | |
* @param _value value of transfer | |
* @return bool success | |
*/ | |
function transfer(address _to, uint256 _value) public returns (bool success) { | |
adjustInvestorCount(msg.sender, _to, _value); | |
require(verifyTransfer(msg.sender, _to, _value), "Transfer is not valid"); | |
adjustBalanceCheckpoints(msg.sender); | |
adjustBalanceCheckpoints(_to); | |
require(super.transfer(_to, _value)); | |
return true; | |
} | |
/** | |
* @notice Overloaded version of the transferFrom function | |
* @param _from sender of transfer | |
* @param _to receiver of transfer | |
* @param _value value of transfer | |
* @return bool success | |
*/ | |
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { | |
adjustInvestorCount(_from, _to, _value); | |
require(verifyTransfer(_from, _to, _value), "Transfer is not valid"); | |
adjustBalanceCheckpoints(_from); | |
adjustBalanceCheckpoints(_to); | |
require(super.transferFrom(_from, _to, _value)); | |
return true; | |
} | |
/** | |
* @notice validate transfer with TransferManager module if it exists | |
* @dev TransferManager module has a key of 2 | |
* @param _from sender of transfer | |
* @param _to receiver of transfer | |
* @param _amount value of transfer | |
* @return bool | |
*/ | |
function verifyTransfer(address _from, address _to, uint256 _amount) public checkGranularity(_amount) returns (bool) { | |
if (!freeze) { | |
bool isTransfer = false; | |
if (transferFunctions[getSig(msg.data)]) { | |
isTransfer = true; | |
} | |
if (modules[TRANSFERMANAGER_KEY].length == 0) { | |
return true; | |
} | |
bool isInvalid = false; | |
bool isValid = false; | |
bool isForceValid = false; | |
for (uint8 i = 0; i < modules[TRANSFERMANAGER_KEY].length; i++) { | |
ITransferManager.Result valid = ITransferManager(modules[TRANSFERMANAGER_KEY][i].moduleAddress).verifyTransfer(_from, _to, _amount, isTransfer); | |
if (valid == ITransferManager.Result.INVALID) { | |
isInvalid = true; | |
} | |
if (valid == ITransferManager.Result.VALID) { | |
isValid = true; | |
} | |
if (valid == ITransferManager.Result.FORCE_VALID) { | |
isForceValid = true; | |
} | |
} | |
return isForceValid ? true : (isInvalid ? false : isValid); | |
} | |
return false; | |
} | |
/** | |
* @notice End token minting period permanently for Issuer | |
*/ | |
function finishMintingIssuer() public onlyOwner { | |
finishedIssuerMinting = true; | |
emit LogFinishMintingIssuer(now); | |
} | |
/** | |
* @notice End token minting period permanently for STOs | |
*/ | |
function finishMintingSTO() public onlyOwner { | |
finishedSTOMinting = true; | |
emit LogFinishMintingSTO(now); | |
} | |
/** | |
* @notice mints new tokens and assigns them to the target _investor. | |
* @dev Can only be called by the STO attached to the token (Or by the ST owner if there's no STO attached yet) | |
* @param _investor Address to whom the minted tokens will be dilivered | |
* @param _amount Number of tokens get minted | |
* @return success | |
*/ | |
function mint(address _investor, uint256 _amount) public onlyModule(STO_KEY, true) checkGranularity(_amount) isMintingAllowed() returns (bool success) { | |
require(_investor != address(0), "Investor address should not be 0x"); | |
adjustInvestorCount(address(0), _investor, _amount); | |
require(verifyTransfer(address(0), _investor, _amount), "Transfer is not valid"); | |
adjustBalanceCheckpoints(_investor); | |
adjustTotalSupplyCheckpoints(); | |
totalSupply_ = totalSupply_.add(_amount); | |
balances[_investor] = balances[_investor].add(_amount); | |
emit Minted(_investor, _amount); | |
emit Transfer(address(0), _investor, _amount); | |
return true; | |
} | |
/** | |
* @notice mints new tokens and assigns them to the target _investor. | |
* Can only be called by the STO attached to the token (Or by the ST owner if there's no STO attached yet) | |
* @param _investors A list of addresses to whom the minted tokens will be dilivered | |
* @param _amounts A list of number of tokens get minted and transfer to corresponding address of the investor from _investor[] list | |
* @return success | |
*/ | |
function mintMulti(address[] _investors, uint256[] _amounts) public onlyModule(STO_KEY, true) returns (bool success) { | |
require(_investors.length == _amounts.length, "Mis-match in the length of the arrays"); | |
for (uint256 i = 0; i < _investors.length; i++) { | |
mint(_investors[i], _amounts[i]); | |
} | |
return true; | |
} | |
/** | |
* @notice Validate permissions with PermissionManager if it exists, If no Permission return false | |
* @dev Note that IModule withPerm will allow ST owner all permissions anyway | |
* @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions) | |
* @param _delegate address of delegate | |
* @param _module address of PermissionManager module | |
* @param _perm the permissions | |
* @return success | |
*/ | |
function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool) { | |
if (modules[PERMISSIONMANAGER_KEY].length == 0) { | |
return false; | |
} | |
for (uint8 i = 0; i < modules[PERMISSIONMANAGER_KEY].length; i++) { | |
if (IPermissionManager(modules[PERMISSIONMANAGER_KEY][i].moduleAddress).checkPermission(_delegate, _module, _perm)) { | |
return true; | |
} | |
} | |
} | |
/** | |
* @notice used to set the token Burner address. It only be called by the owner | |
* @param _tokenBurner Address of the token burner contract | |
*/ | |
function setTokenBurner(address _tokenBurner) public onlyOwner { | |
tokenBurner = ITokenBurner(_tokenBurner); | |
} | |
/** | |
* @notice Burn function used to burn the securityToken | |
* @param _value No. of token that get burned | |
*/ | |
function burn(uint256 _value) checkGranularity(_value) public { | |
adjustInvestorCount(msg.sender, address(0), _value); | |
require(tokenBurner != address(0), "Token Burner contract address is not set yet"); | |
require(verifyTransfer(msg.sender, address(0), _value), "Transfer is not valid"); | |
require(_value <= balances[msg.sender], "Value should no be greater than the balance of msg.sender"); | |
adjustBalanceCheckpoints(msg.sender); | |
adjustTotalSupplyCheckpoints(); | |
// no need to require value <= totalSupply, since that would imply the | |
// sender's balance is greater than the totalSupply, which *should* be an assertion failure | |
balances[msg.sender] = balances[msg.sender].sub(_value); | |
require(tokenBurner.burn(msg.sender, _value), "Token burner process is not validated"); | |
totalSupply_ = totalSupply_.sub(_value); | |
emit Burnt(msg.sender, _value); | |
emit Transfer(msg.sender, address(0), _value); | |
} | |
/** | |
* @notice Get function signature from _data | |
* @param _data passed data | |
* @return bytes4 sig | |
*/ | |
function getSig(bytes _data) internal pure returns (bytes4 sig) { | |
uint len = _data.length < 4 ? _data.length : 4; | |
for (uint i = 0; i < len; i++) { | |
sig = bytes4(uint(sig) + uint(_data[i]) * (2 ** (8 * (len - 1 - i)))); | |
} | |
} | |
/** | |
* @notice Creates a checkpoint that can be used to query historical balances / totalSuppy | |
* @return uint256 | |
*/ | |
function createCheckpoint() public onlyModule(CHECKPOINT_KEY, true) returns(uint256) { | |
require(currentCheckpointId < 2**256 - 1); | |
currentCheckpointId = currentCheckpointId + 1; | |
emit LogCheckpointCreated(currentCheckpointId, now); | |
return currentCheckpointId; | |
} | |
/** | |
* @notice Queries totalSupply as of a defined checkpoint | |
* @param _checkpointId Checkpoint ID to query | |
* @return uint256 | |
*/ | |
function totalSupplyAt(uint256 _checkpointId) public view returns(uint256) { | |
return getValueAt(checkpointTotalSupply, _checkpointId, totalSupply()); | |
} | |
/** | |
* @notice Queries value at a defined checkpoint | |
* @param checkpoints is array of Checkpoint objects | |
* @param _checkpointId Checkpoint ID to query | |
* @param _currentValue Current value of checkpoint | |
* @return uint256 | |
*/ | |
function getValueAt(Checkpoint[] storage checkpoints, uint256 _checkpointId, uint256 _currentValue) internal view returns(uint256) { | |
require(_checkpointId <= currentCheckpointId); | |
//Checkpoint id 0 is when the token is first created - everyone has a zero balance | |
if (_checkpointId == 0) { | |
return 0; | |
} | |
if (checkpoints.length == 0) { | |
return _currentValue; | |
} | |
if (checkpoints[0].checkpointId >= _checkpointId) { | |
return checkpoints[0].value; | |
} | |
if (checkpoints[checkpoints.length - 1].checkpointId < _checkpointId) { | |
return _currentValue; | |
} | |
if (checkpoints[checkpoints.length - 1].checkpointId == _checkpointId) { | |
return checkpoints[checkpoints.length - 1].value; | |
} | |
uint256 min = 0; | |
uint256 max = checkpoints.length - 1; | |
while (max > min) { | |
uint256 mid = (max + min) / 2; | |
if (checkpoints[mid].checkpointId == _checkpointId) { | |
max = mid; | |
break; | |
} | |
if (checkpoints[mid].checkpointId < _checkpointId) { | |
min = mid + 1; | |
} else { | |
max = mid; | |
} | |
} | |
return checkpoints[max].value; | |
} | |
/** | |
* @notice Queries balances as of a defined checkpoint | |
* @param _investor Investor to query balance for | |
* @param _checkpointId Checkpoint ID to query as of | |
*/ | |
function balanceOfAt(address _investor, uint256 _checkpointId) public view returns(uint256) { | |
return getValueAt(checkpointBalances[_investor], _checkpointId, balanceOf(_investor)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment