ETH Price: $2,091.61 (-0.95%)

Transaction Decoder

Block:
6832585 at Dec-05-2018 08:33:16 PM +UTC
Transaction Fee:
0.001539952 ETH $3.22
Gas Used:
192,494 Gas / 8 Gwei

Emitted Events:

75 0x1b6810350bfbe11f1fb3c91c3bf4c50807a21b36.0x6b82e983603887aa12f3f787eaaa4a17a5352c493d705d505c5a2c6b7f0a4a3c( 0x6b82e983603887aa12f3f787eaaa4a17a5352c493d705d505c5a2c6b7f0a4a3c, 0000000000000000000000000000000000000000000000000000000000000002, 00000000000000000000000000000000000000000000000017ef5b3e75a09200, 00000000000000000000000000000000000000000000000017d59c32e22d860d, 00000000000000000000000000000000000000000000000000cf120bba3b880f, 00000000000000000000000000000000000000000000000198ed7af33930d447, 00000000000000000000000000000000000000000000000f9f1156ce9b8617d1 )

Account State Difference:

  Address   Before After State Difference Code
(Nanopool)
8,551.972724326567473522 Eth8,551.974264278567473522 Eth0.001539952
0x7305A192...c0F75f845
1.151298928391185853 Eth
Nonce: 1068
1.149758976391185853 Eth
Nonce: 1069
0.001539952

Execution Trace

