ETH Price: $2,508.30 (+1.28%)
Gas: 0.99 Gwei

Transaction Decoder

Block:
7512938 at Apr-06-2019 06:23:08 AM +UTC
Transaction Fee:
0.000192993 ETH $0.48
Gas Used:
192,993 Gas / 1 Gwei

Emitted Events:

64 PayToken.Transfer( from=[Sender] 0xa85e728d5d08b2af397fb30d19b83d2bcc950fae, to=KyberNetwork, value=35000000000000000000 )
65 PayToken.Transfer( from=KyberNetwork, to=KyberReserve, value=35000000000000000000 )
66 KyberNetwork.EtherReceival( sender=KyberReserve, amount=70722143507513645 )
67 KyberReserve.TradeExecute( origin=KyberNetwork, src=PayToken, srcAmount=35000000000000000000, destToken=0xEeeeeEee...eeeeeEEeE, destAmount=70722143507513645, destAddress=KyberNetwork )
68 FeeBurner.AssignFeeToWallet( reserve=KyberReserve, wallet=0x440bBd6a...16874faa9, walletFee=31255914812663352 )
69 FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=72930467896214490 )
70 KyberNetwork.KyberTrade( trader=[Sender] 0xa85e728d5d08b2af397fb30d19b83d2bcc950fae, src=PayToken, dest=0xEeeeeEee...eeeeeEEeE, srcAmount=35000000000000000000, dstAmount=70722143507513645, destAddress=[Sender] 0xa85e728d5d08b2af397fb30d19b83d2bcc950fae, ethWeiValue=70722143507513645, reserve1=KyberReserve, reserve2=0x00000000...000000000, hint=0x5045524D )
71 KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0xa85e728d5d08b2af397fb30d19b83d2bcc950fae, src=PayToken, dest=0xEeeeeEee...eeeeeEEeE, actualSrcAmount=35000000000000000000, actualDestAmount=70722143507513645 )

Account State Difference:

  Address   Before After State Difference Code
0x52166528...1a4e128A3
0x63825c17...bD36A0D8F
(Kyber: Reserve)
1,347.516756420030462579 Eth1,347.446034276522948934 Eth0.070722143507513645
0x798AbDA6...d3d11191B
(Kyber: Conversion Rates)
0x9ae49C0d...294E20950
(Kyber: Old Contract)
0xA85e728d...bcC950Fae
26.50573151481691918 Eth
Nonce: 14
26.576260665324432825 Eth
Nonce: 15
0.070529150507513645
0xB9704862...1A905B280
(Ethermine)
614.143463693566629993 Eth614.143656686566629993 Eth0.000192993

