ETH Price: $2,047.50 (+2.76%)

Transaction Decoder

Block:
5613034 at May-14-2018 04:08:38 PM +UTC
Transaction Fee:
0.00246288 ETH $5.04
Gas Used:
153,930 Gas / 16 Gwei

Emitted Events:

52 DSToken.Transfer( src=KyberReserve, dst=[Receiver] KyberNetwork, wad=7213591635167127206052 )
53 KyberReserve.TradeExecute( origin=[Receiver] KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=9990000000000000000, destToken=DSToken, destAmount=7213591635167127206052, destAddress=[Receiver] KyberNetwork )
54 DSToken.Transfer( src=[Receiver] KyberNetwork, dst=[Sender] 0xbfe37429a06d15c57d958c761b756b496a670aa6, wad=7213591635167127206052 )
55 FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=3836160000000000000 )
56 KyberNetwork.ExecuteTrade( sender=[Sender] 0xbfe37429a06d15c57d958c761b756b496a670aa6, src=0xEeeeeEee...eeeeeEEeE, dest=DSToken, actualSrcAmount=9990000000000000000, actualDestAmount=7213591635167127206052 )

Account State Difference:

  Address   Before After State Difference Code
0x07f6e905...62A3CA706
0x097B3B7C...2Cce6EF43
0x2AAb2b15...51c872F31 40.534143901151595752 Eth50.524143901151595752 Eth9.99
0x89d24A6b...a23260359
0xBfE37429...96a670aa6
9.995722048 Eth
Nonce: 5
0.003259168 Eth
Nonce: 6
9.99246288
(Ethermine)
692.565090905662399218 Eth692.567553785662399218 Eth0.00246288

Execution Trace