0x1b6810350bfbe11f1fb3c91c3bf4c50807a21b36.8990ee55( )
  • BancorConverter.getReturn( _fromToken=0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315, _toToken=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, _amount=1724697506125746688 ) => ( 288163199077650405329, 0 )
    • SmartToken.CALL( )
    • SmartToken.CALL( )
    • ContractRegistry.addressOf( _contractName=42616E636F72466F726D756C6100000000000000000000000000000000000000 ) => ( 0xFFd2de852B694F88656e91D9DEfa6b425c454742 )
    • BancorFormula.calculatePurchaseReturn( _supply=77625249686266928335429189, _connectorBalance=46459033809816779085173, _connectorWeight=100000, _depositAmount=1724697506125746688 ) => ( 288163199077650405329 )
    • BancorConverter.getReturn( _fromToken=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, _toToken=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, _amount=288163199077650405329 ) => ( 29466343122010166343 )
      • SmartToken.CALL( )
      • SmartToken.balanceOf( 0x751b934E7496E437503D74D0679A45E49C0B7071 ) => ( 59504151923159952602409 )
      • BNB.balanceOf( 0x751b934E7496E437503D74D0679A45E49C0B7071 ) => ( 6126354418217305757841 )
      • ContractRegistry.getAddress( _contractName=42616E636F72466F726D756C6100000000000000000000000000000000000000 ) => ( 0xFFd2de852B694F88656e91D9DEfa6b425c454742 )
      • BancorFormula.calculateCrossConnectorReturn( _fromConnectorBalance=59504151923159952602409, _fromConnectorWeight=500000, _toConnectorBalance=6126354418217305757841, _toConnectorWeight=500000, _amount=288163199077650405329 ) => ( 29525364325296433915 )
      • KyberNetworkProxy.getExpectedRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=29466343122010166343 ) => ( expectedRate=58285161757509647, slippageRate=56536606904784357 )
        • KyberNetwork.getExpectedRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=29466343122010166343 ) => ( expectedRate=58285161757509647, slippageRate=56536606904784357 )
          • ExpectedRate.getExpectedRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=29466343122010166343 ) => ( expectedRate=58285161757509647, slippageRate=56536606904784357 )
            • KyberNetwork.findBestRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=29466343122010166343 ) => ( obsolete=0, rate=58285161757509647 )
              • KyberReserve.getConversionRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=29466343122010166343, blockNumber=6832585 ) => ( 58285161757509648 )
                • ConversionRates.getRate( token=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, currentBlockNumber=6832585, buy=False, qty=29466343122010166343 ) => ( 58285161757509648 )
                • SanityRates.getSanityRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ) => ( 62575370000000026 )
                • KyberNetwork.findBestRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=29466343122010166343 ) => ( obsolete=0, rate=58285161757509647 )
                  • KyberReserve.getConversionRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=29466343122010166343, blockNumber=6832585 ) => ( 58285161757509648 )
                    • ConversionRates.getRate( token=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, currentBlockNumber=6832585, buy=False, qty=29466343122010166343 ) => ( 58285161757509648 )
                    • SanityRates.getSanityRate( src=0xB8c77482e45F1F44dE1745F52C74426C631bDD52, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ) => ( 62575370000000026 )
                    • BNB.CALL( )
                      File 1 of 14: BancorConverter
                      pragma solidity ^0.4.24;
                      
                      // File: contracts/token/interfaces/IERC20Token.sol
                      
                      /*
                          ERC20 Standard Token interface
                      */
                      contract IERC20Token {
                          // these functions aren't abstract since the compiler emits automatically generated getter functions as external
                          function name() public view returns (string) {}
                          function symbol() public view returns (string) {}
                          function decimals() public view returns (uint8) {}
                          function totalSupply() public view returns (uint256) {}
                          function balanceOf(address _owner) public view returns (uint256) { _owner; }
                          function allowance(address _owner, address _spender) public view returns (uint256) { _owner; _spender; }
                      
                          function transfer(address _to, uint256 _value) public returns (bool success);
                          function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
                          function approve(address _spender, uint256 _value) public returns (bool success);
                      }
                      
                      // File: contracts/utility/interfaces/IWhitelist.sol
                      
                      /*
                          Whitelist interface
                      */
                      contract IWhitelist {
                          function isWhitelisted(address _address) public view returns (bool);
                      }
                      
                      // File: contracts/converter/interfaces/IBancorConverter.sol
                      
                      /*
                          Bancor Converter interface
                      */
                      contract IBancorConverter {
                          function getReturn(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256, uint256);
                          function convert(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256);
                          function conversionWhitelist() public view returns (IWhitelist) {}
                          function conversionFee() public view returns (uint32) {}
                          function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool) { _address; }
                          function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256);
                          function claimTokens(address _from, uint256 _amount) public;
                          // deprecated, backward compatibility
                          function change(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256);
                      }
                      
                      // File: contracts/converter/interfaces/IBancorConverterUpgrader.sol
                      
                      /*
                          Bancor Converter Upgrader interface
                      */
                      contract IBancorConverterUpgrader {
                          function upgrade(bytes32 _version) public;
                      }
                      
                      // File: contracts/converter/interfaces/IBancorFormula.sol
                      
                      /*
                          Bancor Formula interface
                      */
                      contract IBancorFormula {
                          function calculatePurchaseReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _depositAmount) public view returns (uint256);
                          function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public view returns (uint256);
                          function calculateCrossConnectorReturn(uint256 _fromConnectorBalance, uint32 _fromConnectorWeight, uint256 _toConnectorBalance, uint32 _toConnectorWeight, uint256 _amount) public view returns (uint256);
                      }
                      
                      // File: contracts/IBancorNetwork.sol
                      
                      /*
                          Bancor Network interface
                      */
                      contract IBancorNetwork {
                          function convert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn) public payable returns (uint256);
                          function convertFor(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, address _for) public payable returns (uint256);
                          function convertForPrioritized2(
                              IERC20Token[] _path,
                              uint256 _amount,
                              uint256 _minReturn,
                              address _for,
                              uint256 _block,
                              uint8 _v,
                              bytes32 _r,
                              bytes32 _s)
                              public payable returns (uint256);
                      
                          // deprecated, backward compatibility
                          function convertForPrioritized(
                              IERC20Token[] _path,
                              uint256 _amount,
                              uint256 _minReturn,
                              address _for,
                              uint256 _block,
                              uint256 _nonce,
                              uint8 _v,
                              bytes32 _r,
                              bytes32 _s)
                              public payable returns (uint256);
                      }
                      
                      // File: contracts/ContractIds.sol
                      
                      /**
                          Id definitions for bancor contracts
                      
                          Can be used in conjunction with the contract registry to get contract addresses
                      */
                      contract ContractIds {
                          // generic
                          bytes32 public constant CONTRACT_FEATURES = "ContractFeatures";
                          bytes32 public constant CONTRACT_REGISTRY = "ContractRegistry";
                      
                          // bancor logic
                          bytes32 public constant BANCOR_NETWORK = "BancorNetwork";
                          bytes32 public constant BANCOR_FORMULA = "BancorFormula";
                          bytes32 public constant BANCOR_GAS_PRICE_LIMIT = "BancorGasPriceLimit";
                          bytes32 public constant BANCOR_CONVERTER_UPGRADER = "BancorConverterUpgrader";
                          bytes32 public constant BANCOR_CONVERTER_FACTORY = "BancorConverterFactory";
                      
                          // Ids of BNT converter and BNT token
                          bytes32 public constant BNT_TOKEN = "BNTToken";
                          bytes32 public constant BNT_CONVERTER = "BNTConverter";
                      
                          // Id of BancorX contract
                          bytes32 public constant BANCOR_X = "BancorX";
                      }
                      
                      // File: contracts/FeatureIds.sol
                      
                      /**
                          Id definitions for bancor contract features
                      
                          Can be used to query the ContractFeatures contract to check whether a certain feature is supported by a contract
                      */
                      contract FeatureIds {
                          // converter features
                          uint256 public constant CONVERTER_CONVERSION_WHITELIST = 1 << 0;
                      }
                      
                      // File: contracts/utility/interfaces/IOwned.sol
                      
                      /*
                          Owned contract interface
                      */
                      contract IOwned {
                          // this function isn't abstract since the compiler emits automatically generated getter functions as external
                          function owner() public view returns (address) {}
                      
                          function transferOwnership(address _newOwner) public;
                          function acceptOwnership() public;
                      }
                      
                      // File: contracts/utility/Owned.sol
                      
                      /*
                          Provides support and utilities for contract ownership
                      */
                      contract Owned is IOwned {
                          address public owner;
                          address public newOwner;
                      
                          event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
                      
                          /**
                              @dev constructor
                          */
                          constructor() public {
                              owner = msg.sender;
                          }
                      
                          // allows execution by the owner only
                          modifier ownerOnly {
                              require(msg.sender == owner);
                              _;
                          }
                      
                          /**
                              @dev allows transferring the contract ownership
                              the new owner still needs to accept the transfer
                              can only be called by the contract owner
                      
                              @param _newOwner    new contract owner
                          */
                          function transferOwnership(address _newOwner) public ownerOnly {
                              require(_newOwner != owner);
                              newOwner = _newOwner;
                          }
                      
                          /**
                              @dev used by a new owner to accept an ownership transfer
                          */
                          function acceptOwnership() public {
                              require(msg.sender == newOwner);
                              emit OwnerUpdate(owner, newOwner);
                              owner = newOwner;
                              newOwner = address(0);
                          }
                      }
                      
                      // File: contracts/utility/Managed.sol
                      
                      /*
                          Provides support and utilities for contract management
                          Note that a managed contract must also have an owner
                      */
                      contract Managed is Owned {
                          address public manager;
                          address public newManager;
                      
                          event ManagerUpdate(address indexed _prevManager, address indexed _newManager);
                      
                          /**
                              @dev constructor
                          */
                          constructor() public {
                              manager = msg.sender;
                          }
                      
                          // allows execution by the manager only
                          modifier managerOnly {
                              assert(msg.sender == manager);
                              _;
                          }
                      
                          // allows execution by either the owner or the manager only
                          modifier ownerOrManagerOnly {
                              require(msg.sender == owner || msg.sender == manager);
                              _;
                          }
                      
                          /**
                              @dev allows transferring the contract management
                              the new manager still needs to accept the transfer
                              can only be called by the contract manager
                      
                              @param _newManager    new contract manager
                          */
                          function transferManagement(address _newManager) public ownerOrManagerOnly {
                              require(_newManager != manager);
                              newManager = _newManager;
                          }
                      
                          /**
                              @dev used by a new manager to accept a management transfer
                          */
                          function acceptManagement() public {
                              require(msg.sender == newManager);
                              emit ManagerUpdate(manager, newManager);
                              manager = newManager;
                              newManager = address(0);
                          }
                      }
                      
                      // File: contracts/utility/Utils.sol
                      
                      /*
                          Utilities & Common Modifiers
                      */
                      contract Utils {
                          /**
                              constructor
                          */
                          constructor() public {
                          }
                      
                          // verifies that an amount is greater than zero
                          modifier greaterThanZero(uint256 _amount) {
                              require(_amount > 0);
                              _;
                          }
                      
                          // validates an address - currently only checks that it isn't null
                          modifier validAddress(address _address) {
                              require(_address != address(0));
                              _;
                          }
                      
                          // verifies that the address is different than this contract address
                          modifier notThis(address _address) {
                              require(_address != address(this));
                              _;
                          }
                      
                          // Overflow protected math functions
                      
                          /**
                              @dev returns the sum of _x and _y, asserts if the calculation overflows
                      
                              @param _x   value 1
                              @param _y   value 2
                      
                              @return sum
                          */
                          function safeAdd(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              uint256 z = _x + _y;
                              assert(z >= _x);
                              return z;
                          }
                      
                          /**
                              @dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
                      
                              @param _x   minuend
                              @param _y   subtrahend
                      
                              @return difference
                          */
                          function safeSub(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              assert(_x >= _y);
                              return _x - _y;
                          }
                      
                          /**
                              @dev returns the product of multiplying _x by _y, asserts if the calculation overflows
                      
                              @param _x   factor 1
                              @param _y   factor 2
                      
                              @return product
                          */
                          function safeMul(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              uint256 z = _x * _y;
                              assert(_x == 0 || z / _x == _y);
                              return z;
                          }
                      }
                      
                      // File: contracts/utility/interfaces/IContractRegistry.sol
                      
                      /*
                          Contract Registry interface
                      */
                      contract IContractRegistry {
                          function addressOf(bytes32 _contractName) public view returns (address);
                      
                          // deprecated, backward compatibility
                          function getAddress(bytes32 _contractName) public view returns (address);
                      }
                      
                      // File: contracts/utility/interfaces/IContractFeatures.sol
                      
                      /*
                          Contract Features interface
                      */
                      contract IContractFeatures {
                          function isSupported(address _contract, uint256 _features) public view returns (bool);
                          function enableFeatures(uint256 _features, bool _enable) public;
                      }
                      
                      // File: contracts/token/interfaces/ISmartToken.sol
                      
                      /*
                          Smart Token interface
                      */
                      contract ISmartToken is IOwned, IERC20Token {
                          function disableTransfers(bool _disable) public;
                          function issue(address _to, uint256 _amount) public;
                          function destroy(address _from, uint256 _amount) public;
                      }
                      
                      // File: contracts/utility/interfaces/ITokenHolder.sol
                      
                      /*
                          Token Holder interface
                      */
                      contract ITokenHolder is IOwned {
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                      }
                      
                      // File: contracts/utility/TokenHolder.sol
                      
                      /*
                          We consider every contract to be a 'token holder' since it's currently not possible
                          for a contract to deny receiving tokens.
                      
                          The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
                          the owner to send tokens that were sent to the contract by mistake back to their sender.
                      */
                      contract TokenHolder is ITokenHolder, Owned, Utils {
                          /**
                              @dev constructor
                          */
                          constructor() public {
                          }
                      
                          /**
                              @dev withdraws tokens held by the contract and sends them to an account
                              can only be called by the owner
                      
                              @param _token   ERC20 token contract address
                              @param _to      account to receive the new amount
                              @param _amount  amount to withdraw
                          */
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
                              public
                              ownerOnly
                              validAddress(_token)
                              validAddress(_to)
                              notThis(_to)
                          {
                              assert(_token.transfer(_to, _amount));
                          }
                      }
                      
                      // File: contracts/token/SmartTokenController.sol
                      
                      /*
                          The smart token controller is an upgradable part of the smart token that allows
                          more functionality as well as fixes for bugs/exploits.
                          Once it accepts ownership of the token, it becomes the token's sole controller
                          that can execute any of its functions.
                      
                          To upgrade the controller, ownership must be transferred to a new controller, along with
                          any relevant data.
                      
                          The smart token must be set on construction and cannot be changed afterwards.
                          Wrappers are provided (as opposed to a single 'execute' function) for each of the token's functions, for easier access.
                      
                          Note that the controller can transfer token ownership to a new controller that
                          doesn't allow executing any function on the token, for a trustless solution.
                          Doing that will also remove the owner's ability to upgrade the controller.
                      */
                      contract SmartTokenController is TokenHolder {
                          ISmartToken public token;   // smart token
                      
                          /**
                              @dev constructor
                          */
                          constructor(ISmartToken _token)
                              public
                              validAddress(_token)
                          {
                              token = _token;
                          }
                      
                          // ensures that the controller is the token's owner
                          modifier active() {
                              require(token.owner() == address(this));
                              _;
                          }
                      
                          // ensures that the controller is not the token's owner
                          modifier inactive() {
                              require(token.owner() != address(this));
                              _;
                          }
                      
                          /**
                              @dev allows transferring the token ownership
                              the new owner needs to accept the transfer
                              can only be called by the contract owner
                      
                              @param _newOwner    new token owner
                          */
                          function transferTokenOwnership(address _newOwner) public ownerOnly {
                              token.transferOwnership(_newOwner);
                          }
                      
                          /**
                              @dev used by a new owner to accept a token ownership transfer
                              can only be called by the contract owner
                          */
                          function acceptTokenOwnership() public ownerOnly {
                              token.acceptOwnership();
                          }
                      
                          /**
                              @dev disables/enables token transfers
                              can only be called by the contract owner
                      
                              @param _disable    true to disable transfers, false to enable them
                          */
                          function disableTokenTransfers(bool _disable) public ownerOnly {
                              token.disableTransfers(_disable);
                          }
                      
                          /**
                              @dev withdraws tokens held by the controller and sends them to an account
                              can only be called by the owner
                      
                              @param _token   ERC20 token contract address
                              @param _to      account to receive the new amount
                              @param _amount  amount to withdraw
                          */
                          function withdrawFromToken(IERC20Token _token, address _to, uint256 _amount) public ownerOnly {
                              ITokenHolder(token).withdrawTokens(_token, _to, _amount);
                          }
                      }
                      
                      // File: contracts/token/interfaces/IEtherToken.sol
                      
                      /*
                          Ether Token interface
                      */
                      contract IEtherToken is ITokenHolder, IERC20Token {
                          function deposit() public payable;
                          function withdraw(uint256 _amount) public;
                          function withdrawTo(address _to, uint256 _amount) public;
                      }
                      
                      // File: contracts/converter/BancorConverter.sol
                      
                      /*
                          Bancor Converter v0.11
                      
                          The Bancor version of the token converter, allows conversion between a smart token and other ERC20 tokens and between different ERC20 tokens and themselves.
                      
                          ERC20 connector balance can be virtual, meaning that the calculations are based on the virtual balance instead of relying on
                          the actual connector balance. This is a security mechanism that prevents the need to keep a very large (and valuable) balance in a single contract.
                      
                          The converter is upgradable (just like any SmartTokenController).
                      
                          WARNING: It is NOT RECOMMENDED to use the converter with Smart Tokens that have less than 8 decimal digits
                                   or with very small numbers because of precision loss
                      
                          Open issues:
                          - Front-running attacks are currently mitigated by the following mechanisms:
                              - minimum return argument for each conversion provides a way to define a minimum/maximum price for the transaction
                              - gas price limit prevents users from having control over the order of execution
                              - gas price limit check can be skipped if the transaction comes from a trusted, whitelisted signer
                            Other potential solutions might include a commit/reveal based schemes
                          - Possibly add getters for the connector fields so that the client won't need to rely on the order in the struct
                      */
                      contract BancorConverter is IBancorConverter, SmartTokenController, Managed, ContractIds, FeatureIds {
                          uint32 private constant MAX_WEIGHT = 1000000;
                          uint64 private constant MAX_CONVERSION_FEE = 1000000;
                      
                          struct Connector {
                              uint256 virtualBalance;         // connector virtual balance
                              uint32 weight;                  // connector weight, represented in ppm, 1-1000000
                              bool isVirtualBalanceEnabled;   // true if virtual balance is enabled, false if not
                              bool isPurchaseEnabled;         // is purchase of the smart token enabled with the connector, can be set by the owner
                              bool isSet;                     // used to tell if the mapping element is defined
                          }
                      
                          bytes32 public version = '0.11';
                          string public converterType = 'bancor';
                      
                          bool public allowRegistryUpdate = true;             // allows the owner to prevent/allow the registry to be updated
                          bool public claimTokensEnabled = false;             // allows BancorX contract to claim tokens without allowance (one transaction instread of two)
                          IContractRegistry public prevRegistry;              // address of previous registry as security mechanism
                          IContractRegistry public registry;                  // contract registry contract
                          IWhitelist public conversionWhitelist;              // whitelist contract with list of addresses that are allowed to use the converter
                          IERC20Token[] public connectorTokens;               // ERC20 standard token addresses
                          mapping (address => Connector) public connectors;   // connector token addresses -> connector data
                          uint32 private totalConnectorWeight = 0;            // used to efficiently prevent increasing the total connector weight above 100%
                          uint32 public maxConversionFee = 0;                 // maximum conversion fee for the lifetime of the contract,
                                                                              // represented in ppm, 0...1000000 (0 = no fee, 100 = 0.01%, 1000000 = 100%)
                          uint32 public conversionFee = 0;                    // current conversion fee, represented in ppm, 0...maxConversionFee
                          bool public conversionsEnabled = true;              // true if token conversions is enabled, false if not
                          IERC20Token[] private convertPath;
                      
                          // triggered when a conversion between two tokens occurs
                          event Conversion(
                              address indexed _fromToken,
                              address indexed _toToken,
                              address indexed _trader,
                              uint256 _amount,
                              uint256 _return,
                              int256 _conversionFee
                          );
                          // triggered after a conversion with new price data
                          event PriceDataUpdate(
                              address indexed _connectorToken,
                              uint256 _tokenSupply,
                              uint256 _connectorBalance,
                              uint32 _connectorWeight
                          );
                          // triggered when the conversion fee is updated
                          event ConversionFeeUpdate(uint32 _prevFee, uint32 _newFee);
                      
                          // triggered when conversions are enabled/disabled
                          event ConversionsEnable(bool _conversionsEnabled);
                      
                          /**
                              @dev constructor
                      
                              @param  _token              smart token governed by the converter
                              @param  _registry           address of a contract registry contract
                              @param  _maxConversionFee   maximum conversion fee, represented in ppm
                              @param  _connectorToken     optional, initial connector, allows defining the first connector at deployment time
                              @param  _connectorWeight    optional, weight for the initial connector
                          */
                          constructor(
                              ISmartToken _token,
                              IContractRegistry _registry,
                              uint32 _maxConversionFee,
                              IERC20Token _connectorToken,
                              uint32 _connectorWeight
                          )
                              public
                              SmartTokenController(_token)
                              validAddress(_registry)
                              validMaxConversionFee(_maxConversionFee)
                          {
                              registry = _registry;
                              prevRegistry = _registry;
                              IContractFeatures features = IContractFeatures(registry.addressOf(ContractIds.CONTRACT_FEATURES));
                      
                              // initialize supported features
                              if (features != address(0))
                                  features.enableFeatures(FeatureIds.CONVERTER_CONVERSION_WHITELIST, true);
                      
                              maxConversionFee = _maxConversionFee;
                      
                              if (_connectorToken != address(0))
                                  addConnector(_connectorToken, _connectorWeight, false);
                          }
                      
                          // validates a connector token address - verifies that the address belongs to one of the connector tokens
                          modifier validConnector(IERC20Token _address) {
                              require(connectors[_address].isSet);
                              _;
                          }
                      
                          // validates a token address - verifies that the address belongs to one of the convertible tokens
                          modifier validToken(IERC20Token _address) {
                              require(_address == token || connectors[_address].isSet);
                              _;
                          }
                      
                          // validates maximum conversion fee
                          modifier validMaxConversionFee(uint32 _conversionFee) {
                              require(_conversionFee >= 0 && _conversionFee <= MAX_CONVERSION_FEE);
                              _;
                          }
                      
                          // validates conversion fee
                          modifier validConversionFee(uint32 _conversionFee) {
                              require(_conversionFee >= 0 && _conversionFee <= maxConversionFee);
                              _;
                          }
                      
                          // validates connector weight range
                          modifier validConnectorWeight(uint32 _weight) {
                              require(_weight > 0 && _weight <= MAX_WEIGHT);
                              _;
                          }
                      
                          // validates a conversion path - verifies that the number of elements is odd and that maximum number of 'hops' is 10
                          modifier validConversionPath(IERC20Token[] _path) {
                              require(_path.length > 2 && _path.length <= (1 + 2 * 10) && _path.length % 2 == 1);
                              _;
                          }
                      
                          // allows execution only when the total weight is 100%
                          modifier maxTotalWeightOnly() {
                              require(totalConnectorWeight == MAX_WEIGHT);
                              _;
                          }
                      
                          // allows execution only when conversions aren't disabled
                          modifier conversionsAllowed {
                              assert(conversionsEnabled);
                              _;
                          }
                      
                          // allows execution by the BancorNetwork contract only
                          modifier bancorNetworkOnly {
                              IBancorNetwork bancorNetwork = IBancorNetwork(registry.addressOf(ContractIds.BANCOR_NETWORK));
                              require(msg.sender == address(bancorNetwork));
                              _;
                          }
                      
                          // allows execution by the converter upgrader contract only
                          modifier converterUpgraderOnly {
                              address converterUpgrader = registry.addressOf(ContractIds.BANCOR_CONVERTER_UPGRADER);
                              require(owner == converterUpgrader);
                              _;
                          }
                      
                          // allows execution only when claim tokens is enabled
                          modifier whenClaimTokensEnabled {
                              require(claimTokensEnabled);
                              _;
                          }
                      
                          /**
                              @dev sets the contract registry to whichever address the current registry is pointing to
                           */
                          function updateRegistry() public {
                              // require that upgrading is allowed or that the caller is the owner
                              require(allowRegistryUpdate || msg.sender == owner);
                      
                              // get the address of whichever registry the current registry is pointing to
                              address newRegistry = registry.addressOf(ContractIds.CONTRACT_REGISTRY);
                      
                              // if the new registry hasn't changed or is the zero address, revert
                              require(newRegistry != address(registry) && newRegistry != address(0));
                      
                              // set the previous registry as current registry and current registry as newRegistry
                              prevRegistry = registry;
                              registry = IContractRegistry(newRegistry);
                          }
                      
                          /**
                              @dev security mechanism allowing the converter owner to revert to the previous registry,
                              to be used in emergency scenario
                          */
                          function restoreRegistry() public ownerOrManagerOnly {
                              // set the registry as previous registry
                              registry = prevRegistry;
                      
                              // after a previous registry is restored, only the owner can allow future updates
                              allowRegistryUpdate = false;
                          }
                      
                          /**
                              @dev disables the registry update functionality
                              this is a safety mechanism in case of a emergency
                              can only be called by the manager or owner
                      
                              @param _disable    true to disable registry updates, false to re-enable them
                          */
                          function disableRegistryUpdate(bool _disable) public ownerOrManagerOnly {
                              allowRegistryUpdate = !_disable;
                          }
                      
                          /**
                              @dev disables/enables the claim tokens functionality
                      
                              @param _enable    true to enable claiming of tokens, false to disable
                           */
                          function enableClaimTokens(bool _enable) public ownerOnly {
                              claimTokensEnabled = _enable;
                          }
                      
                          /**
                              @dev returns the number of connector tokens defined
                      
                              @return number of connector tokens
                          */
                          function connectorTokenCount() public view returns (uint16) {
                              return uint16(connectorTokens.length);
                          }
                      
                          /**
                              @dev allows the owner to update & enable the conversion whitelist contract address
                              when set, only addresses that are whitelisted are actually allowed to use the converter
                              note that the whitelist check is actually done by the BancorNetwork contract
                      
                              @param _whitelist    address of a whitelist contract
                          */
                          function setConversionWhitelist(IWhitelist _whitelist)
                              public
                              ownerOnly
                              notThis(_whitelist)
                          {
                              conversionWhitelist = _whitelist;
                          }
                      
                          /**
                              @dev disables the entire conversion functionality
                              this is a safety mechanism in case of a emergency
                              can only be called by the manager
                      
                              @param _disable true to disable conversions, false to re-enable them
                          */
                          function disableConversions(bool _disable) public ownerOrManagerOnly {
                              if (conversionsEnabled == _disable) {
                                  conversionsEnabled = !_disable;
                                  emit ConversionsEnable(conversionsEnabled);
                              }
                          }
                      
                          /**
                              @dev allows transferring the token ownership
                              the new owner needs to accept the transfer
                              can only be called by the contract owner
                              note that token ownership can only be transferred while the owner is the converter upgrader contract
                      
                              @param _newOwner    new token owner
                          */
                          function transferTokenOwnership(address _newOwner)
                              public
                              ownerOnly
                              converterUpgraderOnly
                          {
                              super.transferTokenOwnership(_newOwner);
                          }
                      
                          /**
                              @dev updates the current conversion fee
                              can only be called by the manager
                      
                              @param _conversionFee new conversion fee, represented in ppm
                          */
                          function setConversionFee(uint32 _conversionFee)
                              public
                              ownerOrManagerOnly
                              validConversionFee(_conversionFee)
                          {
                              emit ConversionFeeUpdate(conversionFee, _conversionFee);
                              conversionFee = _conversionFee;
                          }
                      
                          /**
                              @dev given a return amount, returns the amount minus the conversion fee
                      
                              @param _amount      return amount
                              @param _magnitude   1 for standard conversion, 2 for cross connector conversion
                      
                              @return return amount minus conversion fee
                          */
                          function getFinalAmount(uint256 _amount, uint8 _magnitude) public view returns (uint256) {
                              return safeMul(_amount, (MAX_CONVERSION_FEE - conversionFee) ** _magnitude) / MAX_CONVERSION_FEE ** _magnitude;
                          }
                      
                          /**
                              @dev withdraws tokens held by the converter and sends them to an account
                              can only be called by the owner
                              note that connector tokens can only be withdrawn by the owner while the converter is inactive
                              unless the owner is the converter upgrader contract
                      
                              @param _token   ERC20 token contract address
                              @param _to      account to receive the new amount
                              @param _amount  amount to withdraw
                          */
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public {
                              address converterUpgrader = registry.addressOf(ContractIds.BANCOR_CONVERTER_UPGRADER);
                      
                              // if the token is not a connector token, allow withdrawal
                              // otherwise verify that the converter is inactive or that the owner is the upgrader contract
                              require(!connectors[_token].isSet || token.owner() != address(this) || owner == converterUpgrader);
                              super.withdrawTokens(_token, _to, _amount);
                          }
                      
                          /**
                              @dev allows the BancorX contract to claim BNT from any address (so that users
                              dont have to first give allowance when calling BancorX)
                      
                              @param _from      address to claim the BNT from
                              @param _amount    the amount to claim
                           */
                          function claimTokens(address _from, uint256 _amount) public whenClaimTokensEnabled {
                              address bancorX = registry.addressOf(ContractIds.BANCOR_X);
                      
                              // only the bancorX contract may call this method
                              require(msg.sender == bancorX);
                      
                              // destroy the tokens belonging to _from, and issue the same amount to bancorX contract
                              token.destroy(_from, _amount);
                              token.issue(bancorX, _amount);
                          }
                      
                          /**
                              @dev upgrades the converter to the latest version
                              can only be called by the owner
                              note that the owner needs to call acceptOwnership on the new converter after the upgrade
                          */
                          function upgrade() public ownerOnly {
                              IBancorConverterUpgrader converterUpgrader = IBancorConverterUpgrader(registry.addressOf(ContractIds.BANCOR_CONVERTER_UPGRADER));
                      
                              transferOwnership(converterUpgrader);
                              converterUpgrader.upgrade(version);
                              acceptOwnership();
                          }
                      
                          /**
                              @dev defines a new connector for the token
                              can only be called by the owner while the converter is inactive
                      
                              @param _token                  address of the connector token
                              @param _weight                 constant connector weight, represented in ppm, 1-1000000
                              @param _enableVirtualBalance   true to enable virtual balance for the connector, false to disable it
                          */
                          function addConnector(IERC20Token _token, uint32 _weight, bool _enableVirtualBalance)
                              public
                              ownerOnly
                              inactive
                              validAddress(_token)
                              notThis(_token)
                              validConnectorWeight(_weight)
                          {
                              require(_token != token && !connectors[_token].isSet && totalConnectorWeight + _weight <= MAX_WEIGHT); // validate input
                      
                              connectors[_token].virtualBalance = 0;
                              connectors[_token].weight = _weight;
                              connectors[_token].isVirtualBalanceEnabled = _enableVirtualBalance;
                              connectors[_token].isPurchaseEnabled = true;
                              connectors[_token].isSet = true;
                              connectorTokens.push(_token);
                              totalConnectorWeight += _weight;
                          }
                      
                          /**
                              @dev updates one of the token connectors
                              can only be called by the owner
                      
                              @param _connectorToken         address of the connector token
                              @param _weight                 constant connector weight, represented in ppm, 1-1000000
                              @param _enableVirtualBalance   true to enable virtual balance for the connector, false to disable it
                              @param _virtualBalance         new connector's virtual balance
                          */
                          function updateConnector(IERC20Token _connectorToken, uint32 _weight, bool _enableVirtualBalance, uint256 _virtualBalance)
                              public
                              ownerOnly
                              validConnector(_connectorToken)
                              validConnectorWeight(_weight)
                          {
                              Connector storage connector = connectors[_connectorToken];
                              require(totalConnectorWeight - connector.weight + _weight <= MAX_WEIGHT); // validate input
                      
                              totalConnectorWeight = totalConnectorWeight - connector.weight + _weight;
                              connector.weight = _weight;
                              connector.isVirtualBalanceEnabled = _enableVirtualBalance;
                              connector.virtualBalance = _virtualBalance;
                          }
                      
                          /**
                              @dev disables purchasing with the given connector token in case the connector token got compromised
                              can only be called by the owner
                              note that selling is still enabled regardless of this flag and it cannot be disabled by the owner
                      
                              @param _connectorToken  connector token contract address
                              @param _disable         true to disable the token, false to re-enable it
                          */
                          function disableConnectorPurchases(IERC20Token _connectorToken, bool _disable)
                              public
                              ownerOnly
                              validConnector(_connectorToken)
                          {
                              connectors[_connectorToken].isPurchaseEnabled = !_disable;
                          }
                      
                          /**
                              @dev returns the connector's virtual balance if one is defined, otherwise returns the actual balance
                      
                              @param _connectorToken  connector token contract address
                      
                              @return connector balance
                          */
                          function getConnectorBalance(IERC20Token _connectorToken)
                              public
                              view
                              validConnector(_connectorToken)
                              returns (uint256)
                          {
                              Connector storage connector = connectors[_connectorToken];
                              return connector.isVirtualBalanceEnabled ? connector.virtualBalance : _connectorToken.balanceOf(this);
                          }
                      
                          /**
                              @dev returns the expected return for converting a specific amount of _fromToken to _toToken
                      
                              @param _fromToken  ERC20 token to convert from
                              @param _toToken    ERC20 token to convert to
                              @param _amount     amount to convert, in fromToken
                      
                              @return expected conversion return amount and conversion fee
                          */
                          function getReturn(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256, uint256) {
                              require(_fromToken != _toToken); // validate input
                      
                              // conversion between the token and one of its connectors
                              if (_toToken == token)
                                  return getPurchaseReturn(_fromToken, _amount);
                              else if (_fromToken == token)
                                  return getSaleReturn(_toToken, _amount);
                      
                              // conversion between 2 connectors
                              return getCrossConnectorReturn(_fromToken, _toToken, _amount);
                          }
                      
                          /**
                              @dev returns the expected return for buying the token for a connector token
                      
                              @param _connectorToken  connector token contract address
                              @param _depositAmount   amount to deposit (in the connector token)
                      
                              @return expected purchase return amount and conversion fee
                          */
                          function getPurchaseReturn(IERC20Token _connectorToken, uint256 _depositAmount)
                              public
                              view
                              active
                              validConnector(_connectorToken)
                              returns (uint256, uint256)
                          {
                              Connector storage connector = connectors[_connectorToken];
                              require(connector.isPurchaseEnabled); // validate input
                      
                              uint256 tokenSupply = token.totalSupply();
                              uint256 connectorBalance = getConnectorBalance(_connectorToken);
                              IBancorFormula formula = IBancorFormula(registry.addressOf(ContractIds.BANCOR_FORMULA));
                              uint256 amount = formula.calculatePurchaseReturn(tokenSupply, connectorBalance, connector.weight, _depositAmount);
                              uint256 finalAmount = getFinalAmount(amount, 1);
                      
                              // return the amount minus the conversion fee and the conversion fee
                              return (finalAmount, amount - finalAmount);
                          }
                      
                          /**
                              @dev returns the expected return for selling the token for one of its connector tokens
                      
                              @param _connectorToken  connector token contract address
                              @param _sellAmount      amount to sell (in the smart token)
                      
                              @return expected sale return amount and conversion fee
                          */
                          function getSaleReturn(IERC20Token _connectorToken, uint256 _sellAmount)
                              public
                              view
                              active
                              validConnector(_connectorToken)
                              returns (uint256, uint256)
                          {
                              Connector storage connector = connectors[_connectorToken];
                              uint256 tokenSupply = token.totalSupply();
                              uint256 connectorBalance = getConnectorBalance(_connectorToken);
                              IBancorFormula formula = IBancorFormula(registry.addressOf(ContractIds.BANCOR_FORMULA));
                              uint256 amount = formula.calculateSaleReturn(tokenSupply, connectorBalance, connector.weight, _sellAmount);
                              uint256 finalAmount = getFinalAmount(amount, 1);
                      
                              // return the amount minus the conversion fee and the conversion fee
                              return (finalAmount, amount - finalAmount);
                          }
                      
                          /**
                              @dev returns the expected return for selling one of the connector tokens for another connector token
                      
                              @param _fromConnectorToken  contract address of the connector token to convert from
                              @param _toConnectorToken    contract address of the connector token to convert to
                              @param _sellAmount          amount to sell (in the from connector token)
                      
                              @return expected sale return amount and conversion fee (in the to connector token)
                          */
                          function getCrossConnectorReturn(IERC20Token _fromConnectorToken, IERC20Token _toConnectorToken, uint256 _sellAmount)
                              public
                              view
                              active
                              validConnector(_fromConnectorToken)
                              validConnector(_toConnectorToken)
                              returns (uint256, uint256)
                          {
                              Connector storage fromConnector = connectors[_fromConnectorToken];
                              Connector storage toConnector = connectors[_toConnectorToken];
                              require(toConnector.isPurchaseEnabled); // validate input
                      
                              IBancorFormula formula = IBancorFormula(registry.addressOf(ContractIds.BANCOR_FORMULA));
                              uint256 amount = formula.calculateCrossConnectorReturn(
                                  getConnectorBalance(_fromConnectorToken), 
                                  fromConnector.weight, 
                                  getConnectorBalance(_toConnectorToken), 
                                  toConnector.weight, 
                                  _sellAmount);
                              uint256 finalAmount = getFinalAmount(amount, 2);
                      
                              // return the amount minus the conversion fee and the conversion fee
                              // the fee is higher (magnitude = 2) since cross connector conversion equals 2 conversions (from / to the smart token)
                              return (finalAmount, amount - finalAmount);
                          }
                      
                          /**
                              @dev converts a specific amount of _fromToken to _toToken
                      
                              @param _fromToken  ERC20 token to convert from
                              @param _toToken    ERC20 token to convert to
                              @param _amount     amount to convert, in fromToken
                              @param _minReturn  if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                      
                              @return conversion return amount
                          */
                          function convertInternal(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn)
                              public
                              bancorNetworkOnly
                              conversionsAllowed
                              greaterThanZero(_minReturn)
                              returns (uint256)
                          {
                              require(_fromToken != _toToken); // validate input
                      
                              // conversion between the token and one of its connectors
                              if (_toToken == token)
                                  return buy(_fromToken, _amount, _minReturn);
                              else if (_fromToken == token)
                                  return sell(_toToken, _amount, _minReturn);
                      
                              uint256 amount;
                              uint256 feeAmount;
                      
                              // conversion between 2 connectors
                              (amount, feeAmount) = getCrossConnectorReturn(_fromToken, _toToken, _amount);
                              // ensure the trade gives something in return and meets the minimum requested amount
                              require(amount != 0 && amount >= _minReturn);
                      
                              // update the source token virtual balance if relevant
                              Connector storage fromConnector = connectors[_fromToken];
                              if (fromConnector.isVirtualBalanceEnabled)
                                  fromConnector.virtualBalance = safeAdd(fromConnector.virtualBalance, _amount);
                      
                              // update the target token virtual balance if relevant
                              Connector storage toConnector = connectors[_toToken];
                              if (toConnector.isVirtualBalanceEnabled)
                                  toConnector.virtualBalance = safeSub(toConnector.virtualBalance, amount);
                      
                              // ensure that the trade won't deplete the connector balance
                              uint256 toConnectorBalance = getConnectorBalance(_toToken);
                              assert(amount < toConnectorBalance);
                      
                              // transfer funds from the caller in the from connector token
                              assert(_fromToken.transferFrom(msg.sender, this, _amount));
                              // transfer funds to the caller in the to connector token
                              // the transfer might fail if the actual connector balance is smaller than the virtual balance
                              assert(_toToken.transfer(msg.sender, amount));
                      
                              // dispatch the conversion event
                              // the fee is higher (magnitude = 2) since cross connector conversion equals 2 conversions (from / to the smart token)
                              dispatchConversionEvent(_fromToken, _toToken, _amount, amount, feeAmount);
                      
                              // dispatch price data updates for the smart token / both connectors
                              emit PriceDataUpdate(_fromToken, token.totalSupply(), getConnectorBalance(_fromToken), fromConnector.weight);
                              emit PriceDataUpdate(_toToken, token.totalSupply(), getConnectorBalance(_toToken), toConnector.weight);
                              return amount;
                          }
                      
                          /**
                              @dev converts a specific amount of _fromToken to _toToken
                      
                              @param _fromToken  ERC20 token to convert from
                              @param _toToken    ERC20 token to convert to
                              @param _amount     amount to convert, in fromToken
                              @param _minReturn  if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                      
                              @return conversion return amount
                          */
                          function convert(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256) {
                              convertPath = [_fromToken, token, _toToken];
                              return quickConvert(convertPath, _amount, _minReturn);
                          }
                      
                          /**
                              @dev buys the token by depositing one of its connector tokens
                      
                              @param _connectorToken  connector token contract address
                              @param _depositAmount   amount to deposit (in the connector token)
                              @param _minReturn       if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                      
                              @return buy return amount
                          */
                          function buy(IERC20Token _connectorToken, uint256 _depositAmount, uint256 _minReturn) internal returns (uint256) {
                              uint256 amount;
                              uint256 feeAmount;
                              (amount, feeAmount) = getPurchaseReturn(_connectorToken, _depositAmount);
                              // ensure the trade gives something in return and meets the minimum requested amount
                              require(amount != 0 && amount >= _minReturn);
                      
                              // update virtual balance if relevant
                              Connector storage connector = connectors[_connectorToken];
                              if (connector.isVirtualBalanceEnabled)
                                  connector.virtualBalance = safeAdd(connector.virtualBalance, _depositAmount);
                      
                              // transfer funds from the caller in the connector token
                              assert(_connectorToken.transferFrom(msg.sender, this, _depositAmount));
                              // issue new funds to the caller in the smart token
                              token.issue(msg.sender, amount);
                      
                              // dispatch the conversion event
                              dispatchConversionEvent(_connectorToken, token, _depositAmount, amount, feeAmount);
                      
                              // dispatch price data update for the smart token/connector
                              emit PriceDataUpdate(_connectorToken, token.totalSupply(), getConnectorBalance(_connectorToken), connector.weight);
                              return amount;
                          }
                      
                          /**
                              @dev sells the token by withdrawing from one of its connector tokens
                      
                              @param _connectorToken  connector token contract address
                              @param _sellAmount      amount to sell (in the smart token)
                              @param _minReturn       if the conversion results in an amount smaller the minimum return - it is cancelled, must be nonzero
                      
                              @return sell return amount
                          */
                          function sell(IERC20Token _connectorToken, uint256 _sellAmount, uint256 _minReturn) internal returns (uint256) {
                              require(_sellAmount <= token.balanceOf(msg.sender)); // validate input
                              uint256 amount;
                              uint256 feeAmount;
                              (amount, feeAmount) = getSaleReturn(_connectorToken, _sellAmount);
                              // ensure the trade gives something in return and meets the minimum requested amount
                              require(amount != 0 && amount >= _minReturn);
                      
                              // ensure that the trade will only deplete the connector balance if the total supply is depleted as well
                              uint256 tokenSupply = token.totalSupply();
                              uint256 connectorBalance = getConnectorBalance(_connectorToken);
                              assert(amount < connectorBalance || (amount == connectorBalance && _sellAmount == tokenSupply));
                      
                              // update virtual balance if relevant
                              Connector storage connector = connectors[_connectorToken];
                              if (connector.isVirtualBalanceEnabled)
                                  connector.virtualBalance = safeSub(connector.virtualBalance, amount);
                      
                              // destroy _sellAmount from the caller's balance in the smart token
                              token.destroy(msg.sender, _sellAmount);
                              // transfer funds to the caller in the connector token
                              // the transfer might fail if the actual connector balance is smaller than the virtual balance
                              assert(_connectorToken.transfer(msg.sender, amount));
                      
                              // dispatch the conversion event
                              dispatchConversionEvent(token, _connectorToken, _sellAmount, amount, feeAmount);
                      
                              // dispatch price data update for the smart token/connector
                              emit PriceDataUpdate(_connectorToken, token.totalSupply(), getConnectorBalance(_connectorToken), connector.weight);
                              return amount;
                          }
                      
                          /**
                              @dev converts the token to any other token in the bancor network by following a predefined conversion path
                              note that when converting from an ERC20 token (as opposed to a smart token), allowance must be set beforehand
                      
                              @param _path        conversion path, see conversion path format in the BancorNetwork contract
                              @param _amount      amount to convert from (in the initial source token)
                              @param _minReturn   if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                      
                              @return tokens issued in return
                          */
                          function quickConvert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn)
                              public
                              payable
                              validConversionPath(_path)
                              returns (uint256)
                          {
                              return quickConvertPrioritized(_path, _amount, _minReturn, 0x0, 0x0, 0x0, 0x0);
                          }
                      
                          /**
                              @dev converts the token to any other token in the bancor network by following a predefined conversion path
                              note that when converting from an ERC20 token (as opposed to a smart token), allowance must be set beforehand
                      
                              @param _path        conversion path, see conversion path format in the BancorNetwork contract
                              @param _amount      amount to convert from (in the initial source token)
                              @param _minReturn   if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                              @param _block       if the current block exceeded the given parameter - it is cancelled
                              @param _v           (signature[128:130]) associated with the signer address and helps validating if the signature is legit
                              @param _r           (signature[0:64]) associated with the signer address and helps validating if the signature is legit
                              @param _s           (signature[64:128]) associated with the signer address and helps validating if the signature is legit
                      
                              @return tokens issued in return
                          */
                          function quickConvertPrioritized(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, uint256 _block, uint8 _v, bytes32 _r, bytes32 _s)
                              public
                              payable
                              validConversionPath(_path)
                              returns (uint256)
                          {
                              IERC20Token fromToken = _path[0];
                              IBancorNetwork bancorNetwork = IBancorNetwork(registry.addressOf(ContractIds.BANCOR_NETWORK));
                      
                              // we need to transfer the source tokens from the caller to the BancorNetwork contract,
                              // so it can execute the conversion on behalf of the caller
                              if (msg.value == 0) {
                                  // not ETH, send the source tokens to the BancorNetwork contract
                                  // if the token is the smart token, no allowance is required - destroy the tokens
                                  // from the caller and issue them to the BancorNetwork contract
                                  if (fromToken == token) {
                                      token.destroy(msg.sender, _amount); // destroy _amount tokens from the caller's balance in the smart token
                                      token.issue(bancorNetwork, _amount); // issue _amount new tokens to the BancorNetwork contract
                                  } else {
                                      // otherwise, we assume we already have allowance, transfer the tokens directly to the BancorNetwork contract
                                      assert(fromToken.transferFrom(msg.sender, bancorNetwork, _amount));
                                  }
                              }
                      
                              // execute the conversion and pass on the ETH with the call
                              return bancorNetwork.convertForPrioritized2.value(msg.value)(_path, _amount, _minReturn, msg.sender, _block, _v, _r, _s);
                          }
                      
                          /**
                              @dev buys the token with all connector tokens using the same percentage
                              i.e. if the caller increases the supply by 10%, it will cost an amount equal to
                              10% of each connector token balance
                              can only be called if the max total weight is exactly 100% and while conversions are enabled
                      
                              @param _amount  amount to increase the supply by (in the smart token)
                          */
                          function fund(uint256 _amount)
                              public
                              maxTotalWeightOnly
                              conversionsAllowed
                          {
                              uint256 supply = token.totalSupply();
                      
                              // iterate through the connector tokens and transfer a percentage equal to the ratio between _amount
                              // and the total supply in each connector from the caller to the converter
                              IERC20Token connectorToken;
                              uint256 connectorBalance;
                              uint256 connectorAmount;
                              for (uint16 i = 0; i < connectorTokens.length; i++) {
                                  connectorToken = connectorTokens[i];
                                  connectorBalance = getConnectorBalance(connectorToken);
                                  connectorAmount = safeMul(_amount, connectorBalance) / supply;
                      
                                  // update virtual balance if relevant
                                  Connector storage connector = connectors[connectorToken];
                                  if (connector.isVirtualBalanceEnabled)
                                      connector.virtualBalance = safeAdd(connector.virtualBalance, connectorAmount);
                      
                                  // transfer funds from the caller in the connector token
                                  assert(connectorToken.transferFrom(msg.sender, this, connectorAmount));
                      
                                  // dispatch price data update for the smart token/connector
                                  emit PriceDataUpdate(connectorToken, supply + _amount, connectorBalance + connectorAmount, connector.weight);
                              }
                      
                              // issue new funds to the caller in the smart token
                              token.issue(msg.sender, _amount);
                          }
                      
                          /**
                              @dev sells the token for all connector tokens using the same percentage
                              i.e. if the holder sells 10% of the supply, they will receive 10% of each
                              connector token balance in return
                              can only be called if the max total weight is exactly 100%
                              note that the function can also be called if conversions are disabled
                      
                              @param _amount  amount to liquidate (in the smart token)
                          */
                          function liquidate(uint256 _amount) public maxTotalWeightOnly {
                              uint256 supply = token.totalSupply();
                      
                              // destroy _amount from the caller's balance in the smart token
                              token.destroy(msg.sender, _amount);
                      
                              // iterate through the connector tokens and send a percentage equal to the ratio between _amount
                              // and the total supply from each connector balance to the caller
                              IERC20Token connectorToken;
                              uint256 connectorBalance;
                              uint256 connectorAmount;
                              for (uint16 i = 0; i < connectorTokens.length; i++) {
                                  connectorToken = connectorTokens[i];
                                  connectorBalance = getConnectorBalance(connectorToken);
                                  connectorAmount = safeMul(_amount, connectorBalance) / supply;
                      
                                  // update virtual balance if relevant
                                  Connector storage connector = connectors[connectorToken];
                                  if (connector.isVirtualBalanceEnabled)
                                      connector.virtualBalance = safeSub(connector.virtualBalance, connectorAmount);
                      
                                  // transfer funds to the caller in the connector token
                                  // the transfer might fail if the actual connector balance is smaller than the virtual balance
                                  assert(connectorToken.transfer(msg.sender, connectorAmount));
                      
                                  // dispatch price data update for the smart token/connector
                                  emit PriceDataUpdate(connectorToken, supply - _amount, connectorBalance - connectorAmount, connector.weight);
                              }
                          }
                      
                          // deprecated, backward compatibility
                          function change(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256) {
                              return convertInternal(_fromToken, _toToken, _amount, _minReturn);
                          }
                      
                          /**
                              @dev helper, dispatches the Conversion event
                      
                              @param _fromToken       ERC20 token to convert from
                              @param _toToken         ERC20 token to convert to
                              @param _amount          amount purchased/sold (in the source token)
                              @param _returnAmount    amount returned (in the target token)
                          */
                          function dispatchConversionEvent(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _returnAmount, uint256 _feeAmount) private {
                              // fee amount is converted to 255 bits -
                              // negative amount means the fee is taken from the source token, positive amount means its taken from the target token
                              // currently the fee is always taken from the target token
                              // since we convert it to a signed number, we first ensure that it's capped at 255 bits to prevent overflow
                              assert(_feeAmount <= 2 ** 255);
                              emit Conversion(_fromToken, _toToken, msg.sender, _amount, _returnAmount, int256(_feeAmount));
                          }
                      }

                      File 2 of 14: SmartToken
                      pragma solidity ^0.4.11;
                      
                      /*
                          Overflow protected math functions
                      */
                      contract SafeMath {
                          /**
                              constructor
                          */
                          function SafeMath() {
                          }
                      
                          /**
                              @dev returns the sum of _x and _y, asserts if the calculation overflows
                      
                              @param _x   value 1
                              @param _y   value 2
                      
                              @return sum
                          */
                          function safeAdd(uint256 _x, uint256 _y) internal returns (uint256) {
                              uint256 z = _x + _y;
                              assert(z >= _x);
                              return z;
                          }
                      
                          /**
                              @dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
                      
                              @param _x   minuend
                              @param _y   subtrahend
                      
                              @return difference
                          */
                          function safeSub(uint256 _x, uint256 _y) internal returns (uint256) {
                              assert(_x >= _y);
                              return _x - _y;
                          }
                      
                          /**
                              @dev returns the product of multiplying _x by _y, asserts if the calculation overflows
                      
                              @param _x   factor 1
                              @param _y   factor 2
                      
                              @return product
                          */
                          function safeMul(uint256 _x, uint256 _y) internal returns (uint256) {
                              uint256 z = _x * _y;
                              assert(_x == 0 || z / _x == _y);
                              return z;
                          }
                      } 
                      
                      /*
                          Owned contract interface
                      */
                      contract IOwned {
                          // this function isn't abstract since the compiler emits automatically generated getter functions as external
                          function owner() public constant returns (address owner) { owner; }
                      
                          function transferOwnership(address _newOwner) public;
                          function acceptOwnership() public;
                      }
                      
                      /*
                          Provides support and utilities for contract ownership
                      */
                      contract Owned is IOwned {
                          address public owner;
                          address public newOwner;
                      
                          event OwnerUpdate(address _prevOwner, address _newOwner);
                      
                          /**
                              @dev constructor
                          */
                          function Owned() {
                              owner = msg.sender;
                          }
                      
                          // allows execution by the owner only
                          modifier ownerOnly {
                              assert(msg.sender == owner);
                              _;
                          }
                      
                          /**
                              @dev allows transferring the contract ownership
                              the new owner still need to accept the transfer
                              can only be called by the contract owner
                      
                              @param _newOwner    new contract owner
                          */
                          function transferOwnership(address _newOwner) public ownerOnly {
                              require(_newOwner != owner);
                              newOwner = _newOwner;
                          }
                      
                          /**
                              @dev used by a new owner to accept an ownership transfer
                          */
                          function acceptOwnership() public {
                              require(msg.sender == newOwner);
                              OwnerUpdate(owner, newOwner);
                              owner = newOwner;
                              newOwner = 0x0;
                          }
                      }
                      
                      /*
                          Token Holder interface
                      */
                      contract ITokenHolder is IOwned {
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                      }
                      
                      /*
                          We consider every contract to be a 'token holder' since it's currently not possible
                          for a contract to deny receiving tokens.
                      
                          The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
                          the owner to send tokens that were sent to the contract by mistake back to their sender.
                      */
                      contract TokenHolder is ITokenHolder, Owned {
                          /**
                              @dev constructor
                          */
                          function TokenHolder() {
                          }
                      
                          // validates an address - currently only checks that it isn't null
                          modifier validAddress(address _address) {
                              require(_address != 0x0);
                              _;
                          }
                      
                          // verifies that the address is different than this contract address
                          modifier notThis(address _address) {
                              require(_address != address(this));
                              _;
                          }
                      
                          /**
                              @dev withdraws tokens held by the contract and sends them to an account
                              can only be called by the owner
                      
                              @param _token   ERC20 token contract address
                              @param _to      account to receive the new amount
                              @param _amount  amount to withdraw
                          */
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
                              public
                              ownerOnly
                              validAddress(_token)
                              validAddress(_to)
                              notThis(_to)
                          {
                              assert(_token.transfer(_to, _amount));
                          }
                      }
                      
                      /*
                          ERC20 Standard Token interface
                      */
                      contract IERC20Token {
                          // these functions aren't abstract since the compiler emits automatically generated getter functions as external
                          function name() public constant returns (string name) { name; }
                          function symbol() public constant returns (string symbol) { symbol; }
                          function decimals() public constant returns (uint8 decimals) { decimals; }
                          function totalSupply() public constant returns (uint256 totalSupply) { totalSupply; }
                          function balanceOf(address _owner) public constant returns (uint256 balance) { _owner; balance; }
                          function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { _owner; _spender; remaining; }
                      
                          function transfer(address _to, uint256 _value) public returns (bool success);
                          function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
                          function approve(address _spender, uint256 _value) public returns (bool success);
                      }
                      
                      /**
                          ERC20 Standard Token implementation
                      */
                      contract ERC20Token is IERC20Token, SafeMath {
                          string public standard = 'Token 0.1';
                          string public name = '';
                          string public symbol = '';
                          uint8 public decimals = 0;
                          uint256 public totalSupply = 0;
                          mapping (address => uint256) public balanceOf;
                          mapping (address => mapping (address => uint256)) public allowance;
                      
                          event Transfer(address indexed _from, address indexed _to, uint256 _value);
                          event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                      
                          /**
                              @dev constructor
                      
                              @param _name        token name
                              @param _symbol      token symbol
                              @param _decimals    decimal points, for display purposes
                          */
                          function ERC20Token(string _name, string _symbol, uint8 _decimals) {
                              require(bytes(_name).length > 0 && bytes(_symbol).length > 0); // validate input
                      
                              name = _name;
                              symbol = _symbol;
                              decimals = _decimals;
                          }
                      
                          // validates an address - currently only checks that it isn't null
                          modifier validAddress(address _address) {
                              require(_address != 0x0);
                              _;
                          }
                      
                          /**
                              @dev send coins
                              throws on any error rather then return a false flag to minimize user errors
                      
                              @param _to      target address
                              @param _value   transfer amount
                      
                              @return true if the transfer was successful, false if it wasn't
                          */
                          function transfer(address _to, uint256 _value)
                              public
                              validAddress(_to)
                              returns (bool success)
                          {
                              balanceOf[msg.sender] = safeSub(balanceOf[msg.sender], _value);
                              balanceOf[_to] = safeAdd(balanceOf[_to], _value);
                              Transfer(msg.sender, _to, _value);
                              return true;
                          }
                      
                          /**
                              @dev an account/contract attempts to get the coins
                              throws on any error rather then return a false flag to minimize user errors
                      
                              @param _from    source address
                              @param _to      target address
                              @param _value   transfer amount
                      
                              @return true if the transfer was successful, false if it wasn't
                          */
                          function transferFrom(address _from, address _to, uint256 _value)
                              public
                              validAddress(_from)
                              validAddress(_to)
                              returns (bool success)
                          {
                              allowance[_from][msg.sender] = safeSub(allowance[_from][msg.sender], _value);
                              balanceOf[_from] = safeSub(balanceOf[_from], _value);
                              balanceOf[_to] = safeAdd(balanceOf[_to], _value);
                              Transfer(_from, _to, _value);
                              return true;
                          }
                      
                          /**
                              @dev allow another account/contract to spend some tokens on your behalf
                              throws on any error rather then return a false flag to minimize user errors
                      
                              also, to minimize the risk of the approve/transferFrom attack vector
                              (see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), approve has to be called twice
                              in 2 separate transactions - once to change the allowance to 0 and secondly to change it to the new allowance value
                      
                              @param _spender approved address
                              @param _value   allowance amount
                      
                              @return true if the approval was successful, false if it wasn't
                          */
                          function approve(address _spender, uint256 _value)
                              public
                              validAddress(_spender)
                              returns (bool success)
                          {
                              // if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal
                              require(_value == 0 || allowance[msg.sender][_spender] == 0);
                      
                              allowance[msg.sender][_spender] = _value;
                              Approval(msg.sender, _spender, _value);
                              return true;
                          }
                      }
                      
                      /*
                          Smart Token interface
                      */
                      contract ISmartToken is ITokenHolder, IERC20Token {
                          function disableTransfers(bool _disable) public;
                          function issue(address _to, uint256 _amount) public;
                          function destroy(address _from, uint256 _amount) public;
                      }
                      
                      /*
                          Smart Token v0.2
                      
                          'Owned' is specified here for readability reasons
                      */
                      contract SmartToken is ISmartToken, ERC20Token, Owned, TokenHolder {
                          string public version = '0.2';
                      
                          bool public transfersEnabled = true;    // true if transfer/transferFrom are enabled, false if not
                      
                          // triggered when a smart token is deployed - the _token address is defined for forward compatibility, in case we want to trigger the event from a factory
                          event NewSmartToken(address _token);
                          // triggered when the total supply is increased
                          event Issuance(uint256 _amount);
                          // triggered when the total supply is decreased
                          event Destruction(uint256 _amount);
                      
                          /**
                              @dev constructor
                      
                              @param _name       token name
                              @param _symbol     token short symbol, 1-6 characters
                              @param _decimals   for display purposes only
                          */
                          function SmartToken(string _name, string _symbol, uint8 _decimals)
                              ERC20Token(_name, _symbol, _decimals)
                          {
                              require(bytes(_symbol).length <= 6); // validate input
                              NewSmartToken(address(this));
                          }
                      
                          // allows execution only when transfers aren't disabled
                          modifier transfersAllowed {
                              assert(transfersEnabled);
                              _;
                          }
                      
                          /**
                              @dev disables/enables transfers
                              can only be called by the contract owner
                      
                              @param _disable    true to disable transfers, false to enable them
                          */
                          function disableTransfers(bool _disable) public ownerOnly {
                              transfersEnabled = !_disable;
                          }
                      
                          /**
                              @dev increases the token supply and sends the new tokens to an account
                              can only be called by the contract owner
                      
                              @param _to         account to receive the new amount
                              @param _amount     amount to increase the supply by
                          */
                          function issue(address _to, uint256 _amount)
                              public
                              ownerOnly
                              validAddress(_to)
                              notThis(_to)
                          {
                              totalSupply = safeAdd(totalSupply, _amount);
                              balanceOf[_to] = safeAdd(balanceOf[_to], _amount);
                      
                              Issuance(_amount);
                              Transfer(this, _to, _amount);
                          }
                      
                          /**
                              @dev removes tokens from an account and decreases the token supply
                              can only be called by the contract owner
                      
                              @param _from       account to remove the amount from
                              @param _amount     amount to decrease the supply by
                          */
                          function destroy(address _from, uint256 _amount)
                              public
                              ownerOnly
                          {
                              balanceOf[_from] = safeSub(balanceOf[_from], _amount);
                              totalSupply = safeSub(totalSupply, _amount);
                      
                              Transfer(_from, this, _amount);
                              Destruction(_amount);
                          }
                      
                          // ERC20 standard method overrides with some extra functionality
                      
                          /**
                              @dev send coins
                              throws on any error rather then return a false flag to minimize user errors
                              note that when transferring to the smart token's address, the coins are actually destroyed
                      
                              @param _to      target address
                              @param _value   transfer amount
                      
                              @return true if the transfer was successful, false if it wasn't
                          */
                          function transfer(address _to, uint256 _value) public transfersAllowed returns (bool success) {
                              assert(super.transfer(_to, _value));
                      
                              // transferring to the contract address destroys tokens
                              if (_to == address(this)) {
                                  balanceOf[_to] -= _value;
                                  totalSupply -= _value;
                                  Destruction(_value);
                              }
                      
                              return true;
                          }
                      
                          /**
                              @dev an account/contract attempts to get the coins
                              throws on any error rather then return a false flag to minimize user errors
                              note that when transferring to the smart token's address, the coins are actually destroyed
                      
                              @param _from    source address
                              @param _to      target address
                              @param _value   transfer amount
                      
                              @return true if the transfer was successful, false if it wasn't
                          */
                          function transferFrom(address _from, address _to, uint256 _value) public transfersAllowed returns (bool success) {
                              assert(super.transferFrom(_from, _to, _value));
                      
                              // transferring to the contract address destroys tokens
                              if (_to == address(this)) {
                                  balanceOf[_to] -= _value;
                                  totalSupply -= _value;
                                  Destruction(_value);
                              }
                      
                              return true;
                          }
                      }

                      File 3 of 14: ContractRegistry
                      pragma solidity ^0.4.24;
                      
                      // File: contracts/utility/interfaces/IOwned.sol
                      
                      /*
                          Owned contract interface
                      */
                      contract IOwned {
                          // this function isn't abstract since the compiler emits automatically generated getter functions as external
                          function owner() public view returns (address) {}
                      
                          function transferOwnership(address _newOwner) public;
                          function acceptOwnership() public;
                      }
                      
                      // File: contracts/utility/Owned.sol
                      
                      /*
                          Provides support and utilities for contract ownership
                      */
                      contract Owned is IOwned {
                          address public owner;
                          address public newOwner;
                      
                          event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
                      
                          /**
                              @dev constructor
                          */
                          constructor() public {
                              owner = msg.sender;
                          }
                      
                          // allows execution by the owner only
                          modifier ownerOnly {
                              require(msg.sender == owner);
                              _;
                          }
                      
                          /**
                              @dev allows transferring the contract ownership
                              the new owner still needs to accept the transfer
                              can only be called by the contract owner
                      
                              @param _newOwner    new contract owner
                          */
                          function transferOwnership(address _newOwner) public ownerOnly {
                              require(_newOwner != owner);
                              newOwner = _newOwner;
                          }
                      
                          /**
                              @dev used by a new owner to accept an ownership transfer
                          */
                          function acceptOwnership() public {
                              require(msg.sender == newOwner);
                              emit OwnerUpdate(owner, newOwner);
                              owner = newOwner;
                              newOwner = address(0);
                          }
                      }
                      
                      // File: contracts/utility/Utils.sol
                      
                      /*
                          Utilities & Common Modifiers
                      */
                      contract Utils {
                          /**
                              constructor
                          */
                          constructor() public {
                          }
                      
                          // verifies that an amount is greater than zero
                          modifier greaterThanZero(uint256 _amount) {
                              require(_amount > 0);
                              _;
                          }
                      
                          // validates an address - currently only checks that it isn't null
                          modifier validAddress(address _address) {
                              require(_address != address(0));
                              _;
                          }
                      
                          // verifies that the address is different than this contract address
                          modifier notThis(address _address) {
                              require(_address != address(this));
                              _;
                          }
                      
                          // Overflow protected math functions
                      
                          /**
                              @dev returns the sum of _x and _y, asserts if the calculation overflows
                      
                              @param _x   value 1
                              @param _y   value 2
                      
                              @return sum
                          */
                          function safeAdd(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              uint256 z = _x + _y;
                              assert(z >= _x);
                              return z;
                          }
                      
                          /**
                              @dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
                      
                              @param _x   minuend
                              @param _y   subtrahend
                      
                              @return difference
                          */
                          function safeSub(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              assert(_x >= _y);
                              return _x - _y;
                          }
                      
                          /**
                              @dev returns the product of multiplying _x by _y, asserts if the calculation overflows
                      
                              @param _x   factor 1
                              @param _y   factor 2
                      
                              @return product
                          */
                          function safeMul(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              uint256 z = _x * _y;
                              assert(_x == 0 || z / _x == _y);
                              return z;
                          }
                      }
                      
                      // File: contracts/utility/interfaces/IContractRegistry.sol
                      
                      /*
                          Contract Registry interface
                      */
                      contract IContractRegistry {
                          function addressOf(bytes32 _contractName) public view returns (address);
                      
                          // deprecated, backward compatibility
                          function getAddress(bytes32 _contractName) public view returns (address);
                      }
                      
                      // File: contracts/ContractIds.sol
                      
                      /**
                          Id definitions for bancor contracts
                      
                          Can be used in conjunction with the contract registry to get contract addresses
                      */
                      contract ContractIds {
                          // generic
                          bytes32 public constant CONTRACT_FEATURES = "ContractFeatures";
                          bytes32 public constant CONTRACT_REGISTRY = "ContractRegistry";
                      
                          // bancor logic
                          bytes32 public constant BANCOR_NETWORK = "BancorNetwork";
                          bytes32 public constant BANCOR_FORMULA = "BancorFormula";
                          bytes32 public constant BANCOR_GAS_PRICE_LIMIT = "BancorGasPriceLimit";
                          bytes32 public constant BANCOR_CONVERTER_UPGRADER = "BancorConverterUpgrader";
                          bytes32 public constant BANCOR_CONVERTER_FACTORY = "BancorConverterFactory";
                      
                          // Ids of BNT converter and BNT token
                          bytes32 public constant BNT_TOKEN = "BNTToken";
                          bytes32 public constant BNT_CONVERTER = "BNTConverter";
                      
                          // Id of BancorX contract
                          bytes32 public constant BANCOR_X = "BancorX";
                      }
                      
                      // File: contracts/utility/ContractRegistry.sol
                      
                      /**
                          Contract Registry
                      
                          The contract registry keeps contract addresses by name.
                          The owner can update contract addresses so that a contract name always points to the latest version
                          of the given contract.
                          Other contracts can query the registry to get updated addresses instead of depending on specific
                          addresses.
                      
                          Note that contract names are limited to 32 bytes UTF8 encoded ASCII strings to optimize gas costs
                      */
                      contract ContractRegistry is IContractRegistry, Owned, Utils, ContractIds {
                          struct RegistryItem {
                              address contractAddress;    // contract address
                              uint256 nameIndex;          // index of the item in the list of contract names
                              bool isSet;                 // used to tell if the mapping element is defined
                          }
                      
                          mapping (bytes32 => RegistryItem) private items;    // name -> RegistryItem mapping
                          string[] public contractNames;                      // list of all registered contract names
                      
                          // triggered when an address pointed to by a contract name is modified
                          event AddressUpdate(bytes32 indexed _contractName, address _contractAddress);
                      
                          /**
                              @dev constructor
                          */
                          constructor() public {
                              registerAddress(ContractIds.CONTRACT_REGISTRY, address(this));
                          }
                      
                          /**
                              @dev returns the number of items in the registry
                      
                              @return number of items
                          */
                          function itemCount() public view returns (uint256) {
                              return contractNames.length;
                          }
                      
                          /**
                              @dev returns the address associated with the given contract name
                      
                              @param _contractName    contract name
                      
                              @return contract address
                          */
                          function addressOf(bytes32 _contractName) public view returns (address) {
                              return items[_contractName].contractAddress;
                          }
                      
                          /**
                              @dev registers a new address for the contract name in the registry
                      
                              @param _contractName     contract name
                              @param _contractAddress  contract address
                          */
                          function registerAddress(bytes32 _contractName, address _contractAddress)
                              public
                              ownerOnly
                              validAddress(_contractAddress)
                          {
                              require(_contractName.length > 0); // validate input
                      
                              // update the address in the registry
                              items[_contractName].contractAddress = _contractAddress;
                      
                              if (!items[_contractName].isSet) {
                                  // mark the item as set
                                  items[_contractName].isSet = true;
                                  // add the contract name to the name list
                                  uint256 i = contractNames.push(bytes32ToString(_contractName));
                                  // update the item's index in the list
                                  items[_contractName].nameIndex = i - 1;
                              }
                      
                              // dispatch the address update event
                              emit AddressUpdate(_contractName, _contractAddress);
                          }
                      
                          /**
                              @dev removes an existing contract address from the registry
                      
                              @param _contractName contract name
                          */
                          function unregisterAddress(bytes32 _contractName) public ownerOnly {
                              require(_contractName.length > 0); // validate input
                      
                              // remove the address from the registry
                              items[_contractName].contractAddress = address(0);
                      
                              // if there are multiple items in the registry, move the last element to the deleted element's position
                              // and modify last element's registryItem.nameIndex in the items collection to point to the right position in contractNames
                              if (contractNames.length > 1) {
                                  string memory lastContractNameString = contractNames[contractNames.length - 1];
                                  uint256 unregisterIndex = items[_contractName].nameIndex;
                      
                                  contractNames[unregisterIndex] = lastContractNameString;
                                  bytes32 lastContractName = stringToBytes32(lastContractNameString);
                                  RegistryItem storage registryItem = items[lastContractName];
                                  registryItem.nameIndex = unregisterIndex;
                              }
                      
                              // remove the last element from the name list
                              contractNames.length--;
                              // zero the deleted element's index
                              items[_contractName].nameIndex = 0;
                      
                              // dispatch the address update event
                              emit AddressUpdate(_contractName, address(0));
                          }
                      
                          /**
                              @dev utility, converts bytes32 to a string
                              note that the bytes32 argument is assumed to be UTF8 encoded ASCII string
                      
                              @return string representation of the given bytes32 argument
                          */
                          function bytes32ToString(bytes32 _bytes) private pure returns (string) {
                              bytes memory byteArray = new bytes(32);
                              for (uint256 i; i < 32; i++) {
                                  byteArray[i] = _bytes[i];
                              }
                      
                              return string(byteArray);
                          }
                      
                          // @dev utility, converts string to bytes32
                          function stringToBytes32(string memory _string) private pure returns (bytes32) {
                              bytes32 result;
                              assembly {
                                  result := mload(add(_string,32))
                              }
                              return result;
                          }
                      
                          // deprecated, backward compatibility
                          function getAddress(bytes32 _contractName) public view returns (address) {
                              return addressOf(_contractName);
                          }
                      }

                      File 4 of 14: BancorFormula
                      pragma solidity ^0.4.21;
                      
                      /*
                          Utilities & Common Modifiers
                      */
                      contract Utils {
                          /**
                              constructor
                          */
                          function Utils() public {
                          }
                      
                          // verifies that an amount is greater than zero
                          modifier greaterThanZero(uint256 _amount) {
                              require(_amount > 0);
                              _;
                          }
                      
                          // validates an address - currently only checks that it isn't null
                          modifier validAddress(address _address) {
                              require(_address != address(0));
                              _;
                          }
                      
                          // verifies that the address is different than this contract address
                          modifier notThis(address _address) {
                              require(_address != address(this));
                              _;
                          }
                      
                          // Overflow protected math functions
                      
                          /**
                              @dev returns the sum of _x and _y, asserts if the calculation overflows
                      
                              @param _x   value 1
                              @param _y   value 2
                      
                              @return sum
                          */
                          function safeAdd(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              uint256 z = _x + _y;
                              assert(z >= _x);
                              return z;
                          }
                      
                          /**
                              @dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
                      
                              @param _x   minuend
                              @param _y   subtrahend
                      
                              @return difference
                          */
                          function safeSub(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              assert(_x >= _y);
                              return _x - _y;
                          }
                      
                          /**
                              @dev returns the product of multiplying _x by _y, asserts if the calculation overflows
                      
                              @param _x   factor 1
                              @param _y   factor 2
                      
                              @return product
                          */
                          function safeMul(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              uint256 z = _x * _y;
                              assert(_x == 0 || z / _x == _y);
                              return z;
                          }
                      }
                      
                      /*
                          Bancor Formula interface
                      */
                      contract IBancorFormula {
                          function calculatePurchaseReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _depositAmount) public view returns (uint256);
                          function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public view returns (uint256);
                          function calculateCrossConnectorReturn(uint256 _fromConnectorBalance, uint32 _fromConnectorWeight, uint256 _toConnectorBalance, uint32 _toConnectorWeight, uint256 _amount) public view returns (uint256);
                      }
                      
                      contract BancorFormula is IBancorFormula, Utils {
                          string public version = '0.3';
                      
                          uint256 private constant ONE = 1;
                          uint32 private constant MAX_WEIGHT = 1000000;
                          uint8 private constant MIN_PRECISION = 32;
                          uint8 private constant MAX_PRECISION = 127;
                      
                          /**
                              Auto-generated via 'PrintIntScalingFactors.py'
                          */
                          uint256 private constant FIXED_1 = 0x080000000000000000000000000000000;
                          uint256 private constant FIXED_2 = 0x100000000000000000000000000000000;
                          uint256 private constant MAX_NUM = 0x200000000000000000000000000000000;
                      
                          /**
                              Auto-generated via 'PrintLn2ScalingFactors.py'
                          */
                          uint256 private constant LN2_NUMERATOR   = 0x3f80fe03f80fe03f80fe03f80fe03f8;
                          uint256 private constant LN2_DENOMINATOR = 0x5b9de1d10bf4103d647b0955897ba80;
                      
                          /**
                              Auto-generated via 'PrintFunctionOptimalLog.py' and 'PrintFunctionOptimalExp.py'
                          */
                          uint256 private constant OPT_LOG_MAX_VAL = 0x15bf0a8b1457695355fb8ac404e7a79e3;
                          uint256 private constant OPT_EXP_MAX_VAL = 0x800000000000000000000000000000000;
                      
                          /**
                              Auto-generated via 'PrintFunctionBancorFormula.py'
                          */
                          uint256[128] private maxExpArray;
                          function BancorFormula() public {
                          //  maxExpArray[  0] = 0x6bffffffffffffffffffffffffffffffff;
                          //  maxExpArray[  1] = 0x67ffffffffffffffffffffffffffffffff;
                          //  maxExpArray[  2] = 0x637fffffffffffffffffffffffffffffff;
                          //  maxExpArray[  3] = 0x5f6fffffffffffffffffffffffffffffff;
                          //  maxExpArray[  4] = 0x5b77ffffffffffffffffffffffffffffff;
                          //  maxExpArray[  5] = 0x57b3ffffffffffffffffffffffffffffff;
                          //  maxExpArray[  6] = 0x5419ffffffffffffffffffffffffffffff;
                          //  maxExpArray[  7] = 0x50a2ffffffffffffffffffffffffffffff;
                          //  maxExpArray[  8] = 0x4d517fffffffffffffffffffffffffffff;
                          //  maxExpArray[  9] = 0x4a233fffffffffffffffffffffffffffff;
                          //  maxExpArray[ 10] = 0x47165fffffffffffffffffffffffffffff;
                          //  maxExpArray[ 11] = 0x4429afffffffffffffffffffffffffffff;
                          //  maxExpArray[ 12] = 0x415bc7ffffffffffffffffffffffffffff;
                          //  maxExpArray[ 13] = 0x3eab73ffffffffffffffffffffffffffff;
                          //  maxExpArray[ 14] = 0x3c1771ffffffffffffffffffffffffffff;
                          //  maxExpArray[ 15] = 0x399e96ffffffffffffffffffffffffffff;
                          //  maxExpArray[ 16] = 0x373fc47fffffffffffffffffffffffffff;
                          //  maxExpArray[ 17] = 0x34f9e8ffffffffffffffffffffffffffff;
                          //  maxExpArray[ 18] = 0x32cbfd5fffffffffffffffffffffffffff;
                          //  maxExpArray[ 19] = 0x30b5057fffffffffffffffffffffffffff;
                          //  maxExpArray[ 20] = 0x2eb40f9fffffffffffffffffffffffffff;
                          //  maxExpArray[ 21] = 0x2cc8340fffffffffffffffffffffffffff;
                          //  maxExpArray[ 22] = 0x2af09481ffffffffffffffffffffffffff;
                          //  maxExpArray[ 23] = 0x292c5bddffffffffffffffffffffffffff;
                          //  maxExpArray[ 24] = 0x277abdcdffffffffffffffffffffffffff;
                          //  maxExpArray[ 25] = 0x25daf6657fffffffffffffffffffffffff;
                          //  maxExpArray[ 26] = 0x244c49c65fffffffffffffffffffffffff;
                          //  maxExpArray[ 27] = 0x22ce03cd5fffffffffffffffffffffffff;
                          //  maxExpArray[ 28] = 0x215f77c047ffffffffffffffffffffffff;
                          //  maxExpArray[ 29] = 0x1fffffffffffffffffffffffffffffffff;
                          //  maxExpArray[ 30] = 0x1eaefdbdabffffffffffffffffffffffff;
                          //  maxExpArray[ 31] = 0x1d6bd8b2ebffffffffffffffffffffffff;
                              maxExpArray[ 32] = 0x1c35fedd14ffffffffffffffffffffffff;
                              maxExpArray[ 33] = 0x1b0ce43b323fffffffffffffffffffffff;
                              maxExpArray[ 34] = 0x19f0028ec1ffffffffffffffffffffffff;
                              maxExpArray[ 35] = 0x18ded91f0e7fffffffffffffffffffffff;
                              maxExpArray[ 36] = 0x17d8ec7f0417ffffffffffffffffffffff;
                              maxExpArray[ 37] = 0x16ddc6556cdbffffffffffffffffffffff;
                              maxExpArray[ 38] = 0x15ecf52776a1ffffffffffffffffffffff;
                              maxExpArray[ 39] = 0x15060c256cb2ffffffffffffffffffffff;
                              maxExpArray[ 40] = 0x1428a2f98d72ffffffffffffffffffffff;
                              maxExpArray[ 41] = 0x13545598e5c23fffffffffffffffffffff;
                              maxExpArray[ 42] = 0x1288c4161ce1dfffffffffffffffffffff;
                              maxExpArray[ 43] = 0x11c592761c666fffffffffffffffffffff;
                              maxExpArray[ 44] = 0x110a688680a757ffffffffffffffffffff;
                              maxExpArray[ 45] = 0x1056f1b5bedf77ffffffffffffffffffff;
                              maxExpArray[ 46] = 0x0faadceceeff8bffffffffffffffffffff;
                              maxExpArray[ 47] = 0x0f05dc6b27edadffffffffffffffffffff;
                              maxExpArray[ 48] = 0x0e67a5a25da4107fffffffffffffffffff;
                              maxExpArray[ 49] = 0x0dcff115b14eedffffffffffffffffffff;
                              maxExpArray[ 50] = 0x0d3e7a392431239fffffffffffffffffff;
                              maxExpArray[ 51] = 0x0cb2ff529eb71e4fffffffffffffffffff;
                              maxExpArray[ 52] = 0x0c2d415c3db974afffffffffffffffffff;
                              maxExpArray[ 53] = 0x0bad03e7d883f69bffffffffffffffffff;
                              maxExpArray[ 54] = 0x0b320d03b2c343d5ffffffffffffffffff;
                              maxExpArray[ 55] = 0x0abc25204e02828dffffffffffffffffff;
                              maxExpArray[ 56] = 0x0a4b16f74ee4bb207fffffffffffffffff;
                              maxExpArray[ 57] = 0x09deaf736ac1f569ffffffffffffffffff;
                              maxExpArray[ 58] = 0x0976bd9952c7aa957fffffffffffffffff;
                              maxExpArray[ 59] = 0x09131271922eaa606fffffffffffffffff;
                              maxExpArray[ 60] = 0x08b380f3558668c46fffffffffffffffff;
                              maxExpArray[ 61] = 0x0857ddf0117efa215bffffffffffffffff;
                              maxExpArray[ 62] = 0x07ffffffffffffffffffffffffffffffff;
                              maxExpArray[ 63] = 0x07abbf6f6abb9d087fffffffffffffffff;
                              maxExpArray[ 64] = 0x075af62cbac95f7dfa7fffffffffffffff;
                              maxExpArray[ 65] = 0x070d7fb7452e187ac13fffffffffffffff;
                              maxExpArray[ 66] = 0x06c3390ecc8af379295fffffffffffffff;
                              maxExpArray[ 67] = 0x067c00a3b07ffc01fd6fffffffffffffff;
                              maxExpArray[ 68] = 0x0637b647c39cbb9d3d27ffffffffffffff;
                              maxExpArray[ 69] = 0x05f63b1fc104dbd39587ffffffffffffff;
                              maxExpArray[ 70] = 0x05b771955b36e12f7235ffffffffffffff;
                              maxExpArray[ 71] = 0x057b3d49dda84556d6f6ffffffffffffff;
                              maxExpArray[ 72] = 0x054183095b2c8ececf30ffffffffffffff;
                              maxExpArray[ 73] = 0x050a28be635ca2b888f77fffffffffffff;
                              maxExpArray[ 74] = 0x04d5156639708c9db33c3fffffffffffff;
                              maxExpArray[ 75] = 0x04a23105873875bd52dfdfffffffffffff;
                              maxExpArray[ 76] = 0x0471649d87199aa990756fffffffffffff;
                              maxExpArray[ 77] = 0x04429a21a029d4c1457cfbffffffffffff;
                              maxExpArray[ 78] = 0x0415bc6d6fb7dd71af2cb3ffffffffffff;
                              maxExpArray[ 79] = 0x03eab73b3bbfe282243ce1ffffffffffff;
                              maxExpArray[ 80] = 0x03c1771ac9fb6b4c18e229ffffffffffff;
                              maxExpArray[ 81] = 0x0399e96897690418f785257fffffffffff;
                              maxExpArray[ 82] = 0x0373fc456c53bb779bf0ea9fffffffffff;
                              maxExpArray[ 83] = 0x034f9e8e490c48e67e6ab8bfffffffffff;
                              maxExpArray[ 84] = 0x032cbfd4a7adc790560b3337ffffffffff;
                              maxExpArray[ 85] = 0x030b50570f6e5d2acca94613ffffffffff;
                              maxExpArray[ 86] = 0x02eb40f9f620fda6b56c2861ffffffffff;
                              maxExpArray[ 87] = 0x02cc8340ecb0d0f520a6af58ffffffffff;
                              maxExpArray[ 88] = 0x02af09481380a0a35cf1ba02ffffffffff;
                              maxExpArray[ 89] = 0x0292c5bdd3b92ec810287b1b3fffffffff;
                              maxExpArray[ 90] = 0x0277abdcdab07d5a77ac6d6b9fffffffff;
                              maxExpArray[ 91] = 0x025daf6654b1eaa55fd64df5efffffffff;
                              maxExpArray[ 92] = 0x0244c49c648baa98192dce88b7ffffffff;
                              maxExpArray[ 93] = 0x022ce03cd5619a311b2471268bffffffff;
                              maxExpArray[ 94] = 0x0215f77c045fbe885654a44a0fffffffff;
                              maxExpArray[ 95] = 0x01ffffffffffffffffffffffffffffffff;
                              maxExpArray[ 96] = 0x01eaefdbdaaee7421fc4d3ede5ffffffff;
                              maxExpArray[ 97] = 0x01d6bd8b2eb257df7e8ca57b09bfffffff;
                              maxExpArray[ 98] = 0x01c35fedd14b861eb0443f7f133fffffff;
                              maxExpArray[ 99] = 0x01b0ce43b322bcde4a56e8ada5afffffff;
                              maxExpArray[100] = 0x019f0028ec1fff007f5a195a39dfffffff;
                              maxExpArray[101] = 0x018ded91f0e72ee74f49b15ba527ffffff;
                              maxExpArray[102] = 0x017d8ec7f04136f4e5615fd41a63ffffff;
                              maxExpArray[103] = 0x016ddc6556cdb84bdc8d12d22e6fffffff;
                              maxExpArray[104] = 0x015ecf52776a1155b5bd8395814f7fffff;
                              maxExpArray[105] = 0x015060c256cb23b3b3cc3754cf40ffffff;
                              maxExpArray[106] = 0x01428a2f98d728ae223ddab715be3fffff;
                              maxExpArray[107] = 0x013545598e5c23276ccf0ede68034fffff;
                              maxExpArray[108] = 0x01288c4161ce1d6f54b7f61081194fffff;
                              maxExpArray[109] = 0x011c592761c666aa641d5a01a40f17ffff;
                              maxExpArray[110] = 0x0110a688680a7530515f3e6e6cfdcdffff;
                              maxExpArray[111] = 0x01056f1b5bedf75c6bcb2ce8aed428ffff;
                              maxExpArray[112] = 0x00faadceceeff8a0890f3875f008277fff;
                              maxExpArray[113] = 0x00f05dc6b27edad306388a600f6ba0bfff;
                              maxExpArray[114] = 0x00e67a5a25da41063de1495d5b18cdbfff;
                              maxExpArray[115] = 0x00dcff115b14eedde6fc3aa5353f2e4fff;
                              maxExpArray[116] = 0x00d3e7a3924312399f9aae2e0f868f8fff;
                              maxExpArray[117] = 0x00cb2ff529eb71e41582cccd5a1ee26fff;
                              maxExpArray[118] = 0x00c2d415c3db974ab32a51840c0b67edff;
                              maxExpArray[119] = 0x00bad03e7d883f69ad5b0a186184e06bff;
                              maxExpArray[120] = 0x00b320d03b2c343d4829abd6075f0cc5ff;
                              maxExpArray[121] = 0x00abc25204e02828d73c6e80bcdb1a95bf;
                              maxExpArray[122] = 0x00a4b16f74ee4bb2040a1ec6c15fbbf2df;
                              maxExpArray[123] = 0x009deaf736ac1f569deb1b5ae3f36c130f;
                              maxExpArray[124] = 0x00976bd9952c7aa957f5937d790ef65037;
                              maxExpArray[125] = 0x009131271922eaa6064b73a22d0bd4f2bf;
                              maxExpArray[126] = 0x008b380f3558668c46c91c49a2f8e967b9;
                              maxExpArray[127] = 0x00857ddf0117efa215952912839f6473e6;
                          }
                      
                          /**
                              @dev given a token supply, connector balance, weight and a deposit amount (in the connector token),
                              calculates the return for a given conversion (in the main token)
                      
                              Formula:
                              Return = _supply * ((1 + _depositAmount / _connectorBalance) ^ (_connectorWeight / 1000000) - 1)
                      
                              @param _supply              token total supply
                              @param _connectorBalance    total connector balance
                              @param _connectorWeight     connector weight, represented in ppm, 1-1000000
                              @param _depositAmount       deposit amount, in connector token
                      
                              @return purchase return amount
                          */
                          function calculatePurchaseReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _depositAmount) public view returns (uint256) {
                              // validate input
                              require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT);
                      
                              // special case for 0 deposit amount
                              if (_depositAmount == 0)
                                  return 0;
                      
                              // special case if the weight = 100%
                              if (_connectorWeight == MAX_WEIGHT)
                                  return safeMul(_supply, _depositAmount) / _connectorBalance;
                      
                              uint256 result;
                              uint8 precision;
                              uint256 baseN = safeAdd(_depositAmount, _connectorBalance);
                              (result, precision) = power(baseN, _connectorBalance, _connectorWeight, MAX_WEIGHT);
                              uint256 temp = safeMul(_supply, result) >> precision;
                              return temp - _supply;
                          }
                      
                          /**
                              @dev given a token supply, connector balance, weight and a sell amount (in the main token),
                              calculates the return for a given conversion (in the connector token)
                      
                              Formula:
                              Return = _connectorBalance * (1 - (1 - _sellAmount / _supply) ^ (1 / (_connectorWeight / 1000000)))
                      
                              @param _supply              token total supply
                              @param _connectorBalance    total connector
                              @param _connectorWeight     constant connector Weight, represented in ppm, 1-1000000
                              @param _sellAmount          sell amount, in the token itself
                      
                              @return sale return amount
                          */
                          function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public view returns (uint256) {
                              // validate input
                              require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT && _sellAmount <= _supply);
                      
                              // special case for 0 sell amount
                              if (_sellAmount == 0)
                                  return 0;
                      
                              // special case for selling the entire supply
                              if (_sellAmount == _supply)
                                  return _connectorBalance;
                      
                              // special case if the weight = 100%
                              if (_connectorWeight == MAX_WEIGHT)
                                  return safeMul(_connectorBalance, _sellAmount) / _supply;
                      
                              uint256 result;
                              uint8 precision;
                              uint256 baseD = _supply - _sellAmount;
                              (result, precision) = power(_supply, baseD, MAX_WEIGHT, _connectorWeight);
                              uint256 temp1 = safeMul(_connectorBalance, result);
                              uint256 temp2 = _connectorBalance << precision;
                              return (temp1 - temp2) / result;
                          }
                      
                          /**
                              @dev given two connector balances/weights and a sell amount (in the first connector token),
                              calculates the return for a conversion from the first connector token to the second connector token (in the second connector token)
                      
                              Formula:
                              Return = _toConnectorBalance * (1 - (_fromConnectorBalance / (_fromConnectorBalance + _amount)) ^ (_fromConnectorWeight / _toConnectorWeight))
                      
                              @param _fromConnectorBalance    input connector balance
                              @param _fromConnectorWeight     input connector weight, represented in ppm, 1-1000000
                              @param _toConnectorBalance      output connector balance
                              @param _toConnectorWeight       output connector weight, represented in ppm, 1-1000000
                              @param _amount                  input connector amount
                      
                              @return second connector amount
                          */
                          function calculateCrossConnectorReturn(uint256 _fromConnectorBalance, uint32 _fromConnectorWeight, uint256 _toConnectorBalance, uint32 _toConnectorWeight, uint256 _amount) public view returns (uint256) {
                              // validate input
                              require(_fromConnectorBalance > 0 && _fromConnectorWeight > 0 && _fromConnectorWeight <= MAX_WEIGHT && _toConnectorBalance > 0 && _toConnectorWeight > 0 && _toConnectorWeight <= MAX_WEIGHT);
                      
                              // special case for equal weights
                              if (_fromConnectorWeight == _toConnectorWeight)
                                  return safeMul(_toConnectorBalance, _amount) / safeAdd(_fromConnectorBalance, _amount);
                      
                              uint256 result;
                              uint8 precision;
                              uint256 baseN = safeAdd(_fromConnectorBalance, _amount);
                              (result, precision) = power(baseN, _fromConnectorBalance, _fromConnectorWeight, _toConnectorWeight);
                              uint256 temp1 = safeMul(_toConnectorBalance, result);
                              uint256 temp2 = _toConnectorBalance << precision;
                              return (temp1 - temp2) / result;
                          }
                      
                          /**
                              General Description:
                                  Determine a value of precision.
                                  Calculate an integer approximation of (_baseN / _baseD) ^ (_expN / _expD) * 2 ^ precision.
                                  Return the result along with the precision used.
                      
                              Detailed Description:
                                  Instead of calculating "base ^ exp", we calculate "e ^ (log(base) * exp)".
                                  The value of "log(base)" is represented with an integer slightly smaller than "log(base) * 2 ^ precision".
                                  The larger "precision" is, the more accurately this value represents the real value.
                                  However, the larger "precision" is, the more bits are required in order to store this value.
                                  And the exponentiation function, which takes "x" and calculates "e ^ x", is limited to a maximum exponent (maximum value of "x").
                                  This maximum exponent depends on the "precision" used, and it is given by "maxExpArray[precision] >> (MAX_PRECISION - precision)".
                                  Hence we need to determine the highest precision which can be used for the given input, before calling the exponentiation function.
                                  This allows us to compute "base ^ exp" with maximum accuracy and without exceeding 256 bits in any of the intermediate computations.
                                  This functions assumes that "_expN < 2 ^ 256 / log(MAX_NUM - 1)", otherwise the multiplication should be replaced with a "safeMul".
                          */
                          function power(uint256 _baseN, uint256 _baseD, uint32 _expN, uint32 _expD) internal view returns (uint256, uint8) {
                              assert(_baseN < MAX_NUM);
                      
                              uint256 baseLog;
                              uint256 base = _baseN * FIXED_1 / _baseD;
                              if (base < OPT_LOG_MAX_VAL) {
                                  baseLog = optimalLog(base);
                              }
                              else {
                                  baseLog = generalLog(base);
                              }
                      
                              uint256 baseLogTimesExp = baseLog * _expN / _expD;
                              if (baseLogTimesExp < OPT_EXP_MAX_VAL) {
                                  return (optimalExp(baseLogTimesExp), MAX_PRECISION);
                              }
                              else {
                                  uint8 precision = findPositionInMaxExpArray(baseLogTimesExp);
                                  return (generalExp(baseLogTimesExp >> (MAX_PRECISION - precision), precision), precision);
                              }
                          }
                      
                          /**
                              Compute log(x / FIXED_1) * FIXED_1.
                              This functions assumes that "x >= FIXED_1", because the output would be negative otherwise.
                          */
                          function generalLog(uint256 x) internal pure returns (uint256) {
                              uint256 res = 0;
                      
                              // If x >= 2, then we compute the integer part of log2(x), which is larger than 0.
                              if (x >= FIXED_2) {
                                  uint8 count = floorLog2(x / FIXED_1);
                                  x >>= count; // now x < 2
                                  res = count * FIXED_1;
                              }
                      
                              // If x > 1, then we compute the fraction part of log2(x), which is larger than 0.
                              if (x > FIXED_1) {
                                  for (uint8 i = MAX_PRECISION; i > 0; --i) {
                                      x = (x * x) / FIXED_1; // now 1 < x < 4
                                      if (x >= FIXED_2) {
                                          x >>= 1; // now 1 < x < 2
                                          res += ONE << (i - 1);
                                      }
                                  }
                              }
                      
                              return res * LN2_NUMERATOR / LN2_DENOMINATOR;
                          }
                      
                          /**
                              Compute the largest integer smaller than or equal to the binary logarithm of the input.
                          */
                          function floorLog2(uint256 _n) internal pure returns (uint8) {
                              uint8 res = 0;
                      
                              if (_n < 256) {
                                  // At most 8 iterations
                                  while (_n > 1) {
                                      _n >>= 1;
                                      res += 1;
                                  }
                              }
                              else {
                                  // Exactly 8 iterations
                                  for (uint8 s = 128; s > 0; s >>= 1) {
                                      if (_n >= (ONE << s)) {
                                          _n >>= s;
                                          res |= s;
                                      }
                                  }
                              }
                      
                              return res;
                          }
                      
                          /**
                              The global "maxExpArray" is sorted in descending order, and therefore the following statements are equivalent:
                              - This function finds the position of [the smallest value in "maxExpArray" larger than or equal to "x"]
                              - This function finds the highest position of [a value in "maxExpArray" larger than or equal to "x"]
                          */
                          function findPositionInMaxExpArray(uint256 _x) internal view returns (uint8) {
                              uint8 lo = MIN_PRECISION;
                              uint8 hi = MAX_PRECISION;
                      
                              while (lo + 1 < hi) {
                                  uint8 mid = (lo + hi) / 2;
                                  if (maxExpArray[mid] >= _x)
                                      lo = mid;
                                  else
                                      hi = mid;
                              }
                      
                              if (maxExpArray[hi] >= _x)
                                  return hi;
                              if (maxExpArray[lo] >= _x)
                                  return lo;
                      
                              assert(false);
                              return 0;
                          }
                      
                          /**
                              This function can be auto-generated by the script 'PrintFunctionGeneralExp.py'.
                              It approximates "e ^ x" via maclaurin summation: "(x^0)/0! + (x^1)/1! + ... + (x^n)/n!".
                              It returns "e ^ (x / 2 ^ precision) * 2 ^ precision", that is, the result is upshifted for accuracy.
                              The global "maxExpArray" maps each "precision" to "((maximumExponent + 1) << (MAX_PRECISION - precision)) - 1".
                              The maximum permitted value for "x" is therefore given by "maxExpArray[precision] >> (MAX_PRECISION - precision)".
                          */
                          function generalExp(uint256 _x, uint8 _precision) internal pure returns (uint256) {
                              uint256 xi = _x;
                              uint256 res = 0;
                      
                              xi = (xi * _x) >> _precision; res += xi * 0x3442c4e6074a82f1797f72ac0000000; // add x^02 * (33! / 02!)
                              xi = (xi * _x) >> _precision; res += xi * 0x116b96f757c380fb287fd0e40000000; // add x^03 * (33! / 03!)
                              xi = (xi * _x) >> _precision; res += xi * 0x045ae5bdd5f0e03eca1ff4390000000; // add x^04 * (33! / 04!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00defabf91302cd95b9ffda50000000; // add x^05 * (33! / 05!)
                              xi = (xi * _x) >> _precision; res += xi * 0x002529ca9832b22439efff9b8000000; // add x^06 * (33! / 06!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00054f1cf12bd04e516b6da88000000; // add x^07 * (33! / 07!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000a9e39e257a09ca2d6db51000000; // add x^08 * (33! / 08!)
                              xi = (xi * _x) >> _precision; res += xi * 0x000012e066e7b839fa050c309000000; // add x^09 * (33! / 09!)
                              xi = (xi * _x) >> _precision; res += xi * 0x000001e33d7d926c329a1ad1a800000; // add x^10 * (33! / 10!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000002bee513bdb4a6b19b5f800000; // add x^11 * (33! / 11!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00000003a9316fa79b88eccf2a00000; // add x^12 * (33! / 12!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000048177ebe1fa812375200000; // add x^13 * (33! / 13!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000005263fe90242dcbacf00000; // add x^14 * (33! / 14!)
                              xi = (xi * _x) >> _precision; res += xi * 0x000000000057e22099c030d94100000; // add x^15 * (33! / 15!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000000057e22099c030d9410000; // add x^16 * (33! / 16!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00000000000052b6b54569976310000; // add x^17 * (33! / 17!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00000000000004985f67696bf748000; // add x^18 * (33! / 18!)
                              xi = (xi * _x) >> _precision; res += xi * 0x000000000000003dea12ea99e498000; // add x^19 * (33! / 19!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00000000000000031880f2214b6e000; // add x^20 * (33! / 20!)
                              xi = (xi * _x) >> _precision; res += xi * 0x000000000000000025bcff56eb36000; // add x^21 * (33! / 21!)
                              xi = (xi * _x) >> _precision; res += xi * 0x000000000000000001b722e10ab1000; // add x^22 * (33! / 22!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000001317c70077000; // add x^23 * (33! / 23!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000cba84aafa00; // add x^24 * (33! / 24!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000082573a0a00; // add x^25 * (33! / 25!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000005035ad900; // add x^26 * (33! / 26!)
                              xi = (xi * _x) >> _precision; res += xi * 0x000000000000000000000002f881b00; // add x^27 * (33! / 27!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000001b29340; // add x^28 * (33! / 28!)
                              xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000000000efc40; // add x^29 * (33! / 29!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000007fe0; // add x^30 * (33! / 30!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000420; // add x^31 * (33! / 31!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000021; // add x^32 * (33! / 32!)
                              xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000001; // add x^33 * (33! / 33!)
                      
                              return res / 0x688589cc0e9505e2f2fee5580000000 + _x + (ONE << _precision); // divide by 33! and then add x^1 / 1! + x^0 / 0!
                          }
                      
                          /**
                              Return log(x / FIXED_1) * FIXED_1
                              Input range: FIXED_1 <= x <= LOG_EXP_MAX_VAL - 1
                              Auto-generated via 'PrintFunctionOptimalLog.py'
                          */
                          function optimalLog(uint256 x) internal pure returns (uint256) {
                              uint256 res = 0;
                      
                              uint256 y;
                              uint256 z;
                              uint256 w;
                      
                              if (x >= 0xd3094c70f034de4b96ff7d5b6f99fcd8) {res += 0x40000000000000000000000000000000; x = x * FIXED_1 / 0xd3094c70f034de4b96ff7d5b6f99fcd8;}
                              if (x >= 0xa45af1e1f40c333b3de1db4dd55f29a7) {res += 0x20000000000000000000000000000000; x = x * FIXED_1 / 0xa45af1e1f40c333b3de1db4dd55f29a7;}
                              if (x >= 0x910b022db7ae67ce76b441c27035c6a1) {res += 0x10000000000000000000000000000000; x = x * FIXED_1 / 0x910b022db7ae67ce76b441c27035c6a1;}
                              if (x >= 0x88415abbe9a76bead8d00cf112e4d4a8) {res += 0x08000000000000000000000000000000; x = x * FIXED_1 / 0x88415abbe9a76bead8d00cf112e4d4a8;}
                              if (x >= 0x84102b00893f64c705e841d5d4064bd3) {res += 0x04000000000000000000000000000000; x = x * FIXED_1 / 0x84102b00893f64c705e841d5d4064bd3;}
                              if (x >= 0x8204055aaef1c8bd5c3259f4822735a2) {res += 0x02000000000000000000000000000000; x = x * FIXED_1 / 0x8204055aaef1c8bd5c3259f4822735a2;}
                              if (x >= 0x810100ab00222d861931c15e39b44e99) {res += 0x01000000000000000000000000000000; x = x * FIXED_1 / 0x810100ab00222d861931c15e39b44e99;}
                              if (x >= 0x808040155aabbbe9451521693554f733) {res += 0x00800000000000000000000000000000; x = x * FIXED_1 / 0x808040155aabbbe9451521693554f733;}
                      
                              z = y = x - FIXED_1;
                              w = y * y / FIXED_1;
                              res += z * (0x100000000000000000000000000000000 - y) / 0x100000000000000000000000000000000; z = z * w / FIXED_1;
                              res += z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y) / 0x200000000000000000000000000000000; z = z * w / FIXED_1;
                              res += z * (0x099999999999999999999999999999999 - y) / 0x300000000000000000000000000000000; z = z * w / FIXED_1;
                              res += z * (0x092492492492492492492492492492492 - y) / 0x400000000000000000000000000000000; z = z * w / FIXED_1;
                              res += z * (0x08e38e38e38e38e38e38e38e38e38e38e - y) / 0x500000000000000000000000000000000; z = z * w / FIXED_1;
                              res += z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y) / 0x600000000000000000000000000000000; z = z * w / FIXED_1;
                              res += z * (0x089d89d89d89d89d89d89d89d89d89d89 - y) / 0x700000000000000000000000000000000; z = z * w / FIXED_1;
                              res += z * (0x088888888888888888888888888888888 - y) / 0x800000000000000000000000000000000;
                      
                              return res;
                          }
                      
                          /**
                              Return e ^ (x / FIXED_1) * FIXED_1
                              Input range: 0 <= x <= OPT_EXP_MAX_VAL - 1
                              Auto-generated via 'PrintFunctionOptimalExp.py'
                          */
                          function optimalExp(uint256 x) internal pure returns (uint256) {
                              uint256 res = 0;
                      
                              uint256 y;
                              uint256 z;
                      
                              z = y = x % 0x10000000000000000000000000000000;
                              z = z * y / FIXED_1; res += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
                              z = z * y / FIXED_1; res += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
                              z = z * y / FIXED_1; res += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
                              z = z * y / FIXED_1; res += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
                              z = z * y / FIXED_1; res += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
                              z = z * y / FIXED_1; res += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
                              z = z * y / FIXED_1; res += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
                              z = z * y / FIXED_1; res += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
                              z = z * y / FIXED_1; res += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
                              z = z * y / FIXED_1; res += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
                              z = z * y / FIXED_1; res += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
                              z = z * y / FIXED_1; res += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
                              z = z * y / FIXED_1; res += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
                              z = z * y / FIXED_1; res += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
                              z = z * y / FIXED_1; res += z * 0x000000000001c638; // add y^16 * (20! / 16!)
                              z = z * y / FIXED_1; res += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
                              z = z * y / FIXED_1; res += z * 0x000000000000017c; // add y^18 * (20! / 18!)
                              z = z * y / FIXED_1; res += z * 0x0000000000000014; // add y^19 * (20! / 19!)
                              z = z * y / FIXED_1; res += z * 0x0000000000000001; // add y^20 * (20! / 20!)
                              res = res / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0!
                      
                              if ((x & 0x010000000000000000000000000000000) != 0) res = res * 0x1c3d6a24ed82218787d624d3e5eba95f9 / 0x18ebef9eac820ae8682b9793ac6d1e776;
                              if ((x & 0x020000000000000000000000000000000) != 0) res = res * 0x18ebef9eac820ae8682b9793ac6d1e778 / 0x1368b2fc6f9609fe7aceb46aa619baed4;
                              if ((x & 0x040000000000000000000000000000000) != 0) res = res * 0x1368b2fc6f9609fe7aceb46aa619baed5 / 0x0bc5ab1b16779be3575bd8f0520a9f21f;
                              if ((x & 0x080000000000000000000000000000000) != 0) res = res * 0x0bc5ab1b16779be3575bd8f0520a9f21e / 0x0454aaa8efe072e7f6ddbab84b40a55c9;
                              if ((x & 0x100000000000000000000000000000000) != 0) res = res * 0x0454aaa8efe072e7f6ddbab84b40a55c5 / 0x00960aadc109e7a3bf4578099615711ea;
                              if ((x & 0x200000000000000000000000000000000) != 0) res = res * 0x00960aadc109e7a3bf4578099615711d7 / 0x0002bf84208204f5977f9a8cf01fdce3d;
                              if ((x & 0x400000000000000000000000000000000) != 0) res = res * 0x0002bf84208204f5977f9a8cf01fdc307 / 0x0000003c6ab775dd0b95b4cbee7e65d11;
                      
                              return res;
                          }
                      }

                      File 5 of 14: BancorConverter
                      pragma solidity ^0.4.21;
                      
                      /*
                          Owned contract interface
                      */
                      contract IOwned {
                          // this function isn't abstract since the compiler emits automatically generated getter functions as external
                          function owner() public view returns (address) {}
                      
                          function transferOwnership(address _newOwner) public;
                          function acceptOwnership() public;
                      }
                      
                      /*
                          ERC20 Standard Token interface
                      */
                      contract IERC20Token {
                          // these functions aren't abstract since the compiler emits automatically generated getter functions as external
                          function name() public view returns (string) {}
                          function symbol() public view returns (string) {}
                          function decimals() public view returns (uint8) {}
                          function totalSupply() public view returns (uint256) {}
                          function balanceOf(address _owner) public view returns (uint256) { _owner; }
                          function allowance(address _owner, address _spender) public view returns (uint256) { _owner; _spender; }
                      
                          function transfer(address _to, uint256 _value) public returns (bool success);
                          function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
                          function approve(address _spender, uint256 _value) public returns (bool success);
                      }
                      
                      /*
                          Smart Token interface
                      */
                      contract ISmartToken is IOwned, IERC20Token {
                          function disableTransfers(bool _disable) public;
                          function issue(address _to, uint256 _amount) public;
                          function destroy(address _from, uint256 _amount) public;
                      }
                      
                      /*
                          Contract Registry interface
                      */
                      contract IContractRegistry {
                          function getAddress(bytes32 _contractName) public view returns (address);
                      }
                      
                      /*
                          Contract Features interface
                      */
                      contract IContractFeatures {
                          function isSupported(address _contract, uint256 _features) public view returns (bool);
                          function enableFeatures(uint256 _features, bool _enable) public;
                      }
                      
                      /*
                          Whitelist interface
                      */
                      contract IWhitelist {
                          function isWhitelisted(address _address) public view returns (bool);
                      }
                      
                      /*
                          Token Holder interface
                      */
                      contract ITokenHolder is IOwned {
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                      }
                      
                      /*
                          Bancor Formula interface
                      */
                      contract IBancorFormula {
                          function calculatePurchaseReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _depositAmount) public view returns (uint256);
                          function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public view returns (uint256);
                          function calculateCrossConnectorReturn(uint256 _fromConnectorBalance, uint32 _fromConnectorWeight, uint256 _toConnectorBalance, uint32 _toConnectorWeight, uint256 _amount) public view returns (uint256);
                      }
                      
                      /*
                          Bancor Converter interface
                      */
                      contract IBancorConverter {
                          function getReturn(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256);
                          function convert(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256);
                          function conversionWhitelist() public view returns (IWhitelist) {}
                          // deprecated, backward compatibility
                          function change(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256);
                      }
                      
                      /*
                          Bancor Network interface
                      */
                      contract IBancorNetwork {
                          function convert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn) public payable returns (uint256);
                          function convertFor(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, address _for) public payable returns (uint256);
                          function convertForPrioritized2(
                              IERC20Token[] _path,
                              uint256 _amount,
                              uint256 _minReturn,
                              address _for,
                              uint256 _block,
                              uint8 _v,
                              bytes32 _r,
                              bytes32 _s)
                              public payable returns (uint256);
                      
                          // deprecated, backward compatibility
                          function convertForPrioritized(
                              IERC20Token[] _path,
                              uint256 _amount,
                              uint256 _minReturn,
                              address _for,
                              uint256 _block,
                              uint256 _nonce,
                              uint8 _v,
                              bytes32 _r,
                              bytes32 _s)
                              public payable returns (uint256);
                      }
                      
                      /*
                          Utilities & Common Modifiers
                      */
                      contract Utils {
                          /**
                              constructor
                          */
                          function Utils() public {
                          }
                      
                          // verifies that an amount is greater than zero
                          modifier greaterThanZero(uint256 _amount) {
                              require(_amount > 0);
                              _;
                          }
                      
                          // validates an address - currently only checks that it isn't null
                          modifier validAddress(address _address) {
                              require(_address != address(0));
                              _;
                          }
                      
                          // verifies that the address is different than this contract address
                          modifier notThis(address _address) {
                              require(_address != address(this));
                              _;
                          }
                      
                          // Overflow protected math functions
                      
                          /**
                              @dev returns the sum of _x and _y, asserts if the calculation overflows
                      
                              @param _x   value 1
                              @param _y   value 2
                      
                              @return sum
                          */
                          function safeAdd(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              uint256 z = _x + _y;
                              assert(z >= _x);
                              return z;
                          }
                      
                          /**
                              @dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
                      
                              @param _x   minuend
                              @param _y   subtrahend
                      
                              @return difference
                          */
                          function safeSub(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              assert(_x >= _y);
                              return _x - _y;
                          }
                      
                          /**
                              @dev returns the product of multiplying _x by _y, asserts if the calculation overflows
                      
                              @param _x   factor 1
                              @param _y   factor 2
                      
                              @return product
                          */
                          function safeMul(uint256 _x, uint256 _y) internal pure returns (uint256) {
                              uint256 z = _x * _y;
                              assert(_x == 0 || z / _x == _y);
                              return z;
                          }
                      }
                      
                      /*
                          Provides support and utilities for contract ownership
                      */
                      contract Owned is IOwned {
                          address public owner;
                          address public newOwner;
                      
                          event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
                      
                          /**
                              @dev constructor
                          */
                          function Owned() public {
                              owner = msg.sender;
                          }
                      
                          // allows execution by the owner only
                          modifier ownerOnly {
                              assert(msg.sender == owner);
                              _;
                          }
                      
                          /**
                              @dev allows transferring the contract ownership
                              the new owner still needs to accept the transfer
                              can only be called by the contract owner
                      
                              @param _newOwner    new contract owner
                          */
                          function transferOwnership(address _newOwner) public ownerOnly {
                              require(_newOwner != owner);
                              newOwner = _newOwner;
                          }
                      
                          /**
                              @dev used by a new owner to accept an ownership transfer
                          */
                          function acceptOwnership() public {
                              require(msg.sender == newOwner);
                              emit OwnerUpdate(owner, newOwner);
                              owner = newOwner;
                              newOwner = address(0);
                          }
                      }
                      
                      /*
                          Provides support and utilities for contract management
                          Note that a managed contract must also have an owner
                      */
                      contract Managed is Owned {
                          address public manager;
                          address public newManager;
                      
                          event ManagerUpdate(address indexed _prevManager, address indexed _newManager);
                      
                          /**
                              @dev constructor
                          */
                          function Managed() public {
                              manager = msg.sender;
                          }
                      
                          // allows execution by the manager only
                          modifier managerOnly {
                              assert(msg.sender == manager);
                              _;
                          }
                      
                          // allows execution by either the owner or the manager only
                          modifier ownerOrManagerOnly {
                              require(msg.sender == owner || msg.sender == manager);
                              _;
                          }
                      
                          /**
                              @dev allows transferring the contract management
                              the new manager still needs to accept the transfer
                              can only be called by the contract manager
                      
                              @param _newManager    new contract manager
                          */
                          function transferManagement(address _newManager) public ownerOrManagerOnly {
                              require(_newManager != manager);
                              newManager = _newManager;
                          }
                      
                          /**
                              @dev used by a new manager to accept a management transfer
                          */
                          function acceptManagement() public {
                              require(msg.sender == newManager);
                              emit ManagerUpdate(manager, newManager);
                              manager = newManager;
                              newManager = address(0);
                          }
                      }
                      
                      /**
                          Id definitions for bancor contracts
                      
                          Can be used in conjunction with the contract registry to get contract addresses
                      */
                      contract ContractIds {
                          // generic
                          bytes32 public constant CONTRACT_FEATURES = "ContractFeatures";
                      
                          // bancor logic
                          bytes32 public constant BANCOR_NETWORK = "BancorNetwork";
                          bytes32 public constant BANCOR_FORMULA = "BancorFormula";
                          bytes32 public constant BANCOR_GAS_PRICE_LIMIT = "BancorGasPriceLimit";
                      
                          bytes32 public constant BANCOR_CONVERTER_FACTORY = "BancorConverterFactory";
                          bytes32 public constant BANCOR_CONVERTER_UPGRADER = "BancorConverterUpgrader";
                      
                          // tokens
                          bytes32 public constant BNT_TOKEN = "BNTToken";
                      }
                      
                      /**
                          Id definitions for bancor contract features
                      
                          Can be used to query the ContractFeatures contract to check whether a certain feature is supported by a contract
                      */
                      contract FeatureIds {
                          // converter features
                          uint256 public constant CONVERTER_CONVERSION_WHITELIST = 1 << 0;
                      }
                      
                      /*
                          We consider every contract to be a 'token holder' since it's currently not possible
                          for a contract to deny receiving tokens.
                      
                          The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
                          the owner to send tokens that were sent to the contract by mistake back to their sender.
                      */
                      contract TokenHolder is ITokenHolder, Owned, Utils {
                          /**
                              @dev constructor
                          */
                          function TokenHolder() public {
                          }
                      
                          /**
                              @dev withdraws tokens held by the contract and sends them to an account
                              can only be called by the owner
                      
                              @param _token   ERC20 token contract address
                              @param _to      account to receive the new amount
                              @param _amount  amount to withdraw
                          */
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
                              public
                              ownerOnly
                              validAddress(_token)
                              validAddress(_to)
                              notThis(_to)
                          {
                              assert(_token.transfer(_to, _amount));
                          }
                      }
                      
                      /*
                          The smart token controller is an upgradable part of the smart token that allows
                          more functionality as well as fixes for bugs/exploits.
                          Once it accepts ownership of the token, it becomes the token's sole controller
                          that can execute any of its functions.
                      
                          To upgrade the controller, ownership must be transferred to a new controller, along with
                          any relevant data.
                      
                          The smart token must be set on construction and cannot be changed afterwards.
                          Wrappers are provided (as opposed to a single 'execute' function) for each of the token's functions, for easier access.
                      
                          Note that the controller can transfer token ownership to a new controller that
                          doesn't allow executing any function on the token, for a trustless solution.
                          Doing that will also remove the owner's ability to upgrade the controller.
                      */
                      contract SmartTokenController is TokenHolder {
                          ISmartToken public token;   // smart token
                      
                          /**
                              @dev constructor
                          */
                          function SmartTokenController(ISmartToken _token)
                              public
                              validAddress(_token)
                          {
                              token = _token;
                          }
                      
                          // ensures that the controller is the token's owner
                          modifier active() {
                              assert(token.owner() == address(this));
                              _;
                          }
                      
                          // ensures that the controller is not the token's owner
                          modifier inactive() {
                              assert(token.owner() != address(this));
                              _;
                          }
                      
                          /**
                              @dev allows transferring the token ownership
                              the new owner still need to accept the transfer
                              can only be called by the contract owner
                      
                              @param _newOwner    new token owner
                          */
                          function transferTokenOwnership(address _newOwner) public ownerOnly {
                              token.transferOwnership(_newOwner);
                          }
                      
                          /**
                              @dev used by a new owner to accept a token ownership transfer
                              can only be called by the contract owner
                          */
                          function acceptTokenOwnership() public ownerOnly {
                              token.acceptOwnership();
                          }
                      
                          /**
                              @dev disables/enables token transfers
                              can only be called by the contract owner
                      
                              @param _disable    true to disable transfers, false to enable them
                          */
                          function disableTokenTransfers(bool _disable) public ownerOnly {
                              token.disableTransfers(_disable);
                          }
                      
                          /**
                              @dev withdraws tokens held by the controller and sends them to an account
                              can only be called by the owner
                      
                              @param _token   ERC20 token contract address
                              @param _to      account to receive the new amount
                              @param _amount  amount to withdraw
                          */
                          function withdrawFromToken(
                              IERC20Token _token, 
                              address _to, 
                              uint256 _amount
                          ) 
                              public
                              ownerOnly
                          {
                              ITokenHolder(token).withdrawTokens(_token, _to, _amount);
                          }
                      }
                      
                      /*
                          Bancor Converter v0.9
                      
                          The Bancor version of the token converter, allows conversion between a smart token and other ERC20 tokens and between different ERC20 tokens and themselves.
                      
                          ERC20 connector balance can be virtual, meaning that the calculations are based on the virtual balance instead of relying on
                          the actual connector balance. This is a security mechanism that prevents the need to keep a very large (and valuable) balance in a single contract.
                      
                          The converter is upgradable (just like any SmartTokenController).
                      
                          WARNING: It is NOT RECOMMENDED to use the converter with Smart Tokens that have less than 8 decimal digits
                                   or with very small numbers because of precision loss
                      
                          Open issues:
                          - Front-running attacks are currently mitigated by the following mechanisms:
                              - minimum return argument for each conversion provides a way to define a minimum/maximum price for the transaction
                              - gas price limit prevents users from having control over the order of execution
                              - gas price limit check can be skipped if the transaction comes from a trusted, whitelisted signer
                            Other potential solutions might include a commit/reveal based schemes
                          - Possibly add getters for the connector fields so that the client won't need to rely on the order in the struct
                      */
                      contract BancorConverter is IBancorConverter, SmartTokenController, Managed, ContractIds, FeatureIds {
                          uint32 private constant MAX_WEIGHT = 1000000;
                          uint64 private constant MAX_CONVERSION_FEE = 1000000;
                      
                          struct Connector {
                              uint256 virtualBalance;         // connector virtual balance
                              uint32 weight;                  // connector weight, represented in ppm, 1-1000000
                              bool isVirtualBalanceEnabled;   // true if virtual balance is enabled, false if not
                              bool isPurchaseEnabled;         // is purchase of the smart token enabled with the connector, can be set by the owner
                              bool isSet;                     // used to tell if the mapping element is defined
                          }
                      
                          string public version = '0.9';
                          string public converterType = 'bancor';
                      
                          IContractRegistry public registry;                  // contract registry contract
                          IWhitelist public conversionWhitelist;              // whitelist contract with list of addresses that are allowed to use the converter
                          IERC20Token[] public connectorTokens;               // ERC20 standard token addresses
                          IERC20Token[] public quickBuyPath;                  // conversion path that's used in order to buy the token with ETH
                          mapping (address => Connector) public connectors;   // connector token addresses -> connector data
                          uint32 private totalConnectorWeight = 0;            // used to efficiently prevent increasing the total connector weight above 100%
                          uint32 public maxConversionFee = 0;                 // maximum conversion fee for the lifetime of the contract,
                                                                              // represented in ppm, 0...1000000 (0 = no fee, 100 = 0.01%, 1000000 = 100%)
                          uint32 public conversionFee = 0;                    // current conversion fee, represented in ppm, 0...maxConversionFee
                          bool public conversionsEnabled = true;              // true if token conversions is enabled, false if not
                          IERC20Token[] private convertPath;
                      
                          // triggered when a conversion between two tokens occurs
                          event Conversion(
                              address indexed _fromToken,
                              address indexed _toToken,
                              address indexed _trader,
                              uint256 _amount,
                              uint256 _return,
                              int256 _conversionFee
                          );
                          // triggered after a conversion with new price data
                          event PriceDataUpdate(
                              address indexed _connectorToken,
                              uint256 _tokenSupply,
                              uint256 _connectorBalance,
                              uint32 _connectorWeight
                          );
                          // triggered when the conversion fee is updated
                          event ConversionFeeUpdate(uint32 _prevFee, uint32 _newFee);
                      
                          /**
                              @dev constructor
                      
                              @param  _token              smart token governed by the converter
                              @param  _registry           address of a contract registry contract
                              @param  _maxConversionFee   maximum conversion fee, represented in ppm
                              @param  _connectorToken     optional, initial connector, allows defining the first connector at deployment time
                              @param  _connectorWeight    optional, weight for the initial connector
                          */
                          function BancorConverter(
                              ISmartToken _token,
                              IContractRegistry _registry,
                              uint32 _maxConversionFee,
                              IERC20Token _connectorToken,
                              uint32 _connectorWeight
                          )
                              public
                              SmartTokenController(_token)
                              validAddress(_registry)
                              validMaxConversionFee(_maxConversionFee)
                          {
                              registry = _registry;
                              IContractFeatures features = IContractFeatures(registry.getAddress(ContractIds.CONTRACT_FEATURES));
                      
                              // initialize supported features
                              if (features != address(0))
                                  features.enableFeatures(FeatureIds.CONVERTER_CONVERSION_WHITELIST, true);
                      
                              maxConversionFee = _maxConversionFee;
                      
                              if (_connectorToken != address(0))
                                  addConnector(_connectorToken, _connectorWeight, false);
                          }
                      
                          // validates a connector token address - verifies that the address belongs to one of the connector tokens
                          modifier validConnector(IERC20Token _address) {
                              require(connectors[_address].isSet);
                              _;
                          }
                      
                          // validates a token address - verifies that the address belongs to one of the convertible tokens
                          modifier validToken(IERC20Token _address) {
                              require(_address == token || connectors[_address].isSet);
                              _;
                          }
                      
                          // validates maximum conversion fee
                          modifier validMaxConversionFee(uint32 _conversionFee) {
                              require(_conversionFee >= 0 && _conversionFee <= MAX_CONVERSION_FEE);
                              _;
                          }
                      
                          // validates conversion fee
                          modifier validConversionFee(uint32 _conversionFee) {
                              require(_conversionFee >= 0 && _conversionFee <= maxConversionFee);
                              _;
                          }
                      
                          // validates connector weight range
                          modifier validConnectorWeight(uint32 _weight) {
                              require(_weight > 0 && _weight <= MAX_WEIGHT);
                              _;
                          }
                      
                          // validates a conversion path - verifies that the number of elements is odd and that maximum number of 'hops' is 10
                          modifier validConversionPath(IERC20Token[] _path) {
                              require(_path.length > 2 && _path.length <= (1 + 2 * 10) && _path.length % 2 == 1);
                              _;
                          }
                      
                          // allows execution only when conversions aren't disabled
                          modifier conversionsAllowed {
                              assert(conversionsEnabled);
                              _;
                          }
                      
                          // allows execution by the BancorNetwork contract only
                          modifier bancorNetworkOnly {
                              IBancorNetwork bancorNetwork = IBancorNetwork(registry.getAddress(ContractIds.BANCOR_NETWORK));
                              require(msg.sender == address(bancorNetwork));
                              _;
                          }
                      
                          /**
                              @dev returns the number of connector tokens defined
                      
                              @return number of connector tokens
                          */
                          function connectorTokenCount() public view returns (uint16) {
                              return uint16(connectorTokens.length);
                          }
                      
                          /*
                              @dev allows the owner to update the registry contract address
                      
                              @param _registry    address of a bancor converter registry contract
                          */
                          function setRegistry(IContractRegistry _registry)
                              public
                              ownerOnly
                              validAddress(_registry)
                              notThis(_registry)
                          {
                              registry = _registry;
                          }
                      
                          /*
                              @dev allows the owner to update & enable the conversion whitelist contract address
                              when set, only addresses that are whitelisted are actually allowed to use the converter
                              note that the whitelist check is actually done by the BancorNetwork contract
                      
                              @param _whitelist    address of a whitelist contract
                          */
                          function setConversionWhitelist(IWhitelist _whitelist)
                              public
                              ownerOnly
                              notThis(_whitelist)
                          {
                              conversionWhitelist = _whitelist;
                          }
                      
                          /*
                              @dev allows the manager to update the quick buy path
                      
                              @param _path    new quick buy path, see conversion path format in the bancorNetwork contract
                          */
                          function setQuickBuyPath(IERC20Token[] _path)
                              public
                              ownerOnly
                              validConversionPath(_path)
                          {
                              quickBuyPath = _path;
                          }
                      
                          /*
                              @dev allows the manager to clear the quick buy path
                          */
                          function clearQuickBuyPath() public ownerOnly {
                              quickBuyPath.length = 0;
                          }
                      
                          /**
                              @dev returns the length of the quick buy path array
                      
                              @return quick buy path length
                          */
                          function getQuickBuyPathLength() public view returns (uint256) {
                              return quickBuyPath.length;
                          }
                      
                          /**
                              @dev disables the entire conversion functionality
                              this is a safety mechanism in case of a emergency
                              can only be called by the manager
                      
                              @param _disable true to disable conversions, false to re-enable them
                          */
                          function disableConversions(bool _disable) public ownerOrManagerOnly {
                              conversionsEnabled = !_disable;
                          }
                      
                          /**
                              @dev updates the current conversion fee
                              can only be called by the manager
                      
                              @param _conversionFee new conversion fee, represented in ppm
                          */
                          function setConversionFee(uint32 _conversionFee)
                              public
                              ownerOrManagerOnly
                              validConversionFee(_conversionFee)
                          {
                              emit ConversionFeeUpdate(conversionFee, _conversionFee);
                              conversionFee = _conversionFee;
                          }
                      
                          /*
                              @dev given a return amount, returns the amount minus the conversion fee
                      
                              @param _amount      return amount
                              @param _magnitude   1 for standard conversion, 2 for cross connector conversion
                      
                              @return return amount minus conversion fee
                          */
                          function getFinalAmount(uint256 _amount, uint8 _magnitude) public view returns (uint256) {
                              return safeMul(_amount, (MAX_CONVERSION_FEE - conversionFee) ** _magnitude) / MAX_CONVERSION_FEE ** _magnitude;
                          }
                      
                          /**
                              @dev defines a new connector for the token
                              can only be called by the owner while the converter is inactive
                      
                              @param _token                  address of the connector token
                              @param _weight                 constant connector weight, represented in ppm, 1-1000000
                              @param _enableVirtualBalance   true to enable virtual balance for the connector, false to disable it
                          */
                          function addConnector(IERC20Token _token, uint32 _weight, bool _enableVirtualBalance)
                              public
                              ownerOnly
                              inactive
                              validAddress(_token)
                              notThis(_token)
                              validConnectorWeight(_weight)
                          {
                              require(_token != token && !connectors[_token].isSet && totalConnectorWeight + _weight <= MAX_WEIGHT); // validate input
                      
                              connectors[_token].virtualBalance = 0;
                              connectors[_token].weight = _weight;
                              connectors[_token].isVirtualBalanceEnabled = _enableVirtualBalance;
                              connectors[_token].isPurchaseEnabled = true;
                              connectors[_token].isSet = true;
                              connectorTokens.push(_token);
                              totalConnectorWeight += _weight;
                          }
                      
                          /**
                              @dev updates one of the token connectors
                              can only be called by the owner
                      
                              @param _connectorToken         address of the connector token
                              @param _weight                 constant connector weight, represented in ppm, 1-1000000
                              @param _enableVirtualBalance   true to enable virtual balance for the connector, false to disable it
                              @param _virtualBalance         new connector's virtual balance
                          */
                          function updateConnector(IERC20Token _connectorToken, uint32 _weight, bool _enableVirtualBalance, uint256 _virtualBalance)
                              public
                              ownerOnly
                              validConnector(_connectorToken)
                              validConnectorWeight(_weight)
                          {
                              Connector storage connector = connectors[_connectorToken];
                              require(totalConnectorWeight - connector.weight + _weight <= MAX_WEIGHT); // validate input
                      
                              totalConnectorWeight = totalConnectorWeight - connector.weight + _weight;
                              connector.weight = _weight;
                              connector.isVirtualBalanceEnabled = _enableVirtualBalance;
                              connector.virtualBalance = _virtualBalance;
                          }
                      
                          /**
                              @dev disables purchasing with the given connector token in case the connector token got compromised
                              can only be called by the owner
                              note that selling is still enabled regardless of this flag and it cannot be disabled by the owner
                      
                              @param _connectorToken  connector token contract address
                              @param _disable         true to disable the token, false to re-enable it
                          */
                          function disableConnectorPurchases(IERC20Token _connectorToken, bool _disable)
                              public
                              ownerOnly
                              validConnector(_connectorToken)
                          {
                              connectors[_connectorToken].isPurchaseEnabled = !_disable;
                          }
                      
                          /**
                              @dev returns the connector's virtual balance if one is defined, otherwise returns the actual balance
                      
                              @param _connectorToken  connector token contract address
                      
                              @return connector balance
                          */
                          function getConnectorBalance(IERC20Token _connectorToken)
                              public
                              view
                              validConnector(_connectorToken)
                              returns (uint256)
                          {
                              Connector storage connector = connectors[_connectorToken];
                              return connector.isVirtualBalanceEnabled ? connector.virtualBalance : _connectorToken.balanceOf(this);
                          }
                      
                          /**
                              @dev returns the expected return for converting a specific amount of _fromToken to _toToken
                      
                              @param _fromToken  ERC20 token to convert from
                              @param _toToken    ERC20 token to convert to
                              @param _amount     amount to convert, in fromToken
                      
                              @return expected conversion return amount
                          */
                          function getReturn(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256) {
                              require(_fromToken != _toToken); // validate input
                      
                              // conversion between the token and one of its connectors
                              if (_toToken == token)
                                  return getPurchaseReturn(_fromToken, _amount);
                              else if (_fromToken == token)
                                  return getSaleReturn(_toToken, _amount);
                      
                              // conversion between 2 connectors
                              return getCrossConnectorReturn(_fromToken, _toToken, _amount);
                          }
                      
                          /**
                              @dev returns the expected return for buying the token for a connector token
                      
                              @param _connectorToken  connector token contract address
                              @param _depositAmount   amount to deposit (in the connector token)
                      
                              @return expected purchase return amount
                          */
                          function getPurchaseReturn(IERC20Token _connectorToken, uint256 _depositAmount)
                              public
                              view
                              active
                              validConnector(_connectorToken)
                              returns (uint256)
                          {
                              Connector storage connector = connectors[_connectorToken];
                              require(connector.isPurchaseEnabled); // validate input
                      
                              uint256 tokenSupply = token.totalSupply();
                              uint256 connectorBalance = getConnectorBalance(_connectorToken);
                              IBancorFormula formula = IBancorFormula(registry.getAddress(ContractIds.BANCOR_FORMULA));
                              uint256 amount = formula.calculatePurchaseReturn(tokenSupply, connectorBalance, connector.weight, _depositAmount);
                      
                              // return the amount minus the conversion fee
                              return getFinalAmount(amount, 1);
                          }
                      
                          /**
                              @dev returns the expected return for selling the token for one of its connector tokens
                      
                              @param _connectorToken  connector token contract address
                              @param _sellAmount      amount to sell (in the smart token)
                      
                              @return expected sale return amount
                          */
                          function getSaleReturn(IERC20Token _connectorToken, uint256 _sellAmount)
                              public
                              view
                              active
                              validConnector(_connectorToken)
                              returns (uint256)
                          {
                              Connector storage connector = connectors[_connectorToken];
                              uint256 tokenSupply = token.totalSupply();
                              uint256 connectorBalance = getConnectorBalance(_connectorToken);
                              IBancorFormula formula = IBancorFormula(registry.getAddress(ContractIds.BANCOR_FORMULA));
                              uint256 amount = formula.calculateSaleReturn(tokenSupply, connectorBalance, connector.weight, _sellAmount);
                      
                              // return the amount minus the conversion fee
                              return getFinalAmount(amount, 1);
                          }
                      
                          /**
                              @dev returns the expected return for selling one of the connector tokens for another connector token
                      
                              @param _fromConnectorToken  contract address of the connector token to convert from
                              @param _toConnectorToken    contract address of the connector token to convert to
                              @param _sellAmount          amount to sell (in the from connector token)
                      
                              @return expected sale return amount (in the to connector token)
                          */
                          function getCrossConnectorReturn(IERC20Token _fromConnectorToken, IERC20Token _toConnectorToken, uint256 _sellAmount)
                              public
                              view
                              active
                              validConnector(_fromConnectorToken)
                              validConnector(_toConnectorToken)
                              returns (uint256)
                          {
                              Connector storage fromConnector = connectors[_fromConnectorToken];
                              Connector storage toConnector = connectors[_toConnectorToken];
                              require(toConnector.isPurchaseEnabled); // validate input
                      
                              uint256 fromConnectorBalance = getConnectorBalance(_fromConnectorToken);
                              uint256 toConnectorBalance = getConnectorBalance(_toConnectorToken);
                      
                              IBancorFormula formula = IBancorFormula(registry.getAddress(ContractIds.BANCOR_FORMULA));
                              uint256 amount = formula.calculateCrossConnectorReturn(fromConnectorBalance, fromConnector.weight, toConnectorBalance, toConnector.weight, _sellAmount);
                      
                              // return the amount minus the conversion fee
                              // the fee is higher (magnitude = 2) since cross connector conversion equals 2 conversions (from / to the smart token)
                              return getFinalAmount(amount, 2);
                          }
                      
                          /**
                              @dev converts a specific amount of _fromToken to _toToken
                      
                              @param _fromToken  ERC20 token to convert from
                              @param _toToken    ERC20 token to convert to
                              @param _amount     amount to convert, in fromToken
                              @param _minReturn  if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                      
                              @return conversion return amount
                          */
                          function convertInternal(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn)
                              public
                              bancorNetworkOnly
                              conversionsAllowed
                              greaterThanZero(_minReturn)
                              returns (uint256)
                          {
                              require(_fromToken != _toToken); // validate input
                      
                              // conversion between the token and one of its connectors
                              if (_toToken == token)
                                  return buy(_fromToken, _amount, _minReturn);
                              else if (_fromToken == token)
                                  return sell(_toToken, _amount, _minReturn);
                      
                              // conversion between 2 connectors
                              uint256 amount = getCrossConnectorReturn(_fromToken, _toToken, _amount);
                              // ensure the trade gives something in return and meets the minimum requested amount
                              require(amount != 0 && amount >= _minReturn);
                      
                              // update the source token virtual balance if relevant
                              Connector storage fromConnector = connectors[_fromToken];
                              if (fromConnector.isVirtualBalanceEnabled)
                                  fromConnector.virtualBalance = safeAdd(fromConnector.virtualBalance, _amount);
                      
                              // update the target token virtual balance if relevant
                              Connector storage toConnector = connectors[_toToken];
                              if (toConnector.isVirtualBalanceEnabled)
                                  toConnector.virtualBalance = safeSub(toConnector.virtualBalance, amount);
                      
                              // ensure that the trade won't deplete the connector balance
                              uint256 toConnectorBalance = getConnectorBalance(_toToken);
                              assert(amount < toConnectorBalance);
                      
                              // transfer funds from the caller in the from connector token
                              assert(_fromToken.transferFrom(msg.sender, this, _amount));
                              // transfer funds to the caller in the to connector token
                              // the transfer might fail if the actual connector balance is smaller than the virtual balance
                              assert(_toToken.transfer(msg.sender, amount));
                      
                              // calculate conversion fee and dispatch the conversion event
                              // the fee is higher (magnitude = 2) since cross connector conversion equals 2 conversions (from / to the smart token)
                              uint256 feeAmount = safeSub(amount, getFinalAmount(amount, 2));
                              dispatchConversionEvent(_fromToken, _toToken, _amount, amount, feeAmount);
                      
                              // dispatch price data updates for the smart token / both connectors
                              emit PriceDataUpdate(_fromToken, token.totalSupply(), getConnectorBalance(_fromToken), fromConnector.weight);
                              emit PriceDataUpdate(_toToken, token.totalSupply(), getConnectorBalance(_toToken), toConnector.weight);
                              return amount;
                          }
                      
                          /**
                              @dev converts a specific amount of _fromToken to _toToken
                      
                              @param _fromToken  ERC20 token to convert from
                              @param _toToken    ERC20 token to convert to
                              @param _amount     amount to convert, in fromToken
                              @param _minReturn  if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                      
                              @return conversion return amount
                          */
                          function convert(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256) {
                              convertPath = [_fromToken, token, _toToken];
                              return quickConvert(convertPath, _amount, _minReturn);
                          }
                      
                          /**
                              @dev buys the token by depositing one of its connector tokens
                      
                              @param _connectorToken  connector token contract address
                              @param _depositAmount   amount to deposit (in the connector token)
                              @param _minReturn       if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                      
                              @return buy return amount
                          */
                          function buy(IERC20Token _connectorToken, uint256 _depositAmount, uint256 _minReturn) internal returns (uint256) {
                              uint256 amount = getPurchaseReturn(_connectorToken, _depositAmount);
                              // ensure the trade gives something in return and meets the minimum requested amount
                              require(amount != 0 && amount >= _minReturn);
                      
                              // update virtual balance if relevant
                              Connector storage connector = connectors[_connectorToken];
                              if (connector.isVirtualBalanceEnabled)
                                  connector.virtualBalance = safeAdd(connector.virtualBalance, _depositAmount);
                      
                              // transfer funds from the caller in the connector token
                              assert(_connectorToken.transferFrom(msg.sender, this, _depositAmount));
                              // issue new funds to the caller in the smart token
                              token.issue(msg.sender, amount);
                      
                              // calculate conversion fee and dispatch the conversion event
                              uint256 feeAmount = safeSub(amount, getFinalAmount(amount, 1));
                              dispatchConversionEvent(_connectorToken, token, _depositAmount, amount, feeAmount);
                      
                              // dispatch price data update for the smart token/connector
                              emit PriceDataUpdate(_connectorToken, token.totalSupply(), getConnectorBalance(_connectorToken), connector.weight);
                              return amount;
                          }
                      
                          /**
                              @dev sells the token by withdrawing from one of its connector tokens
                      
                              @param _connectorToken  connector token contract address
                              @param _sellAmount      amount to sell (in the smart token)
                              @param _minReturn       if the conversion results in an amount smaller the minimum return - it is cancelled, must be nonzero
                      
                              @return sell return amount
                          */
                          function sell(IERC20Token _connectorToken, uint256 _sellAmount, uint256 _minReturn) internal returns (uint256) {
                              require(_sellAmount <= token.balanceOf(msg.sender)); // validate input
                      
                              uint256 amount = getSaleReturn(_connectorToken, _sellAmount);
                              // ensure the trade gives something in return and meets the minimum requested amount
                              require(amount != 0 && amount >= _minReturn);
                      
                              // ensure that the trade will only deplete the connector balance if the total supply is depleted as well
                              uint256 tokenSupply = token.totalSupply();
                              uint256 connectorBalance = getConnectorBalance(_connectorToken);
                              assert(amount < connectorBalance || (amount == connectorBalance && _sellAmount == tokenSupply));
                      
                              // update virtual balance if relevant
                              Connector storage connector = connectors[_connectorToken];
                              if (connector.isVirtualBalanceEnabled)
                                  connector.virtualBalance = safeSub(connector.virtualBalance, amount);
                      
                              // destroy _sellAmount from the caller's balance in the smart token
                              token.destroy(msg.sender, _sellAmount);
                              // transfer funds to the caller in the connector token
                              // the transfer might fail if the actual connector balance is smaller than the virtual balance
                              assert(_connectorToken.transfer(msg.sender, amount));
                      
                              // calculate conversion fee and dispatch the conversion event
                              uint256 feeAmount = safeSub(amount, getFinalAmount(amount, 1));
                              dispatchConversionEvent(token, _connectorToken, _sellAmount, amount, feeAmount);
                      
                              // dispatch price data update for the smart token/connector
                              emit PriceDataUpdate(_connectorToken, token.totalSupply(), getConnectorBalance(_connectorToken), connector.weight);
                              return amount;
                          }
                      
                          /**
                              @dev converts the token to any other token in the bancor network by following a predefined conversion path
                              note that when converting from an ERC20 token (as opposed to a smart token), allowance must be set beforehand
                      
                              @param _path        conversion path, see conversion path format in the BancorNetwork contract
                              @param _amount      amount to convert from (in the initial source token)
                              @param _minReturn   if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                      
                              @return tokens issued in return
                          */
                          function quickConvert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn)
                              public
                              payable
                              validConversionPath(_path)
                              returns (uint256)
                          {
                              return quickConvertPrioritized(_path, _amount, _minReturn, 0x0, 0x0, 0x0, 0x0);
                          }
                      
                          /**
                              @dev converts the token to any other token in the bancor network by following a predefined conversion path
                              note that when converting from an ERC20 token (as opposed to a smart token), allowance must be set beforehand
                      
                              @param _path        conversion path, see conversion path format in the BancorNetwork contract
                              @param _amount      amount to convert from (in the initial source token)
                              @param _minReturn   if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                              @param _block       if the current block exceeded the given parameter - it is cancelled
                              @param _v           (signature[128:130]) associated with the signer address and helps validating if the signature is legit
                              @param _r           (signature[0:64]) associated with the signer address and helps validating if the signature is legit
                              @param _s           (signature[64:128]) associated with the signer address and helps validating if the signature is legit
                      
                              @return tokens issued in return
                          */
                          function quickConvertPrioritized(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, uint256 _block, uint8 _v, bytes32 _r, bytes32 _s)
                              public
                              payable
                              validConversionPath(_path)
                              returns (uint256)
                          {
                              IERC20Token fromToken = _path[0];
                              IBancorNetwork bancorNetwork = IBancorNetwork(registry.getAddress(ContractIds.BANCOR_NETWORK));
                      
                              // we need to transfer the source tokens from the caller to the BancorNetwork contract,
                              // so it can execute the conversion on behalf of the caller
                              if (msg.value == 0) {
                                  // not ETH, send the source tokens to the BancorNetwork contract
                                  // if the token is the smart token, no allowance is required - destroy the tokens
                                  // from the caller and issue them to the BancorNetwork contract
                                  if (fromToken == token) {
                                      token.destroy(msg.sender, _amount); // destroy _amount tokens from the caller's balance in the smart token
                                      token.issue(bancorNetwork, _amount); // issue _amount new tokens to the BancorNetwork contract
                                  } else {
                                      // otherwise, we assume we already have allowance, transfer the tokens directly to the BancorNetwork contract
                                      assert(fromToken.transferFrom(msg.sender, bancorNetwork, _amount));
                                  }
                              }
                      
                              // execute the conversion and pass on the ETH with the call
                              return bancorNetwork.convertForPrioritized2.value(msg.value)(_path, _amount, _minReturn, msg.sender, _block, _v, _r, _s);
                          }
                      
                          // deprecated, backward compatibility
                          function change(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256) {
                              return convertInternal(_fromToken, _toToken, _amount, _minReturn);
                          }
                      
                          /**
                              @dev helper, dispatches the Conversion event
                      
                              @param _fromToken       ERC20 token to convert from
                              @param _toToken         ERC20 token to convert to
                              @param _amount          amount purchased/sold (in the source token)
                              @param _returnAmount    amount returned (in the target token)
                          */
                          function dispatchConversionEvent(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _returnAmount, uint256 _feeAmount) private {
                              // fee amount is converted to 255 bits -
                              // negative amount means the fee is taken from the source token, positive amount means its taken from the target token
                              // currently the fee is always taken from the target token
                              // since we convert it to a signed number, we first ensure that it's capped at 255 bits to prevent overflow
                              assert(_feeAmount <= 2 ** 255);
                              emit Conversion(_fromToken, _toToken, msg.sender, _amount, _returnAmount, int256(_feeAmount));
                          }
                      
                          /**
                              @dev fallback, buys the smart token with ETH
                              note that the purchase will use the price at the time of the purchase
                          */
                          function() payable public {
                              quickConvert(quickBuyPath, msg.value, 1);
                          }
                      }

                      File 6 of 14: SmartToken
                      pragma solidity ^0.4.11;
                      
                      /*
                          Utilities & Common Modifiers
                      */
                      contract Utils {
                          /**
                              constructor
                          */
                          function Utils() {
                          }
                      
                          // verifies that an amount is greater than zero
                          modifier greaterThanZero(uint256 _amount) {
                              require(_amount > 0);
                              _;
                          }
                      
                          // validates an address - currently only checks that it isn't null
                          modifier validAddress(address _address) {
                              require(_address != 0x0);
                              _;
                          }
                      
                          // verifies that the address is different than this contract address
                          modifier notThis(address _address) {
                              require(_address != address(this));
                              _;
                          }
                      
                          // Overflow protected math functions
                      
                          /**
                              @dev returns the sum of _x and _y, asserts if the calculation overflows
                      
                              @param _x   value 1
                              @param _y   value 2
                      
                              @return sum
                          */
                          function safeAdd(uint256 _x, uint256 _y) internal constant returns (uint256) {
                              uint256 z = _x + _y;
                              assert(z >= _x);
                              return z;
                          }
                      
                          /**
                              @dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
                      
                              @param _x   minuend
                              @param _y   subtrahend
                      
                              @return difference
                          */
                          function safeSub(uint256 _x, uint256 _y) internal constant returns (uint256) {
                              assert(_x >= _y);
                              return _x - _y;
                          }
                      
                          /**
                              @dev returns the product of multiplying _x by _y, asserts if the calculation overflows
                      
                              @param _x   factor 1
                              @param _y   factor 2
                      
                              @return product
                          */
                          function safeMul(uint256 _x, uint256 _y) internal constant returns (uint256) {
                              uint256 z = _x * _y;
                              assert(_x == 0 || z / _x == _y);
                              return z;
                          }
                      }
                      
                      /*
                          Owned contract interface
                      */
                      contract IOwned {
                          // this function isn't abstract since the compiler emits automatically generated getter functions as external
                          function owner() public constant returns (address) {}
                      
                          function transferOwnership(address _newOwner) public;
                          function acceptOwnership() public;
                      }
                      
                      /*
                          Provides support and utilities for contract ownership
                      */
                      contract Owned is IOwned {
                          address public owner;
                          address public newOwner;
                      
                          event OwnerUpdate(address _prevOwner, address _newOwner);
                      
                          /**
                              @dev constructor
                          */
                          function Owned() {
                              owner = msg.sender;
                          }
                      
                          // allows execution by the owner only
                          modifier ownerOnly {
                              assert(msg.sender == owner);
                              _;
                          }
                      
                          /**
                              @dev allows transferring the contract ownership
                              the new owner still needs to accept the transfer
                              can only be called by the contract owner
                      
                              @param _newOwner    new contract owner
                          */
                          function transferOwnership(address _newOwner) public ownerOnly {
                              require(_newOwner != owner);
                              newOwner = _newOwner;
                          }
                      
                          /**
                              @dev used by a new owner to accept an ownership transfer
                          */
                          function acceptOwnership() public {
                              require(msg.sender == newOwner);
                              OwnerUpdate(owner, newOwner);
                              owner = newOwner;
                              newOwner = 0x0;
                          }
                      }
                      
                      /*
                          ERC20 Standard Token interface
                      */
                      contract IERC20Token {
                          // these functions aren't abstract since the compiler emits automatically generated getter functions as external
                          function name() public constant returns (string) {}
                          function symbol() public constant returns (string) {}
                          function decimals() public constant returns (uint8) {}
                          function totalSupply() public constant returns (uint256) {}
                          function balanceOf(address _owner) public constant returns (uint256) { _owner; }
                          function allowance(address _owner, address _spender) public constant returns (uint256) { _owner; _spender; }
                      
                          function transfer(address _to, uint256 _value) public returns (bool success);
                          function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
                          function approve(address _spender, uint256 _value) public returns (bool success);
                      }
                      
                      /**
                          ERC20 Standard Token implementation
                      */
                      contract ERC20Token is IERC20Token, Utils {
                          string public standard = 'Token 0.1';
                          string public name = '';
                          string public symbol = '';
                          uint8 public decimals = 0;
                          uint256 public totalSupply = 0;
                          mapping (address => uint256) public balanceOf;
                          mapping (address => mapping (address => uint256)) public allowance;
                      
                          event Transfer(address indexed _from, address indexed _to, uint256 _value);
                          event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                      
                          /**
                              @dev constructor
                      
                              @param _name        token name
                              @param _symbol      token symbol
                              @param _decimals    decimal points, for display purposes
                          */
                          function ERC20Token(string _name, string _symbol, uint8 _decimals) {
                              require(bytes(_name).length > 0 && bytes(_symbol).length > 0); // validate input
                      
                              name = _name;
                              symbol = _symbol;
                              decimals = _decimals;
                          }
                      
                          /**
                              @dev send coins
                              throws on any error rather then return a false flag to minimize user errors
                      
                              @param _to      target address
                              @param _value   transfer amount
                      
                              @return true if the transfer was successful, false if it wasn't
                          */
                          function transfer(address _to, uint256 _value)
                              public
                              validAddress(_to)
                              returns (bool success)
                          {
                              balanceOf[msg.sender] = safeSub(balanceOf[msg.sender], _value);
                              balanceOf[_to] = safeAdd(balanceOf[_to], _value);
                              Transfer(msg.sender, _to, _value);
                              return true;
                          }
                      
                          /**
                              @dev an account/contract attempts to get the coins
                              throws on any error rather then return a false flag to minimize user errors
                      
                              @param _from    source address
                              @param _to      target address
                              @param _value   transfer amount
                      
                              @return true if the transfer was successful, false if it wasn't
                          */
                          function transferFrom(address _from, address _to, uint256 _value)
                              public
                              validAddress(_from)
                              validAddress(_to)
                              returns (bool success)
                          {
                              allowance[_from][msg.sender] = safeSub(allowance[_from][msg.sender], _value);
                              balanceOf[_from] = safeSub(balanceOf[_from], _value);
                              balanceOf[_to] = safeAdd(balanceOf[_to], _value);
                              Transfer(_from, _to, _value);
                              return true;
                          }
                      
                          /**
                              @dev allow another account/contract to spend some tokens on your behalf
                              throws on any error rather then return a false flag to minimize user errors
                      
                              also, to minimize the risk of the approve/transferFrom attack vector
                              (see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), approve has to be called twice
                              in 2 separate transactions - once to change the allowance to 0 and secondly to change it to the new allowance value
                      
                              @param _spender approved address
                              @param _value   allowance amount
                      
                              @return true if the approval was successful, false if it wasn't
                          */
                          function approve(address _spender, uint256 _value)
                              public
                              validAddress(_spender)
                              returns (bool success)
                          {
                              // if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal
                              require(_value == 0 || allowance[msg.sender][_spender] == 0);
                      
                              allowance[msg.sender][_spender] = _value;
                              Approval(msg.sender, _spender, _value);
                              return true;
                          }
                      }
                      
                      /*
                          Token Holder interface
                      */
                      contract ITokenHolder is IOwned {
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                      }
                      
                      /*
                          We consider every contract to be a 'token holder' since it's currently not possible
                          for a contract to deny receiving tokens.
                      
                          The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
                          the owner to send tokens that were sent to the contract by mistake back to their sender.
                      */
                      contract TokenHolder is ITokenHolder, Owned, Utils {
                          /**
                              @dev constructor
                          */
                          function TokenHolder() {
                          }
                      
                          /**
                              @dev withdraws tokens held by the contract and sends them to an account
                              can only be called by the owner
                      
                              @param _token   ERC20 token contract address
                              @param _to      account to receive the new amount
                              @param _amount  amount to withdraw
                          */
                          function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
                              public
                              ownerOnly
                              validAddress(_token)
                              validAddress(_to)
                              notThis(_to)
                          {
                              assert(_token.transfer(_to, _amount));
                          }
                      }
                      
                      /*
                          Smart Token interface
                      */
                      contract ISmartToken is IOwned, IERC20Token {
                          function disableTransfers(bool _disable) public;
                          function issue(address _to, uint256 _amount) public;
                          function destroy(address _from, uint256 _amount) public;
                      }
                      
                      /*
                          Smart Token v0.3
                      
                          'Owned' is specified here for readability reasons
                      */
                      contract SmartToken is ISmartToken, Owned, ERC20Token, TokenHolder {
                          string public version = '0.3';
                      
                          bool public transfersEnabled = true;    // true if transfer/transferFrom are enabled, false if not
                      
                          // triggered when a smart token is deployed - the _token address is defined for forward compatibility, in case we want to trigger the event from a factory
                          event NewSmartToken(address _token);
                          // triggered when the total supply is increased
                          event Issuance(uint256 _amount);
                          // triggered when the total supply is decreased
                          event Destruction(uint256 _amount);
                      
                          /**
                              @dev constructor
                      
                              @param _name       token name
                              @param _symbol     token short symbol, minimum 1 character
                              @param _decimals   for display purposes only
                          */
                          function SmartToken(string _name, string _symbol, uint8 _decimals)
                              ERC20Token(_name, _symbol, _decimals)
                          {
                              NewSmartToken(address(this));
                          }
                      
                          // allows execution only when transfers aren't disabled
                          modifier transfersAllowed {
                              assert(transfersEnabled);
                              _;
                          }
                      
                          /**
                              @dev disables/enables transfers
                              can only be called by the contract owner
                      
                              @param _disable    true to disable transfers, false to enable them
                          */
                          function disableTransfers(bool _disable) public ownerOnly {
                              transfersEnabled = !_disable;
                          }
                      
                          /**
                              @dev increases the token supply and sends the new tokens to an account
                              can only be called by the contract owner
                      
                              @param _to         account to receive the new amount
                              @param _amount     amount to increase the supply by
                          */
                          function issue(address _to, uint256 _amount)
                              public
                              ownerOnly
                              validAddress(_to)
                              notThis(_to)
                          {
                              totalSupply = safeAdd(totalSupply, _amount);
                              balanceOf[_to] = safeAdd(balanceOf[_to], _amount);
                      
                              Issuance(_amount);
                              Transfer(this, _to, _amount);
                          }
                      
                          /**
                              @dev removes tokens from an account and decreases the token supply
                              can be called by the contract owner to destroy tokens from any account or by any holder to destroy tokens from his/her own account
                      
                              @param _from       account to remove the amount from
                              @param _amount     amount to decrease the supply by
                          */
                          function destroy(address _from, uint256 _amount) public {
                              require(msg.sender == _from || msg.sender == owner); // validate input
                      
                              balanceOf[_from] = safeSub(balanceOf[_from], _amount);
                              totalSupply = safeSub(totalSupply, _amount);
                      
                              Transfer(_from, this, _amount);
                              Destruction(_amount);
                          }
                      
                          // ERC20 standard method overrides with some extra functionality
                      
                          /**
                              @dev send coins
                              throws on any error rather then return a false flag to minimize user errors
                              in addition to the standard checks, the function throws if transfers are disabled
                      
                              @param _to      target address
                              @param _value   transfer amount
                      
                              @return true if the transfer was successful, false if it wasn't
                          */
                          function transfer(address _to, uint256 _value) public transfersAllowed returns (bool success) {
                              assert(super.transfer(_to, _value));
                              return true;
                          }
                      
                          /**
                              @dev an account/contract attempts to get the coins
                              throws on any error rather then return a false flag to minimize user errors
                              in addition to the standard checks, the function throws if transfers are disabled
                      
                              @param _from    source address
                              @param _to      target address
                              @param _value   transfer amount
                      
                              @return true if the transfer was successful, false if it wasn't
                          */
                          function transferFrom(address _from, address _to, uint256 _value) public transfersAllowed returns (bool success) {
                              assert(super.transferFrom(_from, _to, _value));
                              return true;
                          }
                      }

                      File 7 of 14: BNB
                      pragma solidity ^0.4.8;
                      
                      /**
                       * Math operations with safety checks
                       */
                      contract SafeMath {
                        function safeMul(uint256 a, uint256 b) internal returns (uint256) {
                          uint256 c = a * b;
                          assert(a == 0 || c / a == b);
                          return c;
                        }
                      
                        function safeDiv(uint256 a, uint256 b) internal returns (uint256) {
                          assert(b > 0);
                          uint256 c = a / b;
                          assert(a == b * c + a % b);
                          return c;
                        }
                      
                        function safeSub(uint256 a, uint256 b) internal returns (uint256) {
                          assert(b <= a);
                          return a - b;
                        }
                      
                        function safeAdd(uint256 a, uint256 b) internal returns (uint256) {
                          uint256 c = a + b;
                          assert(c>=a && c>=b);
                          return c;
                        }
                      
                        function assert(bool assertion) internal {
                          if (!assertion) {
                            throw;
                          }
                        }
                      }
                      contract BNB is SafeMath{
                          string public name;
                          string public symbol;
                          uint8 public decimals;
                          uint256 public totalSupply;
                      	address public owner;
                      
                          /* This creates an array with all balances */
                          mapping (address => uint256) public balanceOf;
                      	mapping (address => uint256) public freezeOf;
                          mapping (address => mapping (address => uint256)) public allowance;
                      
                          /* This generates a public event on the blockchain that will notify clients */
                          event Transfer(address indexed from, address indexed to, uint256 value);
                      
                          /* This notifies clients about the amount burnt */
                          event Burn(address indexed from, uint256 value);
                      	
                      	/* This notifies clients about the amount frozen */
                          event Freeze(address indexed from, uint256 value);
                      	
                      	/* This notifies clients about the amount unfrozen */
                          event Unfreeze(address indexed from, uint256 value);
                      
                          /* Initializes contract with initial supply tokens to the creator of the contract */
                          function BNB(
                              uint256 initialSupply,
                              string tokenName,
                              uint8 decimalUnits,
                              string tokenSymbol
                              ) {
                              balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens
                              totalSupply = initialSupply;                        // Update total supply
                              name = tokenName;                                   // Set the name for display purposes
                              symbol = tokenSymbol;                               // Set the symbol for display purposes
                              decimals = decimalUnits;                            // Amount of decimals for display purposes
                      		owner = msg.sender;
                          }
                      
                          /* Send coins */
                          function transfer(address _to, uint256 _value) {
                              if (_to == 0x0) throw;                               // Prevent transfer to 0x0 address. Use burn() instead
                      		if (_value <= 0) throw; 
                              if (balanceOf[msg.sender] < _value) throw;           // Check if the sender has enough
                              if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
                              balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                     // Subtract from the sender
                              balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);                            // Add the same to the recipient
                              Transfer(msg.sender, _to, _value);                   // Notify anyone listening that this transfer took place
                          }
                      
                          /* Allow another contract to spend some tokens in your behalf */
                          function approve(address _spender, uint256 _value)
                              returns (bool success) {
                      		if (_value <= 0) throw; 
                              allowance[msg.sender][_spender] = _value;
                              return true;
                          }
                             
                      
                          /* A contract attempts to get the coins */
                          function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
                              if (_to == 0x0) throw;                                // Prevent transfer to 0x0 address. Use burn() instead
                      		if (_value <= 0) throw; 
                              if (balanceOf[_from] < _value) throw;                 // Check if the sender has enough
                              if (balanceOf[_to] + _value < balanceOf[_to]) throw;  // Check for overflows
                              if (_value > allowance[_from][msg.sender]) throw;     // Check allowance
                              balanceOf[_from] = SafeMath.safeSub(balanceOf[_from], _value);                           // Subtract from the sender
                              balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);                             // Add the same to the recipient
                              allowance[_from][msg.sender] = SafeMath.safeSub(allowance[_from][msg.sender], _value);
                              Transfer(_from, _to, _value);
                              return true;
                          }
                      
                          function burn(uint256 _value) returns (bool success) {
                              if (balanceOf[msg.sender] < _value) throw;            // Check if the sender has enough
                      		if (_value <= 0) throw; 
                              balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
                              totalSupply = SafeMath.safeSub(totalSupply,_value);                                // Updates totalSupply
                              Burn(msg.sender, _value);
                              return true;
                          }
                      	
                      	function freeze(uint256 _value) returns (bool success) {
                              if (balanceOf[msg.sender] < _value) throw;            // Check if the sender has enough
                      		if (_value <= 0) throw; 
                              balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);                      // Subtract from the sender
                              freezeOf[msg.sender] = SafeMath.safeAdd(freezeOf[msg.sender], _value);                                // Updates totalSupply
                              Freeze(msg.sender, _value);
                              return true;
                          }
                      	
                      	function unfreeze(uint256 _value) returns (bool success) {
                              if (freezeOf[msg.sender] < _value) throw;            // Check if the sender has enough
                      		if (_value <= 0) throw; 
                              freezeOf[msg.sender] = SafeMath.safeSub(freezeOf[msg.sender], _value);                      // Subtract from the sender
                      		balanceOf[msg.sender] = SafeMath.safeAdd(balanceOf[msg.sender], _value);
                              Unfreeze(msg.sender, _value);
                              return true;
                          }
                      	
                      	// transfer balance to owner
                      	function withdrawEther(uint256 amount) {
                      		if(msg.sender != owner)throw;
                      		owner.transfer(amount);
                      	}
                      	
                      	// can accept ether
                      	function() payable {
                          }
                      }

                      File 8 of 14: ContractRegistry
                      pragma solidity ^0.4.21;
                      
                      /*
                          Owned contract interface
                      */
                      contract IOwned {
                          // this function isn't abstract since the compiler emits automatically generated getter functions as external
                          function owner() public view returns (address) {}
                      
                          function transferOwnership(address _newOwner) public;
                          function acceptOwnership() public;
                      }
                      
                      /*
                          Provides support and utilities for contract ownership
                      */
                      contract Owned is IOwned {
                          address public owner;
                          address public newOwner;
                      
                          event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
                      
                          /**
                              @dev constructor
                          */
                          function Owned() public {
                              owner = msg.sender;
                          }
                      
                          // allows execution by the owner only
                          modifier ownerOnly {
                              assert(msg.sender == owner);
                              _;
                          }
                      
                          /**
                              @dev allows transferring the contract ownership
                              the new owner still needs to accept the transfer
                              can only be called by the contract owner
                      
                              @param _newOwner    new contract owner
                          */
                          function transferOwnership(address _newOwner) public ownerOnly {
                              require(_newOwner != owner);
                              newOwner = _newOwner;
                          }
                      
                          /**
                              @dev used by a new owner to accept an ownership transfer
                          */
                          function acceptOwnership() public {
                              require(msg.sender == newOwner);
                              emit OwnerUpdate(owner, newOwner);
                              owner = newOwner;
                              newOwner = address(0);
                          }
                      }
                      
                      /*
                          Contract Registry interface
                      */
                      contract IContractRegistry {
                          function getAddress(bytes32 _contractName) public view returns (address);
                      }
                      
                      /**
                          Contract Registry
                      
                          The contract registry keeps contract addresses by name.
                          The owner can update contract addresses so that a contract name always points to the latest version
                          of the given contract.
                          Other contracts can query the registry to get updated addresses instead of depending on specific
                          addresses.
                      
                          Note that contract names are limited to 32 bytes, UTF8 strings to optimize gas costs
                      */
                      contract ContractRegistry is IContractRegistry, Owned {
                          mapping (bytes32 => address) addresses;
                      
                          event AddressUpdate(bytes32 indexed _contractName, address _contractAddress);
                      
                          /**
                              @dev constructor
                          */
                          function ContractRegistry() public {
                          }
                      
                          /**
                              @dev returns the address associated with the given contract name
                      
                              @param _contractName    contract name
                      
                              @return contract address
                          */
                          function getAddress(bytes32 _contractName) public view returns (address) {
                              return addresses[_contractName];
                          }
                      
                          /**
                              @dev registers a new address for the contract name
                      
                             @param _contractName     contract name
                             @param _contractAddress  contract address
                          */
                          function registerAddress(bytes32 _contractName, address _contractAddress) public ownerOnly {
                              require(_contractName.length > 0); // validating input
                      
                              addresses[_contractName] = _contractAddress;
                              emit AddressUpdate(_contractName, _contractAddress);
                          }
                      }

                      File 9 of 14: 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 10 of 14: 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/ExpectedRateInterface.sol
                      
                      interface ExpectedRateInterface {
                          function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
                              returns (uint expectedRate, uint slippageRate);
                      }
                      
                      // File: contracts/FeeBurnerInterface.sol
                      
                      interface FeeBurnerInterface {
                          function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
                      }
                      
                      // 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/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/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/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/KyberNetwork.sol
                      
                      ////////////////////////////////////////////////////////////////////////////////////////////////////////
                      /// @title Kyber Network main contract
                      contract KyberNetwork is Withdrawable, Utils2, KyberNetworkInterface {
                      
                          uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01%
                          KyberReserveInterface[] public reserves;
                          mapping(address=>bool) public isReserve;
                          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
                      
                          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(isReserve[msg.sender]);
                              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
                              payable
                              returns(uint)
                          {
                              require(hint.length == 0);
                              require(msg.sender == kyberNetworkProxyContract);
                      
                              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 reserve, bool add);
                      
                          /// @notice can be called only by admin
                          /// @dev add or deletes a reserve to/from the network.
                          /// @param reserve The reserve address.
                          /// @param add If true, the add reserve. Otherwise delete reserve.
                          function addReserve(KyberReserveInterface reserve, bool add) public onlyAdmin {
                      
                              if (add) {
                                  require(!isReserve[reserve]);
                                  reserves.push(reserve);
                                  isReserve[reserve] = true;
                                  AddReserveToNetwork(reserve, true);
                              } else {
                                  isReserve[reserve] = false;
                                  // will have trouble if more than 50k reserves...
                                  for (uint i = 0; i < reserves.length; i++) {
                                      if (reserves[i] == reserve) {
                                          reserves[i] = reserves[reserves.length - 1];
                                          reserves.length--;
                                          AddReserveToNetwork(reserve, false);
                                          break;
                                      }
                                  }
                              }
                          }
                      
                          event ListReservePairs(address reserve, ERC20 src, ERC20 dest, bool add);
                      
                          /// @notice can be called only by admin
                          /// @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 onlyAdmin
                          {
                              require(isReserve[reserve]);
                      
                              if (ethToToken) {
                                  listPairs(reserve, token, false, add);
                      
                                  ListReservePairs(reserve, ETH_TOKEN_ADDRESS, token, add);
                              }
                      
                              if (tokenToEth) {
                                  listPairs(reserve, token, true, add);
                                  if (add) {
                                      token.approve(reserve, 2**255); // approve infinity
                                  } else {
                                      token.approve(reserve, 0);
                                  }
                      
                                  ListReservePairs(reserve, token, ETH_TOKEN_ADDRESS, add);
                              }
                      
                              setDecimals(token);
                          }
                      
                          function setWhiteList(WhiteListInterface whiteList) public onlyAdmin {
                              require(whiteList != address(0));
                              whiteListContract = whiteList;
                          }
                      
                          function setExpectedRate(ExpectedRateInterface expectedRate) public onlyAdmin {
                              require(expectedRate != address(0));
                              expectedRateContract = expectedRate;
                          }
                      
                          function setFeeBurner(FeeBurnerInterface feeBurner) public onlyAdmin {
                              require(feeBurner != address(0));
                              feeBurnerContract = feeBurner;
                          }
                      
                          function setParams(
                              uint                  _maxGasPrice,
                              uint                  _negligibleRateDiff
                          )
                              public
                              onlyAdmin
                          {
                              require(_negligibleRateDiff <= 100 * 100); // at most 100%
                      
                              maxGasPriceValue = _maxGasPrice;
                              negligibleRateDiff = _negligibleRateDiff;
                          }
                      
                          function setEnable(bool _enable) public onlyAdmin {
                              if (_enable) {
                                  require(whiteListContract != address(0));
                                  require(feeBurnerContract != address(0));
                                  require(expectedRateContract != address(0));
                                  require(kyberNetworkProxyContract != address(0));
                              }
                              isEnabled = _enable;
                          }
                      
                          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 with as much gas as needed
                          /// @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));
                              return expectedRateContract.getExpectedRate(src, dest, srcQty);
                          }
                      
                          function getUserCapInWei(address user) public view returns(uint) {
                              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);
                              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 */
                          // Not sure how solhing defines complexity. Anyway, from our point of view, below code follows the required
                          //  algorithm to choose 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) 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;
                      
                              if (src == ETH_TOKEN_ADDRESS) {
                                  reserveArr = reservesPerTokenDest[dest];
                              } else {
                                  reserveArr = 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.
                                  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 random = 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
                                      random = uint(block.blockhash(block.number-1)) % numRelevantReserves;
                                  }
                      
                                  bestReserve = reserveCandidates[random];
                                  bestRate = rates[bestReserve];
                              }
                      
                              return (reserveArr[bestReserve], bestRate);
                          }
                          /* solhint-enable code-complexity */
                      
                          function findBestRateTokenToToken(ERC20 src, ERC20 dest, uint srcAmount) internal view
                              returns(BestRateResult result)
                          {
                              (result.reserve1, result.rateSrcToEth) = searchBestRate(src, ETH_TOKEN_ADDRESS, srcAmount);
                              result.weiAmount = calcDestAmount(src, ETH_TOKEN_ADDRESS, srcAmount, result.rateSrcToEth);
                      
                              (result.reserve2, result.rateEthToDest) = searchBestRate(ETH_TOKEN_ADDRESS, dest, result.weiAmount);
                              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--;
                                      }
                                  }
                              }
                      
                              if (add && i == reserveArr.length) {
                                  //if reserve wasn't found add it
                                  reserveArr.push(reserve);
                              }
                          }
                      
                          event KyberTrade(address srcAddress, ERC20 srcToken, uint srcAmount, address destAddress, ERC20 destToken,
                              uint destAmount);
                          /* solhint-disable function-max-lines */
                          // Most of the lins here are functions calls spread over multiple lines. We find this function readable enough
                          //  and keep its size as is.
                          /// @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);
                      
                              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);
                      
                              if (actualSrcAmount < tradeInput.srcAmount) {
                                  //if there is "change" send back to trader
                                  if (tradeInput.src == ETH_TOKEN_ADDRESS) {
                                      tradeInput.trader.transfer(tradeInput.srcAmount - actualSrcAmount);
                                  } else {
                                      tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount));
                                  }
                              }
                      
                              // verify trade size is smaller than user cap
                              require(weiAmount <= getUserCapInWei(tradeInput.trader));
                      
                              //do the trade
                              //src to ETH
                              require(doReserveTrade(
                                      tradeInput.src,
                                      actualSrcAmount,
                                      ETH_TOKEN_ADDRESS,
                                      this,
                                      weiAmount,
                                      KyberReserveInterface(rateResult.reserve1),
                                      rateResult.rateSrcToEth,
                                      true));
                      
                              //Eth to dest
                              require(doReserveTrade(
                                      ETH_TOKEN_ADDRESS,
                                      weiAmount,
                                      tradeInput.dest,
                                      tradeInput.destAddress,
                                      actualDestAmount,
                                      KyberReserveInterface(rateResult.reserve2),
                                      rateResult.rateEthToDest,
                                      true));
                      
                              //when src is ether, reserve1 is doing a "fake" trade. (ether to ether) - don't burn.
                              //when dest is ether, reserve2 is doing a "fake" trade. (ether to ether) - don't burn.
                              if (tradeInput.src != ETH_TOKEN_ADDRESS)
                                  require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve1, tradeInput.walletId));
                              if (tradeInput.dest != ETH_TOKEN_ADDRESS)
                                  require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve2, tradeInput.walletId));
                      
                              KyberTrade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest,
                                  actualDestAmount);
                      
                              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;
                          }
                      
                          /// @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 11 of 14: ExpectedRate
                      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/ExpectedRateInterface.sol
                      
                      interface ExpectedRateInterface {
                          function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
                              returns (uint expectedRate, uint slippageRate);
                      }
                      
                      // File: contracts/FeeBurnerInterface.sol
                      
                      interface FeeBurnerInterface {
                          function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
                      }
                      
                      // 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/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/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/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/KyberNetwork.sol
                      
                      ////////////////////////////////////////////////////////////////////////////////////////////////////////
                      /// @title Kyber Network main contract
                      contract KyberNetwork is Withdrawable, Utils2, KyberNetworkInterface {
                      
                          uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01%
                          KyberReserveInterface[] public reserves;
                          mapping(address=>bool) public isReserve;
                          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
                      
                          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(isReserve[msg.sender]);
                              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
                              payable
                              returns(uint)
                          {
                              require(hint.length == 0);
                              require(msg.sender == kyberNetworkProxyContract);
                      
                              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 reserve, bool add);
                      
                          /// @notice can be called only by admin
                          /// @dev add or deletes a reserve to/from the network.
                          /// @param reserve The reserve address.
                          /// @param add If true, the add reserve. Otherwise delete reserve.
                          function addReserve(KyberReserveInterface reserve, bool add) public onlyAdmin {
                      
                              if (add) {
                                  require(!isReserve[reserve]);
                                  reserves.push(reserve);
                                  isReserve[reserve] = true;
                                  AddReserveToNetwork(reserve, true);
                              } else {
                                  isReserve[reserve] = false;
                                  // will have trouble if more than 50k reserves...
                                  for (uint i = 0; i < reserves.length; i++) {
                                      if (reserves[i] == reserve) {
                                          reserves[i] = reserves[reserves.length - 1];
                                          reserves.length--;
                                          AddReserveToNetwork(reserve, false);
                                          break;
                                      }
                                  }
                              }
                          }
                      
                          event ListReservePairs(address reserve, ERC20 src, ERC20 dest, bool add);
                      
                          /// @notice can be called only by admin
                          /// @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 onlyAdmin
                          {
                              require(isReserve[reserve]);
                      
                              if (ethToToken) {
                                  listPairs(reserve, token, false, add);
                      
                                  ListReservePairs(reserve, ETH_TOKEN_ADDRESS, token, add);
                              }
                      
                              if (tokenToEth) {
                                  listPairs(reserve, token, true, add);
                                  if (add) {
                                      token.approve(reserve, 2**255); // approve infinity
                                  } else {
                                      token.approve(reserve, 0);
                                  }
                      
                                  ListReservePairs(reserve, token, ETH_TOKEN_ADDRESS, add);
                              }
                      
                              setDecimals(token);
                          }
                      
                          function setWhiteList(WhiteListInterface whiteList) public onlyAdmin {
                              require(whiteList != address(0));
                              whiteListContract = whiteList;
                          }
                      
                          function setExpectedRate(ExpectedRateInterface expectedRate) public onlyAdmin {
                              require(expectedRate != address(0));
                              expectedRateContract = expectedRate;
                          }
                      
                          function setFeeBurner(FeeBurnerInterface feeBurner) public onlyAdmin {
                              require(feeBurner != address(0));
                              feeBurnerContract = feeBurner;
                          }
                      
                          function setParams(
                              uint                  _maxGasPrice,
                              uint                  _negligibleRateDiff
                          )
                              public
                              onlyAdmin
                          {
                              require(_negligibleRateDiff <= 100 * 100); // at most 100%
                      
                              maxGasPriceValue = _maxGasPrice;
                              negligibleRateDiff = _negligibleRateDiff;
                          }
                      
                          function setEnable(bool _enable) public onlyAdmin {
                              if (_enable) {
                                  require(whiteListContract != address(0));
                                  require(feeBurnerContract != address(0));
                                  require(expectedRateContract != address(0));
                                  require(kyberNetworkProxyContract != address(0));
                              }
                              isEnabled = _enable;
                          }
                      
                          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 with as much gas as needed
                          /// @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));
                              return expectedRateContract.getExpectedRate(src, dest, srcQty);
                          }
                      
                          function getUserCapInWei(address user) public view returns(uint) {
                              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);
                              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 */
                          // Not sure how solhing defines complexity. Anyway, from our point of view, below code follows the required
                          //  algorithm to choose 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) 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;
                      
                              if (src == ETH_TOKEN_ADDRESS) {
                                  reserveArr = reservesPerTokenDest[dest];
                              } else {
                                  reserveArr = 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.
                                  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 random = 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
                                      random = uint(block.blockhash(block.number-1)) % numRelevantReserves;
                                  }
                      
                                  bestReserve = reserveCandidates[random];
                                  bestRate = rates[bestReserve];
                              }
                      
                              return (reserveArr[bestReserve], bestRate);
                          }
                          /* solhint-enable code-complexity */
                      
                          function findBestRateTokenToToken(ERC20 src, ERC20 dest, uint srcAmount) internal view
                              returns(BestRateResult result)
                          {
                              (result.reserve1, result.rateSrcToEth) = searchBestRate(src, ETH_TOKEN_ADDRESS, srcAmount);
                              result.weiAmount = calcDestAmount(src, ETH_TOKEN_ADDRESS, srcAmount, result.rateSrcToEth);
                      
                              (result.reserve2, result.rateEthToDest) = searchBestRate(ETH_TOKEN_ADDRESS, dest, result.weiAmount);
                              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--;
                                      }
                                  }
                              }
                      
                              if (add && i == reserveArr.length) {
                                  //if reserve wasn't found add it
                                  reserveArr.push(reserve);
                              }
                          }
                      
                          event KyberTrade(address srcAddress, ERC20 srcToken, uint srcAmount, address destAddress, ERC20 destToken,
                              uint destAmount);
                          /* solhint-disable function-max-lines */
                          // Most of the lins here are functions calls spread over multiple lines. We find this function readable enough
                          //  and keep its size as is.
                          /// @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);
                      
                              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);
                      
                              if (actualSrcAmount < tradeInput.srcAmount) {
                                  //if there is "change" send back to trader
                                  if (tradeInput.src == ETH_TOKEN_ADDRESS) {
                                      tradeInput.trader.transfer(tradeInput.srcAmount - actualSrcAmount);
                                  } else {
                                      tradeInput.src.transfer(tradeInput.trader, (tradeInput.srcAmount - actualSrcAmount));
                                  }
                              }
                      
                              // verify trade size is smaller than user cap
                              require(weiAmount <= getUserCapInWei(tradeInput.trader));
                      
                              //do the trade
                              //src to ETH
                              require(doReserveTrade(
                                      tradeInput.src,
                                      actualSrcAmount,
                                      ETH_TOKEN_ADDRESS,
                                      this,
                                      weiAmount,
                                      KyberReserveInterface(rateResult.reserve1),
                                      rateResult.rateSrcToEth,
                                      true));
                      
                              //Eth to dest
                              require(doReserveTrade(
                                      ETH_TOKEN_ADDRESS,
                                      weiAmount,
                                      tradeInput.dest,
                                      tradeInput.destAddress,
                                      actualDestAmount,
                                      KyberReserveInterface(rateResult.reserve2),
                                      rateResult.rateEthToDest,
                                      true));
                      
                              //when src is ether, reserve1 is doing a "fake" trade. (ether to ether) - don't burn.
                              //when dest is ether, reserve2 is doing a "fake" trade. (ether to ether) - don't burn.
                              if (tradeInput.src != ETH_TOKEN_ADDRESS)
                                  require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve1, tradeInput.walletId));
                              if (tradeInput.dest != ETH_TOKEN_ADDRESS)
                                  require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve2, tradeInput.walletId));
                      
                              KyberTrade(tradeInput.trader, tradeInput.src, actualSrcAmount, tradeInput.destAddress, tradeInput.dest,
                                  actualDestAmount);
                      
                              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;
                          }
                      
                          /// @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: contracts/ExpectedRate.sol
                      
                      contract ExpectedRate is Withdrawable, ExpectedRateInterface, Utils2 {
                      
                          KyberNetwork public kyberNetwork;
                          uint public quantityFactor = 2;
                          uint public worstCaseRateFactorInBps = 50;
                      
                          function ExpectedRate(KyberNetwork _kyberNetwork, address _admin) public {
                              require(_admin != address(0));
                              require(_kyberNetwork != address(0));
                              kyberNetwork = _kyberNetwork;
                              admin = _admin;
                          }
                      
                          event QuantityFactorSet (uint newFactor, uint oldFactor, address sender);
                      
                          function setQuantityFactor(uint newFactor) public onlyOperator {
                              require(newFactor <= 100);
                      
                              QuantityFactorSet(newFactor, quantityFactor, msg.sender);
                              quantityFactor = newFactor;
                          }
                      
                          event MinSlippageFactorSet (uint newMin, uint oldMin, address sender);
                      
                          function setWorstCaseRateFactor(uint bps) public onlyOperator {
                              require(bps <= 100 * 100);
                      
                              MinSlippageFactorSet(bps, worstCaseRateFactorInBps, msg.sender);
                              worstCaseRateFactorInBps = bps;
                          }
                      
                          //@dev when srcQty too small or 0 the expected rate will be calculated without quantity,
                          // will enable rate reference before committing to any quantity
                          //@dev when srcQty too small (no actual dest qty) slippage rate will be 0.
                          function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty)
                              public view
                              returns (uint expectedRate, uint slippageRate)
                          {
                              require(quantityFactor != 0);
                              require(srcQty <= MAX_QTY);
                              require(srcQty * quantityFactor <= MAX_QTY);
                      
                              if (srcQty == 0) srcQty = 1;
                      
                              uint bestReserve;
                              uint worstCaseSlippageRate;
                      
                              (bestReserve, expectedRate) = kyberNetwork.findBestRate(src, dest, srcQty);
                              (bestReserve, slippageRate) = kyberNetwork.findBestRate(src, dest, (srcQty * quantityFactor));
                      
                              if (expectedRate == 0) {
                                  expectedRate = expectedRateSmallQty(src, dest, srcQty);
                              }
                      
                              require(expectedRate <= MAX_RATE);
                      
                              worstCaseSlippageRate = ((10000 - worstCaseRateFactorInBps) * expectedRate) / 10000;
                              if (slippageRate >= worstCaseSlippageRate) {
                                  slippageRate = worstCaseSlippageRate;
                              }
                      
                              return (expectedRate, slippageRate);
                          }
                      
                          //@dev for small src quantities dest qty might be 0, then returned rate is zero.
                          //@dev for backward compatibility we would like to return non zero rate (correct one) for small src qty
                          function expectedRateSmallQty(ERC20 src, ERC20 dest, uint srcQty) internal view returns(uint) {
                              address reserve;
                              uint rateSrcToEth;
                              uint rateEthToDest;
                              (reserve, rateSrcToEth) = kyberNetwork.searchBestRate(src, ETH_TOKEN_ADDRESS, srcQty);
                      
                              uint ethQty = calcDestAmount(src, ETH_TOKEN_ADDRESS, srcQty, rateSrcToEth);
                      
                              (reserve, rateEthToDest) = kyberNetwork.searchBestRate(ETH_TOKEN_ADDRESS, dest, ethQty);
                              return rateSrcToEth * rateEthToDest / PRECISION;
                          }
                      }

                      File 12 of 14: 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 13 of 14: 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 14 of 14: SanityRates
                      pragma solidity 0.4.18;
                      
                      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 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);
                          }
                      }
                      
                      interface SanityRatesInterface {
                          function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);
                      }
                      
                      contract SanityRates is SanityRatesInterface, Withdrawable, Utils {
                          mapping(address=>uint) public tokenRate;
                          mapping(address=>uint) public reasonableDiffInBps;
                      
                          function SanityRates(address _admin) public {
                              require(_admin != address(0));
                              admin = _admin;
                          }
                      
                          function setReasonableDiff(ERC20[] srcs, uint[] diff) public onlyAdmin {
                              require(srcs.length == diff.length);
                              for (uint i = 0; i < srcs.length; i++) {
                                  require(diff[i] <= 100 * 100);
                                  reasonableDiffInBps[srcs[i]] = diff[i];
                              }
                          }
                      
                          function setSanityRates(ERC20[] srcs, uint[] rates) public onlyOperator {
                              require(srcs.length == rates.length);
                      
                              for (uint i = 0; i < srcs.length; i++) {
                                  require(rates[i] <= MAX_RATE);
                                  tokenRate[srcs[i]] = rates[i];
                              }
                          }
                      
                          function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint) {
                              if (src != ETH_TOKEN_ADDRESS && dest != ETH_TOKEN_ADDRESS) return 0;
                      
                              uint rate;
                              address token;
                              if (src == ETH_TOKEN_ADDRESS) {
                                  rate = (PRECISION*PRECISION)/tokenRate[dest];
                                  token = dest;
                              } else {
                                  rate = tokenRate[src];
                                  token = src;
                              }
                      
                              return rate * (10000 + reasonableDiffInBps[token])/10000;
                          }
                      }