ETH Price: $2,112.11 (-2.59%)
Gas: 0.13 Gwei

Transaction Decoder

Block:
7499260 at Apr-04-2019 03:20:26 AM +UTC
Transaction Fee:
0.0022734375 ETH $4.80
Gas Used:
303,125 Gas / 7.5 Gwei

Emitted Events:

68 RCNToken.Transfer( _from=[Sender] 0x7e6daa9981e0640feb117d5fd1b6a890db301b90, _to=KyberNetwork, _value=4480849505769414000 )
69 RCNToken.Transfer( _from=KyberNetwork, _to=0x6924a03BB710EaF199AB6AC9F2BB148215AE9B5D, _value=4480849505769414000 )
70 KyberNetwork.EtherReceival( sender=KyberReserve, amount=823269591196911 )
71 KyberReserve.TradeExecute( origin=KyberNetwork, src=RCNToken, srcAmount=4480849505769414000, destToken=0xEeeeeEee...eeeeeEEeE, destAmount=823269591196911, destAddress=KyberNetwork )
72 ZilliqaToken.Transfer( from=KyberReserve, to=KyberNetwork, value=5806579728444 )
73 KyberReserve.TradeExecute( origin=KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=823269591196911, destToken=ZilliqaToken, destAmount=5806579728444, destAddress=KyberNetwork )
74 ZilliqaToken.Transfer( from=KyberNetwork, to=[Sender] 0x7e6daa9981e0640feb117d5fd1b6a890db301b90, value=5806579728444 )
75 FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=582155300936499 )
76 FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=1212823543617707 )
77 KyberNetwork.KyberTrade( trader=[Sender] 0x7e6daa9981e0640feb117d5fd1b6a890db301b90, src=RCNToken, dest=ZilliqaToken, srcAmount=4480849505769414000, dstAmount=5806579728444, destAddress=[Sender] 0x7e6daa9981e0640feb117d5fd1b6a890db301b90, ethWeiValue=823269591196911, reserve1=KyberReserve, reserve2=KyberReserve, hint=0x )
78 KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0x7e6daa9981e0640feb117d5fd1b6a890db301b90, src=RCNToken, dest=ZilliqaToken, actualSrcAmount=4480849505769414000, actualDestAmount=5806579728444 )

Account State Difference:

  Address   Before After State Difference Code
0x05f4a42e...cEF1FAD27
0x097B3B7C...2Cce6EF43
0x21433Dec...f5aC2EC18 35.740639639617612022 Eth35.739816370026415111 Eth0.000823269591196911
0x52166528...1a4e128A3
0x63825c17...bD36A0D8F
(Kyber: Reserve)
1,100.50040364760365366 Eth1,100.501226917194850571 Eth0.000823269591196911
0x798AbDA6...d3d11191B
(Kyber: Conversion Rates)
0x7e6DAA99...0db301B90
0.06399426805 Eth
Nonce: 63
0.06172083055 Eth
Nonce: 64
0.0022734375
(F2Pool Old)
3,782.738201739421234026 Eth3,782.740475176921234026 Eth0.0022734375
0x9ae49C0d...294E20950
(Kyber: Old Contract)
0xF970b8E3...8D83375A6

Execution Trace

