EX-4.3 6 filename6.htm
Exhibit 4.3

pragma solidity ^0.4.24;

import "openzeppelin-solidity/contracts/math/SafeMath.sol";
import "./ERC20.sol";
import "./freezable.sol";
import "./Ledger.sol";
import "./ExternalStorage.sol";
import "./Registry.sol";
import "./Library.sol";
import "./displayable.sol";
import "./configurable.sol";
import "./storable.sol";

contract Token is ERC20, freezable, displayable, configurable, IStorable {

  using SafeMath for uint256;
  using Library for address;

  struct allowedAddr {
    bool status;
    string details;
    address vettingAgent;
  }

  ITokenLedger public tokenLedger;
  string public storageName;
  string public ledgerName;
  address public externalStorage;
  address public registry;
  uint8 public constant decimals = 18;
  bool public isTokenContract = true;
  bool public haltPurchase;

  // This state is specific to the first version of the Token
  // token contract and the token generation event, and hence
  // there is no reason to persist in external storage for
  // future contracts.
  bool public allowTransfers;
  mapping (address => allowedAddr) public whitelistedRecipient;
  address[] public whitelistedRecipientForIndex;
  mapping (address => bool) private processedWhitelistedRecipient;
  uint256 public contributionMinimum;

  event Mint(uint256 amountMinted, uint256 totalTokens, uint256 circulationCap);
  event Approval(address indexed _owner, address indexed _spender, uint256 _value);
  event Transfer(address indexed _from, address indexed _to, uint256 _value);
  event WhiteList(address indexed buyer, uint256 holdCap);
  event ConfigChanged(uint256 buyPrice, uint256 circulationCap, uint256 balanceLimit);
  event CommissionCalc(uint time);
  event VestedTokenGrant(address indexed beneficiary, uint256 startDate, uint256 cliffDate,
  uint256 durationSec, uint256 fullyVestedAmount, bool isRevocable);
  event VestedTokenRevocation(address indexed beneficiary);
  event VestedTokenRelease(address indexed beneficiary, uint256 amount);
  event StorageUpdated(address storageAddress, address ledgerAddress);
  event PurchaseHalted();
  event PurchaseResumed();

  modifier onlyFoundation {
    address foundation = externalStorage.getFoundation();
    require(foundation != address(0));
    if (msg.sender != owner && msg.sender != foundation) revert();
    _;
  }

  modifier initStorage {
    address ledgerAddress = Registry(registry).getStorage(ledgerName);
    address storageAddress = Registry(registry).getStorage(storageName);

    tokenLedger = ITokenLedger(ledgerAddress);
    externalStorage = storageAddress;
    _;
  }

  constructor(address _registry, string _storageName, string _ledgerName) public payable {
    isTokenContract = true;
    version = "2";
    require(_registry != address(0));
    storageName = _storageName;
    ledgerName = _ledgerName;
    registry = _registry;

    addSuperAdmin(registry);
  }

  /* This unnamed function is called whenever someone tries to send ether directly to the token contract */
  function () public {
    revert(); // Prevents accidental sending of ether
  }

  function getLedgerNameHash() external view returns (bytes32) {
    return keccak256(abi.encodePacked(ledgerName));
  }
  function getStorageNameHash() external view returns (bytes32) {
    return keccak256(abi.encodePacked(storageName));
  }

  function configure(
    bytes32 _tokenName,
    bytes32 _tokenSymbol,
    uint256 _buyPrice,
    uint256 _circulationCap,
    uint256 _balanceLimit,
    address _foundation,
    uint8 _commissionPercent,
    uint8 _commissionPercentRel,
    uint256 _commissionDate
  ) public onlySuperAdmins initStorage returns (bool) {

    uint256 __buyPrice= externalStorage.getBuyPrice();
    if (__buyPrice> 0 && __buyPrice!= _buyPrice) {
      require(frozenToken);
    }

    commissionPercent = _commissionPercent;
    commissionPercentRel = _commissionPercentRel;
    commissionDate = _commissionDate;

    externalStorage.setTokenName(_tokenName);
    externalStorage.setTokenSymbol(_tokenSymbol);
    externalStorage.setBuyPrice(_buyPrice);
    externalStorage.setCirculationCap(_circulationCap);
    externalStorage.setFoundation(_foundation);
    externalStorage.setBalanceLimit(_balanceLimit);

    emit ConfigChanged(_buyPrice, _circulationCap, _balanceLimit);

    return true;
  }

  function configureFromStorage() public onlySuperAdmins  initStorage returns (bool) {
    freezeToken(true);
    return true;
  }

  function updateStorage(string newStorageName, string newLedgerName) public onlySuperAdmins  returns (bool) {
    require(frozenToken);

    storageName = newStorageName;
    ledgerName = newLedgerName;

    configureFromStorage();

    address ledgerAddress = Registry(registry).getStorage(ledgerName);
    address storageAddress = Registry(registry).getStorage(storageName);
    emit StorageUpdated(storageAddress, ledgerAddress);
    return true;
  }

  function name() public view  returns(string) {
    return bytes32ToString(externalStorage.getTokenName());
  }

  function symbol() public view  returns(string) {
    return bytes32ToString(externalStorage.getTokenSymbol());
  }

  function totalInCirculation() public view  returns(uint256) {
    return tokenLedger.totalInCirculation().add(totalUnvestedAndUnreleasedTokens());
  }

  function tokenBalanceLimit() public view  returns(uint256) {
    return externalStorage.getBalanceLimit();
  }

  function circulationCap() public view unlesspgraded returns(uint256) {
    return externalStorage.getCirculationCap();
  }

  function foundation() public view returns(address) {
    return externalStorage.getFoundation();
  }

  function totalSupply() public view  returns(uint256) {
    return tokenLedger.totalTokens();
  }

  function tokensAvailable() public view unlesspgraded returns(uint256) {
    return totalSupply().sub(totalInCirculation());
  }

  function balanceOf(address account) public view  returns (uint256) {
    address thisAddress = this;
    if (thisAddress == account) {
      return tokensAvailable();
    } else {
      return tokenLedger.balanceOf(account);
    }
  }

  function transfer(address recipient, uint256 amount) public unlessFrozen  returns (bool) {
    require(allowTransfers || whitelistedRecipient[recipient].status);
    require(amount > 0);
    require(!frozenAccount[recipient]);
    uint256 commissionAmount = getCommission(amount);
    uint256 _amount = amount;
    if(commissionAmount > 0) {
      _amount -= commissionAmount;
      tokenLedger.transfer(msg.sender, owner, commissionAmount);
      emit Transfer(msg.sender, owner, commissionAmount);
    }
    tokenLedger.transfer(msg.sender, recipient, _amount);
    emit Transfer(msg.sender, recipient, _amount);

    return true;
  }

  function mintTokens(uint256 mintedAmount) public onlySuperAdmins  returns (bool) {
    uint256 _circulationCap = externalStorage.getCirculationCap();
    tokenLedger.mintTokens(mintedAmount);

    emit Mint(mintedAmount, tokenLedger.totalTokens(), _circulationCap);

    emit Transfer(address(0), this, mintedAmount);

    return true;
  }

  function grantTokens(address recipient, uint256 amount) public onlySuperAdmins  returns (bool) {
    require(amount <= tokensAvailable());
    require(!frozenAccount[recipient]);

    tokenLedger.debitAccount(recipient, amount);
    emit Transfer(this, recipient, amount);

    return true;
  }

  function setHaltPurchase(bool _haltPurchase) public onlySuperAdmins  returns (bool) {
    haltPurchase = _haltPurchase;

    if (_haltPurchase) {
      emit PurchaseHalted();
    } else {
      emit PurchaseResumed();
    }
    return true;
  }

  function foundationDeposit() public payable  returns (bool) {
    return true;
  }

  function allowance(address owner, address spender) public view  returns (uint256) {
    return externalStorage.getAllowance(owner, spender);
  }

  function transferFrom(address from, address to, uint256 value) public unlessFrozen  returns (bool) {
    require(allowTransfers);
    require(!frozenAccount[from]);
    require(!frozenAccount[to]);
    require(from != msg.sender);
    require(value > 0);

    uint256 allowanceValue = allowance(from, msg.sender);
    require(allowanceValue >= value);

    tokenLedger.transfer(from, to, value);
    externalStorage.setAllowance(from, msg.sender, allowanceValue.sub(value));

    emit Transfer(from, to, value);
    return true;
  }

  function approve(address spender, uint256 value) public unlessFrozen  returns (bool) {
    require(spender != address(0));
    require(!frozenAccount[spender]);
    require(msg.sender != spender);

    externalStorage.setAllowance(msg.sender, spender, value);

    emit Approval(msg.sender, spender, value);
    return true;
  }

  function increaseApproval(address spender, uint256 addedValue) public unlessFrozen  returns (bool) {
    return approve(spender, externalStorage.getAllowance(msg.sender, spender).add(addedValue));
  }

  function decreaseApproval(address spender, uint256 subtractedValue) public unlessFrozen  returns (bool) {
    uint256 oldValue = externalStorage.getAllowance(msg.sender, spender);

    if (subtractedValue > oldValue) {
      return approve(spender, 0);
    } else {
      return approve(spender, oldValue.sub(subtractedValue));
    }
  }

  function grantVestedTokens(
    address beneficiary,
    uint256 fullyVestedAmount,
    uint256 startDate, // 0 indicates start "now"
    uint256 cliffSec,
    uint256 durationSec,
    bool isRevocable
  ) public onlySuperAdmins  returns(bool) {

    uint256 _circulationCap = externalStorage.getCirculationCap();

    require(beneficiary != address(0));
    require(!frozenAccount[beneficiary]);
    require(durationSec >= cliffSec);
    require(totalInCirculation().add(fullyVestedAmount) <= _circulationCap);
    require(fullyVestedAmount <= tokensAvailable());

    uint256 _now = now;
    if (startDate == 0) {
      startDate = _now;
    }

    uint256 cliffDate = startDate.add(cliffSec);

    externalStorage.setVestingSchedule(
      beneficiary,
      fullyVestedAmount,
      startDate,
      cliffDate,
      durationSec,
      isRevocable
    );

    emit VestedTokenGrant(beneficiary, startDate, cliffDate, durationSec, fullyVestedAmount, isRevocable);

    return true;
  }


  function revokeVesting(address beneficiary) public onlySuperAdmins  returns (bool) {
    require(beneficiary != address(0));
    externalStorage.revokeVesting(beneficiary);

    releaseVestedTokensForBeneficiary(beneficiary);

    emit VestedTokenRevocation(beneficiary);

    return true;
  }

  function releaseVestedTokens() public unlessFrozen  returns (bool) {
    return releaseVestedTokensForBeneficiary(msg.sender);
  }

  function releaseVestedTokensForBeneficiary(address beneficiary) public unlessFrozen  returns (bool) {
    require(beneficiary != address(0));
    require(!frozenAccount[beneficiary]);

    uint256 unreleased = releasableAmount(beneficiary);

    if (unreleased == 0) { return true; }

    externalStorage.releaseVestedTokens(beneficiary);

    tokenLedger.debitAccount(beneficiary, unreleased);
    emit Transfer(this, beneficiary, unreleased);

    emit VestedTokenRelease(beneficiary, unreleased);

    return true;
  }

  function releasableAmount(address beneficiary) public view  returns (uint256) {
    return externalStorage.releasableAmount(beneficiary);
  }

  function totalUnvestedAndUnreleasedTokens() public view  returns (uint256) {
    return externalStorage.getTotalUnvestedAndUnreleasedTokens();
  }

  function vestingMappingSize() public view  returns (uint256) {
    return externalStorage.vestingMappingSize();
  }

  function vestingBeneficiaryForIndex(uint256 index) public view  returns (address) {
    return externalStorage.vestingBeneficiaryForIndex(index);
  }

  function vestingSchedule(address _beneficiary) public
                                                 view  returns (uint256 startDate,
                                                                              uint256 cliffDate,
                                                                              uint256 durationSec,
                                                                              uint256 fullyVestedAmount,
                                                                              uint256 vestedAmount,
                                                                              uint256 vestedAvailableAmount,
                                                                              uint256 releasedAmount,
                                                                              uint256 revokeDate,
                                                                              bool isRevocable) {
    (
      startDate,
      cliffDate,
      durationSec,
      fullyVestedAmount,
      releasedAmount,
      revokeDate,
      isRevocable
    ) =  externalStorage.getVestingSchedule(_beneficiary);

    vestedAmount = externalStorage.vestedAmount(_beneficiary);
    vestedAvailableAmount = externalStorage.vestedAvailableAmount(_beneficiary);
  }

  function setAllowTransfers(bool _allowTransfers) public onlySuperAdmins returns (bool) {
    allowTransfers = _allowTransfers;
    return true;
  }

  function setContributionMinimum(uint256 _contributionMinimum) public onlySuperAdmins  returns (bool) {
    contributionMinimum = _contributionMinimum;
    return true;
  }

  function totalTransferWhitelistMapping() public view returns (uint256) {
    return whitelistedRecipientForIndex.length;
  }

  function setWhitelistedRecipient(
    address recipient,
    bool _allowTransfers,
    string details,
    address vettingAgent
  ) public onlyAdmins  returns (bool) {
    require(recipient != address(0));
    whitelistedRecipient[recipient] = allowedAddr({status: _allowTransfers, details: details, vettingAgent: vettingAgent});
    if (!processedWhitelistedRecipient[recipient]) {
        whitelistedRecipientForIndex.push(recipient);
        processedWhitelistedRecipient[recipient] = true;
    }

    return true;
  }
}