ETH 9.99 KyberNetwork.trade( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=9990000000000000000, dest=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359, destAddress=0xBfE37429a06d15c57d958C761b756B496a670aa6, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=700418807418629968956, walletId=0x000000000000000000000000000000000055A5e5 ) => ( 7213591635167127206052 )
  • DSToken.balanceOf( src=0xBfE37429a06d15c57d958C761b756B496a670aa6 ) => ( 4638936501837213193800 )
  • KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359, srcQty=9990000000000000000, blockNumber=5613034 ) => ( 722081244761474194800 )
    • ConversionRates.getRate( token=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359, currentBlockNumber=5613034, buy=True, qty=9990000000000000000 ) => ( 722081244761474194800 )
    • DSToken.balanceOf( src=0x2AAb2b157a03915c8a73ADaE735d0Cf51c872F31 ) => ( 27709639006140651599764 )
    • WhiteList.getUserCapInWei( user=0xBfE37429a06d15c57d958C761b756B496a670aa6 ) => ( 11111111111111110000 )
    • ETH 9.99 KyberReserve.trade( srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=9990000000000000000, destToken=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359, destAddress=0x964F35fAe36d75B1e72770e244F6595B68508CF5, conversionRate=722081244761474194800, validate=True ) => ( True )
      • ConversionRates.recordImbalance( token=0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359, buyAmount=7213591635167127206052, rateUpdateBlock=0, currentBlock=5613034 )
      • DSToken.transfer( dst=0x964F35fAe36d75B1e72770e244F6595B68508CF5, wad=7213591635167127206052 ) => ( True )
      • DSToken.transfer( dst=0xBfE37429a06d15c57d958C761b756B496a670aa6, wad=7213591635167127206052 ) => ( True )
      • FeeBurner.handleFees( tradeWeiAmount=9990000000000000000, reserve=0x2AAb2b157a03915c8a73ADaE735d0Cf51c872F31, wallet=0x000000000000000000000000000000000055A5e5 ) => ( True )
      • DSToken.balanceOf( src=0xBfE37429a06d15c57d958C761b756B496a670aa6 ) => ( 11852528137004340399852 )
        File 1 of 6: KyberNetwork
        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
            }
        }
        
        interface FeeBurnerInterface {
            function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
        }
        
        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 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 ExpectedRateInterface {
            function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
                returns (uint expectedRate, uint slippageRate);
        }
        
        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 WhiteListInterface {
            function getUserCapInWei(address user) external view returns (uint userCapWei);
        }
        
        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 KyberNetwork is Withdrawable, Utils {
        
            uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01%
            KyberReserveInterface[] public reserves;
            mapping(address=>bool) public isReserve;
            WhiteListInterface public whiteListContract;
            ExpectedRateInterface public expectedRateContract;
            FeeBurnerInterface    public feeBurnerContract;
            uint                  public maxGasPrice = 50 * 1000 * 1000 * 1000; // 50 gwei
            bool                  public enabled = false; // network is enabled
            mapping(bytes32=>uint) public info; // this is only a UI field for external app.
            mapping(address=>mapping(bytes32=>bool)) public perReserveListedPairs;
        
            function KyberNetwork(address _admin) public {
                require(_admin != address(0));
                admin = _admin;
            }
        
            event EtherReceival(address indexed sender, uint amount);
        
            /* solhint-disable no-complex-fallback */
            function() public payable {
                require(isReserve[msg.sender]);
                EtherReceival(msg.sender, msg.value);
            }
            /* solhint-enable no-complex-fallback */
        
            event ExecuteTrade(address indexed sender, 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
            /// @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)
            {
                require(enabled);
        
                uint userSrcBalanceBefore;
                uint userSrcBalanceAfter;
                uint userDestBalanceBefore;
                uint userDestBalanceAfter;
        
                userSrcBalanceBefore = getBalance(src, msg.sender);
                if (src == ETH_TOKEN_ADDRESS)
                    userSrcBalanceBefore += msg.value;
                userDestBalanceBefore = getBalance(dest, destAddress);
        
                uint actualDestAmount = doTrade(src,
                                                srcAmount,
                                                dest,
                                                destAddress,
                                                maxDestAmount,
                                                minConversionRate,
                                                walletId
                                                );
                require(actualDestAmount > 0);
        
                userSrcBalanceAfter = getBalance(src, msg.sender);
                userDestBalanceAfter = getBalance(dest, destAddress);
        
                require(userSrcBalanceAfter <= userSrcBalanceBefore);
                require(userDestBalanceAfter >= userDestBalanceBefore);
        
                require((userDestBalanceAfter - userDestBalanceBefore) >=
                    calcDstQty((userSrcBalanceBefore - userSrcBalanceAfter), getDecimals(src), getDecimals(dest),
                        minConversionRate));
        
                return actualDestAmount;
            }
        
            event AddReserveToNetwork(KyberReserveInterface reserve, bool add);
        
            /// @notice can be called only by admin
            /// @dev add or deletes a reserve to/from the network.
            /// @param reserve The reserve address.
            /// @param add If true, the add reserve. Otherwise delete reserve.
            function addReserve(KyberReserveInterface reserve, bool add) public onlyAdmin {
        
                if (add) {
                    require(!isReserve[reserve]);
                    reserves.push(reserve);
                    isReserve[reserve] = true;
                    AddReserveToNetwork(reserve, true);
                } else {
                    isReserve[reserve] = false;
                    // will have trouble if more than 50k reserves...
                    for (uint i = 0; i < reserves.length; i++) {
                        if (reserves[i] == reserve) {
                            reserves[i] = reserves[reserves.length - 1];
                            reserves.length--;
                            AddReserveToNetwork(reserve, false);
                            break;
                        }
                    }
                }
            }
        
            event ListReservePairs(address reserve, ERC20 src, ERC20 dest, bool add);
        
            /// @notice can be called only by admin
            /// @dev allow or prevent a specific reserve to trade a pair of tokens
            /// @param reserve The reserve address.
            /// @param src Src token
            /// @param dest Destination token
            /// @param add If true then enable trade, otherwise delist pair.
            function listPairForReserve(address reserve, ERC20 src, ERC20 dest, bool add) public onlyAdmin {
                (perReserveListedPairs[reserve])[keccak256(src, dest)] = add;
        
                if (src != ETH_TOKEN_ADDRESS) {
                    if (add) {
                        src.approve(reserve, 2**255); // approve infinity
                    } else {
                        src.approve(reserve, 0);
                    }
                }
        
                setDecimals(src);
                setDecimals(dest);
        
                ListReservePairs(reserve, src, dest, add);
            }
        
            function setParams(
                WhiteListInterface    _whiteList,
                ExpectedRateInterface _expectedRate,
                FeeBurnerInterface    _feeBurner,
                uint                  _maxGasPrice,
                uint                  _negligibleRateDiff
            )
                public
                onlyAdmin
            {
                require(_whiteList != address(0));
                require(_feeBurner != address(0));
                require(_expectedRate != address(0));
                require(_negligibleRateDiff <= 100 * 100); // at most 100%
        
                whiteListContract = _whiteList;
                expectedRateContract = _expectedRate;
                feeBurnerContract = _feeBurner;
                maxGasPrice = _maxGasPrice;
                negligibleRateDiff = _negligibleRateDiff;
            }
        
            function setEnable(bool _enable) public onlyAdmin {
                if (_enable) {
                    require(whiteListContract != address(0));
                    require(feeBurnerContract != address(0));
                    require(expectedRateContract != address(0));
                }
                enabled = _enable;
            }
        
            function setInfo(bytes32 field, uint value) public onlyOperator {
                info[field] = value;
            }
        
            /// @dev returns number of reserves
            /// @return number of reserves
            function getNumReserves() public view returns(uint) {
                return reserves.length;
            }
        
            /// @notice should be called off chain with as much gas as needed
            /// @dev get an array of all reserves
            /// @return An array of all reserves
            function getReserves() public view returns(KyberReserveInterface[]) {
                return reserves;
            }
        
            /// @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);
            }
        
            /// @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
            /* solhint-disable code-complexity */
            function findBestRate(ERC20 src, ERC20 dest, uint srcQty) public view returns(uint, uint) {
                uint bestRate = 0;
                uint bestReserve = 0;
                uint numRelevantReserves = 0;
                uint numReserves = reserves.length;
                uint[] memory rates = new uint[](numReserves);
                uint[] memory reserveCandidates = new uint[](numReserves);
        
                for (uint i = 0; i < numReserves; i++) {
                    //list all reserves that have this token.
                    if (!(perReserveListedPairs[reserves[i]])[keccak256(src, dest)]) continue;
        
                    rates[i] = reserves[i].getConversionRate(src, dest, srcQty, block.number);
        
                    if (rates[i] > bestRate) {
                        //best rate is highest rate
                        bestRate = rates[i];
                    }
                }
        
                if (bestRate > 0) {
                    uint random = 0;
                    uint smallestRelevantRate = (bestRate * 10000) / (10000 + negligibleRateDiff);
        
                    for (i = 0; i < numReserves; i++) {
                        if (rates[i] >= smallestRelevantRate) {
                            reserveCandidates[numRelevantReserves++] = i;
                        }
                    }
        
                    if (numRelevantReserves > 1) {
                        //when encountering small rate diff from bestRate. draw from relevant reserves
                        random = uint(block.blockhash(block.number-1)) % numRelevantReserves;
                    }
        
                    bestReserve = reserveCandidates[random];
                    bestRate = rates[bestReserve];
                }
        
                return (bestReserve, bestRate);
            }
            /* solhint-enable code-complexity */
        
            function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty)
                public view
                returns (uint expectedRate, uint slippageRate)
            {
                require(expectedRateContract != address(0));
                return expectedRateContract.getExpectedRate(src, dest, srcQty);
            }
        
            function getUserCapInWei(address user) public view returns(uint) {
                return whiteListContract.getUserCapInWei(user);
            }
        
            function doTrade(
                ERC20 src,
                uint srcAmount,
                ERC20 dest,
                address destAddress,
                uint maxDestAmount,
                uint minConversionRate,
                address walletId
            )
                internal
                returns(uint)
            {
                require(tx.gasprice <= maxGasPrice);
                require(validateTradeInput(src, srcAmount, destAddress));
        
                uint reserveInd;
                uint rate;
        
                (reserveInd, rate) = findBestRate(src, dest, srcAmount);
                KyberReserveInterface theReserve = reserves[reserveInd];
                require(rate > 0);
                require(rate < MAX_RATE);
                require(rate >= minConversionRate);
        
                uint actualSrcAmount = srcAmount;
                uint actualDestAmount = calcDestAmount(src, dest, actualSrcAmount, rate);
                if (actualDestAmount > maxDestAmount) {
                    actualDestAmount = maxDestAmount;
                    actualSrcAmount = calcSrcAmount(src, dest, actualDestAmount, rate);
                    require(actualSrcAmount <= srcAmount);
                }
        
                // do the trade
                // verify trade size is smaller than user cap
                uint ethAmount;
                if (src == ETH_TOKEN_ADDRESS) {
                    ethAmount = actualSrcAmount;
                } else {
                    ethAmount = actualDestAmount;
                }
        
                require(ethAmount <= getUserCapInWei(msg.sender));
                require(doReserveTrade(
                        src,
                        actualSrcAmount,
                        dest,
                        destAddress,
                        actualDestAmount,
                        theReserve,
                        rate,
                        true));
        
                if ((actualSrcAmount < srcAmount) && (src == ETH_TOKEN_ADDRESS)) {
                    msg.sender.transfer(srcAmount - actualSrcAmount);
                }
        
                require(feeBurnerContract.handleFees(ethAmount, theReserve, walletId));
        
                ExecuteTrade(msg.sender, src, dest, actualSrcAmount, actualDestAmount);
                return actualDestAmount;
            }
        
            /// @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 == ETH_TOKEN_ADDRESS) {
                    callValue = amount;
                } else {
                    // take src tokens to this contract
                    src.transferFrom(msg.sender, this, amount);
                }
        
                // reserve sends tokens/eth to network. network sends it to destination
                require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, validate));
        
                if (dest == ETH_TOKEN_ADDRESS) {
                    destAddress.transfer(expectedDestAmount);
                } else {
                    require(dest.transfer(destAddress, expectedDestAmount));
                }
        
                return true;
            }
        
            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);
            }
        
            /// @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 input is valid
            function validateTradeInput(ERC20 src, uint srcAmount, address destAddress) internal view returns(bool) {
                if ((srcAmount >= MAX_QTY) || (srcAmount == 0) || (destAddress == 0))
                    return false;
        
                if (src == ETH_TOKEN_ADDRESS) {
                    if (msg.value != srcAmount)
                        return false;
                } else {
                    if ((msg.value != 0) || (src.allowance(msg.sender, this) < srcAmount))
                        return false;
                }
        
                return true;
            }
        }

        File 2 of 6: 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 3 of 6: DSToken
        pragma solidity ^0.4.13;
        
        ////// lib/ds-math/src/math.sol
        /// math.sol -- mixin for inline numerical wizardry
        
        // This program is free software: you can redistribute it and/or modify
        // it under the terms of the GNU General Public License as published by
        // the Free Software Foundation, either version 3 of the License, or
        // (at your option) any later version.
        
        // This program is distributed in the hope that it will be useful,
        // but WITHOUT ANY WARRANTY; without even the implied warranty of
        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        // GNU General Public License for more details.
        
        // You should have received a copy of the GNU General Public License
        // along with this program.  If not, see <http://www.gnu.org/licenses/>.
        
        /* pragma solidity ^0.4.13; */
        
        contract DSMath {
            function add(uint x, uint y) internal pure returns (uint z) {
                require((z = x + y) >= x);
            }
            function sub(uint x, uint y) internal pure returns (uint z) {
                require((z = x - y) <= x);
            }
            function mul(uint x, uint y) internal pure returns (uint z) {
                require(y == 0 || (z = x * y) / y == x);
            }
        
            function min(uint x, uint y) internal pure returns (uint z) {
                return x <= y ? x : y;
            }
            function max(uint x, uint y) internal pure returns (uint z) {
                return x >= y ? x : y;
            }
            function imin(int x, int y) internal pure returns (int z) {
                return x <= y ? x : y;
            }
            function imax(int x, int y) internal pure returns (int z) {
                return x >= y ? x : y;
            }
        
            uint constant WAD = 10 ** 18;
            uint constant RAY = 10 ** 27;
        
            function wmul(uint x, uint y) internal pure returns (uint z) {
                z = add(mul(x, y), WAD / 2) / WAD;
            }
            function rmul(uint x, uint y) internal pure returns (uint z) {
                z = add(mul(x, y), RAY / 2) / RAY;
            }
            function wdiv(uint x, uint y) internal pure returns (uint z) {
                z = add(mul(x, WAD), y / 2) / y;
            }
            function rdiv(uint x, uint y) internal pure returns (uint z) {
                z = add(mul(x, RAY), y / 2) / y;
            }
        
            // This famous algorithm is called "exponentiation by squaring"
            // and calculates x^n with x as fixed-point and n as regular unsigned.
            //
            // It's O(log n), instead of O(n) for naive repeated multiplication.
            //
            // These facts are why it works:
            //
            //  If n is even, then x^n = (x^2)^(n/2).
            //  If n is odd,  then x^n = x * x^(n-1),
            //   and applying the equation for even x gives
            //    x^n = x * (x^2)^((n-1) / 2).
            //
            //  Also, EVM division is flooring and
            //    floor[(n-1) / 2] = floor[n / 2].
            //
            function rpow(uint x, uint n) internal pure returns (uint z) {
                z = n % 2 != 0 ? x : RAY;
        
                for (n /= 2; n != 0; n /= 2) {
                    x = rmul(x, x);
        
                    if (n % 2 != 0) {
                        z = rmul(z, x);
                    }
                }
            }
        }
        
        ////// lib/ds-stop/lib/ds-auth/src/auth.sol
        // This program is free software: you can redistribute it and/or modify
        // it under the terms of the GNU General Public License as published by
        // the Free Software Foundation, either version 3 of the License, or
        // (at your option) any later version.
        
        // This program is distributed in the hope that it will be useful,
        // but WITHOUT ANY WARRANTY; without even the implied warranty of
        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        // GNU General Public License for more details.
        
        // You should have received a copy of the GNU General Public License
        // along with this program.  If not, see <http://www.gnu.org/licenses/>.
        
        /* pragma solidity ^0.4.13; */
        
        contract DSAuthority {
            function canCall(
                address src, address dst, bytes4 sig
            ) public view returns (bool);
        }
        
        contract DSAuthEvents {
            event LogSetAuthority (address indexed authority);
            event LogSetOwner     (address indexed owner);
        }
        
        contract DSAuth is DSAuthEvents {
            DSAuthority  public  authority;
            address      public  owner;
        
            function DSAuth() public {
                owner = msg.sender;
                LogSetOwner(msg.sender);
            }
        
            function setOwner(address owner_)
                public
                auth
            {
                owner = owner_;
                LogSetOwner(owner);
            }
        
            function setAuthority(DSAuthority authority_)
                public
                auth
            {
                authority = authority_;
                LogSetAuthority(authority);
            }
        
            modifier auth {
                require(isAuthorized(msg.sender, msg.sig));
                _;
            }
        
            function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
                if (src == address(this)) {
                    return true;
                } else if (src == owner) {
                    return true;
                } else if (authority == DSAuthority(0)) {
                    return false;
                } else {
                    return authority.canCall(src, this, sig);
                }
            }
        }
        
        ////// lib/ds-stop/lib/ds-note/src/note.sol
        /// note.sol -- the `note' modifier, for logging calls as events
        
        // This program is free software: you can redistribute it and/or modify
        // it under the terms of the GNU General Public License as published by
        // the Free Software Foundation, either version 3 of the License, or
        // (at your option) any later version.
        
        // This program is distributed in the hope that it will be useful,
        // but WITHOUT ANY WARRANTY; without even the implied warranty of
        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        // GNU General Public License for more details.
        
        // You should have received a copy of the GNU General Public License
        // along with this program.  If not, see <http://www.gnu.org/licenses/>.
        
        /* pragma solidity ^0.4.13; */
        
        contract DSNote {
            event LogNote(
                bytes4   indexed  sig,
                address  indexed  guy,
                bytes32  indexed  foo,
                bytes32  indexed  bar,
                uint              wad,
                bytes             fax
            ) anonymous;
        
            modifier note {
                bytes32 foo;
                bytes32 bar;
        
                assembly {
                    foo := calldataload(4)
                    bar := calldataload(36)
                }
        
                LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
        
                _;
            }
        }
        
        ////// lib/ds-stop/src/stop.sol
        /// stop.sol -- mixin for enable/disable functionality
        
        // Copyright (C) 2017  DappHub, LLC
        
        // This program is free software: you can redistribute it and/or modify
        // it under the terms of the GNU General Public License as published by
        // the Free Software Foundation, either version 3 of the License, or
        // (at your option) any later version.
        
        // This program is distributed in the hope that it will be useful,
        // but WITHOUT ANY WARRANTY; without even the implied warranty of
        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        // GNU General Public License for more details.
        
        // You should have received a copy of the GNU General Public License
        // along with this program.  If not, see <http://www.gnu.org/licenses/>.
        
        /* pragma solidity ^0.4.13; */
        
        /* import "ds-auth/auth.sol"; */
        /* import "ds-note/note.sol"; */
        
        contract DSStop is DSNote, DSAuth {
        
            bool public stopped;
        
            modifier stoppable {
                require(!stopped);
                _;
            }
            function stop() public auth note {
                stopped = true;
            }
            function start() public auth note {
                stopped = false;
            }
        
        }
        
        ////// lib/erc20/src/erc20.sol
        /// erc20.sol -- API for the ERC20 token standard
        
        // See <https://github.com/ethereum/EIPs/issues/20>.
        
        // This file likely does not meet the threshold of originality
        // required for copyright to apply.  As a result, this is free and
        // unencumbered software belonging to the public domain.
        
        /* pragma solidity ^0.4.8; */
        
        contract ERC20Events {
            event Approval(address indexed src, address indexed guy, uint wad);
            event Transfer(address indexed src, address indexed dst, uint wad);
        }
        
        contract ERC20 is ERC20Events {
            function totalSupply() public view returns (uint);
            function balanceOf(address guy) public view returns (uint);
            function allowance(address src, address guy) public view returns (uint);
        
            function approve(address guy, uint wad) public returns (bool);
            function transfer(address dst, uint wad) public returns (bool);
            function transferFrom(
                address src, address dst, uint wad
            ) public returns (bool);
        }
        
        ////// src/base.sol
        /// base.sol -- basic ERC20 implementation
        
        // Copyright (C) 2015, 2016, 2017  DappHub, LLC
        
        // This program is free software: you can redistribute it and/or modify
        // it under the terms of the GNU General Public License as published by
        // the Free Software Foundation, either version 3 of the License, or
        // (at your option) any later version.
        
        // This program is distributed in the hope that it will be useful,
        // but WITHOUT ANY WARRANTY; without even the implied warranty of
        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        // GNU General Public License for more details.
        
        // You should have received a copy of the GNU General Public License
        // along with this program.  If not, see <http://www.gnu.org/licenses/>.
        
        /* pragma solidity ^0.4.13; */
        
        /* import "erc20/erc20.sol"; */
        /* import "ds-math/math.sol"; */
        
        contract DSTokenBase is ERC20, DSMath {
            uint256                                            _supply;
            mapping (address => uint256)                       _balances;
            mapping (address => mapping (address => uint256))  _approvals;
        
            function DSTokenBase(uint supply) public {
                _balances[msg.sender] = supply;
                _supply = supply;
            }
        
            function totalSupply() public view returns (uint) {
                return _supply;
            }
            function balanceOf(address src) public view returns (uint) {
                return _balances[src];
            }
            function allowance(address src, address guy) public view returns (uint) {
                return _approvals[src][guy];
            }
        
            function transfer(address dst, uint wad) public returns (bool) {
                return transferFrom(msg.sender, dst, wad);
            }
        
            function transferFrom(address src, address dst, uint wad)
                public
                returns (bool)
            {
                if (src != msg.sender) {
                    _approvals[src][msg.sender] = sub(_approvals[src][msg.sender], wad);
                }
        
                _balances[src] = sub(_balances[src], wad);
                _balances[dst] = add(_balances[dst], wad);
        
                Transfer(src, dst, wad);
        
                return true;
            }
        
            function approve(address guy, uint wad) public returns (bool) {
                _approvals[msg.sender][guy] = wad;
        
                Approval(msg.sender, guy, wad);
        
                return true;
            }
        }
        
        ////// src/token.sol
        /// token.sol -- ERC20 implementation with minting and burning
        
        // Copyright (C) 2015, 2016, 2017  DappHub, LLC
        
        // This program is free software: you can redistribute it and/or modify
        // it under the terms of the GNU General Public License as published by
        // the Free Software Foundation, either version 3 of the License, or
        // (at your option) any later version.
        
        // This program is distributed in the hope that it will be useful,
        // but WITHOUT ANY WARRANTY; without even the implied warranty of
        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        // GNU General Public License for more details.
        
        // You should have received a copy of the GNU General Public License
        // along with this program.  If not, see <http://www.gnu.org/licenses/>.
        
        /* pragma solidity ^0.4.13; */
        
        /* import "ds-stop/stop.sol"; */
        
        /* import "./base.sol"; */
        
        contract DSToken is DSTokenBase(0), DSStop {
        
            bytes32  public  symbol;
            uint256  public  decimals = 18; // standard token precision. override to customize
        
            function DSToken(bytes32 symbol_) public {
                symbol = symbol_;
            }
        
            event Mint(address indexed guy, uint wad);
            event Burn(address indexed guy, uint wad);
        
            function approve(address guy) public stoppable returns (bool) {
                return super.approve(guy, uint(-1));
            }
        
            function approve(address guy, uint wad) public stoppable returns (bool) {
                return super.approve(guy, wad);
            }
        
            function transferFrom(address src, address dst, uint wad)
                public
                stoppable
                returns (bool)
            {
                if (src != msg.sender && _approvals[src][msg.sender] != uint(-1)) {
                    _approvals[src][msg.sender] = sub(_approvals[src][msg.sender], wad);
                }
        
                _balances[src] = sub(_balances[src], wad);
                _balances[dst] = add(_balances[dst], wad);
        
                Transfer(src, dst, wad);
        
                return true;
            }
        
            function push(address dst, uint wad) public {
                transferFrom(msg.sender, dst, wad);
            }
            function pull(address src, uint wad) public {
                transferFrom(src, msg.sender, wad);
            }
            function move(address src, address dst, uint wad) public {
                transferFrom(src, dst, wad);
            }
        
            function mint(uint wad) public {
                mint(msg.sender, wad);
            }
            function burn(uint wad) public {
                burn(msg.sender, wad);
            }
            function mint(address guy, uint wad) public auth stoppable {
                _balances[guy] = add(_balances[guy], wad);
                _supply = add(_supply, wad);
                Mint(guy, wad);
            }
            function burn(address guy, uint wad) public auth stoppable {
                if (guy != msg.sender && _approvals[guy][msg.sender] != uint(-1)) {
                    _approvals[guy][msg.sender] = sub(_approvals[guy][msg.sender], wad);
                }
        
                _balances[guy] = sub(_balances[guy], wad);
                _supply = sub(_supply, wad);
                Burn(guy, wad);
            }
        
            // Optional token name
            bytes32   public  name = "";
        
            function setName(bytes32 name_) public auth {
                name = name_;
            }
        }

        File 4 of 6: FeeBurner
        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/FeeBurnerInterface.sol
        
        interface FeeBurnerInterface {
            function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
        }
        
        // 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/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/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, Utils {
        
            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;
            address public kyberNetwork;
            uint public kncPerETHRate = 300;
        
            function FeeBurner(address _admin, BurnableToken kncToken, address _kyberNetwork) public {
                require(_admin != address(0));
                require(kncToken != address(0));
                require(_kyberNetwork != address(0));
                kyberNetwork = _kyberNetwork;
                admin = _admin;
                knc = kncToken;
            }
        
            function setReserveData(address reserve, uint feesInBps, address kncWallet) public onlyAdmin {
                require(feesInBps < 100); // make sure it is always < 1%
                require(kncWallet != address(0));
                reserveFeesInBps[reserve] = feesInBps;
                reserveKNCWallet[reserve] = kncWallet;
            }
        
            function setWalletFees(address wallet, uint feesInBps) public onlyAdmin {
                require(feesInBps < 10000); // under 100%
                walletFeesInBps[wallet] = feesInBps;
            }
        
            function setTaxInBps(uint _taxFeeBps) public onlyAdmin {
                require(_taxFeeBps < 10000); // under 100%
                taxFeeBps = _taxFeeBps;
            }
        
            function setTaxWallet(address _taxWallet) public onlyAdmin {
                require(_taxWallet != address(0));
                taxWallet = _taxWallet;
            }
        
            function setKNCRate(uint rate) public onlyAdmin {
                require(rate <= MAX_RATE);
                kncPerETHRate = rate;
            }
        
            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 == kyberNetwork);
                require(tradeWeiAmount <= MAX_QTY);
                require(kncPerETHRate <= MAX_RATE);
        
                uint kncAmount = tradeWeiAmount * kncPerETHRate;
                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;
            }
        
        
            // this function is callable by anyone
            event BurnAssignedFees(address indexed reserve, address sender, uint quantity);
            event SendTaxFee(address indexed reserve, address sender, address taxWallet, uint quantity);
        
            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 5 of 6: 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 6 of 6: WhiteList
        pragma solidity ^0.4.18;
        
        contract WhiteListInterface {
            function getUserCapInWei(address user) external view returns (uint userCapWei);
        }
        
        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 WhiteList is WhiteListInterface, Withdrawable {
        
            uint public weiPerSgd; // amount of weis in 1 singapore dollar
            mapping (address=>uint) public userCategory; // each user has a category defining cap on trade. 0 for standard.
            mapping (uint=>uint)    public categoryCap;  // will define cap on trade amount per category in singapore Dollar.
            uint constant public kgtHolderCategory = 2;
            ERC20 public kgtToken;
        
            function WhiteList(address _admin, ERC20 _kgtToken) public {
                require(_admin != address(0));
                require(_kgtToken != address(0));
                kgtToken = _kgtToken;
                admin = _admin;
            }
        
            function getUserCapInWei(address user) external view returns (uint) {
                uint category = getUserCategory(user);
                return (categoryCap[category] * weiPerSgd);
            }
        
            event UserCategorySet(address user, uint category);
        
            function setUserCategory(address user, uint category) public onlyOperator {
                userCategory[user] = category;
                UserCategorySet(user, category);
            }
        
            event CategoryCapSet (uint category, uint sgdCap);
        
            function setCategoryCap(uint category, uint sgdCap) public onlyOperator {
                categoryCap[category] = sgdCap;
                CategoryCapSet(category, sgdCap);
            }
        
            event SgdToWeiRateSet (uint rate);
        
            function setSgdToEthRate(uint _sgdToWeiRate) public onlyOperator {
                weiPerSgd = _sgdToWeiRate;
                SgdToWeiRateSet(_sgdToWeiRate);
            }
        
            function getUserCategory (address user) public view returns(uint) {
                uint category = userCategory[user];
                if (category == 0) {
                    //0 = default category. means category wasn't set.
                    if (kgtToken.balanceOf(user) > 0) {
                        category = kgtHolderCategory;
                    }
                }
                return category;
            }
        }
        
        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);
        }