KyberNetworkProxy.trade( src=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, srcAmount=4480849505769414000, dest=0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27, destAddress=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=1259372766566230000, walletId=0x0000000000000000000000000000000000000000 ) => ( 5806579728444 )
  • RCNToken.balanceOf( _owner=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90 ) => ( balance=4480849505769414370 )
  • ZilliqaToken.balanceOf( _owner=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90 ) => ( balance=0 )
  • RCNToken.transferFrom( _from=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90, _to=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, _value=4480849505769414000 ) => ( success=True )
  • KyberNetwork.tradeWithHint( trader=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90, src=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, srcAmount=4480849505769414000, dest=0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27, destAddress=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=1259372766566230000, walletId=0x0000000000000000000000000000000000000000, hint=0x ) => ( 5806579728444 )
    • RCNToken.balanceOf( _owner=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950 ) => ( balance=4480849505769414004 )
    • KyberReserve.getConversionRate( src=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=4480849505769414000, blockNumber=7499260 ) => ( 183730694400000 )
      • ConversionRates.getRate( token=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, currentBlockNumber=7499260, buy=False, qty=4480849505769414000 ) => ( 183730694400000 )
      • RCNToken.CALL( )
      • KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27, srcQty=823269591196911, blockNumber=7499260 ) => ( 7053072031973211436482 )
        • ConversionRates.getRate( token=0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27, currentBlockNumber=7499260, buy=True, qty=823269591196911 ) => ( 7053072031973211436482 )
        • ZilliqaToken.balanceOf( _owner=0x63825c174ab367968EC60f061753D3bbD36A0D8F ) => ( balance=95707153968752479 )
        • KyberReserve.trade( srcToken=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, srcAmount=4480849505769414000, destToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, conversionRate=183730694400000, validate=True ) => ( True )
          • RCNToken.CALL( )
          • ConversionRates.recordImbalance( token=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, buyAmount=-4480849505769414000, rateUpdateBlock=0, currentBlock=7499260 )
          • RCNToken.transferFrom( _from=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, _to=0x6924a03BB710EaF199AB6AC9F2BB148215AE9B5D, _value=4480849505769414000 ) => ( success=True )
          • ETH 0.000823269591196911 KyberNetwork.CALL( )
          • ETH 0.000823269591196911 KyberReserve.trade( srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=823269591196911, destToken=0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27, destAddress=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, conversionRate=7053072031973211436482, validate=True ) => ( True )
            • ConversionRates.recordImbalance( token=0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27, buyAmount=5806579728444, rateUpdateBlock=0, currentBlock=7499260 )
            • ZilliqaToken.transfer( _to=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, _value=5806579728444 ) => ( True )
            • ZilliqaToken.transfer( _to=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90, _value=5806579728444 ) => ( True )
            • FeeBurner.handleFees( tradeWeiAmount=823269591196911, reserve=0x21433Dec9Cb634A23c6A4BbcCe08c83f5aC2EC18, wallet=0x0000000000000000000000000000000000000000 ) => ( True )
              • KyberNetworkCrystal.CALL( )
              • FeeBurner.handleFees( tradeWeiAmount=823269591196911, reserve=0x63825c174ab367968EC60f061753D3bbD36A0D8F, wallet=0x0000000000000000000000000000000000000000 ) => ( True )
                • KyberNetworkCrystal.CALL( )
                • RCNToken.balanceOf( _owner=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90 ) => ( balance=370 )
                • ZilliqaToken.balanceOf( _owner=0x7e6DAA9981e0640FEb117d5fD1B6a890db301B90 ) => ( balance=5806579728444 )
                  File 1 of 10: KyberNetworkProxy
                  pragma solidity 0.4.18;
                  
                  // File: contracts/ERC20Interface.sol
                  
                  // https://github.com/ethereum/EIPs/issues/20
                  interface ERC20 {
                      function totalSupply() public view returns (uint supply);
                      function balanceOf(address _owner) public view returns (uint balance);
                      function transfer(address _to, uint _value) public returns (bool success);
                      function transferFrom(address _from, address _to, uint _value) public returns (bool success);
                      function approve(address _spender, uint _value) public returns (bool success);
                      function allowance(address _owner, address _spender) public view returns (uint remaining);
                      function decimals() public view returns(uint digits);
                      event Approval(address indexed _owner, address indexed _spender, uint _value);
                  }
                  
                  // File: contracts/KyberNetworkInterface.sol
                  
                  /// @title Kyber Network interface
                  interface KyberNetworkInterface {
                      function maxGasPrice() public view returns(uint);
                      function getUserCapInWei(address user) public view returns(uint);
                      function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
                      function enabled() public view returns(bool);
                      function info(bytes32 id) public view returns(uint);
                  
                      function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
                          returns (uint expectedRate, uint slippageRate);
                  
                      function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,
                          uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
                  }
                  
                  // File: contracts/KyberNetworkProxyInterface.sol
                  
                  /// @title Kyber Network interface
                  interface KyberNetworkProxyInterface {
                      function maxGasPrice() public view returns(uint);
                      function getUserCapInWei(address user) public view returns(uint);
                      function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
                      function enabled() public view returns(bool);
                      function info(bytes32 id) public view returns(uint);
                  
                      function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
                          returns (uint expectedRate, uint slippageRate);
                  
                      function tradeWithHint(ERC20 src, uint srcAmount, ERC20 dest, address destAddress, uint maxDestAmount,
                          uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
                  }
                  
                  // File: contracts/SimpleNetworkInterface.sol
                  
                  /// @title simple interface for Kyber Network 
                  interface SimpleNetworkInterface {
                      function swapTokenToToken(ERC20 src, uint srcAmount, ERC20 dest, uint minConversionRate) public returns(uint);
                      function swapEtherToToken(ERC20 token, uint minConversionRate) public payable returns(uint);
                      function swapTokenToEther(ERC20 token, uint srcAmount, uint minConversionRate) public returns(uint);
                  }
                  
                  // File: contracts/Utils.sol
                  
                  /// @title Kyber constants contract
                  contract Utils {
                  
                      ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
                      uint  constant internal PRECISION = (10**18);
                      uint  constant internal MAX_QTY   = (10**28); // 10B tokens
                      uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
                      uint  constant internal MAX_DECIMALS = 18;
                      uint  constant internal ETH_DECIMALS = 18;
                      mapping(address=>uint) internal decimals;
                  
                      function setDecimals(ERC20 token) internal {
                          if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                          else decimals[token] = token.decimals();
                      }
                  
                      function getDecimals(ERC20 token) internal view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                          uint tokenDecimals = decimals[token];
                          // technically, there might be token with decimals 0
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          if(tokenDecimals == 0) return token.decimals();
                  
                          return tokenDecimals;
                      }
                  
                      function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(srcQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                          }
                      }
                  
                      function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(dstQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                          
                          //source quantity is rounded up. to avoid dest quantity being too low.
                          uint numerator;
                          uint denominator;
                          if (srcDecimals >= dstDecimals) {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                              denominator = rate;
                          } else {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty);
                              denominator = (rate * (10**(dstDecimals - srcDecimals)));
                          }
                          return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                      }
                  }
                  
                  // File: contracts/Utils2.sol
                  
                  contract Utils2 is Utils {
                  
                      /// @dev get the balance of a user.
                      /// @param token The token type
                      /// @return The balance
                      function getBalance(ERC20 token, address user) public view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS)
                              return user.balance;
                          else
                              return token.balanceOf(user);
                      }
                  
                      function getDecimalsSafe(ERC20 token) internal returns(uint) {
                  
                          if (decimals[token] == 0) {
                              setDecimals(token);
                          }
                  
                          return decimals[token];
                      }
                  
                      function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {
                          return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                      }
                  
                      function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {
                          return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
                      }
                  
                      function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)
                          internal pure returns(uint)
                      {
                          require(srcAmount <= MAX_QTY);
                          require(destAmount <= MAX_QTY);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);
                          }
                      }
                  }
                  
                  // File: contracts/PermissionGroups.sol
                  
                  contract PermissionGroups {
                  
                      address public admin;
                      address public pendingAdmin;
                      mapping(address=>bool) internal operators;
                      mapping(address=>bool) internal alerters;
                      address[] internal operatorsGroup;
                      address[] internal alertersGroup;
                      uint constant internal MAX_GROUP_SIZE = 50;
                  
                      function PermissionGroups() public {
                          admin = msg.sender;
                      }
                  
                      modifier onlyAdmin() {
                          require(msg.sender == admin);
                          _;
                      }
                  
                      modifier onlyOperator() {
                          require(operators[msg.sender]);
                          _;
                      }
                  
                      modifier onlyAlerter() {
                          require(alerters[msg.sender]);
                          _;
                      }
                  
                      function getOperators () external view returns(address[]) {
                          return operatorsGroup;
                      }
                  
                      function getAlerters () external view returns(address[]) {
                          return alertersGroup;
                      }
                  
                      event TransferAdminPending(address pendingAdmin);
                  
                      /**
                       * @dev Allows the current admin to set the pendingAdmin address.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdmin(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(pendingAdmin);
                          pendingAdmin = newAdmin;
                      }
                  
                      /**
                       * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdminQuickly(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(newAdmin);
                          AdminClaimed(newAdmin, admin);
                          admin = newAdmin;
                      }
                  
                      event AdminClaimed( address newAdmin, address previousAdmin);
                  
                      /**
                       * @dev Allows the pendingAdmin address to finalize the change admin process.
                       */
                      function claimAdmin() public {
                          require(pendingAdmin == msg.sender);
                          AdminClaimed(pendingAdmin, admin);
                          admin = pendingAdmin;
                          pendingAdmin = address(0);
                      }
                  
                      event AlerterAdded (address newAlerter, bool isAdd);
                  
                      function addAlerter(address newAlerter) public onlyAdmin {
                          require(!alerters[newAlerter]); // prevent duplicates.
                          require(alertersGroup.length < MAX_GROUP_SIZE);
                  
                          AlerterAdded(newAlerter, true);
                          alerters[newAlerter] = true;
                          alertersGroup.push(newAlerter);
                      }
                  
                      function removeAlerter (address alerter) public onlyAdmin {
                          require(alerters[alerter]);
                          alerters[alerter] = false;
                  
                          for (uint i = 0; i < alertersGroup.length; ++i) {
                              if (alertersGroup[i] == alerter) {
                                  alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                  alertersGroup.length--;
                                  AlerterAdded(alerter, false);
                                  break;
                              }
                          }
                      }
                  
                      event OperatorAdded(address newOperator, bool isAdd);
                  
                      function addOperator(address newOperator) public onlyAdmin {
                          require(!operators[newOperator]); // prevent duplicates.
                          require(operatorsGroup.length < MAX_GROUP_SIZE);
                  
                          OperatorAdded(newOperator, true);
                          operators[newOperator] = true;
                          operatorsGroup.push(newOperator);
                      }
                  
                      function removeOperator (address operator) public onlyAdmin {
                          require(operators[operator]);
                          operators[operator] = false;
                  
                          for (uint i = 0; i < operatorsGroup.length; ++i) {
                              if (operatorsGroup[i] == operator) {
                                  operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                  operatorsGroup.length -= 1;
                                  OperatorAdded(operator, false);
                                  break;
                              }
                          }
                      }
                  }
                  
                  // File: contracts/Withdrawable.sol
                  
                  /**
                   * @title Contracts that should be able to recover tokens or ethers
                   * @author Ilan Doron
                   * @dev This allows to recover any tokens or Ethers received in a contract.
                   * This will prevent any accidental loss of tokens.
                   */
                  contract Withdrawable is PermissionGroups {
                  
                      event TokenWithdraw(ERC20 token, uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw all ERC20 compatible tokens
                       * @param token ERC20 The address of the token contract
                       */
                      function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                          require(token.transfer(sendTo, amount));
                          TokenWithdraw(token, amount, sendTo);
                      }
                  
                      event EtherWithdraw(uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw Ethers
                       */
                      function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                          sendTo.transfer(amount);
                          EtherWithdraw(amount, sendTo);
                      }
                  }
                  
                  // File: contracts/KyberNetworkProxy.sol
                  
                  ////////////////////////////////////////////////////////////////////////////////////////////////////////
                  /// @title Kyber Network proxy for main contract
                  contract KyberNetworkProxy is KyberNetworkProxyInterface, SimpleNetworkInterface, Withdrawable, Utils2 {
                  
                      KyberNetworkInterface public kyberNetworkContract;
                  
                      function KyberNetworkProxy(address _admin) public {
                          require(_admin != address(0));
                          admin = _admin;
                      }
                  
                      /// @notice use token address ETH_TOKEN_ADDRESS for ether
                      /// @dev makes a trade between src and dest token and send dest token to destAddress
                      /// @param src Src token
                      /// @param srcAmount amount of src tokens
                      /// @param dest   Destination token
                      /// @param destAddress Address to send tokens to
                      /// @param maxDestAmount A limit on the amount of dest tokens
                      /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
                      /// @param walletId is the wallet ID to send part of the fees
                      /// @return amount of actual dest tokens
                      function trade(
                          ERC20 src,
                          uint srcAmount,
                          ERC20 dest,
                          address destAddress,
                          uint maxDestAmount,
                          uint minConversionRate,
                          address walletId
                      )
                          public
                          payable
                          returns(uint)
                      {
                          bytes memory hint;
                  
                          return tradeWithHint(
                              src,
                              srcAmount,
                              dest,
                              destAddress,
                              maxDestAmount,
                              minConversionRate,
                              walletId,
                              hint
                          );
                      }
                  
                      /// @dev makes a trade between src and dest token and send dest tokens to msg sender
                      /// @param src Src token
                      /// @param srcAmount amount of src tokens
                      /// @param dest Destination token
                      /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
                      /// @return amount of actual dest tokens
                      function swapTokenToToken(
                          ERC20 src,
                          uint srcAmount,
                          ERC20 dest,
                          uint minConversionRate
                      )
                          public
                          returns(uint)
                      {
                          bytes memory hint;
                  
                          return tradeWithHint(
                              src,
                              srcAmount,
                              dest,
                              msg.sender,
                              MAX_QTY,
                              minConversionRate,
                              0,
                              hint
                          );
                      }
                  
                      /// @dev makes a trade from Ether to token. Sends token to msg sender
                      /// @param token Destination token
                      /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
                      /// @return amount of actual dest tokens
                      function swapEtherToToken(ERC20 token, uint minConversionRate) public payable returns(uint) {
                          bytes memory hint;
                  
                          return tradeWithHint(
                              ETH_TOKEN_ADDRESS,
                              msg.value,
                              token,
                              msg.sender,
                              MAX_QTY,
                              minConversionRate,
                              0,
                              hint
                          );
                      }
                  
                      /// @dev makes a trade from token to Ether, sends Ether to msg sender
                      /// @param token Src token
                      /// @param srcAmount amount of src tokens
                      /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
                      /// @return amount of actual dest tokens
                      function swapTokenToEther(ERC20 token, uint srcAmount, uint minConversionRate) public returns(uint) {
                          bytes memory hint;
                  
                          return tradeWithHint(
                              token,
                              srcAmount,
                              ETH_TOKEN_ADDRESS,
                              msg.sender,
                              MAX_QTY,
                              minConversionRate,
                              0,
                              hint
                          );
                      }
                  
                      struct UserBalance {
                          uint srcBalance;
                          uint destBalance;
                      }
                  
                      event ExecuteTrade(address indexed trader, ERC20 src, ERC20 dest, uint actualSrcAmount, uint actualDestAmount);
                  
                      /// @notice use token address ETH_TOKEN_ADDRESS for ether
                      /// @dev makes a trade between src and dest token and send dest token to destAddress
                      /// @param src Src token
                      /// @param srcAmount amount of src tokens
                      /// @param dest Destination token
                      /// @param destAddress Address to send tokens to
                      /// @param maxDestAmount A limit on the amount of dest tokens
                      /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
                      /// @param walletId is the wallet ID to send part of the fees
                      /// @param hint will give hints for the trade.
                      /// @return amount of actual dest tokens
                      function tradeWithHint(
                          ERC20 src,
                          uint srcAmount,
                          ERC20 dest,
                          address destAddress,
                          uint maxDestAmount,
                          uint minConversionRate,
                          address walletId,
                          bytes hint
                      )
                          public
                          payable
                          returns(uint)
                      {
                          require(src == ETH_TOKEN_ADDRESS || msg.value == 0);
                          
                          UserBalance memory userBalanceBefore;
                  
                          userBalanceBefore.srcBalance = getBalance(src, msg.sender);
                          userBalanceBefore.destBalance = getBalance(dest, destAddress);
                  
                          if (src == ETH_TOKEN_ADDRESS) {
                              userBalanceBefore.srcBalance += msg.value;
                          } else {
                              require(src.transferFrom(msg.sender, kyberNetworkContract, srcAmount));
                          }
                  
                          uint reportedDestAmount = kyberNetworkContract.tradeWithHint.value(msg.value)(
                              msg.sender,
                              src,
                              srcAmount,
                              dest,
                              destAddress,
                              maxDestAmount,
                              minConversionRate,
                              walletId,
                              hint
                          );
                  
                          TradeOutcome memory tradeOutcome = calculateTradeOutcome(
                              userBalanceBefore.srcBalance,
                              userBalanceBefore.destBalance,
                              src,
                              dest,
                              destAddress
                          );
                  
                          require(reportedDestAmount == tradeOutcome.userDeltaDestAmount);
                          require(tradeOutcome.userDeltaDestAmount <= maxDestAmount);
                          require(tradeOutcome.actualRate >= minConversionRate);
                  
                          ExecuteTrade(msg.sender, src, dest, tradeOutcome.userDeltaSrcAmount, tradeOutcome.userDeltaDestAmount);
                          return tradeOutcome.userDeltaDestAmount;
                      }
                  
                      event KyberNetworkSet(address newNetworkContract, address oldNetworkContract);
                  
                      function setKyberNetworkContract(KyberNetworkInterface _kyberNetworkContract) public onlyAdmin {
                  
                          require(_kyberNetworkContract != address(0));
                  
                          KyberNetworkSet(_kyberNetworkContract, kyberNetworkContract);
                  
                          kyberNetworkContract = _kyberNetworkContract;
                      }
                  
                      function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty)
                          public view
                          returns(uint expectedRate, uint slippageRate)
                      {
                          return kyberNetworkContract.getExpectedRate(src, dest, srcQty);
                      }
                  
                      function getUserCapInWei(address user) public view returns(uint) {
                          return kyberNetworkContract.getUserCapInWei(user);
                      }
                  
                      function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint) {
                          return kyberNetworkContract.getUserCapInTokenWei(user, token);
                      }
                  
                      function maxGasPrice() public view returns(uint) {
                          return kyberNetworkContract.maxGasPrice();
                      }
                  
                      function enabled() public view returns(bool) {
                          return kyberNetworkContract.enabled();
                      }
                  
                      function info(bytes32 field) public view returns(uint) {
                          return kyberNetworkContract.info(field);
                      }
                  
                      struct TradeOutcome {
                          uint userDeltaSrcAmount;
                          uint userDeltaDestAmount;
                          uint actualRate;
                      }
                  
                      function calculateTradeOutcome (uint srcBalanceBefore, uint destBalanceBefore, ERC20 src, ERC20 dest,
                          address destAddress)
                          internal returns(TradeOutcome outcome)
                      {
                          uint userSrcBalanceAfter;
                          uint userDestBalanceAfter;
                  
                          userSrcBalanceAfter = getBalance(src, msg.sender);
                          userDestBalanceAfter = getBalance(dest, destAddress);
                  
                          //protect from underflow
                          require(userDestBalanceAfter > destBalanceBefore);
                          require(srcBalanceBefore > userSrcBalanceAfter);
                  
                          outcome.userDeltaDestAmount = userDestBalanceAfter - destBalanceBefore;
                          outcome.userDeltaSrcAmount = srcBalanceBefore - userSrcBalanceAfter;
                  
                          outcome.actualRate = calcRateFromQty(
                                  outcome.userDeltaSrcAmount,
                                  outcome.userDeltaDestAmount,
                                  getDecimalsSafe(src),
                                  getDecimalsSafe(dest)
                              );
                      }
                  }

                  File 2 of 10: KyberNetwork
                  pragma solidity 0.4.18;
                  
                  // File: contracts/ERC20Interface.sol
                  
                  // https://github.com/ethereum/EIPs/issues/20
                  interface ERC20 {
                      function totalSupply() public view returns (uint supply);
                      function balanceOf(address _owner) public view returns (uint balance);
                      function transfer(address _to, uint _value) public returns (bool success);
                      function transferFrom(address _from, address _to, uint _value) public returns (bool success);
                      function approve(address _spender, uint _value) public returns (bool success);
                      function allowance(address _owner, address _spender) public view returns (uint remaining);
                      function decimals() public view returns(uint digits);
                      event Approval(address indexed _owner, address indexed _spender, uint _value);
                  }
                  
                  // File: contracts/KyberReserveInterface.sol
                  
                  /// @title Kyber Reserve contract
                  interface KyberReserveInterface {
                  
                      function trade(
                          ERC20 srcToken,
                          uint srcAmount,
                          ERC20 destToken,
                          address destAddress,
                          uint conversionRate,
                          bool validate
                      )
                          public
                          payable
                          returns(bool);
                  
                      function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint);
                  }
                  
                  // File: contracts/KyberNetworkInterface.sol
                  
                  /// @title Kyber Network interface
                  interface KyberNetworkInterface {
                      function maxGasPrice() public view returns(uint);
                      function getUserCapInWei(address user) public view returns(uint);
                      function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
                      function enabled() public view returns(bool);
                      function info(bytes32 id) public view returns(uint);
                  
                      function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
                          returns (uint expectedRate, uint slippageRate);
                  
                      function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,
                          uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
                  }
                  
                  // File: contracts/PermissionGroups.sol
                  
                  contract PermissionGroups {
                  
                      address public admin;
                      address public pendingAdmin;
                      mapping(address=>bool) internal operators;
                      mapping(address=>bool) internal alerters;
                      address[] internal operatorsGroup;
                      address[] internal alertersGroup;
                      uint constant internal MAX_GROUP_SIZE = 50;
                  
                      function PermissionGroups() public {
                          admin = msg.sender;
                      }
                  
                      modifier onlyAdmin() {
                          require(msg.sender == admin);
                          _;
                      }
                  
                      modifier onlyOperator() {
                          require(operators[msg.sender]);
                          _;
                      }
                  
                      modifier onlyAlerter() {
                          require(alerters[msg.sender]);
                          _;
                      }
                  
                      function getOperators () external view returns(address[]) {
                          return operatorsGroup;
                      }
                  
                      function getAlerters () external view returns(address[]) {
                          return alertersGroup;
                      }
                  
                      event TransferAdminPending(address pendingAdmin);
                  
                      /**
                       * @dev Allows the current admin to set the pendingAdmin address.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdmin(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(pendingAdmin);
                          pendingAdmin = newAdmin;
                      }
                  
                      /**
                       * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdminQuickly(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(newAdmin);
                          AdminClaimed(newAdmin, admin);
                          admin = newAdmin;
                      }
                  
                      event AdminClaimed( address newAdmin, address previousAdmin);
                  
                      /**
                       * @dev Allows the pendingAdmin address to finalize the change admin process.
                       */
                      function claimAdmin() public {
                          require(pendingAdmin == msg.sender);
                          AdminClaimed(pendingAdmin, admin);
                          admin = pendingAdmin;
                          pendingAdmin = address(0);
                      }
                  
                      event AlerterAdded (address newAlerter, bool isAdd);
                  
                      function addAlerter(address newAlerter) public onlyAdmin {
                          require(!alerters[newAlerter]); // prevent duplicates.
                          require(alertersGroup.length < MAX_GROUP_SIZE);
                  
                          AlerterAdded(newAlerter, true);
                          alerters[newAlerter] = true;
                          alertersGroup.push(newAlerter);
                      }
                  
                      function removeAlerter (address alerter) public onlyAdmin {
                          require(alerters[alerter]);
                          alerters[alerter] = false;
                  
                          for (uint i = 0; i < alertersGroup.length; ++i) {
                              if (alertersGroup[i] == alerter) {
                                  alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                  alertersGroup.length--;
                                  AlerterAdded(alerter, false);
                                  break;
                              }
                          }
                      }
                  
                      event OperatorAdded(address newOperator, bool isAdd);
                  
                      function addOperator(address newOperator) public onlyAdmin {
                          require(!operators[newOperator]); // prevent duplicates.
                          require(operatorsGroup.length < MAX_GROUP_SIZE);
                  
                          OperatorAdded(newOperator, true);
                          operators[newOperator] = true;
                          operatorsGroup.push(newOperator);
                      }
                  
                      function removeOperator (address operator) public onlyAdmin {
                          require(operators[operator]);
                          operators[operator] = false;
                  
                          for (uint i = 0; i < operatorsGroup.length; ++i) {
                              if (operatorsGroup[i] == operator) {
                                  operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                  operatorsGroup.length -= 1;
                                  OperatorAdded(operator, false);
                                  break;
                              }
                          }
                      }
                  }
                  
                  // File: contracts/Withdrawable.sol
                  
                  /**
                   * @title Contracts that should be able to recover tokens or ethers
                   * @author Ilan Doron
                   * @dev This allows to recover any tokens or Ethers received in a contract.
                   * This will prevent any accidental loss of tokens.
                   */
                  contract Withdrawable is PermissionGroups {
                  
                      event TokenWithdraw(ERC20 token, uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw all ERC20 compatible tokens
                       * @param token ERC20 The address of the token contract
                       */
                      function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                          require(token.transfer(sendTo, amount));
                          TokenWithdraw(token, amount, sendTo);
                      }
                  
                      event EtherWithdraw(uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw Ethers
                       */
                      function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                          sendTo.transfer(amount);
                          EtherWithdraw(amount, sendTo);
                      }
                  }
                  
                  // File: contracts/Utils.sol
                  
                  /// @title Kyber constants contract
                  contract Utils {
                  
                      ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
                      uint  constant internal PRECISION = (10**18);
                      uint  constant internal MAX_QTY   = (10**28); // 10B tokens
                      uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
                      uint  constant internal MAX_DECIMALS = 18;
                      uint  constant internal ETH_DECIMALS = 18;
                      mapping(address=>uint) internal decimals;
                  
                      function setDecimals(ERC20 token) internal {
                          if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                          else decimals[token] = token.decimals();
                      }
                  
                      function getDecimals(ERC20 token) internal view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                          uint tokenDecimals = decimals[token];
                          // technically, there might be token with decimals 0
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          if(tokenDecimals == 0) return token.decimals();
                  
                          return tokenDecimals;
                      }
                  
                      function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(srcQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                          }
                      }
                  
                      function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(dstQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                          
                          //source quantity is rounded up. to avoid dest quantity being too low.
                          uint numerator;
                          uint denominator;
                          if (srcDecimals >= dstDecimals) {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                              denominator = rate;
                          } else {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty);
                              denominator = (rate * (10**(dstDecimals - srcDecimals)));
                          }
                          return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                      }
                  }
                  
                  // File: contracts/Utils2.sol
                  
                  contract Utils2 is Utils {
                  
                      /// @dev get the balance of a user.
                      /// @param token The token type
                      /// @return The balance
                      function getBalance(ERC20 token, address user) public view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS)
                              return user.balance;
                          else
                              return token.balanceOf(user);
                      }
                  
                      function getDecimalsSafe(ERC20 token) internal returns(uint) {
                  
                          if (decimals[token] == 0) {
                              setDecimals(token);
                          }
                  
                          return decimals[token];
                      }
                  
                      function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {
                          return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                      }
                  
                      function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {
                          return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
                      }
                  
                      function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)
                          internal pure returns(uint)
                      {
                          require(srcAmount <= MAX_QTY);
                          require(destAmount <= MAX_QTY);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);
                          }
                      }
                  }
                  
                  // File: contracts/WhiteListInterface.sol
                  
                  contract WhiteListInterface {
                      function getUserCapInWei(address user) external view returns (uint userCapWei);
                  }
                  
                  // File: contracts/ExpectedRateInterface.sol
                  
                  interface ExpectedRateInterface {
                      function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty, bool usePermissionless) public view
                          returns (uint expectedRate, uint slippageRate);
                  }
                  
                  // File: contracts/FeeBurnerInterface.sol
                  
                  interface FeeBurnerInterface {
                      function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
                      function setReserveData(address reserve, uint feesInBps, address kncWallet) public;
                  }
                  
                  // File: contracts/KyberNetwork.sol
                  
                  /**
                   * @title Helps contracts guard against reentrancy attacks.
                   */
                  contract ReentrancyGuard {
                  
                      /// @dev counter to allow mutex lock with only one SSTORE operation
                      uint256 private guardCounter = 1;
                  
                      /**
                       * @dev Prevents a function from calling itself, directly or indirectly.
                       * Calling one `nonReentrant` function from
                       * another is not supported. Instead, you can implement a
                       * `private` function doing the actual work, and an `external`
                       * wrapper marked as `nonReentrant`.
                       */
                      modifier nonReentrant() {
                          guardCounter += 1;
                          uint256 localCounter = guardCounter;
                          _;
                          require(localCounter == guardCounter);
                      }
                  }
                  
                  
                  ////////////////////////////////////////////////////////////////////////////////////////////////////////
                  /// @title Kyber Network main contract
                  contract KyberNetwork is Withdrawable, Utils2, KyberNetworkInterface, ReentrancyGuard {
                  
                      bytes public constant PERM_HINT = "PERM";
                      uint  public constant PERM_HINT_GET_RATE = 1 << 255; // for get rate. bit mask hint.
                  
                      uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01%
                      KyberReserveInterface[] public reserves;
                      mapping(address=>ReserveType) public reserveType;
                      WhiteListInterface public whiteListContract;
                      ExpectedRateInterface public expectedRateContract;
                      FeeBurnerInterface    public feeBurnerContract;
                      address               public kyberNetworkProxyContract;
                      uint                  public maxGasPriceValue = 50 * 1000 * 1000 * 1000; // 50 gwei
                      bool                  public isEnabled = false; // network is enabled
                      mapping(bytes32=>uint) public infoFields; // this is only a UI field for external app.
                  
                      mapping(address=>address[]) public reservesPerTokenSrc; //reserves supporting token to eth
                      mapping(address=>address[]) public reservesPerTokenDest;//reserves support eth to token
                  
                      enum ReserveType {NONE, PERMISSIONED, PERMISSIONLESS}
                      bytes internal constant EMPTY_HINT = "";
                  
                      function KyberNetwork(address _admin) public {
                          require(_admin != address(0));
                          admin = _admin;
                      }
                  
                      event EtherReceival(address indexed sender, uint amount);
                  
                      /* solhint-disable no-complex-fallback */
                      // To avoid users trying to swap tokens using default payable function. We added this short code
                      //  to verify Ethers will be received only from reserves if transferred without a specific function call.
                      function() public payable {
                          require(reserveType[msg.sender] != ReserveType.NONE);
                          EtherReceival(msg.sender, msg.value);
                      }
                      /* solhint-enable no-complex-fallback */
                  
                      struct TradeInput {
                          address trader;
                          ERC20 src;
                          uint srcAmount;
                          ERC20 dest;
                          address destAddress;
                          uint maxDestAmount;
                          uint minConversionRate;
                          address walletId;
                          bytes hint;
                      }
                  
                      function tradeWithHint(
                          address trader,
                          ERC20 src,
                          uint srcAmount,
                          ERC20 dest,
                          address destAddress,
                          uint maxDestAmount,
                          uint minConversionRate,
                          address walletId,
                          bytes hint
                      )
                          public
                          nonReentrant
                          payable
                          returns(uint)
                      {
                          require(msg.sender == kyberNetworkProxyContract);
                          require((hint.length == 0) || (hint.length == 4));
                  
                          TradeInput memory tradeInput;
                  
                          tradeInput.trader = trader;
                          tradeInput.src = src;
                          tradeInput.srcAmount = srcAmount;
                          tradeInput.dest = dest;
                          tradeInput.destAddress = destAddress;
                          tradeInput.maxDestAmount = maxDestAmount;
                          tradeInput.minConversionRate = minConversionRate;
                          tradeInput.walletId = walletId;
                          tradeInput.hint = hint;
                  
                          return trade(tradeInput);
                      }
                  
                      event AddReserveToNetwork(KyberReserveInterface indexed reserve, bool add, bool isPermissionless);
                  
                      /// @notice can be called only by operator
                      /// @dev add or deletes a reserve to/from the network.
                      /// @param reserve The reserve address.
                      /// @param isPermissionless is the new reserve from permissionless type.
                      function addReserve(KyberReserveInterface reserve, bool isPermissionless) public onlyOperator
                          returns(bool)
                      {
                          require(reserveType[reserve] == ReserveType.NONE);
                          reserves.push(reserve);
                  
                          reserveType[reserve] = isPermissionless ? ReserveType.PERMISSIONLESS : ReserveType.PERMISSIONED;
                  
                          AddReserveToNetwork(reserve, true, isPermissionless);
                  
                          return true;
                      }
                  
                      event RemoveReserveFromNetwork(KyberReserveInterface reserve);
                  
                      /// @notice can be called only by operator
                      /// @dev removes a reserve from Kyber network.
                      /// @param reserve The reserve address.
                      /// @param index in reserve array.
                      function removeReserve(KyberReserveInterface reserve, uint index) public onlyOperator
                          returns(bool)
                      {
                  
                          require(reserveType[reserve] != ReserveType.NONE);
                          require(reserves[index] == reserve);
                  
                          reserveType[reserve] = ReserveType.NONE;
                          reserves[index] = reserves[reserves.length - 1];
                          reserves.length--;
                  
                          RemoveReserveFromNetwork(reserve);
                  
                          return true;
                      }
                  
                      event ListReservePairs(address indexed reserve, ERC20 src, ERC20 dest, bool add);
                  
                      /// @notice can be called only by operator
                      /// @dev allow or prevent a specific reserve to trade a pair of tokens
                      /// @param reserve The reserve address.
                      /// @param token token address
                      /// @param ethToToken will it support ether to token trade
                      /// @param tokenToEth will it support token to ether trade
                      /// @param add If true then list this pair, otherwise unlist it.
                      function listPairForReserve(address reserve, ERC20 token, bool ethToToken, bool tokenToEth, bool add)
                          public
                          onlyOperator
                          returns(bool)
                      {
                          require(reserveType[reserve] != ReserveType.NONE);
                  
                          if (ethToToken) {
                              listPairs(reserve, token, false, add);
                  
                              ListReservePairs(reserve, ETH_TOKEN_ADDRESS, token, add);
                          }
                  
                          if (tokenToEth) {
                              listPairs(reserve, token, true, add);
                  
                              if (add) {
                                  require(token.approve(reserve, 2**255)); // approve infinity
                              } else {
                                  require(token.approve(reserve, 0));
                              }
                  
                              ListReservePairs(reserve, token, ETH_TOKEN_ADDRESS, add);
                          }
                  
                          setDecimals(token);
                  
                          return true;
                      }
                  
                      event WhiteListContractSet(WhiteListInterface newContract, WhiteListInterface currentContract);
                  
                      ///@param whiteList can be empty
                      function setWhiteList(WhiteListInterface whiteList) public onlyAdmin {
                          WhiteListContractSet(whiteList, whiteListContract);
                          whiteListContract = whiteList;
                      }
                  
                      event ExpectedRateContractSet(ExpectedRateInterface newContract, ExpectedRateInterface currentContract);
                  
                      function setExpectedRate(ExpectedRateInterface expectedRate) public onlyAdmin {
                          require(expectedRate != address(0));
                  
                          ExpectedRateContractSet(expectedRate, expectedRateContract);
                          expectedRateContract = expectedRate;
                      }
                  
                      event FeeBurnerContractSet(FeeBurnerInterface newContract, FeeBurnerInterface currentContract);
                  
                      function setFeeBurner(FeeBurnerInterface feeBurner) public onlyAdmin {
                          require(feeBurner != address(0));
                  
                          FeeBurnerContractSet(feeBurner, feeBurnerContract);
                          feeBurnerContract = feeBurner;
                      }
                  
                      event KyberNetwrokParamsSet(uint maxGasPrice, uint negligibleRateDiff);
                  
                      function setParams(
                          uint                  _maxGasPrice,
                          uint                  _negligibleRateDiff
                      )
                          public
                          onlyAdmin
                      {
                          require(_negligibleRateDiff <= 100 * 100); // at most 100%
                  
                          maxGasPriceValue = _maxGasPrice;
                          negligibleRateDiff = _negligibleRateDiff;
                          KyberNetwrokParamsSet(maxGasPriceValue, negligibleRateDiff);
                      }
                  
                      event KyberNetworkSetEnable(bool isEnabled);
                  
                      function setEnable(bool _enable) public onlyAdmin {
                          if (_enable) {
                              require(feeBurnerContract != address(0));
                              require(expectedRateContract != address(0));
                              require(kyberNetworkProxyContract != address(0));
                          }
                          isEnabled = _enable;
                  
                          KyberNetworkSetEnable(isEnabled);
                      }
                  
                      function setInfo(bytes32 field, uint value) public onlyOperator {
                          infoFields[field] = value;
                      }
                  
                      event KyberProxySet(address proxy, address sender);
                  
                      function setKyberProxy(address networkProxy) public onlyAdmin {
                          require(networkProxy != address(0));
                          kyberNetworkProxyContract = networkProxy;
                          KyberProxySet(kyberNetworkProxyContract, msg.sender);
                      }
                  
                      /// @dev returns number of reserves
                      /// @return number of reserves
                      function getNumReserves() public view returns(uint) {
                          return reserves.length;
                      }
                  
                      /// @notice should be called off chain
                      /// @dev get an array of all reserves
                      /// @return An array of all reserves
                      function getReserves() public view returns(KyberReserveInterface[]) {
                          return reserves;
                      }
                  
                      function maxGasPrice() public view returns(uint) {
                          return maxGasPriceValue;
                      }
                  
                      function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty)
                          public view
                          returns(uint expectedRate, uint slippageRate)
                      {
                          require(expectedRateContract != address(0));
                          bool includePermissionless = true;
                  
                          if (srcQty & PERM_HINT_GET_RATE > 0) {
                              includePermissionless = false;
                              srcQty = srcQty & ~PERM_HINT_GET_RATE;
                          }
                  
                          return expectedRateContract.getExpectedRate(src, dest, srcQty, includePermissionless);
                      }
                  
                      function getExpectedRateOnlyPermission(ERC20 src, ERC20 dest, uint srcQty)
                          public view
                          returns(uint expectedRate, uint slippageRate)
                      {
                          require(expectedRateContract != address(0));
                          return expectedRateContract.getExpectedRate(src, dest, srcQty, false);
                      }
                  
                      function getUserCapInWei(address user) public view returns(uint) {
                          if (whiteListContract == address(0)) return (2 ** 255);
                          return whiteListContract.getUserCapInWei(user);
                      }
                  
                      function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint) {
                          //future feature
                          user;
                          token;
                          require(false);
                      }
                  
                      struct BestRateResult {
                          uint rate;
                          address reserve1;
                          address reserve2;
                          uint weiAmount;
                          uint rateSrcToEth;
                          uint rateEthToDest;
                          uint destAmount;
                      }
                  
                      /// @notice use token address ETH_TOKEN_ADDRESS for ether
                      /// @dev best conversion rate for a pair of tokens, if number of reserves have small differences. randomize
                      /// @param src Src token
                      /// @param dest Destination token
                      /// @return obsolete - used to return best reserve index. not relevant anymore for this API.
                      function findBestRate(ERC20 src, ERC20 dest, uint srcAmount) public view returns(uint obsolete, uint rate) {
                          BestRateResult memory result = findBestRateTokenToToken(src, dest, srcAmount, EMPTY_HINT);
                          return(0, result.rate);
                      }
                  
                      function findBestRateOnlyPermission(ERC20 src, ERC20 dest, uint srcAmount)
                          public
                          view
                          returns(uint obsolete, uint rate)
                      {
                          BestRateResult memory result = findBestRateTokenToToken(src, dest, srcAmount, PERM_HINT);
                          return(0, result.rate);
                      }
                  
                      function enabled() public view returns(bool) {
                          return isEnabled;
                      }
                  
                      function info(bytes32 field) public view returns(uint) {
                          return infoFields[field];
                      }
                  
                      /* solhint-disable code-complexity */
                      // Regarding complexity. Below code follows the required algorithm for choosing a reserve.
                      //  It has been tested, reviewed and found to be clear enough.
                      //@dev this function always src or dest are ether. can't do token to token
                      function searchBestRate(ERC20 src, ERC20 dest, uint srcAmount, bool usePermissionless)
                          public
                          view
                          returns(address, uint)
                      {
                          uint bestRate = 0;
                          uint bestReserve = 0;
                          uint numRelevantReserves = 0;
                  
                          //return 1 for ether to ether
                          if (src == dest) return (reserves[bestReserve], PRECISION);
                  
                          address[] memory reserveArr;
                  
                          reserveArr = src == ETH_TOKEN_ADDRESS ? reservesPerTokenDest[dest] : reservesPerTokenSrc[src];
                  
                          if (reserveArr.length == 0) return (reserves[bestReserve], bestRate);
                  
                          uint[] memory rates = new uint[](reserveArr.length);
                          uint[] memory reserveCandidates = new uint[](reserveArr.length);
                  
                          for (uint i = 0; i < reserveArr.length; i++) {
                              //list all reserves that have this token.
                              if (!usePermissionless && reserveType[reserveArr[i]] == ReserveType.PERMISSIONLESS) {
                                  continue;
                              }
                  
                              rates[i] = (KyberReserveInterface(reserveArr[i])).getConversionRate(src, dest, srcAmount, block.number);
                  
                              if (rates[i] > bestRate) {
                                  //best rate is highest rate
                                  bestRate = rates[i];
                              }
                          }
                  
                          if (bestRate > 0) {
                              uint smallestRelevantRate = (bestRate * 10000) / (10000 + negligibleRateDiff);
                  
                              for (i = 0; i < reserveArr.length; i++) {
                                  if (rates[i] >= smallestRelevantRate) {
                                      reserveCandidates[numRelevantReserves++] = i;
                                  }
                              }
                  
                              if (numRelevantReserves > 1) {
                                  //when encountering small rate diff from bestRate. draw from relevant reserves
                                  bestReserve = reserveCandidates[uint(block.blockhash(block.number-1)) % numRelevantReserves];
                              } else {
                                  bestReserve = reserveCandidates[0];
                              }
                  
                              bestRate = rates[bestReserve];
                          }
                  
                          return (reserveArr[bestReserve], bestRate);
                      }
                      /* solhint-enable code-complexity */
                  
                      function findBestRateTokenToToken(ERC20 src, ERC20 dest, uint srcAmount, bytes hint) internal view
                          returns(BestRateResult result)
                      {
                          //by default we use permission less reserves
                          bool usePermissionless = true;
                  
                          // if hint in first 4 bytes == 'PERM' only permissioned reserves will be used.
                          if ((hint.length >= 4) && (keccak256(hint[0], hint[1], hint[2], hint[3]) == keccak256(PERM_HINT))) {
                              usePermissionless = false;
                          }
                  
                          (result.reserve1, result.rateSrcToEth) =
                              searchBestRate(src, ETH_TOKEN_ADDRESS, srcAmount, usePermissionless);
                  
                          result.weiAmount = calcDestAmount(src, ETH_TOKEN_ADDRESS, srcAmount, result.rateSrcToEth);
                  
                          (result.reserve2, result.rateEthToDest) =
                              searchBestRate(ETH_TOKEN_ADDRESS, dest, result.weiAmount, usePermissionless);
                  
                          result.destAmount = calcDestAmount(ETH_TOKEN_ADDRESS, dest, result.weiAmount, result.rateEthToDest);
                  
                          result.rate = calcRateFromQty(srcAmount, result.destAmount, getDecimals(src), getDecimals(dest));
                      }
                  
                      function listPairs(address reserve, ERC20 token, bool isTokenToEth, bool add) internal {
                          uint i;
                          address[] storage reserveArr = reservesPerTokenDest[token];
                  
                          if (isTokenToEth) {
                              reserveArr = reservesPerTokenSrc[token];
                          }
                  
                          for (i = 0; i < reserveArr.length; i++) {
                              if (reserve == reserveArr[i]) {
                                  if (add) {
                                      break; //already added
                                  } else {
                                      //remove
                                      reserveArr[i] = reserveArr[reserveArr.length - 1];
                                      reserveArr.length--;
                                      break;
                                  }
                              }
                          }
                  
                          if (add && i == reserveArr.length) {
                              //if reserve wasn't found add it
                              reserveArr.push(reserve);
                          }
                      }
                  
                      event KyberTrade(address indexed trader, ERC20 src, ERC20 dest, uint srcAmount, uint dstAmount,
                          address destAddress, uint ethWeiValue, address reserve1, address reserve2, bytes hint);
                  
                      /* solhint-disable function-max-lines */
                      //  Most of the lines here are functions calls spread over multiple lines. We find this function readable enough
                      /// @notice use token address ETH_TOKEN_ADDRESS for ether
                      /// @dev trade api for kyber network.
                      /// @param tradeInput structure of trade inputs
                      function trade(TradeInput tradeInput) internal returns(uint) {
                          require(isEnabled);
                          require(tx.gasprice <= maxGasPriceValue);
                          require(validateTradeInput(tradeInput.src, tradeInput.srcAmount, tradeInput.dest, tradeInput.destAddress));
                  
                          BestRateResult memory rateResult =
                              findBestRateTokenToToken(tradeInput.src, tradeInput.dest, tradeInput.srcAmount, tradeInput.hint);
                  
                          require(rateResult.rate > 0);
                          require(rateResult.rate < MAX_RATE);
                          require(rateResult.rate >= tradeInput.minConversionRate);
                  
                          uint actualDestAmount;
                          uint weiAmount;
                          uint actualSrcAmount;
                  
                          (actualSrcAmount, weiAmount, actualDestAmount) = calcActualAmounts(tradeInput.src,
                              tradeInput.dest,
                              tradeInput.srcAmount,
                              tradeInput.maxDestAmount,
                              rateResult);
                  
                          require(getUserCapInWei(tradeInput.trader) >= weiAmount);
                          require(handleChange(tradeInput.src, tradeInput.srcAmount, actualSrcAmount, tradeInput.trader));
                  
                          require(doReserveTrade(     //src to ETH
                                  tradeInput.src,
                                  actualSrcAmount,
                                  ETH_TOKEN_ADDRESS,
                                  this,
                                  weiAmount,
                                  KyberReserveInterface(rateResult.reserve1),
                                  rateResult.rateSrcToEth,
                                  true));
                  
                          require(doReserveTrade(     //Eth to dest
                                  ETH_TOKEN_ADDRESS,
                                  weiAmount,
                                  tradeInput.dest,
                                  tradeInput.destAddress,
                                  actualDestAmount,
                                  KyberReserveInterface(rateResult.reserve2),
                                  rateResult.rateEthToDest,
                                  true));
                  
                          if (tradeInput.src != ETH_TOKEN_ADDRESS) //"fake" trade. (ether to ether) - don't burn.
                              require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve1, tradeInput.walletId));
                          if (tradeInput.dest != ETH_TOKEN_ADDRESS) //"fake" trade. (ether to ether) - don't burn.
                              require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve2, tradeInput.walletId));
                  
                          KyberTrade({
                              trader: tradeInput.trader,
                              src: tradeInput.src,
                              dest: tradeInput.dest,
                              srcAmount: actualSrcAmount,
                              dstAmount: actualDestAmount,
                              destAddress: tradeInput.destAddress,
                              ethWeiValue: weiAmount,
                              reserve1: (tradeInput.src == ETH_TOKEN_ADDRESS) ? address(0) : rateResult.reserve1,
                              reserve2:  (tradeInput.dest == ETH_TOKEN_ADDRESS) ? address(0) : rateResult.reserve2,
                              hint: tradeInput.hint
                          });
                  
                          return actualDestAmount;
                      }
                      /* solhint-enable function-max-lines */
                  
                      function calcActualAmounts (ERC20 src, ERC20 dest, uint srcAmount, uint maxDestAmount, BestRateResult rateResult)
                          internal view returns(uint actualSrcAmount, uint weiAmount, uint actualDestAmount)
                      {
                          if (rateResult.destAmount > maxDestAmount) {
                              actualDestAmount = maxDestAmount;
                              weiAmount = calcSrcAmount(ETH_TOKEN_ADDRESS, dest, actualDestAmount, rateResult.rateEthToDest);
                              actualSrcAmount = calcSrcAmount(src, ETH_TOKEN_ADDRESS, weiAmount, rateResult.rateSrcToEth);
                              require(actualSrcAmount <= srcAmount);
                          } else {
                              actualDestAmount = rateResult.destAmount;
                              actualSrcAmount = srcAmount;
                              weiAmount = rateResult.weiAmount;
                          }
                      }
                  
                      /// @notice use token address ETH_TOKEN_ADDRESS for ether
                      /// @dev do one trade with a reserve
                      /// @param src Src token
                      /// @param amount amount of src tokens
                      /// @param dest   Destination token
                      /// @param destAddress Address to send tokens to
                      /// @param reserve Reserve to use
                      /// @param validate If true, additional validations are applicable
                      /// @return true if trade is successful
                      function doReserveTrade(
                          ERC20 src,
                          uint amount,
                          ERC20 dest,
                          address destAddress,
                          uint expectedDestAmount,
                          KyberReserveInterface reserve,
                          uint conversionRate,
                          bool validate
                      )
                          internal
                          returns(bool)
                      {
                          uint callValue = 0;
                  
                          if (src == dest) {
                              //this is for a "fake" trade when both src and dest are ethers.
                              if (destAddress != (address(this)))
                                  destAddress.transfer(amount);
                              return true;
                          }
                  
                          if (src == ETH_TOKEN_ADDRESS) {
                              callValue = amount;
                          }
                  
                          // reserve sends tokens/eth to network. network sends it to destination
                          require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, validate));
                  
                          if (destAddress != address(this)) {
                              //for token to token dest address is network. and Ether / token already here...
                              if (dest == ETH_TOKEN_ADDRESS) {
                                  destAddress.transfer(expectedDestAmount);
                              } else {
                                  require(dest.transfer(destAddress, expectedDestAmount));
                              }
                          }
                  
                          return true;
                      }
                  
                      /// when user sets max dest amount we could have too many source tokens == change. so we send it back to user.
                      function handleChange (ERC20 src, uint srcAmount, uint requiredSrcAmount, address trader) internal returns (bool) {
                  
                          if (requiredSrcAmount < srcAmount) {
                              //if there is "change" send back to trader
                              if (src == ETH_TOKEN_ADDRESS) {
                                  trader.transfer(srcAmount - requiredSrcAmount);
                              } else {
                                  require(src.transfer(trader, (srcAmount - requiredSrcAmount)));
                              }
                          }
                  
                          return true;
                      }
                  
                      /// @notice use token address ETH_TOKEN_ADDRESS for ether
                      /// @dev checks that user sent ether/tokens to contract before trade
                      /// @param src Src token
                      /// @param srcAmount amount of src tokens
                      /// @return true if tradeInput is valid
                      function validateTradeInput(ERC20 src, uint srcAmount, ERC20 dest, address destAddress)
                          internal
                          view
                          returns(bool)
                      {
                          require(srcAmount <= MAX_QTY);
                          require(srcAmount != 0);
                          require(destAddress != address(0));
                          require(src != dest);
                  
                          if (src == ETH_TOKEN_ADDRESS) {
                              require(msg.value == srcAmount);
                          } else {
                              require(msg.value == 0);
                              //funds should have been moved to this contract already.
                              require(src.balanceOf(this) >= srcAmount);
                          }
                  
                          return true;
                      }
                  }

                  File 3 of 10: RCNToken
                  pragma solidity ^0.4.11;
                  
                  contract Crowdsale {
                      function buyTokens(address _recipient) payable;
                  }
                  
                  contract CapWhitelist {
                      address public owner;
                      mapping (address => uint256) public whitelist;
                  
                      event Set(address _address, uint256 _amount);
                  
                      function CapWhitelist() {
                          owner = msg.sender;
                          // Set in prod
                      }
                  
                      function destruct() {
                          require(msg.sender == owner);
                          selfdestruct(owner);
                      }
                  
                      function setWhitelisted(address _address, uint256 _amount) {
                          require(msg.sender == owner);
                          setWhitelistInternal(_address, _amount);
                      }
                  
                      function setWhitelistInternal(address _address, uint256 _amount) private {
                          whitelist[_address] = _amount;
                          Set(_address, _amount);
                      }
                  }
                  
                  contract Token {
                      uint256 public totalSupply;
                      function balanceOf(address _owner) constant returns (uint256 balance);
                      function transfer(address _to, uint256 _value) returns (bool success);
                      function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
                      function approve(address _spender, uint256 _value) returns (bool success);
                      function allowance(address _owner, address _spender) constant returns (uint256 remaining);
                      event Transfer(address indexed _from, address indexed _to, uint256 _value);
                      event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                  }
                  
                  
                  /*  ERC 20 token */
                  contract StandardToken is Token {
                      using SafeMath for uint256;
                      function transfer(address _to, uint256 _value) returns (bool success) {
                        if (balances[msg.sender] >= _value) {
                          balances[msg.sender] = balances[msg.sender].sub(_value);
                          balances[_to] = balances[_to].add(_value);
                          Transfer(msg.sender, _to, _value);
                          return true;
                        } else {
                          return false;
                        }
                      }
                  
                      function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
                        if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value) {
                          balances[_to] = balances[_to].add(_value);
                          balances[_from] = balances[_from].sub(_value);
                          allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
                          Transfer(_from, _to, _value);
                          return true;
                        } else {
                          return false;
                        }
                      }
                  
                      function balanceOf(address _owner) constant returns (uint256 balance) {
                          return balances[_owner];
                      }
                  
                      function approve(address _spender,  uint256 _value) returns (bool success) {
                          allowed[msg.sender][_spender] = _value;
                          Approval(msg.sender, _spender, _value);
                          return true;
                      }
                  
                      function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
                        return allowed[_owner][_spender];
                      }
                  
                      function increaseApproval (address _spender, uint _addedValue) public returns (bool success) {
                        allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
                        Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
                        return true;
                      }
                  
                      function decreaseApproval (address _spender, uint _subtractedValue) public returns (bool success) {
                        uint oldValue = allowed[msg.sender][_spender];
                        if (_subtractedValue > oldValue) {
                          allowed[msg.sender][_spender] = 0;
                        } else {
                          allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
                        }
                        Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
                        return true;
                      }
                  
                      mapping (address => uint256) balances;
                      mapping (address => mapping (address => uint256)) allowed;
                  }
                  
                  contract Ownable {
                    address public owner;
                  
                  
                    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                  
                  
                    /**
                     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                     * account.
                     */
                    function Ownable() {
                      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 transfer control of the contract to a newOwner.
                     * @param newOwner The address to transfer ownership to.
                     */
                    function transferOwnership(address newOwner) onlyOwner public {
                      require(newOwner != address(0));
                      OwnershipTransferred(owner, newOwner);
                      owner = newOwner;
                    }
                  
                  }
                  
                  library SafeMath {
                    function mul(uint256 a, uint256 b) internal constant returns (uint256) {
                      uint256 c = a * b;
                      assert(a == 0 || c / a == b);
                      return c;
                    }
                  
                    function div(uint256 a, uint256 b) internal constant 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 c;
                    }
                  
                    function sub(uint256 a, uint256 b) internal constant returns (uint256) {
                      assert(b <= a);
                      return a - b;
                    }
                  
                    function add(uint256 a, uint256 b) internal constant returns (uint256) {
                      uint256 c = a + b;
                      assert(c >= a);
                      return c;
                    }
                  }
                  
                  contract MintableToken is StandardToken, Ownable {
                    using SafeMath for uint256;
                    event Mint(address indexed to, uint256 amount);
                    event MintFinished();
                  
                    bool public mintingFinished = false;
                  
                    modifier canMint() {
                      require(!mintingFinished);
                      _;
                    }
                  
                    /**
                     * @dev Function to mint tokens
                     * @param _to The address that will receive the minted tokens.
                     * @param _amount The amount of tokens to mint.
                     */
                    function mint(address _to, uint256 _amount) onlyOwner canMint public {
                      totalSupply = totalSupply.add(_amount);
                      balances[_to] = balances[_to].add(_amount);
                      Mint(_to, _amount);
                      Transfer(0x0, _to, _amount);
                    }
                  
                    /**
                     * @dev Function to stop minting new tokens.
                     */
                    function finishMinting() onlyOwner public {
                      mintingFinished = true;
                      MintFinished();
                    }
                  }
                  contract RCNToken is MintableToken {
                      string public constant name = "Ripio Credit Network Token";
                      string public constant symbol = "RCN";
                      uint8 public constant decimals = 18;
                      string public version = "1.0";
                  }

                  File 4 of 10: KyberReserve
                  pragma solidity ^0.4.13;
                  
                  interface ConversionRatesInterface {
                  
                      function recordImbalance(
                          ERC20 token,
                          int buyAmount,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          public;
                  
                      function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);
                  }
                  
                  interface ERC20 {
                      function totalSupply() public view returns (uint supply);
                      function balanceOf(address _owner) public view returns (uint balance);
                      function transfer(address _to, uint _value) public returns (bool success);
                      function transferFrom(address _from, address _to, uint _value) public returns (bool success);
                      function approve(address _spender, uint _value) public returns (bool success);
                      function allowance(address _owner, address _spender) public view returns (uint remaining);
                      function decimals() public view returns(uint digits);
                      event Approval(address indexed _owner, address indexed _spender, uint _value);
                  }
                  
                  interface KyberReserveInterface {
                  
                      function trade(
                          ERC20 srcToken,
                          uint srcAmount,
                          ERC20 destToken,
                          address destAddress,
                          uint conversionRate,
                          bool validate
                      )
                          public
                          payable
                          returns(bool);
                  
                      function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint);
                  }
                  
                  contract PermissionGroups {
                  
                      address public admin;
                      address public pendingAdmin;
                      mapping(address=>bool) internal operators;
                      mapping(address=>bool) internal alerters;
                      address[] internal operatorsGroup;
                      address[] internal alertersGroup;
                      uint constant internal MAX_GROUP_SIZE = 50;
                  
                      function PermissionGroups() public {
                          admin = msg.sender;
                      }
                  
                      modifier onlyAdmin() {
                          require(msg.sender == admin);
                          _;
                      }
                  
                      modifier onlyOperator() {
                          require(operators[msg.sender]);
                          _;
                      }
                  
                      modifier onlyAlerter() {
                          require(alerters[msg.sender]);
                          _;
                      }
                  
                      function getOperators () external view returns(address[]) {
                          return operatorsGroup;
                      }
                  
                      function getAlerters () external view returns(address[]) {
                          return alertersGroup;
                      }
                  
                      event TransferAdminPending(address pendingAdmin);
                  
                      /**
                       * @dev Allows the current admin to set the pendingAdmin address.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdmin(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(pendingAdmin);
                          pendingAdmin = newAdmin;
                      }
                  
                      /**
                       * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdminQuickly(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(newAdmin);
                          AdminClaimed(newAdmin, admin);
                          admin = newAdmin;
                      }
                  
                      event AdminClaimed( address newAdmin, address previousAdmin);
                  
                      /**
                       * @dev Allows the pendingAdmin address to finalize the change admin process.
                       */
                      function claimAdmin() public {
                          require(pendingAdmin == msg.sender);
                          AdminClaimed(pendingAdmin, admin);
                          admin = pendingAdmin;
                          pendingAdmin = address(0);
                      }
                  
                      event AlerterAdded (address newAlerter, bool isAdd);
                  
                      function addAlerter(address newAlerter) public onlyAdmin {
                          require(!alerters[newAlerter]); // prevent duplicates.
                          require(alertersGroup.length < MAX_GROUP_SIZE);
                  
                          AlerterAdded(newAlerter, true);
                          alerters[newAlerter] = true;
                          alertersGroup.push(newAlerter);
                      }
                  
                      function removeAlerter (address alerter) public onlyAdmin {
                          require(alerters[alerter]);
                          alerters[alerter] = false;
                  
                          for (uint i = 0; i < alertersGroup.length; ++i) {
                              if (alertersGroup[i] == alerter) {
                                  alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                  alertersGroup.length--;
                                  AlerterAdded(alerter, false);
                                  break;
                              }
                          }
                      }
                  
                      event OperatorAdded(address newOperator, bool isAdd);
                  
                      function addOperator(address newOperator) public onlyAdmin {
                          require(!operators[newOperator]); // prevent duplicates.
                          require(operatorsGroup.length < MAX_GROUP_SIZE);
                  
                          OperatorAdded(newOperator, true);
                          operators[newOperator] = true;
                          operatorsGroup.push(newOperator);
                      }
                  
                      function removeOperator (address operator) public onlyAdmin {
                          require(operators[operator]);
                          operators[operator] = false;
                  
                          for (uint i = 0; i < operatorsGroup.length; ++i) {
                              if (operatorsGroup[i] == operator) {
                                  operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                  operatorsGroup.length -= 1;
                                  OperatorAdded(operator, false);
                                  break;
                              }
                          }
                      }
                  }
                  
                  interface SanityRatesInterface {
                      function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);
                  }
                  
                  contract Utils {
                  
                      ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
                      uint  constant internal PRECISION = (10**18);
                      uint  constant internal MAX_QTY   = (10**28); // 10B tokens
                      uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
                      uint  constant internal MAX_DECIMALS = 18;
                      uint  constant internal ETH_DECIMALS = 18;
                      mapping(address=>uint) internal decimals;
                  
                      function setDecimals(ERC20 token) internal {
                          if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                          else decimals[token] = token.decimals();
                      }
                  
                      function getDecimals(ERC20 token) internal view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                          uint tokenDecimals = decimals[token];
                          // technically, there might be token with decimals 0
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          if(tokenDecimals == 0) return token.decimals();
                  
                          return tokenDecimals;
                      }
                  
                      function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(srcQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                          }
                      }
                  
                      function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(dstQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                          
                          //source quantity is rounded up. to avoid dest quantity being too low.
                          uint numerator;
                          uint denominator;
                          if (srcDecimals >= dstDecimals) {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                              denominator = rate;
                          } else {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty);
                              denominator = (rate * (10**(dstDecimals - srcDecimals)));
                          }
                          return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                      }
                  }
                  
                  contract Withdrawable is PermissionGroups {
                  
                      event TokenWithdraw(ERC20 token, uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw all ERC20 compatible tokens
                       * @param token ERC20 The address of the token contract
                       */
                      function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                          require(token.transfer(sendTo, amount));
                          TokenWithdraw(token, amount, sendTo);
                      }
                  
                      event EtherWithdraw(uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw Ethers
                       */
                      function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                          sendTo.transfer(amount);
                          EtherWithdraw(amount, sendTo);
                      }
                  }
                  
                  contract KyberReserve is KyberReserveInterface, Withdrawable, Utils {
                  
                      address public kyberNetwork;
                      bool public tradeEnabled;
                      ConversionRatesInterface public conversionRatesContract;
                      SanityRatesInterface public sanityRatesContract;
                      mapping(bytes32=>bool) public approvedWithdrawAddresses; // sha3(token,address)=>bool
                      mapping(address=>address) public tokenWallet;
                  
                      function KyberReserve(address _kyberNetwork, ConversionRatesInterface _ratesContract, address _admin) public {
                          require(_admin != address(0));
                          require(_ratesContract != address(0));
                          require(_kyberNetwork != address(0));
                          kyberNetwork = _kyberNetwork;
                          conversionRatesContract = _ratesContract;
                          admin = _admin;
                          tradeEnabled = true;
                      }
                  
                      event DepositToken(ERC20 token, uint amount);
                  
                      function() public payable {
                          DepositToken(ETH_TOKEN_ADDRESS, msg.value);
                      }
                  
                      event TradeExecute(
                          address indexed origin,
                          address src,
                          uint srcAmount,
                          address destToken,
                          uint destAmount,
                          address destAddress
                      );
                  
                      function trade(
                          ERC20 srcToken,
                          uint srcAmount,
                          ERC20 destToken,
                          address destAddress,
                          uint conversionRate,
                          bool validate
                      )
                          public
                          payable
                          returns(bool)
                      {
                          require(tradeEnabled);
                          require(msg.sender == kyberNetwork);
                  
                          require(doTrade(srcToken, srcAmount, destToken, destAddress, conversionRate, validate));
                  
                          return true;
                      }
                  
                      event TradeEnabled(bool enable);
                  
                      function enableTrade() public onlyAdmin returns(bool) {
                          tradeEnabled = true;
                          TradeEnabled(true);
                  
                          return true;
                      }
                  
                      function disableTrade() public onlyAlerter returns(bool) {
                          tradeEnabled = false;
                          TradeEnabled(false);
                  
                          return true;
                      }
                  
                      event WithdrawAddressApproved(ERC20 token, address addr, bool approve);
                  
                      function approveWithdrawAddress(ERC20 token, address addr, bool approve) public onlyAdmin {
                          approvedWithdrawAddresses[keccak256(token, addr)] = approve;
                          WithdrawAddressApproved(token, addr, approve);
                  
                          setDecimals(token);
                          if ((tokenWallet[token] == address(0x0)) && (token != ETH_TOKEN_ADDRESS)) {
                              tokenWallet[token] = this; // by default
                              require(token.approve(this, 2 ** 255));
                          }
                      }
                  
                      event NewTokenWallet(ERC20 token, address wallet);
                  
                      function setTokenWallet(ERC20 token, address wallet) public onlyAdmin {
                          require(wallet != address(0x0));
                          tokenWallet[token] = wallet;
                          NewTokenWallet(token, wallet);
                      }
                  
                      event WithdrawFunds(ERC20 token, uint amount, address destination);
                  
                      function withdraw(ERC20 token, uint amount, address destination) public onlyOperator returns(bool) {
                          require(approvedWithdrawAddresses[keccak256(token, destination)]);
                  
                          if (token == ETH_TOKEN_ADDRESS) {
                              destination.transfer(amount);
                          } else {
                              require(token.transferFrom(tokenWallet[token], destination, amount));
                          }
                  
                          WithdrawFunds(token, amount, destination);
                  
                          return true;
                      }
                  
                      event SetContractAddresses(address network, address rate, address sanity);
                  
                      function setContracts(
                          address _kyberNetwork,
                          ConversionRatesInterface _conversionRates,
                          SanityRatesInterface _sanityRates
                      )
                          public
                          onlyAdmin
                      {
                          require(_kyberNetwork != address(0));
                          require(_conversionRates != address(0));
                  
                          kyberNetwork = _kyberNetwork;
                          conversionRatesContract = _conversionRates;
                          sanityRatesContract = _sanityRates;
                  
                          SetContractAddresses(kyberNetwork, conversionRatesContract, sanityRatesContract);
                      }
                  
                      ////////////////////////////////////////////////////////////////////////////
                      /// status functions ///////////////////////////////////////////////////////
                      ////////////////////////////////////////////////////////////////////////////
                      function getBalance(ERC20 token) public view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS)
                              return this.balance;
                          else {
                              address wallet = tokenWallet[token];
                              uint balanceOfWallet = token.balanceOf(wallet);
                              uint allowanceOfWallet = token.allowance(wallet, this);
                  
                              return (balanceOfWallet < allowanceOfWallet) ? balanceOfWallet : allowanceOfWallet;
                          }
                      }
                  
                      function getDestQty(ERC20 src, ERC20 dest, uint srcQty, uint rate) public view returns(uint) {
                          uint dstDecimals = getDecimals(dest);
                          uint srcDecimals = getDecimals(src);
                  
                          return calcDstQty(srcQty, srcDecimals, dstDecimals, rate);
                      }
                  
                      function getSrcQty(ERC20 src, ERC20 dest, uint dstQty, uint rate) public view returns(uint) {
                          uint dstDecimals = getDecimals(dest);
                          uint srcDecimals = getDecimals(src);
                  
                          return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate);
                      }
                  
                      function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint) {
                          ERC20 token;
                          bool  isBuy;
                  
                          if (!tradeEnabled) return 0;
                  
                          if (ETH_TOKEN_ADDRESS == src) {
                              isBuy = true;
                              token = dest;
                          } else if (ETH_TOKEN_ADDRESS == dest) {
                              isBuy = false;
                              token = src;
                          } else {
                              return 0; // pair is not listed
                          }
                  
                          uint rate = conversionRatesContract.getRate(token, blockNumber, isBuy, srcQty);
                          uint destQty = getDestQty(src, dest, srcQty, rate);
                  
                          if (getBalance(dest) < destQty) return 0;
                  
                          if (sanityRatesContract != address(0)) {
                              uint sanityRate = sanityRatesContract.getSanityRate(src, dest);
                              if (rate > sanityRate) return 0;
                          }
                  
                          return rate;
                      }
                  
                      /// @dev do a trade
                      /// @param srcToken Src token
                      /// @param srcAmount Amount of src token
                      /// @param destToken Destination token
                      /// @param destAddress Destination address to send tokens to
                      /// @param validate If true, additional validations are applicable
                      /// @return true iff trade is successful
                      function doTrade(
                          ERC20 srcToken,
                          uint srcAmount,
                          ERC20 destToken,
                          address destAddress,
                          uint conversionRate,
                          bool validate
                      )
                          internal
                          returns(bool)
                      {
                          // can skip validation if done at kyber network level
                          if (validate) {
                              require(conversionRate > 0);
                              if (srcToken == ETH_TOKEN_ADDRESS)
                                  require(msg.value == srcAmount);
                              else
                                  require(msg.value == 0);
                          }
                  
                          uint destAmount = getDestQty(srcToken, destToken, srcAmount, conversionRate);
                          // sanity check
                          require(destAmount > 0);
                  
                          // add to imbalance
                          ERC20 token;
                          int tradeAmount;
                          if (srcToken == ETH_TOKEN_ADDRESS) {
                              tradeAmount = int(destAmount);
                              token = destToken;
                          } else {
                              tradeAmount = -1 * int(srcAmount);
                              token = srcToken;
                          }
                  
                          conversionRatesContract.recordImbalance(
                              token,
                              tradeAmount,
                              0,
                              block.number
                          );
                  
                          // collect src tokens
                          if (srcToken != ETH_TOKEN_ADDRESS) {
                              require(srcToken.transferFrom(msg.sender, tokenWallet[srcToken], srcAmount));
                          }
                  
                          // send dest tokens
                          if (destToken == ETH_TOKEN_ADDRESS) {
                              destAddress.transfer(destAmount);
                          } else {
                              require(destToken.transferFrom(tokenWallet[destToken], destAddress, destAmount));
                          }
                  
                          TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress);
                  
                          return true;
                      }
                  }

                  File 5 of 10: KyberReserve
                  pragma solidity 0.4.18;
                  
                  contract Utils {
                  
                      ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
                      uint  constant internal PRECISION = (10**18);
                      uint  constant internal MAX_QTY   = (10**28); // 10B tokens
                      uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
                      uint  constant internal MAX_DECIMALS = 18;
                      uint  constant internal ETH_DECIMALS = 18;
                      mapping(address=>uint) internal decimals;
                  
                      function setDecimals(ERC20 token) internal {
                          if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                          else decimals[token] = token.decimals();
                      }
                  
                      function getDecimals(ERC20 token) internal view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                          uint tokenDecimals = decimals[token];
                          // technically, there might be token with decimals 0
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          if(tokenDecimals == 0) return token.decimals();
                  
                          return tokenDecimals;
                      }
                  
                      function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(srcQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                          }
                      }
                  
                      function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(dstQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          //source quantity is rounded up. to avoid dest quantity being too low.
                          uint numerator;
                          uint denominator;
                          if (srcDecimals >= dstDecimals) {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                              denominator = rate;
                          } else {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty);
                              denominator = (rate * (10**(dstDecimals - srcDecimals)));
                          }
                          return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                      }
                  }
                  
                  contract PermissionGroups {
                  
                      address public admin;
                      address public pendingAdmin;
                      mapping(address=>bool) internal operators;
                      mapping(address=>bool) internal alerters;
                      address[] internal operatorsGroup;
                      address[] internal alertersGroup;
                      uint constant internal MAX_GROUP_SIZE = 50;
                  
                      function PermissionGroups() public {
                          admin = msg.sender;
                      }
                  
                      modifier onlyAdmin() {
                          require(msg.sender == admin);
                          _;
                      }
                  
                      modifier onlyOperator() {
                          require(operators[msg.sender]);
                          _;
                      }
                  
                      modifier onlyAlerter() {
                          require(alerters[msg.sender]);
                          _;
                      }
                  
                      function getOperators () external view returns(address[]) {
                          return operatorsGroup;
                      }
                  
                      function getAlerters () external view returns(address[]) {
                          return alertersGroup;
                      }
                  
                      event TransferAdminPending(address pendingAdmin);
                  
                      /**
                       * @dev Allows the current admin to set the pendingAdmin address.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdmin(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(pendingAdmin);
                          pendingAdmin = newAdmin;
                      }
                  
                      /**
                       * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdminQuickly(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(newAdmin);
                          AdminClaimed(newAdmin, admin);
                          admin = newAdmin;
                      }
                  
                      event AdminClaimed( address newAdmin, address previousAdmin);
                  
                      /**
                       * @dev Allows the pendingAdmin address to finalize the change admin process.
                       */
                      function claimAdmin() public {
                          require(pendingAdmin == msg.sender);
                          AdminClaimed(pendingAdmin, admin);
                          admin = pendingAdmin;
                          pendingAdmin = address(0);
                      }
                  
                      event AlerterAdded (address newAlerter, bool isAdd);
                  
                      function addAlerter(address newAlerter) public onlyAdmin {
                          require(!alerters[newAlerter]); // prevent duplicates.
                          require(alertersGroup.length < MAX_GROUP_SIZE);
                  
                          AlerterAdded(newAlerter, true);
                          alerters[newAlerter] = true;
                          alertersGroup.push(newAlerter);
                      }
                  
                      function removeAlerter (address alerter) public onlyAdmin {
                          require(alerters[alerter]);
                          alerters[alerter] = false;
                  
                          for (uint i = 0; i < alertersGroup.length; ++i) {
                              if (alertersGroup[i] == alerter) {
                                  alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                  alertersGroup.length--;
                                  AlerterAdded(alerter, false);
                                  break;
                              }
                          }
                      }
                  
                      event OperatorAdded(address newOperator, bool isAdd);
                  
                      function addOperator(address newOperator) public onlyAdmin {
                          require(!operators[newOperator]); // prevent duplicates.
                          require(operatorsGroup.length < MAX_GROUP_SIZE);
                  
                          OperatorAdded(newOperator, true);
                          operators[newOperator] = true;
                          operatorsGroup.push(newOperator);
                      }
                  
                      function removeOperator (address operator) public onlyAdmin {
                          require(operators[operator]);
                          operators[operator] = false;
                  
                          for (uint i = 0; i < operatorsGroup.length; ++i) {
                              if (operatorsGroup[i] == operator) {
                                  operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                  operatorsGroup.length -= 1;
                                  OperatorAdded(operator, false);
                                  break;
                              }
                          }
                      }
                  }
                  
                  interface ConversionRatesInterface {
                  
                      function recordImbalance(
                          ERC20 token,
                          int buyAmount,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          public;
                  
                      function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);
                  }
                  
                  interface ERC20 {
                      function totalSupply() public view returns (uint supply);
                      function balanceOf(address _owner) public view returns (uint balance);
                      function transfer(address _to, uint _value) public returns (bool success);
                      function transferFrom(address _from, address _to, uint _value) public returns (bool success);
                      function approve(address _spender, uint _value) public returns (bool success);
                      function allowance(address _owner, address _spender) public view returns (uint remaining);
                      function decimals() public view returns(uint digits);
                      event Approval(address indexed _owner, address indexed _spender, uint _value);
                  }
                  
                  interface KyberReserveInterface {
                  
                      function trade(
                          ERC20 srcToken,
                          uint srcAmount,
                          ERC20 destToken,
                          address destAddress,
                          uint conversionRate,
                          bool validate
                      )
                          public
                          payable
                          returns(bool);
                  
                      function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint);
                  }
                  
                  interface SanityRatesInterface {
                      function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);
                  }
                  
                  contract Withdrawable is PermissionGroups {
                  
                      event TokenWithdraw(ERC20 token, uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw all ERC20 compatible tokens
                       * @param token ERC20 The address of the token contract
                       */
                      function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                          require(token.transfer(sendTo, amount));
                          TokenWithdraw(token, amount, sendTo);
                      }
                  
                      event EtherWithdraw(uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw Ethers
                       */
                      function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                          sendTo.transfer(amount);
                          EtherWithdraw(amount, sendTo);
                      }
                  }
                  
                  contract KyberReserve is KyberReserveInterface, Withdrawable, Utils {
                  
                      address public kyberNetwork;
                      bool public tradeEnabled;
                      ConversionRatesInterface public conversionRatesContract;
                      SanityRatesInterface public sanityRatesContract;
                      mapping(bytes32=>bool) public approvedWithdrawAddresses; // sha3(token,address)=>bool
                  
                      function KyberReserve(address _kyberNetwork, ConversionRatesInterface _ratesContract, address _admin) public {
                          require(_admin != address(0));
                          require(_ratesContract != address(0));
                          require(_kyberNetwork != address(0));
                          kyberNetwork = _kyberNetwork;
                          conversionRatesContract = _ratesContract;
                          admin = _admin;
                          tradeEnabled = true;
                      }
                  
                      event DepositToken(ERC20 token, uint amount);
                  
                      function() public payable {
                          DepositToken(ETH_TOKEN_ADDRESS, msg.value);
                      }
                  
                      event TradeExecute(
                          address indexed origin,
                          address src,
                          uint srcAmount,
                          address destToken,
                          uint destAmount,
                          address destAddress
                      );
                  
                      function trade(
                          ERC20 srcToken,
                          uint srcAmount,
                          ERC20 destToken,
                          address destAddress,
                          uint conversionRate,
                          bool validate
                      )
                          public
                          payable
                          returns(bool)
                      {
                          require(tradeEnabled);
                          require(msg.sender == kyberNetwork);
                  
                          require(doTrade(srcToken, srcAmount, destToken, destAddress, conversionRate, validate));
                  
                          return true;
                      }
                  
                      event TradeEnabled(bool enable);
                  
                      function enableTrade() public onlyAdmin returns(bool) {
                          tradeEnabled = true;
                          TradeEnabled(true);
                  
                          return true;
                      }
                  
                      function disableTrade() public onlyAlerter returns(bool) {
                          tradeEnabled = false;
                          TradeEnabled(false);
                  
                          return true;
                      }
                  
                      event WithdrawAddressApproved(ERC20 token, address addr, bool approve);
                  
                      function approveWithdrawAddress(ERC20 token, address addr, bool approve) public onlyAdmin {
                          approvedWithdrawAddresses[keccak256(token, addr)] = approve;
                          WithdrawAddressApproved(token, addr, approve);
                  
                          setDecimals(token);
                      }
                  
                      event WithdrawFunds(ERC20 token, uint amount, address destination);
                  
                      function withdraw(ERC20 token, uint amount, address destination) public onlyOperator returns(bool) {
                          require(approvedWithdrawAddresses[keccak256(token, destination)]);
                  
                          if (token == ETH_TOKEN_ADDRESS) {
                              destination.transfer(amount);
                          } else {
                              require(token.transfer(destination, amount));
                          }
                  
                          WithdrawFunds(token, amount, destination);
                  
                          return true;
                      }
                  
                      event SetContractAddresses(address network, address rate, address sanity);
                  
                      function setContracts(address _kyberNetwork, ConversionRatesInterface _conversionRates, SanityRatesInterface _sanityRates)
                          public
                          onlyAdmin
                      {
                          require(_kyberNetwork != address(0));
                          require(_conversionRates != address(0));
                  
                          kyberNetwork = _kyberNetwork;
                          conversionRatesContract = _conversionRates;
                          sanityRatesContract = _sanityRates;
                  
                          SetContractAddresses(kyberNetwork, conversionRatesContract, sanityRatesContract);
                      }
                  
                      ////////////////////////////////////////////////////////////////////////////
                      /// status functions ///////////////////////////////////////////////////////
                      ////////////////////////////////////////////////////////////////////////////
                      function getBalance(ERC20 token) public view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS)
                              return this.balance;
                          else
                              return token.balanceOf(this);
                      }
                  
                      function getDestQty(ERC20 src, ERC20 dest, uint srcQty, uint rate) public view returns(uint) {
                          uint dstDecimals = getDecimals(dest);
                          uint srcDecimals = getDecimals(src);
                  
                          return calcDstQty(srcQty, srcDecimals, dstDecimals, rate);
                      }
                  
                      function getSrcQty(ERC20 src, ERC20 dest, uint dstQty, uint rate) public view returns(uint) {
                          uint dstDecimals = getDecimals(dest);
                          uint srcDecimals = getDecimals(src);
                  
                          return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate);
                      }
                  
                      function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint) {
                          ERC20 token;
                          bool  buy;
                  
                          if (!tradeEnabled) return 0;
                  
                          if (ETH_TOKEN_ADDRESS == src) {
                              buy = true;
                              token = dest;
                          } else if (ETH_TOKEN_ADDRESS == dest) {
                              buy = false;
                              token = src;
                          } else {
                              return 0; // pair is not listed
                          }
                  
                          uint rate = conversionRatesContract.getRate(token, blockNumber, buy, srcQty);
                          uint destQty = getDestQty(src, dest, srcQty, rate);
                  
                          if (getBalance(dest) < destQty) return 0;
                  
                          if (sanityRatesContract != address(0)) {
                              uint sanityRate = sanityRatesContract.getSanityRate(src, dest);
                              if (rate > sanityRate) return 0;
                          }
                  
                          return rate;
                      }
                  
                      /// @dev do a trade
                      /// @param srcToken Src token
                      /// @param srcAmount Amount of src token
                      /// @param destToken Destination token
                      /// @param destAddress Destination address to send tokens to
                      /// @param validate If true, additional validations are applicable
                      /// @return true iff trade is successful
                      function doTrade(
                          ERC20 srcToken,
                          uint srcAmount,
                          ERC20 destToken,
                          address destAddress,
                          uint conversionRate,
                          bool validate
                      )
                          internal
                          returns(bool)
                      {
                          // can skip validation if done at kyber network level
                          if (validate) {
                              require(conversionRate > 0);
                              if (srcToken == ETH_TOKEN_ADDRESS)
                                  require(msg.value == srcAmount);
                              else
                                  require(msg.value == 0);
                          }
                  
                          uint destAmount = getDestQty(srcToken, destToken, srcAmount, conversionRate);
                          // sanity check
                          require(destAmount > 0);
                  
                          // add to imbalance
                          ERC20 token;
                          int buy;
                          if (srcToken == ETH_TOKEN_ADDRESS) {
                              buy = int(destAmount);
                              token = destToken;
                          } else {
                              buy = -1 * int(srcAmount);
                              token = srcToken;
                          }
                  
                          conversionRatesContract.recordImbalance(
                              token,
                              buy,
                              0,
                              block.number
                          );
                  
                          // collect src tokens
                          if (srcToken != ETH_TOKEN_ADDRESS) {
                              require(srcToken.transferFrom(msg.sender, this, srcAmount));
                          }
                  
                          // send dest tokens
                          if (destToken == ETH_TOKEN_ADDRESS) {
                              destAddress.transfer(destAmount);
                          } else {
                              require(destToken.transfer(destAddress, destAmount));
                          }
                  
                          TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress);
                  
                          return true;
                      }
                  }

                  File 6 of 10: ZilliqaToken
                  pragma solidity ^0.4.18;
                  
                  /**
                   * @title ERC20Basic
                   * @dev Simpler version of ERC20 interface
                   * @dev see https://github.com/ethereum/EIPs/issues/179
                   */
                  contract ERC20Basic {
                    uint256 public totalSupply;
                    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);
                  }
                  
                  
                  /**
                   * @title SafeMath
                   * @dev Math operations with safety checks that throw on error
                   */
                  library SafeMath {
                    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                      if (a == 0) {
                        return 0;
                      }
                      uint256 c = a * b;
                      assert(c / a == b);
                      return c;
                    }
                  
                    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 c;
                    }
                  
                    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      assert(b <= a);
                      return a - b;
                    }
                  
                    function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 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;
                  
                    /**
                    * @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]);
                  
                      // SafeMath.sub will throw if there is not enough balance.
                      balances[msg.sender] = balances[msg.sender].sub(_value);
                      balances[_to] = balances[_to].add(_value);
                      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 balance) {
                      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);
                      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;
                      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];
                    }
                  
                    /**
                     * 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
                     */
                    function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
                      allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
                      Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
                      return true;
                    }
                  
                    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);
                      }
                      Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
                      return true;
                    }
                  
                  }
                  
                  /**
                   * @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 OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                  
                  
                    /**
                     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                     * account.
                     */
                    function Ownable() 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 transfer control of the contract to a newOwner.
                     * @param newOwner The address to transfer ownership to.
                     */
                    function transferOwnership(address newOwner) public onlyOwner {
                      require(newOwner != address(0));
                      OwnershipTransferred(owner, newOwner);
                      owner = newOwner;
                    }
                  
                  }
                  
                  /**
                   * @title Pausable
                   * @dev Base contract which allows children to implement an emergency stop mechanism.
                   */
                  contract Pausable is Ownable {
                    event PausePublic(bool newState);
                    event PauseOwnerAdmin(bool newState);
                  
                    bool public pausedPublic = true;
                    bool public pausedOwnerAdmin = false;
                  
                    address public admin;
                  
                    /**
                     * @dev Modifier to make a function callable based on pause states.
                     */
                    modifier whenNotPaused() {
                      if(pausedPublic) {
                        if(!pausedOwnerAdmin) {
                          require(msg.sender == admin || msg.sender == owner);
                        } else {
                          revert();
                        }
                      }
                      _;
                    }
                  
                    /**
                     * @dev called by the owner to set new pause flags
                     * pausedPublic can't be false while pausedOwnerAdmin is true
                     */
                    function pause(bool newPausedPublic, bool newPausedOwnerAdmin) onlyOwner public {
                      require(!(newPausedPublic == false && newPausedOwnerAdmin == true));
                  
                      pausedPublic = newPausedPublic;
                      pausedOwnerAdmin = newPausedOwnerAdmin;
                  
                      PausePublic(newPausedPublic);
                      PauseOwnerAdmin(newPausedOwnerAdmin);
                    }
                  }
                  
                  contract PausableToken is StandardToken, Pausable {
                  
                    function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) {
                      return super.transfer(_to, _value);
                    }
                  
                    function transferFrom(address _from, address _to, uint256 _value) public whenNotPaused returns (bool) {
                      return super.transferFrom(_from, _to, _value);
                    }
                  
                    function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) {
                      return super.approve(_spender, _value);
                    }
                  
                    function increaseApproval(address _spender, uint _addedValue) public whenNotPaused returns (bool success) {
                      return super.increaseApproval(_spender, _addedValue);
                    }
                  
                    function decreaseApproval(address _spender, uint _subtractedValue) public whenNotPaused returns (bool success) {
                      return super.decreaseApproval(_spender, _subtractedValue);
                    }
                  }
                  
                  
                  contract ZilliqaToken is PausableToken {
                      string  public  constant name = "Zilliqa";
                      string  public  constant symbol = "ZIL";
                      uint8   public  constant decimals = 12;
                  
                      modifier validDestination( address to )
                      {
                          require(to != address(0x0));
                          require(to != address(this));
                          _;
                      }
                  
                      function ZilliqaToken( address _admin, uint _totalTokenAmount ) 
                      {
                          // assign the admin account
                          admin = _admin;
                  
                          // assign the total tokens to zilliqa
                          totalSupply = _totalTokenAmount;
                          balances[msg.sender] = _totalTokenAmount;
                          Transfer(address(0x0), msg.sender, _totalTokenAmount);
                      }
                  
                      function transfer(address _to, uint _value) validDestination(_to) returns (bool) 
                      {
                          return super.transfer(_to, _value);
                      }
                  
                      function transferFrom(address _from, address _to, uint _value) validDestination(_to) returns (bool) 
                      {
                          return super.transferFrom(_from, _to, _value);
                      }
                  
                      event Burn(address indexed _burner, uint _value);
                  
                      function burn(uint _value) returns (bool)
                      {
                          balances[msg.sender] = balances[msg.sender].sub(_value);
                          totalSupply = totalSupply.sub(_value);
                          Burn(msg.sender, _value);
                          Transfer(msg.sender, address(0x0), _value);
                          return true;
                      }
                  
                      // save some gas by making only one contract call
                      function burnFrom(address _from, uint256 _value) returns (bool) 
                      {
                          assert( transferFrom( _from, msg.sender, _value ) );
                          return burn(_value);
                      }
                  
                      function emergencyERC20Drain( ERC20 token, uint amount ) onlyOwner {
                          // owner can drain tokens that are sent here by mistake
                          token.transfer( owner, amount );
                      }
                  
                      event AdminTransferred(address indexed previousAdmin, address indexed newAdmin);
                  
                      function changeAdmin(address newAdmin) onlyOwner {
                          // owner can re-assign the admin
                          AdminTransferred(admin, newAdmin);
                          admin = newAdmin;
                      }
                  }

                  File 7 of 10: FeeBurner
                  pragma solidity 0.4.18;
                  
                  // File: contracts/FeeBurnerInterface.sol
                  
                  interface FeeBurnerInterface {
                      function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
                      function setReserveData(address reserve, uint feesInBps, address kncWallet) public;
                  }
                  
                  // File: contracts/ERC20Interface.sol
                  
                  // https://github.com/ethereum/EIPs/issues/20
                  interface ERC20 {
                      function totalSupply() public view returns (uint supply);
                      function balanceOf(address _owner) public view returns (uint balance);
                      function transfer(address _to, uint _value) public returns (bool success);
                      function transferFrom(address _from, address _to, uint _value) public returns (bool success);
                      function approve(address _spender, uint _value) public returns (bool success);
                      function allowance(address _owner, address _spender) public view returns (uint remaining);
                      function decimals() public view returns(uint digits);
                      event Approval(address indexed _owner, address indexed _spender, uint _value);
                  }
                  
                  // File: contracts/PermissionGroups.sol
                  
                  contract PermissionGroups {
                  
                      address public admin;
                      address public pendingAdmin;
                      mapping(address=>bool) internal operators;
                      mapping(address=>bool) internal alerters;
                      address[] internal operatorsGroup;
                      address[] internal alertersGroup;
                      uint constant internal MAX_GROUP_SIZE = 50;
                  
                      function PermissionGroups() public {
                          admin = msg.sender;
                      }
                  
                      modifier onlyAdmin() {
                          require(msg.sender == admin);
                          _;
                      }
                  
                      modifier onlyOperator() {
                          require(operators[msg.sender]);
                          _;
                      }
                  
                      modifier onlyAlerter() {
                          require(alerters[msg.sender]);
                          _;
                      }
                  
                      function getOperators () external view returns(address[]) {
                          return operatorsGroup;
                      }
                  
                      function getAlerters () external view returns(address[]) {
                          return alertersGroup;
                      }
                  
                      event TransferAdminPending(address pendingAdmin);
                  
                      /**
                       * @dev Allows the current admin to set the pendingAdmin address.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdmin(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(pendingAdmin);
                          pendingAdmin = newAdmin;
                      }
                  
                      /**
                       * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdminQuickly(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(newAdmin);
                          AdminClaimed(newAdmin, admin);
                          admin = newAdmin;
                      }
                  
                      event AdminClaimed( address newAdmin, address previousAdmin);
                  
                      /**
                       * @dev Allows the pendingAdmin address to finalize the change admin process.
                       */
                      function claimAdmin() public {
                          require(pendingAdmin == msg.sender);
                          AdminClaimed(pendingAdmin, admin);
                          admin = pendingAdmin;
                          pendingAdmin = address(0);
                      }
                  
                      event AlerterAdded (address newAlerter, bool isAdd);
                  
                      function addAlerter(address newAlerter) public onlyAdmin {
                          require(!alerters[newAlerter]); // prevent duplicates.
                          require(alertersGroup.length < MAX_GROUP_SIZE);
                  
                          AlerterAdded(newAlerter, true);
                          alerters[newAlerter] = true;
                          alertersGroup.push(newAlerter);
                      }
                  
                      function removeAlerter (address alerter) public onlyAdmin {
                          require(alerters[alerter]);
                          alerters[alerter] = false;
                  
                          for (uint i = 0; i < alertersGroup.length; ++i) {
                              if (alertersGroup[i] == alerter) {
                                  alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                  alertersGroup.length--;
                                  AlerterAdded(alerter, false);
                                  break;
                              }
                          }
                      }
                  
                      event OperatorAdded(address newOperator, bool isAdd);
                  
                      function addOperator(address newOperator) public onlyAdmin {
                          require(!operators[newOperator]); // prevent duplicates.
                          require(operatorsGroup.length < MAX_GROUP_SIZE);
                  
                          OperatorAdded(newOperator, true);
                          operators[newOperator] = true;
                          operatorsGroup.push(newOperator);
                      }
                  
                      function removeOperator (address operator) public onlyAdmin {
                          require(operators[operator]);
                          operators[operator] = false;
                  
                          for (uint i = 0; i < operatorsGroup.length; ++i) {
                              if (operatorsGroup[i] == operator) {
                                  operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                  operatorsGroup.length -= 1;
                                  OperatorAdded(operator, false);
                                  break;
                              }
                          }
                      }
                  }
                  
                  // File: contracts/Withdrawable.sol
                  
                  /**
                   * @title Contracts that should be able to recover tokens or ethers
                   * @author Ilan Doron
                   * @dev This allows to recover any tokens or Ethers received in a contract.
                   * This will prevent any accidental loss of tokens.
                   */
                  contract Withdrawable is PermissionGroups {
                  
                      event TokenWithdraw(ERC20 token, uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw all ERC20 compatible tokens
                       * @param token ERC20 The address of the token contract
                       */
                      function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                          require(token.transfer(sendTo, amount));
                          TokenWithdraw(token, amount, sendTo);
                      }
                  
                      event EtherWithdraw(uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw Ethers
                       */
                      function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                          sendTo.transfer(amount);
                          EtherWithdraw(amount, sendTo);
                      }
                  }
                  
                  // File: contracts/Utils.sol
                  
                  /// @title Kyber constants contract
                  contract Utils {
                  
                      ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
                      uint  constant internal PRECISION = (10**18);
                      uint  constant internal MAX_QTY   = (10**28); // 10B tokens
                      uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
                      uint  constant internal MAX_DECIMALS = 18;
                      uint  constant internal ETH_DECIMALS = 18;
                      mapping(address=>uint) internal decimals;
                  
                      function setDecimals(ERC20 token) internal {
                          if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                          else decimals[token] = token.decimals();
                      }
                  
                      function getDecimals(ERC20 token) internal view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                          uint tokenDecimals = decimals[token];
                          // technically, there might be token with decimals 0
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          if(tokenDecimals == 0) return token.decimals();
                  
                          return tokenDecimals;
                      }
                  
                      function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(srcQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                          }
                      }
                  
                      function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(dstQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                          
                          //source quantity is rounded up. to avoid dest quantity being too low.
                          uint numerator;
                          uint denominator;
                          if (srcDecimals >= dstDecimals) {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                              denominator = rate;
                          } else {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty);
                              denominator = (rate * (10**(dstDecimals - srcDecimals)));
                          }
                          return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                      }
                  }
                  
                  // File: contracts/Utils2.sol
                  
                  contract Utils2 is Utils {
                  
                      /// @dev get the balance of a user.
                      /// @param token The token type
                      /// @return The balance
                      function getBalance(ERC20 token, address user) public view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS)
                              return user.balance;
                          else
                              return token.balanceOf(user);
                      }
                  
                      function getDecimalsSafe(ERC20 token) internal returns(uint) {
                  
                          if (decimals[token] == 0) {
                              setDecimals(token);
                          }
                  
                          return decimals[token];
                      }
                  
                      function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {
                          return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                      }
                  
                      function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {
                          return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
                      }
                  
                      function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)
                          internal pure returns(uint)
                      {
                          require(srcAmount <= MAX_QTY);
                          require(destAmount <= MAX_QTY);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);
                          }
                      }
                  }
                  
                  // File: contracts/KyberNetworkInterface.sol
                  
                  /// @title Kyber Network interface
                  interface KyberNetworkInterface {
                      function maxGasPrice() public view returns(uint);
                      function getUserCapInWei(address user) public view returns(uint);
                      function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
                      function enabled() public view returns(bool);
                      function info(bytes32 id) public view returns(uint);
                  
                      function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
                          returns (uint expectedRate, uint slippageRate);
                  
                      function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,
                          uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
                  }
                  
                  // File: contracts/FeeBurner.sol
                  
                  interface BurnableToken {
                      function transferFrom(address _from, address _to, uint _value) public returns (bool);
                      function burnFrom(address _from, uint256 _value) public returns (bool);
                  }
                  
                  
                  contract FeeBurner is Withdrawable, FeeBurnerInterface, Utils2 {
                  
                      mapping(address=>uint) public reserveFeesInBps;
                      mapping(address=>address) public reserveKNCWallet; //wallet holding knc per reserve. from here burn and send fees.
                      mapping(address=>uint) public walletFeesInBps; // wallet that is the source of tx is entitled so some fees.
                      mapping(address=>uint) public reserveFeeToBurn;
                      mapping(address=>uint) public feePayedPerReserve; // track burned fees and sent wallet fees per reserve.
                      mapping(address=>mapping(address=>uint)) public reserveFeeToWallet;
                      address public taxWallet;
                      uint public taxFeeBps = 0; // burned fees are taxed. % out of burned fees.
                  
                      BurnableToken public knc;
                      KyberNetworkInterface public kyberNetwork;
                      uint public kncPerEthRatePrecision = 600 * PRECISION; //--> 1 ether = 600 knc tokens
                  
                      function FeeBurner(
                          address _admin,
                          BurnableToken _kncToken,
                          KyberNetworkInterface _kyberNetwork,
                          uint _initialKncToEthRatePrecision
                      )
                          public
                      {
                          require(_admin != address(0));
                          require(_kncToken != address(0));
                          require(_kyberNetwork != address(0));
                          require(_initialKncToEthRatePrecision != 0);
                  
                          kyberNetwork = _kyberNetwork;
                          admin = _admin;
                          knc = _kncToken;
                          kncPerEthRatePrecision = _initialKncToEthRatePrecision;
                      }
                  
                      event ReserveDataSet(address reserve, uint feeInBps, address kncWallet);
                  
                      function setReserveData(address reserve, uint feesInBps, address kncWallet) public onlyOperator {
                          require(feesInBps < 100); // make sure it is always < 1%
                          require(kncWallet != address(0));
                          reserveFeesInBps[reserve] = feesInBps;
                          reserveKNCWallet[reserve] = kncWallet;
                          ReserveDataSet(reserve, feesInBps, kncWallet);
                      }
                  
                      event WalletFeesSet(address wallet, uint feesInBps);
                  
                      function setWalletFees(address wallet, uint feesInBps) public onlyAdmin {
                          require(feesInBps < 10000); // under 100%
                          walletFeesInBps[wallet] = feesInBps;
                          WalletFeesSet(wallet, feesInBps);
                      }
                  
                      event TaxFeesSet(uint feesInBps);
                  
                      function setTaxInBps(uint _taxFeeBps) public onlyAdmin {
                          require(_taxFeeBps < 10000); // under 100%
                          taxFeeBps = _taxFeeBps;
                          TaxFeesSet(_taxFeeBps);
                      }
                  
                      event TaxWalletSet(address taxWallet);
                  
                      function setTaxWallet(address _taxWallet) public onlyAdmin {
                          require(_taxWallet != address(0));
                          taxWallet = _taxWallet;
                          TaxWalletSet(_taxWallet);
                      }
                  
                      event KNCRateSet(uint ethToKncRatePrecision, uint kyberEthKnc, uint kyberKncEth, address updater);
                  
                      function setKNCRate() public {
                          //query kyber for knc rate sell and buy
                          uint kyberEthKncRate;
                          uint kyberKncEthRate;
                          (kyberEthKncRate, ) = kyberNetwork.getExpectedRate(ETH_TOKEN_ADDRESS, ERC20(knc), (10 ** 18));
                          (kyberKncEthRate, ) = kyberNetwork.getExpectedRate(ERC20(knc), ETH_TOKEN_ADDRESS, (10 ** 18));
                  
                          //check "reasonable" spread == diff not too big. rate wasn't tampered.
                          require(kyberEthKncRate * kyberKncEthRate < PRECISION ** 2 * 2);
                          require(kyberEthKncRate * kyberKncEthRate > PRECISION ** 2 / 2);
                  
                          require(kyberEthKncRate <= MAX_RATE);
                          kncPerEthRatePrecision = kyberEthKncRate;
                          KNCRateSet(kncPerEthRatePrecision, kyberEthKncRate, kyberKncEthRate, msg.sender);
                      }
                  
                      event AssignFeeToWallet(address reserve, address wallet, uint walletFee);
                      event AssignBurnFees(address reserve, uint burnFee);
                  
                      function handleFees(uint tradeWeiAmount, address reserve, address wallet) public returns(bool) {
                          require(msg.sender == address(kyberNetwork));
                          require(tradeWeiAmount <= MAX_QTY);
                  
                          uint kncAmount = calcDestAmount(ETH_TOKEN_ADDRESS, ERC20(knc), tradeWeiAmount, kncPerEthRatePrecision);
                          uint fee = kncAmount * reserveFeesInBps[reserve] / 10000;
                  
                          uint walletFee = fee * walletFeesInBps[wallet] / 10000;
                          require(fee >= walletFee);
                          uint feeToBurn = fee - walletFee;
                  
                          if (walletFee > 0) {
                              reserveFeeToWallet[reserve][wallet] += walletFee;
                              AssignFeeToWallet(reserve, wallet, walletFee);
                          }
                  
                          if (feeToBurn > 0) {
                              AssignBurnFees(reserve, feeToBurn);
                              reserveFeeToBurn[reserve] += feeToBurn;
                          }
                  
                          return true;
                      }
                  
                      event BurnAssignedFees(address indexed reserve, address sender, uint quantity);
                  
                      event SendTaxFee(address indexed reserve, address sender, address taxWallet, uint quantity);
                  
                      // this function is callable by anyone
                      function burnReserveFees(address reserve) public {
                          uint burnAmount = reserveFeeToBurn[reserve];
                          uint taxToSend = 0;
                          require(burnAmount > 2);
                          reserveFeeToBurn[reserve] = 1; // leave 1 twei to avoid spikes in gas fee
                          if (taxWallet != address(0) && taxFeeBps != 0) {
                              taxToSend = (burnAmount - 1) * taxFeeBps / 10000;
                              require(burnAmount - 1 > taxToSend);
                              burnAmount -= taxToSend;
                              if (taxToSend > 0) {
                                  require(knc.transferFrom(reserveKNCWallet[reserve], taxWallet, taxToSend));
                                  SendTaxFee(reserve, msg.sender, taxWallet, taxToSend);
                              }
                          }
                          require(knc.burnFrom(reserveKNCWallet[reserve], burnAmount - 1));
                  
                          //update reserve "payments" so far
                          feePayedPerReserve[reserve] += (taxToSend + burnAmount - 1);
                  
                          BurnAssignedFees(reserve, msg.sender, (burnAmount - 1));
                      }
                  
                      event SendWalletFees(address indexed wallet, address reserve, address sender);
                  
                      // this function is callable by anyone
                      function sendFeeToWallet(address wallet, address reserve) public {
                          uint feeAmount = reserveFeeToWallet[reserve][wallet];
                          require(feeAmount > 1);
                          reserveFeeToWallet[reserve][wallet] = 1; // leave 1 twei to avoid spikes in gas fee
                          require(knc.transferFrom(reserveKNCWallet[reserve], wallet, feeAmount - 1));
                  
                          feePayedPerReserve[reserve] += (feeAmount - 1);
                          SendWalletFees(wallet, reserve, msg.sender);
                      }
                  }

                  File 8 of 10: ConversionRates
                  pragma solidity 0.4.18;
                  
                  interface ConversionRatesInterface {
                  
                      function recordImbalance(
                          ERC20 token,
                          int buyAmount,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          public;
                  
                      function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);
                  }
                  
                  interface ERC20 {
                      function totalSupply() public view returns (uint supply);
                      function balanceOf(address _owner) public view returns (uint balance);
                      function transfer(address _to, uint _value) public returns (bool success);
                      function transferFrom(address _from, address _to, uint _value) public returns (bool success);
                      function approve(address _spender, uint _value) public returns (bool success);
                      function allowance(address _owner, address _spender) public view returns (uint remaining);
                      function decimals() public view returns(uint digits);
                      event Approval(address indexed _owner, address indexed _spender, uint _value);
                  }
                  
                  contract PermissionGroups {
                  
                      address public admin;
                      address public pendingAdmin;
                      mapping(address=>bool) internal operators;
                      mapping(address=>bool) internal alerters;
                      address[] internal operatorsGroup;
                      address[] internal alertersGroup;
                      uint constant internal MAX_GROUP_SIZE = 50;
                  
                      function PermissionGroups() public {
                          admin = msg.sender;
                      }
                  
                      modifier onlyAdmin() {
                          require(msg.sender == admin);
                          _;
                      }
                  
                      modifier onlyOperator() {
                          require(operators[msg.sender]);
                          _;
                      }
                  
                      modifier onlyAlerter() {
                          require(alerters[msg.sender]);
                          _;
                      }
                  
                      function getOperators () external view returns(address[]) {
                          return operatorsGroup;
                      }
                  
                      function getAlerters () external view returns(address[]) {
                          return alertersGroup;
                      }
                  
                      event TransferAdminPending(address pendingAdmin);
                  
                      /**
                       * @dev Allows the current admin to set the pendingAdmin address.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdmin(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(pendingAdmin);
                          pendingAdmin = newAdmin;
                      }
                  
                      /**
                       * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdminQuickly(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(newAdmin);
                          AdminClaimed(newAdmin, admin);
                          admin = newAdmin;
                      }
                  
                      event AdminClaimed( address newAdmin, address previousAdmin);
                  
                      /**
                       * @dev Allows the pendingAdmin address to finalize the change admin process.
                       */
                      function claimAdmin() public {
                          require(pendingAdmin == msg.sender);
                          AdminClaimed(pendingAdmin, admin);
                          admin = pendingAdmin;
                          pendingAdmin = address(0);
                      }
                  
                      event AlerterAdded (address newAlerter, bool isAdd);
                  
                      function addAlerter(address newAlerter) public onlyAdmin {
                          require(!alerters[newAlerter]); // prevent duplicates.
                          require(alertersGroup.length < MAX_GROUP_SIZE);
                  
                          AlerterAdded(newAlerter, true);
                          alerters[newAlerter] = true;
                          alertersGroup.push(newAlerter);
                      }
                  
                      function removeAlerter (address alerter) public onlyAdmin {
                          require(alerters[alerter]);
                          alerters[alerter] = false;
                  
                          for (uint i = 0; i < alertersGroup.length; ++i) {
                              if (alertersGroup[i] == alerter) {
                                  alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                  alertersGroup.length--;
                                  AlerterAdded(alerter, false);
                                  break;
                              }
                          }
                      }
                  
                      event OperatorAdded(address newOperator, bool isAdd);
                  
                      function addOperator(address newOperator) public onlyAdmin {
                          require(!operators[newOperator]); // prevent duplicates.
                          require(operatorsGroup.length < MAX_GROUP_SIZE);
                  
                          OperatorAdded(newOperator, true);
                          operators[newOperator] = true;
                          operatorsGroup.push(newOperator);
                      }
                  
                      function removeOperator (address operator) public onlyAdmin {
                          require(operators[operator]);
                          operators[operator] = false;
                  
                          for (uint i = 0; i < operatorsGroup.length; ++i) {
                              if (operatorsGroup[i] == operator) {
                                  operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                  operatorsGroup.length -= 1;
                                  OperatorAdded(operator, false);
                                  break;
                              }
                          }
                      }
                  }
                  
                  contract Withdrawable is PermissionGroups {
                  
                      event TokenWithdraw(ERC20 token, uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw all ERC20 compatible tokens
                       * @param token ERC20 The address of the token contract
                       */
                      function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                          require(token.transfer(sendTo, amount));
                          TokenWithdraw(token, amount, sendTo);
                      }
                  
                      event EtherWithdraw(uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw Ethers
                       */
                      function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                          sendTo.transfer(amount);
                          EtherWithdraw(amount, sendTo);
                      }
                  }
                  
                  contract VolumeImbalanceRecorder is Withdrawable {
                  
                      uint constant internal SLIDING_WINDOW_SIZE = 5;
                      uint constant internal POW_2_64 = 2 ** 64;
                  
                      struct TokenControlInfo {
                          uint minimalRecordResolution; // can be roughly 1 cent
                          uint maxPerBlockImbalance; // in twei resolution
                          uint maxTotalImbalance; // max total imbalance (between rate updates)
                                              // before halting trade
                      }
                  
                      mapping(address => TokenControlInfo) internal tokenControlInfo;
                  
                      struct TokenImbalanceData {
                          int  lastBlockBuyUnitsImbalance;
                          uint lastBlock;
                  
                          int  totalBuyUnitsImbalance;
                          uint lastRateUpdateBlock;
                      }
                  
                      mapping(address => mapping(uint=>uint)) public tokenImbalanceData;
                  
                      function VolumeImbalanceRecorder(address _admin) public {
                          require(_admin != address(0));
                          admin = _admin;
                      }
                  
                      function setTokenControlInfo(
                          ERC20 token,
                          uint minimalRecordResolution,
                          uint maxPerBlockImbalance,
                          uint maxTotalImbalance
                      )
                          public
                          onlyAdmin
                      {
                          tokenControlInfo[token] =
                              TokenControlInfo(
                                  minimalRecordResolution,
                                  maxPerBlockImbalance,
                                  maxTotalImbalance
                              );
                      }
                  
                      function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) {
                          return (tokenControlInfo[token].minimalRecordResolution,
                                  tokenControlInfo[token].maxPerBlockImbalance,
                                  tokenControlInfo[token].maxTotalImbalance);
                      }
                  
                      function addImbalance(
                          ERC20 token,
                          int buyAmount,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          internal
                      {
                          uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE;
                          int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution));
                  
                          int prevImbalance = 0;
                  
                          TokenImbalanceData memory currentBlockData =
                              decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]);
                  
                          // first scenario - this is not the first tx in the current block
                          if (currentBlockData.lastBlock == currentBlock) {
                              if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) {
                                  // just increase imbalance
                                  currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;
                                  currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount;
                              } else {
                                  // imbalance was changed in the middle of the block
                                  prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock);
                                  currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;
                                  currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;
                                  currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);
                              }
                          } else {
                              // first tx in the current block
                              int currentBlockImbalance;
                              (prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock);
                  
                              currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount;
                              currentBlockData.lastBlock = uint(currentBlock);
                              currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);
                              currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;
                          }
                  
                          tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData);
                      }
                  
                      function setGarbageToVolumeRecorder(ERC20 token) internal {
                          for (uint i = 0; i < SLIDING_WINDOW_SIZE; i++) {
                              tokenImbalanceData[token][i] = 0x1;
                          }
                      }
                  
                      function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) {
                          // check the imbalance in the sliding window
                          require(startBlock <= endBlock);
                  
                          buyImbalance = 0;
                  
                          for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) {
                              TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);
                  
                              if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) {
                                  buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance);
                              }
                          }
                      }
                  
                      function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock)
                          internal view
                          returns(int buyImbalance, int currentBlockImbalance)
                      {
                          buyImbalance = 0;
                          currentBlockImbalance = 0;
                          uint latestBlock = 0;
                          int imbalanceInRange = 0;
                          uint startBlock = rateUpdateBlock;
                          uint endBlock = currentBlock;
                  
                          for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) {
                              TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);
                  
                              if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) {
                                  imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance;
                              }
                  
                              if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue;
                              if (perBlockData.lastBlock < latestBlock) continue;
                  
                              latestBlock = perBlockData.lastBlock;
                              buyImbalance = perBlockData.totalBuyUnitsImbalance;
                              if (uint(perBlockData.lastBlock) == currentBlock) {
                                  currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance;
                              }
                          }
                  
                          if (buyImbalance == 0) {
                              buyImbalance = imbalanceInRange;
                          }
                      }
                  
                      function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock)
                          internal view
                          returns(int totalImbalance, int currentBlockImbalance)
                      {
                  
                          int resolution = int(tokenControlInfo[token].minimalRecordResolution);
                  
                          (totalImbalance, currentBlockImbalance) =
                              getImbalanceSinceRateUpdate(
                                  token,
                                  rateUpdateBlock,
                                  currentBlock);
                  
                          totalImbalance *= resolution;
                          currentBlockImbalance *= resolution;
                      }
                  
                      function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) {
                          return tokenControlInfo[token].maxPerBlockImbalance;
                      }
                  
                      function getMaxTotalImbalance(ERC20 token) internal view returns(uint) {
                          return tokenControlInfo[token].maxTotalImbalance;
                      }
                  
                      function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) {
                          // check for overflows
                          require(data.lastBlockBuyUnitsImbalance < int(POW_2_64 / 2));
                          require(data.lastBlockBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2));
                          require(data.lastBlock < POW_2_64);
                          require(data.totalBuyUnitsImbalance < int(POW_2_64 / 2));
                          require(data.totalBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2));
                          require(data.lastRateUpdateBlock < POW_2_64);
                  
                          // do encoding
                          uint result = uint(data.lastBlockBuyUnitsImbalance) & (POW_2_64 - 1);
                          result |= data.lastBlock * POW_2_64;
                          result |= (uint(data.totalBuyUnitsImbalance) & (POW_2_64 - 1)) * POW_2_64 * POW_2_64;
                          result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64;
                  
                          return result;
                      }
                  
                      function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) {
                          TokenImbalanceData memory data;
                  
                          data.lastBlockBuyUnitsImbalance = int(int64(input & (POW_2_64 - 1)));
                          data.lastBlock = uint(uint64((input / POW_2_64) & (POW_2_64 - 1)));
                          data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) & (POW_2_64 - 1)));
                          data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64))));
                  
                          return data;
                      }
                  }
                  
                  contract Utils {
                  
                      ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
                      uint  constant internal PRECISION = (10**18);
                      uint  constant internal MAX_QTY   = (10**28); // 10B tokens
                      uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
                      uint  constant internal MAX_DECIMALS = 18;
                      uint  constant internal ETH_DECIMALS = 18;
                      mapping(address=>uint) internal decimals;
                  
                      function setDecimals(ERC20 token) internal {
                          if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                          else decimals[token] = token.decimals();
                      }
                  
                      function getDecimals(ERC20 token) internal view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                          uint tokenDecimals = decimals[token];
                          // technically, there might be token with decimals 0
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          if(tokenDecimals == 0) return token.decimals();
                  
                          return tokenDecimals;
                      }
                  
                      function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(srcQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                          }
                      }
                  
                      function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(dstQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          //source quantity is rounded up. to avoid dest quantity being too low.
                          uint numerator;
                          uint denominator;
                          if (srcDecimals >= dstDecimals) {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                              denominator = rate;
                          } else {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty);
                              denominator = (rate * (10**(dstDecimals - srcDecimals)));
                          }
                          return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                      }
                  }
                  
                  contract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils {
                  
                      // bps - basic rate steps. one step is 1 / 10000 of the rate.
                      struct StepFunction {
                          int[] x; // quantity for each step. Quantity of each step includes previous steps.
                          int[] y; // rate change per quantity step  in bps.
                      }
                  
                      struct TokenData {
                          bool listed;  // was added to reserve
                          bool enabled; // whether trade is enabled
                  
                          // position in the compact data
                          uint compactDataArrayIndex;
                          uint compactDataFieldIndex;
                  
                          // rate data. base and changes according to quantity and reserve balance.
                          // generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction.
                          uint baseBuyRate;  // in PRECISION units. see KyberConstants
                          uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate
                          StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate.
                          StepFunction sellRateQtyStepFunction;// in bps. higher the qua
                          StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate.
                          StepFunction sellRateImbalanceStepFunction;
                      }
                  
                      /*
                      this is the data for tokenRatesCompactData
                      but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write
                      so we represent it as bytes32 and do the byte tricks ourselves.
                      struct TokenRatesCompactData {
                          bytes14 buy;  // change buy rate of token from baseBuyRate in 10 bps
                          bytes14 sell; // change sell rate of token from baseSellRate in 10 bps
                  
                          uint32 blockNumber;
                      } */
                      uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks
                      ERC20[] internal listedTokens;
                      mapping(address=>TokenData) internal tokenData;
                      bytes32[] internal tokenRatesCompactData;
                      uint public numTokensInCurrentCompactData = 0;
                      address public reserveContract;
                      uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14;
                      uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA));
                      uint constant internal MAX_STEPS_IN_FUNCTION = 10;
                      int  constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B %
                      int  constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100%
                  
                      function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin)
                          { } // solhint-disable-line no-empty-blocks
                  
                      function addToken(ERC20 token) public onlyAdmin {
                  
                          require(!tokenData[token].listed);
                          tokenData[token].listed = true;
                          listedTokens.push(token);
                  
                          if (numTokensInCurrentCompactData == 0) {
                              tokenRatesCompactData.length++; // add new structure
                          }
                  
                          tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1;
                          tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData;
                  
                          numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA;
                  
                          setGarbageToVolumeRecorder(token);
                  
                          setDecimals(token);
                      }
                  
                      function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator {
                  
                          require(buy.length == sell.length);
                          require(indices.length == buy.length);
                          require(blockNumber <= 0xFFFFFFFF);
                  
                          uint bytes14Offset = BYTES_14_OFFSET;
                  
                          for (uint i = 0; i < indices.length; i++) {
                              require(indices[i] < tokenRatesCompactData.length);
                              uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset));
                              tokenRatesCompactData[indices[i]] = bytes32(data);
                          }
                      }
                  
                      function setBaseRate(
                          ERC20[] tokens,
                          uint[] baseBuy,
                          uint[] baseSell,
                          bytes14[] buy,
                          bytes14[] sell,
                          uint blockNumber,
                          uint[] indices
                      )
                          public
                          onlyOperator
                      {
                          require(tokens.length == baseBuy.length);
                          require(tokens.length == baseSell.length);
                          require(sell.length == buy.length);
                          require(sell.length == indices.length);
                  
                          for (uint ind = 0; ind < tokens.length; ind++) {
                              require(tokenData[tokens[ind]].listed);
                              tokenData[tokens[ind]].baseBuyRate = baseBuy[ind];
                              tokenData[tokens[ind]].baseSellRate = baseSell[ind];
                          }
                  
                          setCompactData(buy, sell, blockNumber, indices);
                      }
                  
                      function setQtyStepFunction(
                          ERC20 token,
                          int[] xBuy,
                          int[] yBuy,
                          int[] xSell,
                          int[] ySell
                      )
                          public
                          onlyOperator
                      {
                          require(xBuy.length == yBuy.length);
                          require(xSell.length == ySell.length);
                          require(xBuy.length <= MAX_STEPS_IN_FUNCTION);
                          require(xSell.length <= MAX_STEPS_IN_FUNCTION);
                          require(tokenData[token].listed);
                  
                          tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy);
                          tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell);
                      }
                  
                      function setImbalanceStepFunction(
                          ERC20 token,
                          int[] xBuy,
                          int[] yBuy,
                          int[] xSell,
                          int[] ySell
                      )
                          public
                          onlyOperator
                      {
                          require(xBuy.length == yBuy.length);
                          require(xSell.length == ySell.length);
                          require(xBuy.length <= MAX_STEPS_IN_FUNCTION);
                          require(xSell.length <= MAX_STEPS_IN_FUNCTION);
                          require(tokenData[token].listed);
                  
                          tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy);
                          tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell);
                      }
                  
                      function setValidRateDurationInBlocks(uint duration) public onlyAdmin {
                          validRateDurationInBlocks = duration;
                      }
                  
                      function enableTokenTrade(ERC20 token) public onlyAdmin {
                          require(tokenData[token].listed);
                          require(tokenControlInfo[token].minimalRecordResolution != 0);
                          tokenData[token].enabled = true;
                      }
                  
                      function disableTokenTrade(ERC20 token) public onlyAlerter {
                          require(tokenData[token].listed);
                          tokenData[token].enabled = false;
                      }
                  
                      function setReserveAddress(address reserve) public onlyAdmin {
                          reserveContract = reserve;
                      }
                  
                      function recordImbalance(
                          ERC20 token,
                          int buyAmount,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          public
                      {
                          require(msg.sender == reserveContract);
                  
                          if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token);
                  
                          return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock);
                      }
                  
                      /* solhint-disable function-max-lines */
                      function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) {
                          // check if trade is enabled
                          if (!tokenData[token].enabled) return 0;
                          if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set
                  
                          // get rate update block
                          bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
                  
                          uint updateRateBlock = getLast4Bytes(compactData);
                          if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired
                          // check imbalance
                          int totalImbalance;
                          int blockImbalance;
                          (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber);
                  
                          // calculate actual rate
                          int imbalanceQty;
                          int extraBps;
                          int8 rateUpdate;
                          uint rate;
                  
                          if (buy) {
                              // start with base rate
                              rate = tokenData[token].baseBuyRate;
                  
                              // add rate update
                              rateUpdate = getRateByteFromCompactData(compactData, token, true);
                              extraBps = int(rateUpdate) * 10;
                              rate = addBps(rate, extraBps);
                  
                              // compute token qty
                              qty = getTokenQty(token, rate, qty);
                              imbalanceQty = int(qty);
                              totalImbalance += imbalanceQty;
                  
                              // add qty overhead
                              extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty));
                              rate = addBps(rate, extraBps);
                  
                              // add imbalance overhead
                              extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance);
                              rate = addBps(rate, extraBps);
                          } else {
                              // start with base rate
                              rate = tokenData[token].baseSellRate;
                  
                              // add rate update
                              rateUpdate = getRateByteFromCompactData(compactData, token, false);
                              extraBps = int(rateUpdate) * 10;
                              rate = addBps(rate, extraBps);
                  
                              // compute token qty
                              imbalanceQty = -1 * int(qty);
                              totalImbalance += imbalanceQty;
                  
                              // add qty overhead
                              extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty));
                              rate = addBps(rate, extraBps);
                  
                              // add imbalance overhead
                              extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance);
                              rate = addBps(rate, extraBps);
                          }
                  
                          if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0;
                          if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0;
                  
                          return rate;
                      }
                      /* solhint-enable function-max-lines */
                  
                      function getBasicRate(ERC20 token, bool buy) public view returns(uint) {
                          if (buy)
                              return tokenData[token].baseBuyRate;
                          else
                              return tokenData[token].baseSellRate;
                      }
                  
                      function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) {
                          require(tokenData[token].listed);
                  
                          uint arrayIndex = tokenData[token].compactDataArrayIndex;
                          uint fieldOffset = tokenData[token].compactDataFieldIndex;
                  
                          return (
                              arrayIndex,
                              fieldOffset,
                              byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)),
                              byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false))
                          );
                      }
                  
                      function getTokenBasicData(ERC20 token) public view returns(bool, bool) {
                          return (tokenData[token].listed, tokenData[token].enabled);
                      }
                  
                      /* solhint-disable code-complexity */
                      function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) {
                          if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length);
                          if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param];
                          if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length);
                          if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param];
                  
                          if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length);
                          if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param];
                          if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length);
                          if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param];
                  
                          if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length);
                          if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param];
                          if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length);
                          if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param];
                  
                          if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length);
                          if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param];
                          if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length);
                          if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param];
                  
                          revert();
                      }
                      /* solhint-enable code-complexity */
                  
                      function getRateUpdateBlock(ERC20 token) public view returns(uint) {
                          bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
                          return getLast4Bytes(compactData);
                      }
                  
                      function getListedTokens() public view returns(ERC20[]) {
                          return listedTokens;
                      }
                  
                      function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) {
                          uint dstDecimals = getDecimals(token);
                          uint srcDecimals = ETH_DECIMALS;
                  
                          return calcDstQty(ethQty, srcDecimals, dstDecimals, rate);
                      }
                  
                      function getLast4Bytes(bytes32 b) internal pure returns(uint) {
                          // cannot trust compiler with not turning bit operations into EXP opcode
                          return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET);
                      }
                  
                      function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) {
                          uint fieldOffset = tokenData[token].compactDataFieldIndex;
                          uint byteOffset;
                          if (buy)
                              byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset;
                          else
                              byteOffset = 4 + fieldOffset;
                  
                          return int8(data[byteOffset]);
                      }
                  
                      function executeStepFunction(StepFunction f, int x) internal pure returns(int) {
                          uint len = f.y.length;
                          for (uint ind = 0; ind < len; ind++) {
                              if (x <= f.x[ind]) return f.y[ind];
                          }
                  
                          return f.y[len-1];
                      }
                  
                      function addBps(uint rate, int bps) internal pure returns(uint) {
                          require(rate <= MAX_RATE);
                          require(bps >= MIN_BPS_ADJUSTMENT);
                          require(bps <= MAX_BPS_ADJUSTMENT);
                  
                          uint maxBps = 100 * 100;
                          return (rate * uint(int(maxBps) + bps)) / maxBps;
                      }
                  
                      function abs(int x) internal pure returns(uint) {
                          if (x < 0)
                              return uint(-1 * x);
                          else
                              return uint(x);
                      }
                  }

                  File 9 of 10: ConversionRates
                  pragma solidity 0.4.18;
                  
                  interface ConversionRatesInterface {
                  
                      function recordImbalance(
                          ERC20 token,
                          int buyAmount,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          public;
                  
                      function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);
                  }
                  
                  interface ERC20 {
                      function totalSupply() public view returns (uint supply);
                      function balanceOf(address _owner) public view returns (uint balance);
                      function transfer(address _to, uint _value) public returns (bool success);
                      function transferFrom(address _from, address _to, uint _value) public returns (bool success);
                      function approve(address _spender, uint _value) public returns (bool success);
                      function allowance(address _owner, address _spender) public view returns (uint remaining);
                      function decimals() public view returns(uint digits);
                      event Approval(address indexed _owner, address indexed _spender, uint _value);
                  }
                  
                  contract PermissionGroups {
                  
                      address public admin;
                      address public pendingAdmin;
                      mapping(address=>bool) internal operators;
                      mapping(address=>bool) internal alerters;
                      address[] internal operatorsGroup;
                      address[] internal alertersGroup;
                      uint constant internal MAX_GROUP_SIZE = 50;
                  
                      function PermissionGroups() public {
                          admin = msg.sender;
                      }
                  
                      modifier onlyAdmin() {
                          require(msg.sender == admin);
                          _;
                      }
                  
                      modifier onlyOperator() {
                          require(operators[msg.sender]);
                          _;
                      }
                  
                      modifier onlyAlerter() {
                          require(alerters[msg.sender]);
                          _;
                      }
                  
                      function getOperators () external view returns(address[]) {
                          return operatorsGroup;
                      }
                  
                      function getAlerters () external view returns(address[]) {
                          return alertersGroup;
                      }
                  
                      event TransferAdminPending(address pendingAdmin);
                  
                      /**
                       * @dev Allows the current admin to set the pendingAdmin address.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdmin(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(pendingAdmin);
                          pendingAdmin = newAdmin;
                      }
                  
                      /**
                       * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdminQuickly(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(newAdmin);
                          AdminClaimed(newAdmin, admin);
                          admin = newAdmin;
                      }
                  
                      event AdminClaimed( address newAdmin, address previousAdmin);
                  
                      /**
                       * @dev Allows the pendingAdmin address to finalize the change admin process.
                       */
                      function claimAdmin() public {
                          require(pendingAdmin == msg.sender);
                          AdminClaimed(pendingAdmin, admin);
                          admin = pendingAdmin;
                          pendingAdmin = address(0);
                      }
                  
                      event AlerterAdded (address newAlerter, bool isAdd);
                  
                      function addAlerter(address newAlerter) public onlyAdmin {
                          require(!alerters[newAlerter]); // prevent duplicates.
                          require(alertersGroup.length < MAX_GROUP_SIZE);
                  
                          AlerterAdded(newAlerter, true);
                          alerters[newAlerter] = true;
                          alertersGroup.push(newAlerter);
                      }
                  
                      function removeAlerter (address alerter) public onlyAdmin {
                          require(alerters[alerter]);
                          alerters[alerter] = false;
                  
                          for (uint i = 0; i < alertersGroup.length; ++i) {
                              if (alertersGroup[i] == alerter) {
                                  alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                  alertersGroup.length--;
                                  AlerterAdded(alerter, false);
                                  break;
                              }
                          }
                      }
                  
                      event OperatorAdded(address newOperator, bool isAdd);
                  
                      function addOperator(address newOperator) public onlyAdmin {
                          require(!operators[newOperator]); // prevent duplicates.
                          require(operatorsGroup.length < MAX_GROUP_SIZE);
                  
                          OperatorAdded(newOperator, true);
                          operators[newOperator] = true;
                          operatorsGroup.push(newOperator);
                      }
                  
                      function removeOperator (address operator) public onlyAdmin {
                          require(operators[operator]);
                          operators[operator] = false;
                  
                          for (uint i = 0; i < operatorsGroup.length; ++i) {
                              if (operatorsGroup[i] == operator) {
                                  operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                  operatorsGroup.length -= 1;
                                  OperatorAdded(operator, false);
                                  break;
                              }
                          }
                      }
                  }
                  
                  contract Withdrawable is PermissionGroups {
                  
                      event TokenWithdraw(ERC20 token, uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw all ERC20 compatible tokens
                       * @param token ERC20 The address of the token contract
                       */
                      function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                          require(token.transfer(sendTo, amount));
                          TokenWithdraw(token, amount, sendTo);
                      }
                  
                      event EtherWithdraw(uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw Ethers
                       */
                      function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                          sendTo.transfer(amount);
                          EtherWithdraw(amount, sendTo);
                      }
                  }
                  
                  contract VolumeImbalanceRecorder is Withdrawable {
                  
                      uint constant internal SLIDING_WINDOW_SIZE = 5;
                      uint constant internal POW_2_64 = 2 ** 64;
                  
                      struct TokenControlInfo {
                          uint minimalRecordResolution; // can be roughly 1 cent
                          uint maxPerBlockImbalance; // in twei resolution
                          uint maxTotalImbalance; // max total imbalance (between rate updates)
                                              // before halting trade
                      }
                  
                      mapping(address => TokenControlInfo) internal tokenControlInfo;
                  
                      struct TokenImbalanceData {
                          int  lastBlockBuyUnitsImbalance;
                          uint lastBlock;
                  
                          int  totalBuyUnitsImbalance;
                          uint lastRateUpdateBlock;
                      }
                  
                      mapping(address => mapping(uint=>uint)) public tokenImbalanceData;
                  
                      function VolumeImbalanceRecorder(address _admin) public {
                          require(_admin != address(0));
                          admin = _admin;
                      }
                  
                      function setTokenControlInfo(
                          ERC20 token,
                          uint minimalRecordResolution,
                          uint maxPerBlockImbalance,
                          uint maxTotalImbalance
                      )
                          public
                          onlyAdmin
                      {
                          tokenControlInfo[token] =
                              TokenControlInfo(
                                  minimalRecordResolution,
                                  maxPerBlockImbalance,
                                  maxTotalImbalance
                              );
                      }
                  
                      function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) {
                          return (tokenControlInfo[token].minimalRecordResolution,
                                  tokenControlInfo[token].maxPerBlockImbalance,
                                  tokenControlInfo[token].maxTotalImbalance);
                      }
                  
                      function addImbalance(
                          ERC20 token,
                          int buyAmount,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          internal
                      {
                          uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE;
                          int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution));
                  
                          int prevImbalance = 0;
                  
                          TokenImbalanceData memory currentBlockData =
                              decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]);
                  
                          // first scenario - this is not the first tx in the current block
                          if (currentBlockData.lastBlock == currentBlock) {
                              if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) {
                                  // just increase imbalance
                                  currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;
                                  currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount;
                              } else {
                                  // imbalance was changed in the middle of the block
                                  prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock);
                                  currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;
                                  currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;
                                  currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);
                              }
                          } else {
                              // first tx in the current block
                              int currentBlockImbalance;
                              (prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock);
                  
                              currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount;
                              currentBlockData.lastBlock = uint(currentBlock);
                              currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);
                              currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;
                          }
                  
                          tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData);
                      }
                  
                      function setGarbageToVolumeRecorder(ERC20 token) internal {
                          for (uint i = 0; i < SLIDING_WINDOW_SIZE; i++) {
                              tokenImbalanceData[token][i] = 0x1;
                          }
                      }
                  
                      function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) {
                          // check the imbalance in the sliding window
                          require(startBlock <= endBlock);
                  
                          buyImbalance = 0;
                  
                          for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) {
                              TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);
                  
                              if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) {
                                  buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance);
                              }
                          }
                      }
                  
                      function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock)
                          internal view
                          returns(int buyImbalance, int currentBlockImbalance)
                      {
                          buyImbalance = 0;
                          currentBlockImbalance = 0;
                          uint latestBlock = 0;
                          int imbalanceInRange = 0;
                          uint startBlock = rateUpdateBlock;
                          uint endBlock = currentBlock;
                  
                          for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) {
                              TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);
                  
                              if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) {
                                  imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance;
                              }
                  
                              if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue;
                              if (perBlockData.lastBlock < latestBlock) continue;
                  
                              latestBlock = perBlockData.lastBlock;
                              buyImbalance = perBlockData.totalBuyUnitsImbalance;
                              if (uint(perBlockData.lastBlock) == currentBlock) {
                                  currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance;
                              }
                          }
                  
                          if (buyImbalance == 0) {
                              buyImbalance = imbalanceInRange;
                          }
                      }
                  
                      function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock)
                          internal view
                          returns(int totalImbalance, int currentBlockImbalance)
                      {
                  
                          int resolution = int(tokenControlInfo[token].minimalRecordResolution);
                  
                          (totalImbalance, currentBlockImbalance) =
                              getImbalanceSinceRateUpdate(
                                  token,
                                  rateUpdateBlock,
                                  currentBlock);
                  
                          totalImbalance *= resolution;
                          currentBlockImbalance *= resolution;
                      }
                  
                      function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) {
                          return tokenControlInfo[token].maxPerBlockImbalance;
                      }
                  
                      function getMaxTotalImbalance(ERC20 token) internal view returns(uint) {
                          return tokenControlInfo[token].maxTotalImbalance;
                      }
                  
                      function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) {
                          // check for overflows
                          require(data.lastBlockBuyUnitsImbalance < int(POW_2_64 / 2));
                          require(data.lastBlockBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2));
                          require(data.lastBlock < POW_2_64);
                          require(data.totalBuyUnitsImbalance < int(POW_2_64 / 2));
                          require(data.totalBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2));
                          require(data.lastRateUpdateBlock < POW_2_64);
                  
                          // do encoding
                          uint result = uint(data.lastBlockBuyUnitsImbalance) & (POW_2_64 - 1);
                          result |= data.lastBlock * POW_2_64;
                          result |= (uint(data.totalBuyUnitsImbalance) & (POW_2_64 - 1)) * POW_2_64 * POW_2_64;
                          result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64;
                  
                          return result;
                      }
                  
                      function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) {
                          TokenImbalanceData memory data;
                  
                          data.lastBlockBuyUnitsImbalance = int(int64(input & (POW_2_64 - 1)));
                          data.lastBlock = uint(uint64((input / POW_2_64) & (POW_2_64 - 1)));
                          data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) & (POW_2_64 - 1)));
                          data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64))));
                  
                          return data;
                      }
                  }
                  
                  contract Utils {
                  
                      ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
                      uint  constant internal PRECISION = (10**18);
                      uint  constant internal MAX_QTY   = (10**28); // 10B tokens
                      uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
                      uint  constant internal MAX_DECIMALS = 18;
                      uint  constant internal ETH_DECIMALS = 18;
                      mapping(address=>uint) internal decimals;
                  
                      function setDecimals(ERC20 token) internal {
                          if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                          else decimals[token] = token.decimals();
                      }
                  
                      function getDecimals(ERC20 token) internal view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                          uint tokenDecimals = decimals[token];
                          // technically, there might be token with decimals 0
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          if(tokenDecimals == 0) return token.decimals();
                  
                          return tokenDecimals;
                      }
                  
                      function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(srcQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                          }
                      }
                  
                      function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(dstQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          //source quantity is rounded up. to avoid dest quantity being too low.
                          uint numerator;
                          uint denominator;
                          if (srcDecimals >= dstDecimals) {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                              denominator = rate;
                          } else {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty);
                              denominator = (rate * (10**(dstDecimals - srcDecimals)));
                          }
                          return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                      }
                  }
                  
                  contract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils {
                  
                      // bps - basic rate steps. one step is 1 / 10000 of the rate.
                      struct StepFunction {
                          int[] x; // quantity for each step. Quantity of each step includes previous steps.
                          int[] y; // rate change per quantity step  in bps.
                      }
                  
                      struct TokenData {
                          bool listed;  // was added to reserve
                          bool enabled; // whether trade is enabled
                  
                          // position in the compact data
                          uint compactDataArrayIndex;
                          uint compactDataFieldIndex;
                  
                          // rate data. base and changes according to quantity and reserve balance.
                          // generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction.
                          uint baseBuyRate;  // in PRECISION units. see KyberConstants
                          uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate
                          StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate.
                          StepFunction sellRateQtyStepFunction;// in bps. higher the qua
                          StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate.
                          StepFunction sellRateImbalanceStepFunction;
                      }
                  
                      /*
                      this is the data for tokenRatesCompactData
                      but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write
                      so we represent it as bytes32 and do the byte tricks ourselves.
                      struct TokenRatesCompactData {
                          bytes14 buy;  // change buy rate of token from baseBuyRate in 10 bps
                          bytes14 sell; // change sell rate of token from baseSellRate in 10 bps
                  
                          uint32 blockNumber;
                      } */
                      uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks
                      ERC20[] internal listedTokens;
                      mapping(address=>TokenData) internal tokenData;
                      bytes32[] internal tokenRatesCompactData;
                      uint public numTokensInCurrentCompactData = 0;
                      address public reserveContract;
                      uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14;
                      uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA));
                      uint constant internal MAX_STEPS_IN_FUNCTION = 10;
                      int  constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B %
                      int  constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100%
                  
                      function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin)
                          { } // solhint-disable-line no-empty-blocks
                  
                      function addToken(ERC20 token) public onlyAdmin {
                  
                          require(!tokenData[token].listed);
                          tokenData[token].listed = true;
                          listedTokens.push(token);
                  
                          if (numTokensInCurrentCompactData == 0) {
                              tokenRatesCompactData.length++; // add new structure
                          }
                  
                          tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1;
                          tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData;
                  
                          numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA;
                  
                          setGarbageToVolumeRecorder(token);
                  
                          setDecimals(token);
                      }
                  
                      function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator {
                  
                          require(buy.length == sell.length);
                          require(indices.length == buy.length);
                          require(blockNumber <= 0xFFFFFFFF);
                  
                          uint bytes14Offset = BYTES_14_OFFSET;
                  
                          for (uint i = 0; i < indices.length; i++) {
                              require(indices[i] < tokenRatesCompactData.length);
                              uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset));
                              tokenRatesCompactData[indices[i]] = bytes32(data);
                          }
                      }
                  
                      function setBaseRate(
                          ERC20[] tokens,
                          uint[] baseBuy,
                          uint[] baseSell,
                          bytes14[] buy,
                          bytes14[] sell,
                          uint blockNumber,
                          uint[] indices
                      )
                          public
                          onlyOperator
                      {
                          require(tokens.length == baseBuy.length);
                          require(tokens.length == baseSell.length);
                          require(sell.length == buy.length);
                          require(sell.length == indices.length);
                  
                          for (uint ind = 0; ind < tokens.length; ind++) {
                              require(tokenData[tokens[ind]].listed);
                              tokenData[tokens[ind]].baseBuyRate = baseBuy[ind];
                              tokenData[tokens[ind]].baseSellRate = baseSell[ind];
                          }
                  
                          setCompactData(buy, sell, blockNumber, indices);
                      }
                  
                      function setQtyStepFunction(
                          ERC20 token,
                          int[] xBuy,
                          int[] yBuy,
                          int[] xSell,
                          int[] ySell
                      )
                          public
                          onlyOperator
                      {
                          require(xBuy.length == yBuy.length);
                          require(xSell.length == ySell.length);
                          require(xBuy.length <= MAX_STEPS_IN_FUNCTION);
                          require(xSell.length <= MAX_STEPS_IN_FUNCTION);
                          require(tokenData[token].listed);
                  
                          tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy);
                          tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell);
                      }
                  
                      function setImbalanceStepFunction(
                          ERC20 token,
                          int[] xBuy,
                          int[] yBuy,
                          int[] xSell,
                          int[] ySell
                      )
                          public
                          onlyOperator
                      {
                          require(xBuy.length == yBuy.length);
                          require(xSell.length == ySell.length);
                          require(xBuy.length <= MAX_STEPS_IN_FUNCTION);
                          require(xSell.length <= MAX_STEPS_IN_FUNCTION);
                          require(tokenData[token].listed);
                  
                          tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy);
                          tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell);
                      }
                  
                      function setValidRateDurationInBlocks(uint duration) public onlyAdmin {
                          validRateDurationInBlocks = duration;
                      }
                  
                      function enableTokenTrade(ERC20 token) public onlyAdmin {
                          require(tokenData[token].listed);
                          require(tokenControlInfo[token].minimalRecordResolution != 0);
                          tokenData[token].enabled = true;
                      }
                  
                      function disableTokenTrade(ERC20 token) public onlyAlerter {
                          require(tokenData[token].listed);
                          tokenData[token].enabled = false;
                      }
                  
                      function setReserveAddress(address reserve) public onlyAdmin {
                          reserveContract = reserve;
                      }
                  
                      function recordImbalance(
                          ERC20 token,
                          int buyAmount,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          public
                      {
                          require(msg.sender == reserveContract);
                  
                          if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token);
                  
                          return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock);
                      }
                  
                      /* solhint-disable function-max-lines */
                      function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) {
                          // check if trade is enabled
                          if (!tokenData[token].enabled) return 0;
                          if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set
                  
                          // get rate update block
                          bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
                  
                          uint updateRateBlock = getLast4Bytes(compactData);
                          if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired
                          // check imbalance
                          int totalImbalance;
                          int blockImbalance;
                          (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber);
                  
                          // calculate actual rate
                          int imbalanceQty;
                          int extraBps;
                          int8 rateUpdate;
                          uint rate;
                  
                          if (buy) {
                              // start with base rate
                              rate = tokenData[token].baseBuyRate;
                  
                              // add rate update
                              rateUpdate = getRateByteFromCompactData(compactData, token, true);
                              extraBps = int(rateUpdate) * 10;
                              rate = addBps(rate, extraBps);
                  
                              // compute token qty
                              qty = getTokenQty(token, rate, qty);
                              imbalanceQty = int(qty);
                              totalImbalance += imbalanceQty;
                  
                              // add qty overhead
                              extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty));
                              rate = addBps(rate, extraBps);
                  
                              // add imbalance overhead
                              extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance);
                              rate = addBps(rate, extraBps);
                          } else {
                              // start with base rate
                              rate = tokenData[token].baseSellRate;
                  
                              // add rate update
                              rateUpdate = getRateByteFromCompactData(compactData, token, false);
                              extraBps = int(rateUpdate) * 10;
                              rate = addBps(rate, extraBps);
                  
                              // compute token qty
                              imbalanceQty = -1 * int(qty);
                              totalImbalance += imbalanceQty;
                  
                              // add qty overhead
                              extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty));
                              rate = addBps(rate, extraBps);
                  
                              // add imbalance overhead
                              extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance);
                              rate = addBps(rate, extraBps);
                          }
                  
                          if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0;
                          if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0;
                  
                          return rate;
                      }
                      /* solhint-enable function-max-lines */
                  
                      function getBasicRate(ERC20 token, bool buy) public view returns(uint) {
                          if (buy)
                              return tokenData[token].baseBuyRate;
                          else
                              return tokenData[token].baseSellRate;
                      }
                  
                      function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) {
                          require(tokenData[token].listed);
                  
                          uint arrayIndex = tokenData[token].compactDataArrayIndex;
                          uint fieldOffset = tokenData[token].compactDataFieldIndex;
                  
                          return (
                              arrayIndex,
                              fieldOffset,
                              byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)),
                              byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false))
                          );
                      }
                  
                      function getTokenBasicData(ERC20 token) public view returns(bool, bool) {
                          return (tokenData[token].listed, tokenData[token].enabled);
                      }
                  
                      /* solhint-disable code-complexity */
                      function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) {
                          if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length);
                          if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param];
                          if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length);
                          if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param];
                  
                          if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length);
                          if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param];
                          if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length);
                          if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param];
                  
                          if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length);
                          if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param];
                          if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length);
                          if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param];
                  
                          if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length);
                          if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param];
                          if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length);
                          if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param];
                  
                          revert();
                      }
                      /* solhint-enable code-complexity */
                  
                      function getRateUpdateBlock(ERC20 token) public view returns(uint) {
                          bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
                          return getLast4Bytes(compactData);
                      }
                  
                      function getListedTokens() public view returns(ERC20[]) {
                          return listedTokens;
                      }
                  
                      function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) {
                          uint dstDecimals = getDecimals(token);
                          uint srcDecimals = ETH_DECIMALS;
                  
                          return calcDstQty(ethQty, srcDecimals, dstDecimals, rate);
                      }
                  
                      function getLast4Bytes(bytes32 b) internal pure returns(uint) {
                          // cannot trust compiler with not turning bit operations into EXP opcode
                          return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET);
                      }
                  
                      function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) {
                          uint fieldOffset = tokenData[token].compactDataFieldIndex;
                          uint byteOffset;
                          if (buy)
                              byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset;
                          else
                              byteOffset = 4 + fieldOffset;
                  
                          return int8(data[byteOffset]);
                      }
                  
                      function executeStepFunction(StepFunction f, int x) internal pure returns(int) {
                          uint len = f.y.length;
                          for (uint ind = 0; ind < len; ind++) {
                              if (x <= f.x[ind]) return f.y[ind];
                          }
                  
                          return f.y[len-1];
                      }
                  
                      function addBps(uint rate, int bps) internal pure returns(uint) {
                          require(rate <= MAX_RATE);
                          require(bps >= MIN_BPS_ADJUSTMENT);
                          require(bps <= MAX_BPS_ADJUSTMENT);
                  
                          uint maxBps = 100 * 100;
                          return (rate * uint(int(maxBps) + bps)) / maxBps;
                      }
                  
                      function abs(int x) internal pure returns(uint) {
                          if (x < 0)
                              return uint(-1 * x);
                          else
                              return uint(x);
                      }
                  }

                  File 10 of 10: KyberNetworkCrystal
                  pragma solidity ^0.4.13;
                  
                  library SafeMath {
                    function mul(uint256 a, uint256 b) internal constant returns (uint256) {
                      uint256 c = a * b;
                      assert(a == 0 || c / a == b);
                      return c;
                    }
                  
                    function div(uint256 a, uint256 b) internal constant 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 c;
                    }
                  
                    function sub(uint256 a, uint256 b) internal constant returns (uint256) {
                      assert(b <= a);
                      return a - b;
                    }
                  
                    function add(uint256 a, uint256 b) internal constant returns (uint256) {
                      uint256 c = a + b;
                      assert(c >= a);
                      return c;
                    }
                  }
                  
                  contract Ownable {
                    address public owner;
                  
                  
                    /**
                     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                     * account.
                     */
                    function Ownable() {
                      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 transfer control of the contract to a newOwner.
                     * @param newOwner The address to transfer ownership to.
                     */
                    function transferOwnership(address newOwner) onlyOwner {
                      if (newOwner != address(0)) {
                        owner = newOwner;
                      }
                    }
                  
                  }
                  
                  
                  
                  contract ERC20Basic {
                    uint256 public totalSupply;
                    function balanceOf(address who) constant returns (uint256);
                    function transfer(address to, uint256 value) returns (bool);
                    
                    // KYBER-NOTE! code changed to comply with ERC20 standard
                    event Transfer(address indexed _from, address indexed _to, uint _value);
                    //event Transfer(address indexed from, address indexed to, uint256 value);
                  }
                  
                  contract BasicToken is ERC20Basic {
                    using SafeMath for uint256;
                  
                    mapping(address => uint256) balances;
                  
                    /**
                    * @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) returns (bool) {
                      balances[msg.sender] = balances[msg.sender].sub(_value);
                      balances[_to] = balances[_to].add(_value);
                      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) constant returns (uint256 balance) {
                      return balances[_owner];
                    }
                  
                  }
                  
                  contract ERC20 is ERC20Basic {
                    function allowance(address owner, address spender) constant returns (uint256);
                    function transferFrom(address from, address to, uint256 value) returns (bool);
                    function approve(address spender, uint256 value) returns (bool);
                    
                    // KYBER-NOTE! code changed to comply with ERC20 standard
                    event Approval(address indexed _owner, address indexed _spender, uint _value);
                    //event Approval(address indexed owner, address indexed spender, uint256 value);
                  }
                  
                  contract StandardToken is ERC20, BasicToken {
                  
                    mapping (address => mapping (address => uint256)) 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 amout of tokens to be transfered
                     */
                    function transferFrom(address _from, address _to, uint256 _value) returns (bool) {
                      var _allowance = allowed[_from][msg.sender];
                  
                      // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
                      // require (_value <= _allowance);
                  
                      // KYBER-NOTE! code changed to comply with ERC20 standard
                      balances[_from] = balances[_from].sub(_value);
                      balances[_to] = balances[_to].add(_value);
                      //balances[_from] = balances[_from].sub(_value); // this was removed
                      allowed[_from][msg.sender] = _allowance.sub(_value);
                      Transfer(_from, _to, _value);
                      return true;
                    }
                  
                    /**
                     * @dev Aprove the passed address to spend the specified amount of tokens on behalf of msg.sender.
                     * @param _spender The address which will spend the funds.
                     * @param _value The amount of tokens to be spent.
                     */
                    function approve(address _spender, uint256 _value) returns (bool) {
                  
                      // To change the approve amount you first have to reduce the addresses`
                      //  allowance to zero by calling `approve(_spender, 0)` if it is not
                      //  already 0 to mitigate the race condition described here:
                      //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                      require((_value == 0) || (allowed[msg.sender][_spender] == 0));
                  
                      allowed[msg.sender][_spender] = _value;
                      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 specifing the amount of tokens still avaible for the spender.
                     */
                    function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
                      return allowed[_owner][_spender];
                    }
                  
                  }
                  
                  contract KyberNetworkCrystal is StandardToken, Ownable {
                      string  public  constant name = "Kyber Network Crystal";
                      string  public  constant symbol = "KNC";
                      uint    public  constant decimals = 18;
                  
                      uint    public  saleStartTime;
                      uint    public  saleEndTime;
                  
                      address public  tokenSaleContract;
                  
                      modifier onlyWhenTransferEnabled() {
                          if( now <= saleEndTime && now >= saleStartTime ) {
                              require( msg.sender == tokenSaleContract );
                          }
                          _;
                      }
                  
                      modifier validDestination( address to ) {
                          require(to != address(0x0));
                          require(to != address(this) );
                          _;
                      }
                  
                      function KyberNetworkCrystal( uint tokenTotalAmount, uint startTime, uint endTime, address admin ) {
                          // Mint all tokens. Then disable minting forever.
                          balances[msg.sender] = tokenTotalAmount;
                          totalSupply = tokenTotalAmount;
                          Transfer(address(0x0), msg.sender, tokenTotalAmount);
                  
                          saleStartTime = startTime;
                          saleEndTime = endTime;
                  
                          tokenSaleContract = msg.sender;
                          transferOwnership(admin); // admin could drain tokens that were sent here by mistake
                      }
                  
                      function transfer(address _to, uint _value)
                          onlyWhenTransferEnabled
                          validDestination(_to)
                          returns (bool) {
                          return super.transfer(_to, _value);
                      }
                  
                      function transferFrom(address _from, address _to, uint _value)
                          onlyWhenTransferEnabled
                          validDestination(_to)
                          returns (bool) {
                          return super.transferFrom(_from, _to, _value);
                      }
                  
                      event Burn(address indexed _burner, uint _value);
                  
                      function burn(uint _value) onlyWhenTransferEnabled
                          returns (bool){
                          balances[msg.sender] = balances[msg.sender].sub(_value);
                          totalSupply = totalSupply.sub(_value);
                          Burn(msg.sender, _value);
                          Transfer(msg.sender, address(0x0), _value);
                          return true;
                      }
                  
                      // save some gas by making only one contract call
                      function burnFrom(address _from, uint256 _value) onlyWhenTransferEnabled
                          returns (bool) {
                          assert( transferFrom( _from, msg.sender, _value ) );
                          return burn(_value);
                      }
                  
                      function emergencyERC20Drain( ERC20 token, uint amount ) onlyOwner {
                          token.transfer( owner, amount );
                      }
                  }