Execution Trace

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

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

            File 3 of 8: PayToken
            pragma solidity 0.4.11;
            
            
            /**
             * @title Ownable
             * @dev The Ownable contract has an owner address, and provides basic authorization control 
             * functions, this simplifies the implementation of "user permissions". 
             */
            contract Ownable {
              address public owner;
            
            
              /** 
               * @dev The Ownable constructor sets the original `owner` of the contract to the sender
               * account.
               */
              function Ownable() {
                owner = msg.sender;
              }
            
            
              /**
               * @dev Throws if called by any account other than the owner. 
               */
              modifier onlyOwner() {
                if (msg.sender != owner) {
                  throw;
                }
                _;
              }
            
            
              /**
               * @dev Allows the current owner to transfer control of the contract to a newOwner.
               * @param newOwner The address to transfer ownership to. 
               */
              function transferOwnership(address newOwner) onlyOwner {
                if (newOwner != address(0)) {
                  owner = newOwner;
                }
              }
            
            }
            
            
            
            /**
             * @title Authorizable
             * @dev Allows to authorize access to certain function calls
             * 
             * ABI
             * [{"constant":true,"inputs":[{"name":"authorizerIndex","type":"uint256"}],"name":"getAuthorizer","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"addAuthorized","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"isAuthorized","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"}]
             */
            contract Authorizable {
            
              address[] authorizers;
              mapping(address => uint) authorizerIndex;
            
              /**
               * @dev Throws if called by any account tat is not authorized. 
               */
              modifier onlyAuthorized {
                require(isAuthorized(msg.sender));
                _;
              }
            
              /**
               * @dev Contructor that authorizes the msg.sender. 
               */
              function Authorizable() {
                authorizers.length = 2;
                authorizers[1] = msg.sender;
                authorizerIndex[msg.sender] = 1;
              }
            
              /**
               * @dev Function to get a specific authorizer
               * @param authorizerIndex index of the authorizer to be retrieved.
               * @return The address of the authorizer.
               */
              function getAuthorizer(uint authorizerIndex) external constant returns(address) {
                return address(authorizers[authorizerIndex + 1]);
              }
            
              /**
               * @dev Function to check if an address is authorized
               * @param _addr the address to check if it is authorized.
               * @return boolean flag if address is authorized.
               */
              function isAuthorized(address _addr) constant returns(bool) {
                return authorizerIndex[_addr] > 0;
              }
            
              /**
               * @dev Function to add a new authorizer
               * @param _addr the address to add as a new authorizer.
               */
              function addAuthorized(address _addr) external onlyAuthorized {
                authorizerIndex[_addr] = authorizers.length;
                authorizers.length++;
                authorizers[authorizers.length - 1] = _addr;
              }
            
            }
            
            /**
             * @title ExchangeRate
             * @dev Allows updating and retrieveing of Conversion Rates for PAY tokens
             *
             * ABI
             * [{"constant":false,"inputs":[{"name":"_symbol","type":"string"},{"name":"_rate","type":"uint256"}],"name":"updateRate","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"uint256[]"}],"name":"updateRates","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_symbol","type":"string"}],"name":"getRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"rates","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"name":"symbol","type":"bytes32"},{"indexed":false,"name":"rate","type":"uint256"}],"name":"RateUpdated","type":"event"}]
             */
            contract ExchangeRate is Ownable {
            
              event RateUpdated(uint timestamp, bytes32 symbol, uint rate);
            
              mapping(bytes32 => uint) public rates;
            
              /**
               * @dev Allows the current owner to update a single rate.
               * @param _symbol The symbol to be updated. 
               * @param _rate the rate for the symbol. 
               */
              function updateRate(string _symbol, uint _rate) public onlyOwner {
                rates[sha3(_symbol)] = _rate;
                RateUpdated(now, sha3(_symbol), _rate);
              }
            
              /**
               * @dev Allows the current owner to update multiple rates.
               * @param data an array that alternates sha3 hashes of the symbol and the corresponding rate . 
               */
              function updateRates(uint[] data) public onlyOwner {
                if (data.length % 2 > 0)
                  throw;
                uint i = 0;
                while (i < data.length / 2) {
                  bytes32 symbol = bytes32(data[i * 2]);
                  uint rate = data[i * 2 + 1];
                  rates[symbol] = rate;
                  RateUpdated(now, symbol, rate);
                  i++;
                }
              }
            
              /**
               * @dev Allows the anyone to read the current rate.
               * @param _symbol the symbol to be retrieved. 
               */
              function getRate(string _symbol) public constant returns(uint) {
                return rates[sha3(_symbol)];
              }
            
            }
            
            /**
             * Math operations with safety checks
             */
            library SafeMath {
              function mul(uint a, uint b) internal returns (uint) {
                uint c = a * b;
                assert(a == 0 || c / a == b);
                return c;
              }
            
              function div(uint a, uint b) internal returns (uint) {
                // assert(b > 0); // Solidity automatically throws when dividing by 0
                uint c = a / b;
                // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                return c;
              }
            
              function sub(uint a, uint b) internal returns (uint) {
                assert(b <= a);
                return a - b;
              }
            
              function add(uint a, uint b) internal returns (uint) {
                uint c = a + b;
                assert(c >= a);
                return c;
              }
            
              function max64(uint64 a, uint64 b) internal constant returns (uint64) {
                return a >= b ? a : b;
              }
            
              function min64(uint64 a, uint64 b) internal constant returns (uint64) {
                return a < b ? a : b;
              }
            
              function max256(uint256 a, uint256 b) internal constant returns (uint256) {
                return a >= b ? a : b;
              }
            
              function min256(uint256 a, uint256 b) internal constant returns (uint256) {
                return a < b ? a : b;
              }
            
              function assert(bool assertion) internal {
                if (!assertion) {
                  throw;
                }
              }
            }
            
            
            /**
             * @title ERC20Basic
             * @dev Simpler version of ERC20 interface
             * @dev see https://github.com/ethereum/EIPs/issues/20
             */
            contract ERC20Basic {
              uint public totalSupply;
              function balanceOf(address who) constant returns (uint);
              function transfer(address to, uint value);
              event Transfer(address indexed from, address indexed to, uint value);
            }
            
            
            
            
            /**
             * @title ERC20 interface
             * @dev see https://github.com/ethereum/EIPs/issues/20
             */
            contract ERC20 is ERC20Basic {
              function allowance(address owner, address spender) constant returns (uint);
              function transferFrom(address from, address to, uint value);
              function approve(address spender, uint value);
              event Approval(address indexed owner, address indexed spender, uint value);
            }
            
            
            
            
            /**
             * @title Basic token
             * @dev Basic version of StandardToken, with no allowances. 
             */
            contract BasicToken is ERC20Basic {
              using SafeMath for uint;
            
              mapping(address => uint) balances;
            
              /**
               * @dev Fix for the ERC20 short address attack.
               */
              modifier onlyPayloadSize(uint size) {
                 if(msg.data.length < size + 4) {
                   throw;
                 }
                 _;
              }
            
              /**
              * @dev transfer token for a specified address
              * @param _to The address to transfer to.
              * @param _value The amount to be transferred.
              */
              function transfer(address _to, uint _value) onlyPayloadSize(2 * 32) {
                balances[msg.sender] = balances[msg.sender].sub(_value);
                balances[_to] = balances[_to].add(_value);
                Transfer(msg.sender, _to, _value);
              }
            
              /**
              * @dev Gets the balance of the specified address.
              * @param _owner The address to query the the balance of. 
              * @return An uint representing the amount owned by the passed address.
              */
              function balanceOf(address _owner) constant returns (uint balance) {
                return balances[_owner];
              }
            
            }
            
            
            
            
            /**
             * @title Standard ERC20 token
             *
             * @dev Implemantation of the basic standart token.
             * @dev https://github.com/ethereum/EIPs/issues/20
             * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
             */
            contract StandardToken is BasicToken, ERC20 {
            
              mapping (address => mapping (address => uint)) allowed;
            
            
              /**
               * @dev Transfer tokens from one address to another
               * @param _from address The address which you want to send tokens from
               * @param _to address The address which you want to transfer to
               * @param _value uint the amout of tokens to be transfered
               */
              function transferFrom(address _from, address _to, uint _value) onlyPayloadSize(3 * 32) {
                var _allowance = allowed[_from][msg.sender];
            
                // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
                // if (_value > _allowance) throw;
            
                balances[_to] = balances[_to].add(_value);
                balances[_from] = balances[_from].sub(_value);
                allowed[_from][msg.sender] = _allowance.sub(_value);
                Transfer(_from, _to, _value);
              }
            
              /**
               * @dev Aprove the passed address to spend the specified amount of tokens on beahlf of msg.sender.
               * @param _spender The address which will spend the funds.
               * @param _value The amount of tokens to be spent.
               */
              function approve(address _spender, uint _value) {
            
                // To change the approve amount you first have to reduce the addresses`
                //  allowance to zero by calling `approve(_spender, 0)` if it is not
                //  already 0 to mitigate the race condition described here:
                //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) throw;
            
                allowed[msg.sender][_spender] = _value;
                Approval(msg.sender, _spender, _value);
              }
            
              /**
               * @dev Function to check the amount of tokens than an owner allowed to a spender.
               * @param _owner address The address which owns the funds.
               * @param _spender address The address which will spend the funds.
               * @return A uint specifing the amount of tokens still avaible for the spender.
               */
              function allowance(address _owner, address _spender) constant returns (uint remaining) {
                return allowed[_owner][_spender];
              }
            
            }
            
            
            
            
            
            
            /**
             * @title Mintable token
             * @dev Simple ERC20 Token example, with mintable token creation
             * @dev Issue: * https://github.com/OpenZeppelin/zeppelin-solidity/issues/120
             * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol
             */
            
            contract MintableToken is StandardToken, Ownable {
              event Mint(address indexed to, uint value);
              event MintFinished();
            
              bool public mintingFinished = false;
              uint public totalSupply = 0;
            
            
              modifier canMint() {
                if(mintingFinished) throw;
                _;
              }
            
              /**
               * @dev Function to mint tokens
               * @param _to The address that will recieve the minted tokens.
               * @param _amount The amount of tokens to mint.
               * @return A boolean that indicates if the operation was successful.
               */
              function mint(address _to, uint _amount) onlyOwner canMint returns (bool) {
                totalSupply = totalSupply.add(_amount);
                balances[_to] = balances[_to].add(_amount);
                Mint(_to, _amount);
                return true;
              }
            
              /**
               * @dev Function to stop minting new tokens.
               * @return True if the operation was successful.
               */
              function finishMinting() onlyOwner returns (bool) {
                mintingFinished = true;
                MintFinished();
                return true;
              }
            }
            
            
            /**
             * @title PayToken
             * @dev The main PAY token contract
             * 
             * ABI 
             * [{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"startTrading","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"tradingStarted","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]
             */
            contract PayToken is MintableToken {
            
              string public name = "TenX Pay Token";
              string public symbol = "PAY";
              uint public decimals = 18;
            
              bool public tradingStarted = false;
            
              /**
               * @dev modifier that throws if trading has not started yet
               */
              modifier hasStartedTrading() {
                require(tradingStarted);
                _;
              }
            
              /**
               * @dev Allows the owner to enable the trading. This can not be undone
               */
              function startTrading() onlyOwner {
                tradingStarted = true;
              }
            
              /**
               * @dev Allows anyone to transfer the PAY tokens once trading has started
               * @param _to the recipient address of the tokens. 
               * @param _value number of tokens to be transfered. 
               */
              function transfer(address _to, uint _value) hasStartedTrading {
                super.transfer(_to, _value);
              }
            
               /**
               * @dev Allows anyone to transfer the PAY tokens once trading has started
               * @param _from address The address which you want to send tokens from
               * @param _to address The address which you want to transfer to
               * @param _value uint the amout of tokens to be transfered
               */
              function transferFrom(address _from, address _to, uint _value) hasStartedTrading {
                super.transferFrom(_from, _to, _value);
              }
            
            }
            
            
            /**
             * @title MainSale
             * @dev The main PAY token sale contract
             * 
             * ABI
             * [{"constant":false,"inputs":[{"name":"_multisigVault","type":"address"}],"name":"setMultisigVault","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"authorizerIndex","type":"uint256"}],"name":"getAuthorizer","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"exchangeRate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"altDeposits","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"recipient","type":"address"},{"name":"tokens","type":"uint256"}],"name":"authorizedCreateTokens","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_exchangeRate","type":"address"}],"name":"setExchangeRate","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"retrieveTokens","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"totalAltDeposits","type":"uint256"}],"name":"setAltDeposit","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"start","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"recipient","type":"address"}],"name":"createTokens","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"addAuthorized","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"multisigVault","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hardcap","type":"uint256"}],"name":"setHardCap","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_start","type":"uint256"}],"name":"setStart","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"isAuthorized","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"recipient","type":"address"},{"indexed":false,"name":"ether_amount","type":"uint256"},{"indexed":false,"name":"pay_amount","type":"uint256"},{"indexed":false,"name":"exchangerate","type":"uint256"}],"name":"TokenSold","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"recipient","type":"address"},{"indexed":false,"name":"pay_amount","type":"uint256"}],"name":"AuthorizedCreate","type":"event"},{"anonymous":false,"inputs":[],"name":"MainSaleClosed","type":"event"}]
             */
            contract MainSale is Ownable, Authorizable {
              using SafeMath for uint;
              event TokenSold(address recipient, uint ether_amount, uint pay_amount, uint exchangerate);
              event AuthorizedCreate(address recipient, uint pay_amount);
              event MainSaleClosed();
            
              PayToken public token = new PayToken();
            
              address public multisigVault;
            
              uint hardcap = 200000 ether;
              ExchangeRate public exchangeRate;
            
              uint public altDeposits = 0;
              uint public start = 1498302000; //new Date("Jun 24 2017 11:00:00 GMT").getTime() / 1000
            
              /**
               * @dev modifier to allow token creation only when the sale IS ON
               */
              modifier saleIsOn() {
                require(now > start && now < start + 28 days);
                _;
              }
            
              /**
               * @dev modifier to allow token creation only when the hardcap has not been reached
               */
              modifier isUnderHardCap() {
                require(multisigVault.balance + altDeposits <= hardcap);
                _;
              }
            
              /**
               * @dev Allows anyone to create tokens by depositing ether.
               * @param recipient the recipient to receive tokens. 
               */
              function createTokens(address recipient) public isUnderHardCap saleIsOn payable {
                uint rate = exchangeRate.getRate("ETH");
                uint tokens = rate.mul(msg.value).div(1 ether);
                token.mint(recipient, tokens);
                require(multisigVault.send(msg.value));
                TokenSold(recipient, msg.value, tokens, rate);
              }
            
            
              /**
               * @dev Allows to set the toal alt deposit measured in ETH to make sure the hardcap includes other deposits
               * @param totalAltDeposits total amount ETH equivalent
               */
              function setAltDeposit(uint totalAltDeposits) public onlyOwner {
                altDeposits = totalAltDeposits;
              }
            
              /**
               * @dev Allows authorized acces to create tokens. This is used for Bitcoin and ERC20 deposits
               * @param recipient the recipient to receive tokens.
               * @param tokens number of tokens to be created. 
               */
              function authorizedCreateTokens(address recipient, uint tokens) public onlyAuthorized {
                token.mint(recipient, tokens);
                AuthorizedCreate(recipient, tokens);
              }
            
              /**
               * @dev Allows the owner to set the hardcap.
               * @param _hardcap the new hardcap
               */
              function setHardCap(uint _hardcap) public onlyOwner {
                hardcap = _hardcap;
              }
            
              /**
               * @dev Allows the owner to set the starting time.
               * @param _start the new _start
               */
              function setStart(uint _start) public onlyOwner {
                start = _start;
              }
            
              /**
               * @dev Allows the owner to set the multisig contract.
               * @param _multisigVault the multisig contract address
               */
              function setMultisigVault(address _multisigVault) public onlyOwner {
                if (_multisigVault != address(0)) {
                  multisigVault = _multisigVault;
                }
              }
            
              /**
               * @dev Allows the owner to set the exchangerate contract.
               * @param _exchangeRate the exchangerate address
               */
              function setExchangeRate(address _exchangeRate) public onlyOwner {
                exchangeRate = ExchangeRate(_exchangeRate);
              }
            
              /**
               * @dev Allows the owner to finish the minting. This will create the 
               * restricted tokens and then close the minting.
               * Then the ownership of the PAY token contract is transfered 
               * to this owner.
               */
              function finishMinting() public onlyOwner {
                uint issuedTokenSupply = token.totalSupply();
                uint restrictedTokens = issuedTokenSupply.mul(49).div(51);
                token.mint(multisigVault, restrictedTokens);
                token.finishMinting();
                token.transferOwnership(owner);
                MainSaleClosed();
              }
            
              /**
               * @dev Allows the owner to transfer ERC20 tokens to the multi sig vault
               * @param _token the contract address of the ERC20 contract
               */
              function retrieveTokens(address _token) public onlyOwner {
                ERC20 token = ERC20(_token);
                token.transfer(multisigVault, token.balanceOf(this));
              }
            
              /**
               * @dev Fallback function which receives ether and created the appropriate number of tokens for the 
               * msg.sender.
               */
              function() external payable {
                createTokens(msg.sender);
              }
            
            }

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

            File 6 of 8: 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 7 of 8: 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;
                }
            }

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