ETH Price: $1,952.89 (+7.57%)

Transaction Decoder

Block:
10670250 at Aug-16-2020 09:16:04 AM +UTC
Transaction Fee:
0.052288416 ETH $102.11
Gas Used:
544,671 Gas / 96 Gwei

Emitted Events:

136 SmartToken.Transfer( _from=0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, _to=KyberNetwork, _value=3278546914203000613861 )
137 KyberReserve.TradeExecute( origin=KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=16492078666679263871, destToken=SmartToken, destAmount=3278546914203000613861, destAddress=KyberNetwork )
138 SmartToken.Transfer( _from=KyberNetwork, _to=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, _value=3278546914203000613861 )
139 KyberFeeHandler.FeeDistributed( token=0xEeeeeEee...eeeeeEEeE, platformWallet=0x00000000...000000000, platformFeeWei=0, rewardWei=21581818375433986, rebateWei=10146429159660388, rebateWallets=[0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9], rebatePercentBpsPerWallet=[10000], burnAmtWei=1322010313962267 )
140 KyberNetwork.KyberTrade( 0x30bbea603a7b36858fe5e3ec6ba5ff59dde039d02120d758eacfaed01520577d, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 0x0000000000000000000000001f573d6fb3f13d689ff844b4ce37794d79a7ff1c, 000000000000000000000000000000000000000000000000e5550d33f60b2000, 00000000000000000000000000000000000000000000000000756b08ced3fd81, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000120, 0000000000000000000000000000000000000000000000000000000000000140, 0000000000000000000000000000000000000000000000000000000000000180, 00000000000000000000000000000000000000000000000000000000000001a0, 00000000000000000000000000000000000000000000000000000000000001e0, 0000000000000000000000000000000000000000000000000000000000000200, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, ffabcd0000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000000000e4dfa22b2737227f, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000ac6d69b4f4aac655a )
141 KyberNetworkProxy.ExecuteTrade( trader=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, src=0xEeeeeEee...eeeeeEEeE, dest=SmartToken, destAddress=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, actualSrcAmount=16525128924528320512, actualDestAmount=3278546914203000613861, platformWallet=0x00000000...000000000, platformFeeBps=0 )
142 SmartToken.Transfer( _from=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, _to=LiquidityPoolV1Converter, _value=3278546914203000635392 )
143 LiquidityPoolV1Converter.Conversion( _fromToken=SmartToken, _toToken=0xEeeeeEee...eeeeeEEeE, _trader=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, _amount=3278546914203000635392, _return=16792965095137177722, _conversionFee=16809774870007184 )
144 LiquidityPoolV1Converter.TokenRateUpdate( _token1=SmartToken, _token2=0xEeeeeEee...eeeeeEEeE, _rateN=10070154208758833758178000000, _rateD=1965701594705949851679102000000 )
145 LiquidityPoolV1Converter.TokenRateUpdate( _token1=SmartToken, _token2=SmartToken, _rateN=3931403189411899703358204000000, _rateD=5885696366553071107328661500000 )
146 LiquidityPoolV1Converter.TokenRateUpdate( _token1=SmartToken, _token2=0xEeeeeEee...eeeeeEEeE, _rateN=20140308417517667516356000000, _rateD=5885696366553071107328661500000 )
147 LiquidityPoolV1Converter.PriceDataUpdate( _connectorToken=SmartToken, _tokenSupply=11771392733106142214657323, _connectorBalance=3931403189411899703358204, _connectorWeight=500000 )
148 LiquidityPoolV1Converter.PriceDataUpdate( _connectorToken=0xEeeeeEee...eeeeeEEeE, _tokenSupply=11771392733106142214657323, _connectorBalance=20140308417517667516356, _connectorWeight=500000 )
149 BancorNetwork.Conversion( _smartToken=SmartToken, _fromToken=SmartToken, _toToken=0xEeeeeEee...eeeeeEEeE, _fromAmount=3278546914203000635392, _toAmount=16792965095137177722, _trader=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e )

Account State Difference:

  Address   Before After State Difference Code
0x1F573D6F...d79a7FF1C
(Spark Pool)
29.095750208286904277 Eth29.148038624286904277 Eth0.052288416
0x693c188E...b84FBc43e 921.858568728911925491 Eth922.126404899520782701 Eth0.26783617060885721
0x7a337007...5552d4F7D
(Kyber: Reserve 2)
1,121.185346500057580789 Eth1,137.67742516673684466 Eth16.492078666679263871
0xd3d2b564...a941114fe
(Kyber: Fee Handler)
1,420.582838747283624275 Eth1,420.615889005132680916 Eth0.033050257849056641
0xE870D001...E22be4ABd
(Bancor: Converter 216)
20,157.101382612804694078 Eth20,140.308417517667516356 Eth16.792965095137177722
0xFB80Bfa1...deeb10483
0xfF1b9745...895FB701A
89.610512558364816037 Eth
Nonce: 185343
89.558224142364816037 Eth
Nonce: 185344
0.052288416

Execution Trace

0x693c188e40f760ecf00d2946ef45260b84fbc43e.00040503( )
  • 0x398c39714e3c6f9f6f967bb81556e529db5078a9.1ca4695f( )
    • KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, srcQty=16525128924528320512, blockNumber=10670250 ) => ( 198795250766478861658 )
      • ConversionRates.getRate( token=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, currentBlockNumber=10670250, buy=True, qty=16525128924528320512 ) => ( 198795250766478861658 )
      • SmartToken.CALL( )
      • SmartToken.balanceOf( 0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9 ) => ( 3968236531845659051467 )
      • SmartToken.allowance( 0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, 0x7a3370075a54B187d7bD5DceBf0ff2B5552d4F7D ) => ( 115792089237316195423570985008687907853269984665640563632633342951265004345438 )
      • 0xaaf0e98629e639703dbf6133836bed7cdfc556cd.78789120( )
      • 0x398c39714e3c6f9f6f967bb81556e529db5078a9.51d88471( )
        • ETH 16.525128924528320512 KyberNetworkProxy.tradeWithHint( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=16525128924528320512, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, destAddress=0x693c188E40F760ecF00d2946ef45260b84FBc43e, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=0, walletId=0x0000000000000000000000000000000000000000, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FFABCD00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) => ( 3278546914203000613861 )
          • SmartToken.balanceOf( 0x693c188E40F760ecF00d2946ef45260b84FBc43e ) => ( 392719474705303 )
          • ETH 16.525128924528320512 KyberNetwork.tradeWithHintAndFee( trader=0x693c188E40F760ecF00d2946ef45260b84FBc43e, src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=16525128924528320512, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, destAddress=0x693c188E40F760ecF00d2946ef45260b84FBc43e, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=0, platformWallet=0x0000000000000000000000000000000000000000, platformFeeBps=0, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FFABCD00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) => ( destAmount=3278546914203000613861 )
            • KyberMatchingEngine.getTradingReserves( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, isTokenToToken=False, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FFABCD00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 )
            • KyberStorage.getReservesData( ) => ( areAllReservesListed=True, feeAccountedArr=[true], entitledRebateArr=[true], reserveAddresses=[0x7a3370075a54B187d7bD5DceBf0ff2B5552d4F7D] )
            • KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, srcQty=16492078666679263871, blockNumber=10670250 ) => ( 198795250766478861658 )
              • ConversionRates.getRate( token=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, currentBlockNumber=10670250, buy=True, qty=16492078666679263871 ) => ( 198795250766478861658 )
              • SmartToken.CALL( )
              • SmartToken.balanceOf( 0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9 ) => ( 3968236531845659051467 )
              • SmartToken.allowance( 0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, 0x7a3370075a54B187d7bD5DceBf0ff2B5552d4F7D ) => ( 115792089237316195423570985008687907853269984665640563632633342951265004345438 )
              • KyberMatchingEngine.doMatch( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, srcAmounts=[16492078666679263871], feesAccountedDestBps=[0], rates=[198795250766478861658] ) => ( reserveIndexes=[0] )
              • SmartToken.balanceOf( 0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 1966 )
              • ETH 16.492078666679263871 KyberReserve.trade( srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=16492078666679263871, destToken=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, destAddress=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, conversionRate=198795250766478861658, validate=True ) => ( True )
                • SmartToken.CALL( )
                • ConversionRates.recordImbalance( token=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, buyAmount=3278546914203000613861, rateUpdateBlock=0, currentBlock=10670250 )
                • SmartToken.transferFrom( _from=0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, _to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, _value=3278546914203000613861 ) => ( success=True )
                • SmartToken.balanceOf( 0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 3278546914203000615827 )
                • SmartToken.transfer( _to=0x693c188E40F760ecF00d2946ef45260b84FBc43e, _value=3278546914203000613861 ) => ( success=True )
                • KyberStorage.getRebateWalletsFromIds( ) => ( rebateWallets=[0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9] )
                • ETH 0.033050257849056641 KyberFeeHandler.handleFees( token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, rebateWallets=[0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9], rebateBpsPerWallet=[10000], platformWallet=0x0000000000000000000000000000000000000000, platformFee=0, networkFee=33050257849056641 )
                • SmartToken.balanceOf( 0x693c188E40F760ecF00d2946ef45260b84FBc43e ) => ( 3278547306922475319164 )
                • 0xaaf0e98629e639703dbf6133836bed7cdfc556cd.401687f4( )
                  • SmartToken.balanceOf( 0x693c188E40F760ecF00d2946ef45260b84FBc43e ) => ( 3278547306922475319164 )
                  • BancorNetwork.convert( _path=[0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, 0xb1CD6e4153B2a390Cf00A6556b0fC1458C4A5533, 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE], _amount=3278546914203000635392, _minReturn=1 ) => ( 16792965095137177722 )
                    • SmartToken.CALL( )
                    • LiquidityPoolV1Converter.STATICCALL( )
                    • SmartToken.transferFrom( _from=0x693c188E40F760ecF00d2946ef45260b84FBc43e, _to=0xE870D00176b2C71AFD4c43ceA550228E22be4ABd, _value=3278546914203000635392 ) => ( success=True )
                    • ContractRegistry.addressOf( _contractName=424E54546F6B656E000000000000000000000000000000000000000000000000 ) => ( 0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C )
                    • SmartToken.CALL( )
                    • LiquidityPoolV1Converter.STATICCALL( )
                    • LiquidityPoolV1Converter.convert( _sourceToken=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, _targetToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, _amount=3278546914203000635392, _trader=0x693c188E40F760ecF00d2946ef45260b84FBc43e, _beneficiary=0x693c188E40F760ecF00d2946ef45260b84FBc43e ) => ( 16792965095137177722 )
                      • ContractRegistry.addressOf( _contractName=42616E636F724E6574776F726B00000000000000000000000000000000000000 ) => ( 0x2F9EC37d6CcFFf1caB21733BdaDEdE11c823cCB0 )
                      • SmartToken.CALL( )
                      • ContractRegistry.addressOf( _contractName=42616E636F72466F726D756C6100000000000000000000000000000000000000 ) => ( 0xA049894d5dcaD406b7C827D6dc6A0B58CA4AE73a )
                      • BancorFormula.crossReserveTargetAmount( _sourceReserveBalance=3928124642497696702722812, _sourceReserveWeight=500000, _targetReserveBalance=20157101382612804694078, _targetReserveWeight=500000, _amount=3278546914203000635392 ) => ( 16809774870007184906 )
                      • SmartToken.balanceOf( 0xE870D00176b2C71AFD4c43ceA550228E22be4ABd ) => ( 3931403189411899703358204 )
                      • SmartToken.balanceOf( 0xE870D00176b2C71AFD4c43ceA550228E22be4ABd ) => ( 3931403189411899703358204 )
                      • ETH 16.792965095137177722 0x693c188e40f760ecf00d2946ef45260b84fbc43e.CALL( )
                      • SmartToken.CALL( )
                        File 1 of 13: KyberNetwork
                        // File: contracts/sol6/IERC20.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        interface IERC20 {
                            event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                        
                            function approve(address _spender, uint256 _value) external returns (bool success);
                        
                            function transfer(address _to, uint256 _value) external returns (bool success);
                        
                            function transferFrom(
                                address _from,
                                address _to,
                                uint256 _value
                            ) external returns (bool success);
                        
                            function allowance(address _owner, address _spender) external view returns (uint256 remaining);
                        
                            function balanceOf(address _owner) external view returns (uint256 balance);
                        
                            function decimals() external view returns (uint8 digits);
                        
                            function totalSupply() external view returns (uint256 supply);
                        }
                        
                        
                        // to support backward compatible contract name -- so function signature remains same
                        abstract contract ERC20 is IERC20 {
                        
                        }
                        
                        // File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        contract PermissionGroupsNoModifiers {
                            address public admin;
                            address public pendingAdmin;
                            mapping(address => bool) internal operators;
                            mapping(address => bool) internal alerters;
                            address[] internal operatorsGroup;
                            address[] internal alertersGroup;
                            uint256 internal constant MAX_GROUP_SIZE = 50;
                        
                            event AdminClaimed(address newAdmin, address previousAdmin);
                            event AlerterAdded(address newAlerter, bool isAdd);
                            event OperatorAdded(address newOperator, bool isAdd);
                            event TransferAdminPending(address pendingAdmin);
                        
                            constructor(address _admin) public {
                                require(_admin != address(0), "admin 0");
                                admin = _admin;
                            }
                        
                            function getOperators() external view returns (address[] memory) {
                                return operatorsGroup;
                            }
                        
                            function getAlerters() external view returns (address[] memory) {
                                return alertersGroup;
                            }
                        
                            function addAlerter(address newAlerter) public {
                                onlyAdmin();
                                require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
                                require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
                        
                                emit AlerterAdded(newAlerter, true);
                                alerters[newAlerter] = true;
                                alertersGroup.push(newAlerter);
                            }
                        
                            function addOperator(address newOperator) public {
                                onlyAdmin();
                                require(!operators[newOperator], "operator exists"); // prevent duplicates.
                                require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
                        
                                emit OperatorAdded(newOperator, true);
                                operators[newOperator] = true;
                                operatorsGroup.push(newOperator);
                            }
                        
                            /// @dev Allows the pendingAdmin address to finalize the change admin process.
                            function claimAdmin() public {
                                require(pendingAdmin == msg.sender, "not pending");
                                emit AdminClaimed(pendingAdmin, admin);
                                admin = pendingAdmin;
                                pendingAdmin = address(0);
                            }
                        
                            function removeAlerter(address alerter) public {
                                onlyAdmin();
                                require(alerters[alerter], "not alerter");
                                delete alerters[alerter];
                        
                                for (uint256 i = 0; i < alertersGroup.length; ++i) {
                                    if (alertersGroup[i] == alerter) {
                                        alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                        alertersGroup.pop();
                                        emit AlerterAdded(alerter, false);
                                        break;
                                    }
                                }
                            }
                        
                            function removeOperator(address operator) public {
                                onlyAdmin();
                                require(operators[operator], "not operator");
                                delete operators[operator];
                        
                                for (uint256 i = 0; i < operatorsGroup.length; ++i) {
                                    if (operatorsGroup[i] == operator) {
                                        operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                        operatorsGroup.pop();
                                        emit OperatorAdded(operator, false);
                                        break;
                                    }
                                }
                            }
                        
                            /// @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), "new admin 0");
                                emit TransferAdminPending(newAdmin);
                                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), "admin 0");
                                emit TransferAdminPending(newAdmin);
                                emit AdminClaimed(newAdmin, admin);
                                admin = newAdmin;
                            }
                        
                            function onlyAdmin() internal view {
                                require(msg.sender == admin, "only admin");
                            }
                        
                            function onlyAlerter() internal view {
                                require(alerters[msg.sender], "only alerter");
                            }
                        
                            function onlyOperator() internal view {
                                require(operators[msg.sender], "only operator");
                            }
                        }
                        
                        // File: contracts/sol6/utils/WithdrawableNoModifiers.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        contract WithdrawableNoModifiers is PermissionGroupsNoModifiers {
                            constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {}
                        
                            event EtherWithdraw(uint256 amount, address sendTo);
                            event TokenWithdraw(IERC20 token, uint256 amount, address sendTo);
                        
                            /// @dev Withdraw Ethers
                            function withdrawEther(uint256 amount, address payable sendTo) external {
                                onlyAdmin();
                                (bool success, ) = sendTo.call{value: amount}("");
                                require(success);
                                emit EtherWithdraw(amount, sendTo);
                            }
                        
                            /// @dev Withdraw all IERC20 compatible tokens
                            /// @param token IERC20 The address of the token contract
                            function withdrawToken(
                                IERC20 token,
                                uint256 amount,
                                address sendTo
                            ) external {
                                onlyAdmin();
                                token.transfer(sendTo, amount);
                                emit TokenWithdraw(token, amount, sendTo);
                            }
                        }
                        
                        // File: contracts/sol6/utils/Utils5.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        /**
                         * @title Kyber utility file
                         * mostly shared constants and rate calculation helpers
                         * inherited by most of kyber contracts.
                         * previous utils implementations are for previous solidity versions.
                         */
                        contract Utils5 {
                            IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
                                0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
                            );
                            uint256 internal constant PRECISION = (10**18);
                            uint256 internal constant MAX_QTY = (10**28); // 10B tokens
                            uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
                            uint256 internal constant MAX_DECIMALS = 18;
                            uint256 internal constant ETH_DECIMALS = 18;
                            uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
                            uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
                        
                            mapping(IERC20 => uint256) internal decimals;
                        
                            function getUpdateDecimals(IERC20 token) internal returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // moreover, very possible that old tokens have decimals 0
                                // these tokens will just have higher gas fees.
                                if (tokenDecimals == 0) {
                                    tokenDecimals = token.decimals();
                                    decimals[token] = tokenDecimals;
                                }
                        
                                return tokenDecimals;
                            }
                        
                            function setDecimals(IERC20 token) internal {
                                if (decimals[token] != 0) return; //already set
                        
                                if (token == ETH_TOKEN_ADDRESS) {
                                    decimals[token] = ETH_DECIMALS;
                                } else {
                                    decimals[token] = token.decimals();
                                }
                            }
                        
                            /// @dev get the balance of a user.
                            /// @param token The token type
                            /// @return The balance
                            function getBalance(IERC20 token, address user) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) {
                                    return user.balance;
                                } else {
                                    return token.balanceOf(user);
                                }
                            }
                        
                            function getDecimals(IERC20 token) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // 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 calcDestAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcSrcAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 destAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcDstQty(
                                uint256 srcQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                                }
                            }
                        
                            function calcSrcQty(
                                uint256 dstQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                //source quantity is rounded up. to avoid dest quantity being too low.
                                uint256 numerator;
                                uint256 denominator;
                                if (srcDecimals >= dstDecimals) {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                                    denominator = rate;
                                } else {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty);
                                    denominator = (rate * (10**(dstDecimals - srcDecimals)));
                                }
                                return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                            }
                        
                            function calcRateFromQty(
                                uint256 srcAmount,
                                uint256 destAmount,
                                uint256 srcDecimals,
                                uint256 dstDecimals
                            ) internal pure returns (uint256) {
                                require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
                                require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
                                }
                            }
                        
                            function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
                                return x > y ? y : x;
                            }
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/ReentrancyGuard.sol
                        
                        pragma solidity 0.6.6;
                        
                        /**
                         * @dev Contract module that helps prevent reentrant calls to a function.
                         *
                         * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
                         * available, which can be applied to functions to make sure there are no nested
                         * (reentrant) calls to them.
                         *
                         * Note that because there is a single `nonReentrant` guard, functions marked as
                         * `nonReentrant` may not call one another. This can be worked around by making
                         * those functions `private`, and then adding `external` `nonReentrant` entry
                         * points to them.
                         *
                         * TIP: If you would like to learn more about reentrancy and alternative ways
                         * to protect against it, check out our blog post
                         * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
                         */
                        contract ReentrancyGuard {
                            bool private _notEntered;
                        
                            constructor () internal {
                                // Storing an initial non-zero value makes deployment a bit more
                                // expensive, but in exchange the refund on every call to nonReentrant
                                // will be lower in amount. Since refunds are capped to a percetange of
                                // the total transaction's gas, it is best to keep them low in cases
                                // like this one, to increase the likelihood of the full refund coming
                                // into effect.
                                _notEntered = true;
                            }
                        
                            /**
                             * @dev Prevents a contract from calling itself, directly or indirectly.
                             * Calling a `nonReentrant` function from another `nonReentrant`
                             * function is not supported. It is possible to prevent this from happening
                             * by making the `nonReentrant` function external, and make it call a
                             * `private` function that does the actual work.
                             */
                            modifier nonReentrant() {
                                // On the first call to nonReentrant, _notEntered will be true
                                require(_notEntered, "ReentrancyGuard: reentrant call");
                        
                                // Any calls to nonReentrant after this point will fail
                                _notEntered = false;
                        
                                _;
                        
                                // By storing the original value once again, a refund is triggered (see
                                // https://eips.ethereum.org/EIPS/eip-2200)
                                _notEntered = true;
                            }
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/SafeMath.sol
                        
                        pragma solidity 0.6.6;
                        
                        /**
                         * @dev Wrappers over Solidity's arithmetic operations with added overflow
                         * checks.
                         *
                         * Arithmetic operations in Solidity wrap on overflow. This can easily result
                         * in bugs, because programmers usually assume that an overflow raises an
                         * error, which is the standard behavior in high level programming languages.
                         * `SafeMath` restores this intuition by reverting the transaction when an
                         * operation overflows.
                         *
                         * Using this library instead of the unchecked operations eliminates an entire
                         * class of bugs, so it's recommended to use it always.
                         */
                        library SafeMath {
                            /**
                             * @dev Returns the addition of two unsigned integers, reverting on
                             * overflow.
                             *
                             * Counterpart to Solidity's `+` operator.
                             *
                             * Requirements:
                             * - Addition cannot overflow.
                             */
                            function add(uint256 a, uint256 b) internal pure returns (uint256) {
                                uint256 c = a + b;
                                require(c >= a, "SafeMath: addition overflow");
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the subtraction of two unsigned integers, reverting on
                             * overflow (when the result is negative).
                             *
                             * Counterpart to Solidity's `-` operator.
                             *
                             * Requirements:
                             * - Subtraction cannot overflow.
                             */
                            function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                                return sub(a, b, "SafeMath: subtraction overflow");
                            }
                        
                            /**
                             * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                             * overflow (when the result is negative).
                             *
                             * Counterpart to Solidity's `-` operator.
                             *
                             * Requirements:
                             * - Subtraction cannot overflow.
                             */
                            function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                require(b <= a, errorMessage);
                                uint256 c = a - b;
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the multiplication of two unsigned integers, reverting on
                             * overflow.
                             *
                             * Counterpart to Solidity's `*` operator.
                             *
                             * Requirements:
                             * - Multiplication cannot overflow.
                             */
                            function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                                // benefit is lost if 'b' is also tested.
                                // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                                if (a == 0) {
                                    return 0;
                                }
                        
                                uint256 c = a * b;
                                require(c / a == b, "SafeMath: multiplication overflow");
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the integer division of two unsigned integers. Reverts on
                             * division by zero. The result is rounded towards zero.
                             *
                             * Counterpart to Solidity's `/` operator. Note: this function uses a
                             * `revert` opcode (which leaves remaining gas untouched) while Solidity
                             * uses an invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function div(uint256 a, uint256 b) internal pure returns (uint256) {
                                return div(a, b, "SafeMath: division by zero");
                            }
                        
                            /**
                             * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                             * division by zero. The result is rounded towards zero.
                             *
                             * Counterpart to Solidity's `/` operator. Note: this function uses a
                             * `revert` opcode (which leaves remaining gas untouched) while Solidity
                             * uses an invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                // Solidity only automatically asserts when dividing by 0
                                require(b > 0, errorMessage);
                                uint256 c = a / b;
                                // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                             * Reverts when dividing by zero.
                             *
                             * Counterpart to Solidity's `%` operator. This function uses a `revert`
                             * opcode (which leaves remaining gas untouched) while Solidity uses an
                             * invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                                return mod(a, b, "SafeMath: modulo by zero");
                            }
                        
                            /**
                             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                             * Reverts with custom message when dividing by zero.
                             *
                             * Counterpart to Solidity's `%` operator. This function uses a `revert`
                             * opcode (which leaves remaining gas untouched) while Solidity uses an
                             * invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                require(b != 0, errorMessage);
                                return a % b;
                            }
                        
                            /**
                             * @dev Returns the smallest of two numbers.
                             */
                            function min(uint256 a, uint256 b) internal pure returns (uint256) {
                                return a < b ? a : b;
                            }
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/Address.sol
                        
                        pragma solidity 0.6.6;
                        
                        /**
                         * @dev Collection of functions related to the address type
                         */
                        library Address {
                            /**
                             * @dev Returns true if `account` is a contract.
                             *
                             * [IMPORTANT]
                             * ====
                             * It is unsafe to assume that an address for which this function returns
                             * false is an externally-owned account (EOA) and not a contract.
                             *
                             * Among others, `isContract` will return false for the following
                             * types of addresses:
                             *
                             *  - an externally-owned account
                             *  - a contract in construction
                             *  - an address where a contract will be created
                             *  - an address where a contract lived, but was destroyed
                             * ====
                             */
                            function isContract(address account) internal view returns (bool) {
                                // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                                // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                                // for accounts without code, i.e. `keccak256('')`
                                bytes32 codehash;
                                bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                                // solhint-disable-next-line no-inline-assembly
                                assembly { codehash := extcodehash(account) }
                                return (codehash != accountHash && codehash != 0x0);
                            }
                        
                            /**
                             * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                             * `recipient`, forwarding all available gas and reverting on errors.
                             *
                             * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                             * of certain opcodes, possibly making contracts go over the 2300 gas limit
                             * imposed by `transfer`, making them unable to receive funds via
                             * `transfer`. {sendValue} removes this limitation.
                             *
                             * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                             *
                             * IMPORTANT: because control is transferred to `recipient`, care must be
                             * taken to not create reentrancy vulnerabilities. Consider using
                             * {ReentrancyGuard} or the
                             * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                             */
                            function sendValue(address payable recipient, uint256 amount) internal {
                                require(address(this).balance >= amount, "Address: insufficient balance");
                        
                                // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                                (bool success, ) = recipient.call{ value: amount }("");
                                require(success, "Address: unable to send value, recipient may have reverted");
                            }
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/SafeERC20.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        /**
                         * @title SafeERC20
                         * @dev Wrappers around ERC20 operations that throw on failure (when the token
                         * contract returns false). Tokens that return no value (and instead revert or
                         * throw on failure) are also supported, non-reverting calls are assumed to be
                         * successful.
                         * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                         * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                         */
                        library SafeERC20 {
                            using SafeMath for uint256;
                            using Address for address;
                        
                            function safeTransfer(IERC20 token, address to, uint256 value) internal {
                                _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                            }
                        
                            function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                                _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                            }
                        
                            function safeApprove(IERC20 token, address spender, uint256 value) internal {
                                // safeApprove should only be called when setting an initial allowance,
                                // or when resetting it to zero. To increase and decrease it, use
                                // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                                // solhint-disable-next-line max-line-length
                                require((value == 0) || (token.allowance(address(this), spender) == 0),
                                    "SafeERC20: approve from non-zero to non-zero allowance"
                                );
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                            }
                        
                            function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                uint256 newAllowance = token.allowance(address(this), spender).add(value);
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                            }
                        
                            function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                            }
                        
                            /**
                             * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                             * on the return value: the return value is optional (but if data is returned, it must not be false).
                             * @param token The token targeted by the call.
                             * @param data The call data (encoded using abi.encode or one of its variants).
                             */
                            function _callOptionalReturn(IERC20 token, bytes memory data) private {
                                // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                                // we're implementing it ourselves.
                        
                                // A Solidity high level call has three parts:
                                //  1. The target address is checked to verify it contains contract code
                                //  2. The call itself is made, and success asserted
                                //  3. The return value is decoded, which in turn checks the size of the returned data.
                                // solhint-disable-next-line max-line-length
                                require(address(token).isContract(), "SafeERC20: call to non-contract");
                        
                                // solhint-disable-next-line avoid-low-level-calls
                                (bool success, bytes memory returndata) = address(token).call(data);
                                require(success, "SafeERC20: low-level call failed");
                        
                                if (returndata.length > 0) { // Return data is optional
                                    // solhint-disable-next-line max-line-length
                                    require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                                }
                            }
                        }
                        
                        // File: contracts/sol6/IKyberNetwork.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetwork {
                            event KyberTrade(
                                IERC20 indexed src,
                                IERC20 indexed dest,
                                uint256 ethWeiValue,
                                uint256 networkFeeWei,
                                uint256 customPlatformFeeWei,
                                bytes32[] t2eIds,
                                bytes32[] e2tIds,
                                uint256[] t2eSrcAmounts,
                                uint256[] e2tSrcAmounts,
                                uint256[] t2eRates,
                                uint256[] e2tRates
                            );
                        
                            function tradeWithHintAndFee(
                                address payable trader,
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function listTokenForReserve(
                                address reserve,
                                IERC20 token,
                                bool add
                            ) external;
                        
                            function enabled() external view returns (bool);
                        
                            function getExpectedRateWithHintAndFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            )
                                external
                                view
                                returns (
                                    uint256 expectedRateAfterNetworkFee,
                                    uint256 expectedRateAfterAllFees
                                );
                        
                            function getNetworkData()
                                external
                                view
                                returns (
                                    uint256 negligibleDiffBps,
                                    uint256 networkFeeBps,
                                    uint256 expiryTimestamp
                                );
                        
                            function maxGasPrice() external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberReserve.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberReserve {
                            function trade(
                                IERC20 srcToken,
                                uint256 srcAmount,
                                IERC20 destToken,
                                address payable destAddress,
                                uint256 conversionRate,
                                bool validate
                            ) external payable returns (bool);
                        
                            function getConversionRate(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 blockNumber
                            ) external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberFeeHandler.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberFeeHandler {
                            event RewardPaid(address indexed staker, uint256 indexed epoch, IERC20 indexed token, uint256 amount);
                            event RebatePaid(address indexed rebateWallet, IERC20 indexed token, uint256 amount);
                            event PlatformFeePaid(address indexed platformWallet, IERC20 indexed token, uint256 amount);
                            event KncBurned(uint256 kncTWei, IERC20 indexed token, uint256 amount);
                        
                            function handleFees(
                                IERC20 token,
                                address[] calldata eligibleWallets,
                                uint256[] calldata rebatePercentages,
                                address platformWallet,
                                uint256 platformFee,
                                uint256 networkFee
                            ) external payable;
                        
                            function claimReserveRebate(address rebateWallet) external returns (uint256);
                        
                            function claimPlatformFee(address platformWallet) external returns (uint256);
                        
                            function claimStakerReward(
                                address staker,
                                uint256 epoch
                            ) external returns(uint amount);
                        }
                        
                        // File: contracts/sol6/Dao/IEpochUtils.sol
                        
                        pragma solidity 0.6.6;
                        
                        interface IEpochUtils {
                            function epochPeriodInSeconds() external view returns (uint256);
                        
                            function firstEpochStartTimestamp() external view returns (uint256);
                        
                            function getCurrentEpochNumber() external view returns (uint256);
                        
                            function getEpochNumber(uint256 timestamp) external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberDao.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberDao is IEpochUtils {
                            event Voted(address indexed staker, uint indexed epoch, uint indexed campaignID, uint option);
                        
                            function getLatestNetworkFeeDataWithCache()
                                external
                                returns (uint256 feeInBps, uint256 expiryTimestamp);
                        
                            function getLatestBRRDataWithCache()
                                external
                                returns (
                                    uint256 burnInBps,
                                    uint256 rewardInBps,
                                    uint256 rebateInBps,
                                    uint256 epoch,
                                    uint256 expiryTimestamp
                                );
                        
                            function handleWithdrawal(address staker, uint256 penaltyAmount) external;
                        
                            function vote(uint256 campaignID, uint256 option) external;
                        
                            function getLatestNetworkFeeData()
                                external
                                view
                                returns (uint256 feeInBps, uint256 expiryTimestamp);
                        
                            function shouldBurnRewardForEpoch(uint256 epoch) external view returns (bool);
                        
                            /**
                             * @dev  return staker's reward percentage in precision for a past epoch only
                             *       fee handler should call this function when a staker wants to claim reward
                             *       return 0 if staker has no votes or stakes
                             */
                            function getPastEpochRewardPercentageInPrecision(address staker, uint256 epoch)
                                external
                                view
                                returns (uint256);
                        
                            /**
                             * @dev  return staker's reward percentage in precision for the current epoch
                             *       reward percentage is not finalized until the current epoch is ended
                             */
                            function getCurrentEpochRewardPercentageInPrecision(address staker)
                                external
                                view
                                returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberNetworkProxy.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetworkProxy {
                        
                            event ExecuteTrade(
                                address indexed trader,
                                IERC20 src,
                                IERC20 dest,
                                address destAddress,
                                uint256 actualSrcAmount,
                                uint256 actualDestAmount,
                                address platformWallet,
                                uint256 platformFeeBps
                            );
                        
                            /// @notice backward compatible
                            function tradeWithHint(
                                ERC20 src,
                                uint256 srcAmount,
                                ERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable walletId,
                                bytes calldata hint
                            ) external payable returns (uint256);
                        
                            function tradeWithHintAndFee(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function trade(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet
                            ) external payable returns (uint256);
                        
                            /// @notice backward compatible
                            /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
                            function getExpectedRate(
                                ERC20 src,
                                ERC20 dest,
                                uint256 srcQty
                            ) external view returns (uint256 expectedRate, uint256 worstRate);
                        
                            function getExpectedRateAfterFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external view returns (uint256 expectedRate);
                        }
                        
                        // File: contracts/sol6/IKyberStorage.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        interface IKyberStorage {
                            enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST}
                        
                            function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies)
                                external;
                        
                            function removeKyberProxy(address kyberProxy) external;
                        
                            function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external;
                        
                            function setKyberDaoContract(address _kyberDao) external;
                        
                            function getReserveId(address reserve) external view returns (bytes32 reserveId);
                        
                            function getReserveIdsFromAddresses(address[] calldata reserveAddresses)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesFromIds(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getReserveIdsPerTokenSrc(IERC20 token)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getReserveIdsPerTokenDest(IERC20 token)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesByReserveId(bytes32 reserveId)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getRebateWalletsFromIds(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (address[] memory rebateWallets);
                        
                            function getKyberProxies() external view returns (IKyberNetworkProxy[] memory);
                        
                            function getReserveDetailsByAddress(address reserve)
                                external
                                view
                                returns (
                                    bytes32 reserveId,
                                    address rebateWallet,
                                    ReserveType resType,
                                    bool isFeeAccountedFlag,
                                    bool isEntitledRebateFlag
                                );
                        
                            function getReserveDetailsById(bytes32 reserveId)
                                external
                                view
                                returns (
                                    address reserveAddress,
                                    address rebateWallet,
                                    ReserveType resType,
                                    bool isFeeAccountedFlag,
                                    bool isEntitledRebateFlag
                                );
                        
                            function getFeeAccountedData(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (bool[] memory feeAccountedArr);
                        
                            function getEntitledRebateData(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (bool[] memory entitledRebateArr);
                        
                            function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest)
                                external
                                view
                                returns (
                                    bool areAllReservesListed,
                                    bool[] memory feeAccountedArr,
                                    bool[] memory entitledRebateArr,
                                    IKyberReserve[] memory reserveAddresses);
                        
                            function isKyberProxyAdded() external view returns (bool);
                        }
                        
                        // File: contracts/sol6/IKyberMatchingEngine.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        
                        interface IKyberMatchingEngine {
                            enum ProcessWithRate {NotRequired, Required}
                        
                            function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps) external;
                        
                            function setKyberStorage(IKyberStorage _kyberStorage) external;
                        
                            function getNegligibleRateDiffBps() external view returns (uint256);
                        
                            function getTradingReserves(
                                IERC20 src,
                                IERC20 dest,
                                bool isTokenToToken,
                                bytes calldata hint
                            )
                                external
                                view
                                returns (
                                    bytes32[] memory reserveIds,
                                    uint256[] memory splitValuesBps,
                                    ProcessWithRate processWithRate
                                );
                        
                            function doMatch(
                                IERC20 src,
                                IERC20 dest,
                                uint256[] calldata srcAmounts,
                                uint256[] calldata feesAccountedDestBps,
                                uint256[] calldata rates
                            ) external view returns (uint256[] memory reserveIndexes);
                        }
                        
                        // File: contracts/sol6/IGasHelper.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IGasHelper {
                            function freeGas(
                                address platformWallet,
                                IERC20 src,
                                IERC20 dest,
                                uint256 tradeWei,
                                bytes32[] calldata t2eReserveIds,
                                bytes32[] calldata e2tReserveIds
                            ) external;
                        }
                        
                        // File: contracts/sol6/KyberNetwork.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        /**
                         *   @title kyberNetwork main contract
                         *   Interacts with contracts:
                         *       kyberDao: to retrieve fee data
                         *       kyberFeeHandler: accumulates and distributes trade fees
                         *       kyberMatchingEngine: parse user hint and run reserve matching algorithm
                         *       kyberStorage: store / access reserves, token listings and contract addresses
                         *       kyberReserve(s): query rate and trade
                         */
                        contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, ReentrancyGuard {
                            using SafeERC20 for IERC20;
                        
                            struct NetworkFeeData {
                                uint64 expiryTimestamp;
                                uint16 feeBps;
                            }
                        
                            /// @notice Stores work data for reserves (either for token -> eth, or eth -> token)
                            /// @dev Variables are in-place, ie. reserve with addresses[i] has id of ids[i], offers rate of rates[i], etc.
                            /// @param addresses List of reserve addresses selected for the trade
                            /// @param ids List of reserve ids, to be used for KyberTrade event
                            /// @param rates List of rates that were offered by the reserves
                            /// @param isFeeAccountedFlags List of reserves requiring users to pay network fee
                            /// @param isEntitledRebateFlags List of reserves eligible for rebates
                            /// @param splitsBps List of proportions of trade amount allocated to the reserves.
                            ///     If there is only 1 reserve, then it should have a value of 10000 bps
                            /// @param srcAmounts Source amount per reserve.
                            /// @param decimals Token decimals. Src decimals when for src -> eth, dest decimals when eth -> dest
                            struct ReservesData {
                                IKyberReserve[] addresses;
                                bytes32[] ids;
                                uint256[] rates;
                                bool[] isFeeAccountedFlags;
                                bool[] isEntitledRebateFlags;
                                uint256[] splitsBps;
                                uint256[] srcAmounts;
                                uint256 decimals;
                            }
                        
                            /// @notice Main trade data structure, is initialised and used for the entire trade flow
                            /// @param input Initialised when initTradeInput is called. Stores basic trade info
                            /// @param tokenToEth Stores information about reserves that were selected for src -> eth side of trade
                            /// @param ethToToken Stores information about reserves that were selected for eth -> dest side of trade
                            /// @param tradeWei Trade amount in ether wei, before deducting fees.
                            /// @param networkFeeWei Network fee in ether wei. For t2t trades, it can go up to 200% of networkFeeBps
                            /// @param platformFeeWei Platform fee in ether wei
                            /// @param networkFeeBps Network fee bps determined by kyberDao, or default value
                            /// @param numEntitledRebateReserves No. of reserves that are eligible for rebates
                            /// @param feeAccountedBps Proportion of this trade that fee is accounted to, in BPS. Up to 2 * BPS
                            struct TradeData {
                                TradeInput input;
                                ReservesData tokenToEth;
                                ReservesData ethToToken;
                                uint256 tradeWei;
                                uint256 networkFeeWei;
                                uint256 platformFeeWei;
                                uint256 networkFeeBps;
                                uint256 numEntitledRebateReserves;
                                uint256 feeAccountedBps; // what part of this trade is fee paying. for token -> token - up to 200%
                            }
                        
                            struct TradeInput {
                                address payable trader;
                                IERC20 src;
                                uint256 srcAmount;
                                IERC20 dest;
                                address payable destAddress;
                                uint256 maxDestAmount;
                                uint256 minConversionRate;
                                address platformWallet;
                                uint256 platformFeeBps;
                            }
                        
                            uint256 internal constant PERM_HINT_GET_RATE = 1 << 255; // for backwards compatibility
                            uint256 internal constant DEFAULT_NETWORK_FEE_BPS = 25; // till we read value from kyberDao
                            uint256 internal constant MAX_APPROVED_PROXIES = 2; // limit number of proxies that can trade here
                        
                            IKyberFeeHandler internal kyberFeeHandler;
                            IKyberDao internal kyberDao;
                            IKyberMatchingEngine internal kyberMatchingEngine;
                            IKyberStorage internal kyberStorage;
                            IGasHelper internal gasHelper;
                        
                            NetworkFeeData internal networkFeeData; // data is feeBps and expiry timestamp
                            uint256 internal maxGasPriceValue = 50 * 1000 * 1000 * 1000; // 50 gwei
                            bool internal isEnabled = false; // is network enabled
                        
                            mapping(address => bool) internal kyberProxyContracts;
                        
                            event EtherReceival(address indexed sender, uint256 amount);
                            event KyberFeeHandlerUpdated(IKyberFeeHandler newKyberFeeHandler);
                            event KyberMatchingEngineUpdated(IKyberMatchingEngine newKyberMatchingEngine);
                            event GasHelperUpdated(IGasHelper newGasHelper);
                            event KyberDaoUpdated(IKyberDao newKyberDao);
                            event KyberNetworkParamsSet(uint256 maxGasPrice, uint256 negligibleRateDiffBps);
                            event KyberNetworkSetEnable(bool isEnabled);
                            event KyberProxyAdded(address kyberProxy);
                            event KyberProxyRemoved(address kyberProxy);
                        
                            event ListedReservesForToken(
                                IERC20 indexed token,
                                address[] reserves,
                                bool add
                            );
                        
                            constructor(address _admin, IKyberStorage _kyberStorage)
                                public
                                WithdrawableNoModifiers(_admin)
                            {
                                updateNetworkFee(now, DEFAULT_NETWORK_FEE_BPS);
                                kyberStorage = _kyberStorage;
                            }
                        
                            receive() external payable {
                                emit EtherReceival(msg.sender, msg.value);
                            }
                        
                            /// @notice Backward compatible function
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Trade from src to dest token and sends dest token to destAddress
                            /// @param trader Address of the taker side of this trade
                            /// @param src Source token
                            /// @param srcAmount Amount of src tokens in twei
                            /// @param dest Destination token
                            /// @param destAddress Address to send tokens to
                            /// @param maxDestAmount A limit on the amount of dest tokens in twei
                            /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
                            /// @param walletId Platform wallet address for receiving fees
                            /// @param hint Advanced instructions for running the trade 
                            /// @return destAmount Amount of actual dest tokens in twei
                            function tradeWithHint(
                                address payable trader,
                                ERC20 src,
                                uint256 srcAmount,
                                ERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable walletId,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount) {
                                TradeData memory tradeData = initTradeInput({
                                    trader: trader,
                                    src: src,
                                    dest: dest,
                                    srcAmount: srcAmount,
                                    destAddress: destAddress,
                                    maxDestAmount: maxDestAmount,
                                    minConversionRate: minConversionRate,
                                    platformWallet: walletId,
                                    platformFeeBps: 0
                                });
                        
                                return trade(tradeData, hint);
                            }
                        
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Trade from src to dest token and sends dest token to destAddress
                            /// @param trader Address of the taker side of this trade
                            /// @param src Source token
                            /// @param srcAmount Amount of src tokens in twei
                            /// @param dest Destination token
                            /// @param destAddress Address to send tokens to
                            /// @param maxDestAmount A limit on the amount of dest tokens in twei
                            /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
                            /// @param platformWallet Platform wallet address for receiving fees
                            /// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 1000 = 10%
                            /// @param hint Advanced instructions for running the trade 
                            /// @return destAmount Amount of actual dest tokens in twei
                            function tradeWithHintAndFee(
                                address payable trader,
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable override returns (uint256 destAmount) {
                                TradeData memory tradeData = initTradeInput({
                                    trader: trader,
                                    src: src,
                                    dest: dest,
                                    srcAmount: srcAmount,
                                    destAddress: destAddress,
                                    maxDestAmount: maxDestAmount,
                                    minConversionRate: minConversionRate,
                                    platformWallet: platformWallet,
                                    platformFeeBps: platformFeeBps
                                });
                        
                                return trade(tradeData, hint);
                            }
                        
                            /// @notice Can be called only by kyberStorage
                            /// @dev Allow or prevent to trade token -> eth for a reserve
                            /// @param reserve The reserve address
                            /// @param token Token address
                            /// @param add If true, then give reserve token allowance, otherwise set zero allowance
                            function listTokenForReserve(
                                address reserve,
                                IERC20 token,
                                bool add
                            ) external override {
                                require(msg.sender == address(kyberStorage), "only kyberStorage");
                        
                                if (add) {
                                    token.safeApprove(reserve, MAX_ALLOWANCE);
                                    setDecimals(token);
                                } else {
                                    token.safeApprove(reserve, 0);
                                }
                            }
                        
                            /// @notice Can be called only by operator
                            /// @dev Allow or prevent to trade token -> eth for list of reserves
                            ///      Useful for migration to new network contract
                            ///      Call storage to get list of reserves supporting token -> eth
                            /// @param token Token address
                            /// @param startIndex start index in reserves list
                            /// @param endIndex end index in reserves list (can be larger)
                            /// @param add If true, then give reserve token allowance, otherwise set zero allowance
                            function listReservesForToken(
                                IERC20 token,
                                uint256 startIndex,
                                uint256 endIndex,
                                bool add
                            ) external {
                                onlyOperator();
                        
                                if (startIndex > endIndex) {
                                    // no need to do anything
                                    return;
                                }
                        
                                address[] memory reserves = kyberStorage.getReserveAddressesPerTokenSrc(
                                    token, startIndex, endIndex
                                );
                        
                                if (reserves.length == 0) {
                                    // no need to do anything
                                    return;
                                }
                        
                                for(uint i = 0; i < reserves.length; i++) {
                                    if (add) {
                                        token.safeApprove(reserves[i], MAX_ALLOWANCE);
                                        setDecimals(token);
                                    } else {
                                        token.safeApprove(reserves[i], 0);
                                    }
                                }
                        
                                emit ListedReservesForToken(token, reserves, add);
                            }
                        
                            function setContracts(
                                IKyberFeeHandler _kyberFeeHandler,
                                IKyberMatchingEngine _kyberMatchingEngine,
                                IGasHelper _gasHelper
                            ) external virtual {
                                onlyAdmin();
                        
                                if (kyberFeeHandler != _kyberFeeHandler) {
                                    kyberFeeHandler = _kyberFeeHandler;
                                    emit KyberFeeHandlerUpdated(_kyberFeeHandler);
                                }
                        
                                if (kyberMatchingEngine != _kyberMatchingEngine) {
                                    kyberMatchingEngine = _kyberMatchingEngine;
                                    emit KyberMatchingEngineUpdated(_kyberMatchingEngine);
                                }
                        
                                if ((_gasHelper != IGasHelper(0)) && (_gasHelper != gasHelper)) {
                                    gasHelper = _gasHelper;
                                    emit GasHelperUpdated(_gasHelper);
                                }
                        
                                kyberStorage.setContracts(address(_kyberFeeHandler), address(_kyberMatchingEngine));
                                require(_kyberFeeHandler != IKyberFeeHandler(0));
                                require(_kyberMatchingEngine != IKyberMatchingEngine(0));
                            }
                        
                            function setKyberDaoContract(IKyberDao _kyberDao) external {
                                // enable setting null kyberDao address
                                onlyAdmin();
                                if (kyberDao != _kyberDao) {
                                    kyberDao = _kyberDao;
                                    kyberStorage.setKyberDaoContract(address(_kyberDao));
                                    emit KyberDaoUpdated(_kyberDao);
                                }
                            }
                        
                            function setParams(uint256 _maxGasPrice, uint256 _negligibleRateDiffBps) external {
                                onlyAdmin();
                                maxGasPriceValue = _maxGasPrice;
                                kyberMatchingEngine.setNegligibleRateDiffBps(_negligibleRateDiffBps);
                                emit KyberNetworkParamsSet(maxGasPriceValue, _negligibleRateDiffBps);
                            }
                        
                            function setEnable(bool enable) external {
                                onlyAdmin();
                        
                                if (enable) {
                                    require(kyberFeeHandler != IKyberFeeHandler(0));
                                    require(kyberMatchingEngine != IKyberMatchingEngine(0));
                                    require(kyberStorage.isKyberProxyAdded());
                                }
                        
                                isEnabled = enable;
                        
                                emit KyberNetworkSetEnable(isEnabled);
                            }
                        
                            /// @dev No. of kyberProxies is capped
                            function addKyberProxy(address kyberProxy) external virtual {
                                onlyAdmin();
                                kyberStorage.addKyberProxy(kyberProxy, MAX_APPROVED_PROXIES);
                                require(kyberProxy != address(0));
                                require(!kyberProxyContracts[kyberProxy]);
                        
                                kyberProxyContracts[kyberProxy] = true;
                        
                                emit KyberProxyAdded(kyberProxy);
                            }
                        
                            function removeKyberProxy(address kyberProxy) external virtual {
                                onlyAdmin();
                        
                                kyberStorage.removeKyberProxy(kyberProxy);
                        
                                require(kyberProxyContracts[kyberProxy]);
                        
                                kyberProxyContracts[kyberProxy] = false;
                        
                                emit KyberProxyRemoved(kyberProxy);
                            }
                        
                            /// @dev gets the expected rates when trading src -> dest token, with / without fees
                            /// @param src Source token
                            /// @param dest Destination token
                            /// @param srcQty Amount of src tokens in twei
                            /// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 1000 = 10%
                            /// @param hint Advanced instructions for running the trade 
                            /// @return rateWithNetworkFee Rate after deducting network fee but excluding platform fee
                            /// @return rateWithAllFees = actual rate. Rate after accounting for both network and platform fees
                            function getExpectedRateWithHintAndFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            )
                                external
                                view
                                override
                                returns (
                                    uint256 rateWithNetworkFee,
                                    uint256 rateWithAllFees
                                )
                            {
                                if (src == dest) return (0, 0);
                        
                                TradeData memory tradeData = initTradeInput({
                                    trader: payable(address(0)),
                                    src: src,
                                    dest: dest,
                                    srcAmount: (srcQty == 0) ? 1 : srcQty,
                                    destAddress: payable(address(0)),
                                    maxDestAmount: 2**255,
                                    minConversionRate: 0,
                                    platformWallet: payable(address(0)),
                                    platformFeeBps: platformFeeBps
                                });
                        
                                tradeData.networkFeeBps = getNetworkFee();
                        
                                uint256 destAmount;
                                (destAmount, rateWithNetworkFee) = calcRatesAndAmounts(tradeData, hint);
                        
                                rateWithAllFees = calcRateFromQty(
                                    tradeData.input.srcAmount,
                                    destAmount,
                                    tradeData.tokenToEth.decimals,
                                    tradeData.ethToToken.decimals
                                );
                            }
                        
                            /// @notice Backward compatible API
                            /// @dev Gets the expected and slippage rate for exchanging src -> dest token
                            /// @dev worstRate is hardcoded to be 3% lower of expectedRate
                            /// @param src Source token
                            /// @param dest Destination token
                            /// @param srcQty Amount of src tokens in twei
                            /// @return expectedRate for a trade after deducting network fee. 
                            /// @return worstRate for a trade. Calculated to be expectedRate * 97 / 100
                            function getExpectedRate(
                                ERC20 src,
                                ERC20 dest,
                                uint256 srcQty
                            ) external view returns (uint256 expectedRate, uint256 worstRate) {
                                if (src == dest) return (0, 0);
                                uint256 qty = srcQty & ~PERM_HINT_GET_RATE;
                        
                                TradeData memory tradeData = initTradeInput({
                                    trader: payable(address(0)),
                                    src: src,
                                    dest: dest,
                                    srcAmount: (qty == 0) ? 1 : qty,
                                    destAddress: payable(address(0)),
                                    maxDestAmount: 2**255,
                                    minConversionRate: 0,
                                    platformWallet: payable(address(0)),
                                    platformFeeBps: 0
                                });
                        
                                tradeData.networkFeeBps = getNetworkFee();
                        
                                (, expectedRate) = calcRatesAndAmounts(tradeData, "");
                        
                                worstRate = (expectedRate * 97) / 100; // backward compatible formula
                            }
                        
                            /// @notice Returns some data about the network
                            /// @param negligibleDiffBps Negligible rate difference (in basis pts) when searching best rate
                            /// @param networkFeeBps Network fees to be charged (in basis pts)
                            /// @param expiryTimestamp Timestamp for which networkFeeBps will expire,
                            ///     and needs to be updated by calling kyberDao contract / set to default
                            function getNetworkData()
                                external
                                view
                                override
                                returns (
                                    uint256 negligibleDiffBps,
                                    uint256 networkFeeBps,
                                    uint256 expiryTimestamp
                                )
                            {
                                (networkFeeBps, expiryTimestamp) = readNetworkFeeData();
                                negligibleDiffBps = kyberMatchingEngine.getNegligibleRateDiffBps();
                                return (negligibleDiffBps, networkFeeBps, expiryTimestamp);
                            }
                        
                            function getContracts()
                                external
                                view
                                returns (
                                    IKyberFeeHandler kyberFeeHandlerAddress,
                                    IKyberDao kyberDaoAddress,
                                    IKyberMatchingEngine kyberMatchingEngineAddress,
                                    IKyberStorage kyberStorageAddress,
                                    IGasHelper gasHelperAddress,
                                    IKyberNetworkProxy[] memory kyberProxyAddresses
                                )
                            {
                                return (
                                    kyberFeeHandler,
                                    kyberDao,
                                    kyberMatchingEngine,
                                    kyberStorage,
                                    gasHelper,
                                    kyberStorage.getKyberProxies()
                                );
                            }
                        
                            /// @notice returns the max gas price allowable for trades
                            function maxGasPrice() external view override returns (uint256) {
                                return maxGasPriceValue;
                            }
                        
                            /// @notice returns status of the network. If disabled, trades cannot happen.
                            function enabled() external view override returns (bool) {
                                return isEnabled;
                            }
                        
                            /// @notice Gets network fee from the kyberDao (or use default).
                            ///     For trade function, so that data can be updated and cached.
                            /// @dev Note that this function can be triggered by anyone, so that
                            ///     the first trader of a new epoch can avoid incurring extra gas costs
                            function getAndUpdateNetworkFee() public returns (uint256 networkFeeBps) {
                                uint256 expiryTimestamp;
                        
                                (networkFeeBps, expiryTimestamp) = readNetworkFeeData();
                        
                                if (expiryTimestamp < now && kyberDao != IKyberDao(0)) {
                                    (networkFeeBps, expiryTimestamp) = kyberDao.getLatestNetworkFeeDataWithCache();
                                    updateNetworkFee(expiryTimestamp, networkFeeBps);
                                }
                            }
                        
                            /// @notice Calculates platform fee and reserve rebate percentages for the trade.
                            ///     Transfers eth and rebate wallet data to kyberFeeHandler
                            function handleFees(TradeData memory tradeData) internal {
                                uint256 sentFee = tradeData.networkFeeWei + tradeData.platformFeeWei;
                                //no need to handle fees if total fee is zero
                                if (sentFee == 0)
                                    return;
                        
                                // update reserve eligibility and rebate percentages
                                (
                                    address[] memory rebateWallets,
                                    uint256[] memory rebatePercentBps
                                ) = calculateRebates(tradeData);
                        
                                // send total fee amount to fee handler with reserve data
                                kyberFeeHandler.handleFees{value: sentFee}(
                                    ETH_TOKEN_ADDRESS,
                                    rebateWallets,
                                    rebatePercentBps,
                                    tradeData.input.platformWallet,
                                    tradeData.platformFeeWei,
                                    tradeData.networkFeeWei
                                );
                            }
                        
                            function updateNetworkFee(uint256 expiryTimestamp, uint256 feeBps) internal {
                                require(expiryTimestamp < 2**64, "expiry overflow");
                                require(feeBps < BPS / 2, "fees exceed BPS");
                        
                                networkFeeData.expiryTimestamp = uint64(expiryTimestamp);
                                networkFeeData.feeBps = uint16(feeBps);
                            }
                        
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Do one trade with each reserve in reservesData, verifying network balance 
                            ///    as expected to ensure reserves take correct src amount
                            /// @param src Source token
                            /// @param dest Destination token
                            /// @param destAddress Address to send tokens to
                            /// @param reservesData reservesData to trade
                            /// @param expectedDestAmount Amount to be transferred to destAddress
                            /// @param srcDecimals Decimals of source token
                            /// @param destDecimals Decimals of destination token
                            function doReserveTrades(
                                IERC20 src,
                                IERC20 dest,
                                address payable destAddress,
                                ReservesData memory reservesData,
                                uint256 expectedDestAmount,
                                uint256 srcDecimals,
                                uint256 destDecimals
                            ) internal virtual {
                        
                                if (src == dest) {
                                    // eth -> eth, need not do anything except for token -> eth: transfer eth to destAddress
                                    if (destAddress != (address(this))) {
                                        (bool success, ) = destAddress.call{value: expectedDestAmount}("");
                                        require(success, "send dest qty failed");
                                    }
                                    return;
                                }
                        
                                tradeAndVerifyNetworkBalance(
                                    reservesData,
                                    src,
                                    dest,
                                    srcDecimals,
                                    destDecimals
                                );
                        
                                if (destAddress != address(this)) {
                                    // for eth -> token / token -> token, transfer tokens to destAddress
                                    dest.safeTransfer(destAddress, expectedDestAmount);
                                }
                            }
                        
                            /// @dev call trade from reserves and verify balances
                            /// @param reservesData reservesData to trade
                            /// @param src Source token of trade
                            /// @param dest Destination token of trade
                            /// @param srcDecimals Decimals of source token
                            /// @param destDecimals Decimals of destination token
                            function tradeAndVerifyNetworkBalance(
                                ReservesData memory reservesData,
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcDecimals,
                                uint256 destDecimals
                            ) internal
                            {
                                // only need to verify src balance if src is not eth
                                uint256 srcBalanceBefore = (src == ETH_TOKEN_ADDRESS) ? 0 : getBalance(src, address(this));
                                uint256 destBalanceBefore = getBalance(dest, address(this));
                        
                                for(uint256 i = 0; i < reservesData.addresses.length; i++) {
                                    uint256 callValue = (src == ETH_TOKEN_ADDRESS) ? reservesData.srcAmounts[i] : 0;
                                    require(
                                        reservesData.addresses[i].trade{value: callValue}(
                                            src,
                                            reservesData.srcAmounts[i],
                                            dest,
                                            address(this),
                                            reservesData.rates[i],
                                            true
                                        ),
                                        "reserve trade failed"
                                    );
                        
                                    uint256 balanceAfter;
                                    if (src != ETH_TOKEN_ADDRESS) {
                                        // verify src balance only if it is not eth
                                        balanceAfter = getBalance(src, address(this));
                                        // verify correct src amount is taken
                                        if (srcBalanceBefore >= balanceAfter && srcBalanceBefore - balanceAfter > reservesData.srcAmounts[i]) {
                                            revert("reserve takes high amount");
                                        }
                                        srcBalanceBefore = balanceAfter;
                                    }
                        
                                    // verify correct dest amount is received
                                    uint256 expectedDestAmount = calcDstQty(
                                        reservesData.srcAmounts[i],
                                        srcDecimals,
                                        destDecimals,
                                        reservesData.rates[i]
                                    );
                                    balanceAfter = getBalance(dest, address(this));
                                    if (balanceAfter < destBalanceBefore || balanceAfter - destBalanceBefore < expectedDestAmount) {
                                        revert("reserve returns low amount");
                                    }
                                    destBalanceBefore = balanceAfter;
                                }
                            }
                        
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Trade API for kyberNetwork
                            /// @param tradeData Main trade data object for trade info to be stored
                            function trade(TradeData memory tradeData, bytes memory hint)
                                internal
                                virtual
                                nonReentrant
                                returns (uint256 destAmount)
                            {
                                tradeData.networkFeeBps = getAndUpdateNetworkFee();
                        
                                validateTradeInput(tradeData.input);
                        
                                uint256 rateWithNetworkFee;
                                (destAmount, rateWithNetworkFee) = calcRatesAndAmounts(tradeData, hint);
                        
                                require(rateWithNetworkFee > 0, "trade invalid, if hint involved, try parseHint API");
                                require(rateWithNetworkFee < MAX_RATE, "rate > MAX_RATE");
                                require(rateWithNetworkFee >= tradeData.input.minConversionRate, "rate < min rate");
                        
                                uint256 actualSrcAmount;
                        
                                if (destAmount > tradeData.input.maxDestAmount) {
                                    // notice tradeData passed by reference and updated
                                    destAmount = tradeData.input.maxDestAmount;
                                    actualSrcAmount = calcTradeSrcAmountFromDest(tradeData);
                                } else {
                                    actualSrcAmount = tradeData.input.srcAmount;
                                }
                        
                                // token -> eth
                                doReserveTrades(
                                    tradeData.input.src,
                                    ETH_TOKEN_ADDRESS,
                                    address(this),
                                    tradeData.tokenToEth,
                                    tradeData.tradeWei,
                                    tradeData.tokenToEth.decimals,
                                    ETH_DECIMALS
                                );
                        
                                // eth -> token
                                doReserveTrades(
                                    ETH_TOKEN_ADDRESS,
                                    tradeData.input.dest,
                                    tradeData.input.destAddress,
                                    tradeData.ethToToken,
                                    destAmount,
                                    ETH_DECIMALS,
                                    tradeData.ethToToken.decimals
                                );
                        
                                handleChange(
                                    tradeData.input.src,
                                    tradeData.input.srcAmount,
                                    actualSrcAmount,
                                    tradeData.input.trader
                                );
                        
                                handleFees(tradeData);
                        
                                emit KyberTrade({
                                    src: tradeData.input.src,
                                    dest: tradeData.input.dest,
                                    ethWeiValue: tradeData.tradeWei,
                                    networkFeeWei: tradeData.networkFeeWei,
                                    customPlatformFeeWei: tradeData.platformFeeWei,
                                    t2eIds: tradeData.tokenToEth.ids,
                                    e2tIds: tradeData.ethToToken.ids,
                                    t2eSrcAmounts: tradeData.tokenToEth.srcAmounts,
                                    e2tSrcAmounts: tradeData.ethToToken.srcAmounts,
                                    t2eRates: tradeData.tokenToEth.rates,
                                    e2tRates: tradeData.ethToToken.rates
                                });
                        
                                if (gasHelper != IGasHelper(0)) {
                                    (bool success, ) = address(gasHelper).call(
                                        abi.encodeWithSignature(
                                            "freeGas(address,address,address,uint256,bytes32[],bytes32[])",
                                            tradeData.input.platformWallet,
                                            tradeData.input.src,
                                            tradeData.input.dest,
                                            tradeData.tradeWei,
                                            tradeData.tokenToEth.ids,
                                            tradeData.ethToToken.ids
                                        )
                                    );
                                    // remove compilation warning
                                    success;
                                }
                        
                                return (destAmount);
                            }
                        
                            /// @notice If user maxDestAmount < actual dest amount, actualSrcAmount will be < srcAmount
                            /// Calculate the change, and send it back to the user
                            function handleChange(
                                IERC20 src,
                                uint256 srcAmount,
                                uint256 requiredSrcAmount,
                                address payable trader
                            ) internal {
                                if (requiredSrcAmount < srcAmount) {
                                    // if there is "change" send back to trader
                                    if (src == ETH_TOKEN_ADDRESS) {
                                        (bool success, ) = trader.call{value: (srcAmount - requiredSrcAmount)}("");
                                        require(success, "Send change failed");
                                    } else {
                                        src.safeTransfer(trader, (srcAmount - requiredSrcAmount));
                                    }
                                }
                            }
                        
                            function initTradeInput(
                                address payable trader,
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps
                            ) internal view returns (TradeData memory tradeData) {
                                tradeData.input.trader = trader;
                                tradeData.input.src = src;
                                tradeData.input.srcAmount = srcAmount;
                                tradeData.input.dest = dest;
                                tradeData.input.destAddress = destAddress;
                                tradeData.input.maxDestAmount = maxDestAmount;
                                tradeData.input.minConversionRate = minConversionRate;
                                tradeData.input.platformWallet = platformWallet;
                                tradeData.input.platformFeeBps = platformFeeBps;
                        
                                tradeData.tokenToEth.decimals = getDecimals(src);
                                tradeData.ethToToken.decimals = getDecimals(dest);
                            }
                        
                            /// @notice This function does all calculations to find trade dest amount without accounting 
                            ///        for maxDestAmount. Part of this process includes:
                            ///        - Call kyberMatchingEngine to parse hint and get an optional reserve list to trade.
                            ///        - Query reserve rates and call kyberMatchingEngine to use best reserve.
                            ///        - Calculate trade values and fee values.
                            ///     This function should set all TradeData information so that it can be later used without 
                            ///         any ambiguity
                            /// @param tradeData Main trade data object for trade info to be stored
                            /// @param hint Advanced user instructions for the trade 
                            function calcRatesAndAmounts(TradeData memory tradeData, bytes memory hint)
                                internal
                                view
                                returns (uint256 destAmount, uint256 rateWithNetworkFee)
                            {
                                validateFeeInput(tradeData.input, tradeData.networkFeeBps);
                        
                                // token -> eth: find best reserves match and calculate wei amount
                                tradeData.tradeWei = calcDestQtyAndMatchReserves(
                                    tradeData.input.src,
                                    ETH_TOKEN_ADDRESS,
                                    tradeData.input.srcAmount,
                                    tradeData,
                                    tradeData.tokenToEth,
                                    hint
                                );
                        
                                require(tradeData.tradeWei <= MAX_QTY, "Trade wei > MAX_QTY");
                                if (tradeData.tradeWei == 0) {
                                    return (0, 0);
                                }
                        
                                // calculate fees
                                tradeData.platformFeeWei = (tradeData.tradeWei * tradeData.input.platformFeeBps) / BPS;
                                tradeData.networkFeeWei =
                                    (((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) /
                                    BPS;
                        
                                assert(tradeData.tradeWei >= (tradeData.networkFeeWei + tradeData.platformFeeWei));
                        
                                // eth -> token: find best reserves match and calculate trade dest amount
                                uint256 actualSrcWei = tradeData.tradeWei -
                                    tradeData.networkFeeWei -
                                    tradeData.platformFeeWei;
                        
                                destAmount = calcDestQtyAndMatchReserves(
                                    ETH_TOKEN_ADDRESS,
                                    tradeData.input.dest,
                                    actualSrcWei,
                                    tradeData,
                                    tradeData.ethToToken,
                                    hint
                                );
                        
                                tradeData.networkFeeWei =
                                    (((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) /
                                    BPS;
                        
                                rateWithNetworkFee = calcRateFromQty(
                                    tradeData.input.srcAmount * (BPS - tradeData.input.platformFeeBps) / BPS,
                                    destAmount,
                                    tradeData.tokenToEth.decimals,
                                    tradeData.ethToToken.decimals
                                );
                            }
                        
                            /// @notice Get trading reserves, source amounts, and calculate dest qty
                            /// Store information into tradeData
                            function calcDestQtyAndMatchReserves(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                TradeData memory tradeData,
                                ReservesData memory reservesData,
                                bytes memory hint
                            ) internal view returns (uint256 destAmount) {
                                if (src == dest) {
                                    return srcAmount;
                                }
                        
                                IKyberMatchingEngine.ProcessWithRate processWithRate;
                        
                                // get reserve list from kyberMatchingEngine
                                (reservesData.ids, reservesData.splitsBps, processWithRate) =
                                    kyberMatchingEngine.getTradingReserves(
                                    src,
                                    dest,
                                    (tradeData.input.src != ETH_TOKEN_ADDRESS) && (tradeData.input.dest != ETH_TOKEN_ADDRESS),
                                    hint
                                );
                                bool areAllReservesListed;
                                (areAllReservesListed, reservesData.isFeeAccountedFlags, reservesData.isEntitledRebateFlags, reservesData.addresses)
                                    = kyberStorage.getReservesData(reservesData.ids, src, dest);
                        
                                if(!areAllReservesListed) {
                                    return 0;
                                }
                        
                                require(reservesData.ids.length == reservesData.splitsBps.length, "bad split array");
                                require(reservesData.ids.length == reservesData.isFeeAccountedFlags.length, "bad fee array");
                                require(reservesData.ids.length == reservesData.isEntitledRebateFlags.length, "bad rebate array");
                                require(reservesData.ids.length == reservesData.addresses.length, "bad addresses array");
                        
                                // calculate src trade amount per reserve and query rates
                                // set data in reservesData struct
                                uint256[] memory feesAccountedDestBps = calcSrcAmountsAndGetRates(
                                    reservesData,
                                    src,
                                    dest,
                                    srcAmount,
                                    tradeData
                                );
                        
                                // if matching engine requires processing with rate data. call doMatch and update reserve list
                                if (processWithRate == IKyberMatchingEngine.ProcessWithRate.Required) {
                                    uint256[] memory selectedIndexes = kyberMatchingEngine.doMatch(
                                        src,
                                        dest,
                                        reservesData.srcAmounts,
                                        feesAccountedDestBps,
                                        reservesData.rates
                                    );
                        
                                    updateReservesList(reservesData, selectedIndexes);
                                }
                        
                                // calculate dest amount and fee paying data of this part (t2e or e2t)
                                destAmount = validateTradeCalcDestQtyAndFeeData(src, reservesData, tradeData);
                            }
                        
                            /// @notice Calculates source amounts per reserve. Does get rate call
                            function calcSrcAmountsAndGetRates(
                                ReservesData memory reservesData,
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                TradeData memory tradeData
                            ) internal view returns (uint256[] memory feesAccountedDestBps) {
                                uint256 numReserves = reservesData.ids.length;
                                uint256 srcAmountAfterFee;
                                uint256 destAmountFeeBps;
                        
                                if (src == ETH_TOKEN_ADDRESS) {
                                    // @notice srcAmount is after deducting fees from tradeWei
                                    // @notice using tradeWei to calculate fee so eth -> token symmetric to token -> eth
                                    srcAmountAfterFee = srcAmount - 
                                        (tradeData.tradeWei * tradeData.networkFeeBps / BPS);
                                } else { 
                                    srcAmountAfterFee = srcAmount;
                                    destAmountFeeBps = tradeData.networkFeeBps;
                                }
                        
                                reservesData.srcAmounts = new uint256[](numReserves);
                                reservesData.rates = new uint256[](numReserves);
                                feesAccountedDestBps = new uint256[](numReserves);
                        
                                // iterate reserve list. validate data. calculate srcAmount according to splits and fee data.
                                for (uint256 i = 0; i < numReserves; i++) {
                                    require(
                                        reservesData.splitsBps[i] > 0 && reservesData.splitsBps[i] <= BPS,
                                        "invalid split bps"
                                    );
                        
                                    if (reservesData.isFeeAccountedFlags[i]) {
                                        reservesData.srcAmounts[i] = srcAmountAfterFee * reservesData.splitsBps[i] / BPS;
                                        feesAccountedDestBps[i] = destAmountFeeBps;
                                    } else {
                                        reservesData.srcAmounts[i] = (srcAmount * reservesData.splitsBps[i]) / BPS;
                                    }
                        
                                    // get rate with calculated src amount
                                    reservesData.rates[i] = reservesData.addresses[i].getConversionRate(
                                        src,
                                        dest,
                                        reservesData.srcAmounts[i],
                                        block.number
                                    );
                                }
                            }
                        
                            function calculateRebates(TradeData memory tradeData)
                                internal
                                view
                                returns (address[] memory rebateWallets, uint256[] memory rebatePercentBps)
                            {
                                rebateWallets = new address[](tradeData.numEntitledRebateReserves);
                                rebatePercentBps = new uint256[](tradeData.numEntitledRebateReserves);
                                if (tradeData.numEntitledRebateReserves == 0) {
                                    return (rebateWallets, rebatePercentBps);
                                }
                        
                                uint256 index;
                                bytes32[] memory rebateReserveIds = new bytes32[](tradeData.numEntitledRebateReserves);
                        
                                // token -> eth
                                index = createRebateEntitledList(
                                    rebateReserveIds,
                                    rebatePercentBps,
                                    tradeData.tokenToEth,
                                    index,
                                    tradeData.feeAccountedBps
                                );
                        
                                // eth -> token
                                createRebateEntitledList(
                                    rebateReserveIds,
                                    rebatePercentBps,
                                    tradeData.ethToToken,
                                    index,
                                    tradeData.feeAccountedBps
                                );
                        
                                rebateWallets = kyberStorage.getRebateWalletsFromIds(rebateReserveIds);
                            }
                        
                            function createRebateEntitledList(
                                bytes32[] memory rebateReserveIds,
                                uint256[] memory rebatePercentBps,
                                ReservesData memory reservesData,
                                uint256 index,
                                uint256 feeAccountedBps
                            ) internal pure returns (uint256) {
                                uint256 _index = index;
                        
                                for (uint256 i = 0; i < reservesData.isEntitledRebateFlags.length; i++) {
                                    if (reservesData.isEntitledRebateFlags[i]) {
                                        rebateReserveIds[_index] = reservesData.ids[i];
                                        rebatePercentBps[_index] = (reservesData.splitsBps[i] * BPS) / feeAccountedBps;
                                        _index++;
                                    }
                                }
                                return _index;
                            }
                        
                            /// @dev Checks a trade input validity, including correct src amounts
                            /// @param input Trade input structure
                            function validateTradeInput(TradeInput memory input) internal view
                            {
                                require(isEnabled, "network disabled");
                                require(kyberProxyContracts[msg.sender], "bad sender");
                                require(tx.gasprice <= maxGasPriceValue, "gas price");
                                require(input.srcAmount <= MAX_QTY, "srcAmt > MAX_QTY");
                                require(input.srcAmount != 0, "0 srcAmt");
                                require(input.destAddress != address(0), "dest add 0");
                                require(input.src != input.dest, "src = dest");
                        
                                if (input.src == ETH_TOKEN_ADDRESS) {
                                    require(msg.value == input.srcAmount); // kyberProxy issues message here
                                } else {
                                    require(msg.value == 0); // kyberProxy issues message here
                                    // funds should have been moved to this contract already.
                                    require(input.src.balanceOf(address(this)) >= input.srcAmount, "no tokens");
                                }
                            }
                        
                            /// @notice Gets the network fee from kyberDao (or use default). View function for getExpectedRate
                            function getNetworkFee() internal view returns (uint256 networkFeeBps) {
                                uint256 expiryTimestamp;
                                (networkFeeBps, expiryTimestamp) = readNetworkFeeData();
                        
                                if (expiryTimestamp < now && kyberDao != IKyberDao(0)) {
                                    (networkFeeBps, expiryTimestamp) = kyberDao.getLatestNetworkFeeData();
                                }
                            }
                        
                            function readNetworkFeeData() internal view returns (uint256 feeBps, uint256 expiryTimestamp) {
                                feeBps = uint256(networkFeeData.feeBps);
                                expiryTimestamp = uint256(networkFeeData.expiryTimestamp);
                            }
                        
                            /// @dev Checks fee input validity, including correct src amounts
                            /// @param input Trade input structure
                            /// @param networkFeeBps Network fee in bps.
                            function validateFeeInput(TradeInput memory input, uint256 networkFeeBps) internal pure {
                                require(input.platformFeeBps < BPS, "platformFee high");
                                require(input.platformFeeBps + networkFeeBps + networkFeeBps < BPS, "fees high");
                            }
                        
                            /// @notice Update reserve data with selected reserves from kyberMatchingEngine
                            function updateReservesList(ReservesData memory reservesData, uint256[] memory selectedIndexes)
                                internal
                                pure
                            {
                                uint256 numReserves = selectedIndexes.length;
                        
                                require(numReserves <= reservesData.addresses.length, "doMatch: too many reserves");
                        
                                IKyberReserve[] memory reserveAddresses = new IKyberReserve[](numReserves);
                                bytes32[] memory reserveIds = new bytes32[](numReserves);
                                uint256[] memory splitsBps = new uint256[](numReserves);
                                bool[] memory isFeeAccountedFlags = new bool[](numReserves);
                                bool[] memory isEntitledRebateFlags = new bool[](numReserves);
                                uint256[] memory srcAmounts = new uint256[](numReserves);
                                uint256[] memory rates = new uint256[](numReserves);
                        
                                // update participating resevres and all data (rates, srcAmounts, feeAcounted etc.)
                                for (uint256 i = 0; i < numReserves; i++) {
                                    reserveAddresses[i] = reservesData.addresses[selectedIndexes[i]];
                                    reserveIds[i] = reservesData.ids[selectedIndexes[i]];
                                    splitsBps[i] = reservesData.splitsBps[selectedIndexes[i]];
                                    isFeeAccountedFlags[i] = reservesData.isFeeAccountedFlags[selectedIndexes[i]];
                                    isEntitledRebateFlags[i] = reservesData.isEntitledRebateFlags[selectedIndexes[i]];
                                    srcAmounts[i] = reservesData.srcAmounts[selectedIndexes[i]];
                                    rates[i] = reservesData.rates[selectedIndexes[i]];
                                }
                        
                                // update values
                                reservesData.addresses = reserveAddresses;
                                reservesData.ids = reserveIds;
                                reservesData.splitsBps = splitsBps;
                                reservesData.isFeeAccountedFlags = isFeeAccountedFlags;
                                reservesData.isEntitledRebateFlags = isEntitledRebateFlags;
                                reservesData.rates = rates;
                                reservesData.srcAmounts = srcAmounts;
                            }
                        
                            /// @notice Verify split values bps and reserve ids,
                            ///     then calculate the destQty from srcAmounts and rates
                            /// @dev Each split bps must be in range (0, BPS]
                            /// @dev Total split bps must be 100%
                            /// @dev Reserve ids must be increasing
                            function validateTradeCalcDestQtyAndFeeData(
                                IERC20 src,
                                ReservesData memory reservesData,
                                TradeData memory tradeData
                            ) internal pure returns (uint256 totalDestAmount) {
                                uint256 totalBps;
                                uint256 srcDecimals = (src == ETH_TOKEN_ADDRESS) ? ETH_DECIMALS : reservesData.decimals;
                                uint256 destDecimals = (src == ETH_TOKEN_ADDRESS) ? reservesData.decimals : ETH_DECIMALS;
                                
                                for (uint256 i = 0; i < reservesData.addresses.length; i++) {
                                    if (i > 0 && (uint256(reservesData.ids[i]) <= uint256(reservesData.ids[i - 1]))) {
                                        return 0; // ids are not in increasing order
                                    }
                                    totalBps += reservesData.splitsBps[i];
                        
                                    uint256 destAmount = calcDstQty(
                                        reservesData.srcAmounts[i],
                                        srcDecimals,
                                        destDecimals,
                                        reservesData.rates[i]
                                    );
                                    if (destAmount == 0) {
                                        return 0;
                                    }
                                    totalDestAmount += destAmount;
                        
                                    if (reservesData.isFeeAccountedFlags[i]) {
                                        tradeData.feeAccountedBps += reservesData.splitsBps[i];
                        
                                        if (reservesData.isEntitledRebateFlags[i]) {
                                            tradeData.numEntitledRebateReserves++;
                                        }
                                    }
                                }
                        
                                if (totalBps != BPS) {
                                    return 0;
                                }
                            }
                        
                            /// @notice Recalculates tradeWei, network and platform fees, and actual source amount needed for the trade
                            /// in the event actualDestAmount > maxDestAmount
                            function calcTradeSrcAmountFromDest(TradeData memory tradeData)
                                internal
                                pure
                                virtual
                                returns (uint256 actualSrcAmount)
                            {
                                uint256 weiAfterDeductingFees;
                                if (tradeData.input.dest != ETH_TOKEN_ADDRESS) {
                                    weiAfterDeductingFees = calcTradeSrcAmount(
                                        tradeData.tradeWei - tradeData.platformFeeWei - tradeData.networkFeeWei,
                                        ETH_DECIMALS,
                                        tradeData.ethToToken.decimals,
                                        tradeData.input.maxDestAmount,
                                        tradeData.ethToToken
                                    );
                                } else {
                                    weiAfterDeductingFees = tradeData.input.maxDestAmount;
                                }
                        
                                // reverse calculation, because we are working backwards
                                uint256 newTradeWei =
                                    (weiAfterDeductingFees * BPS * BPS) /
                                    ((BPS * BPS) -
                                        (tradeData.networkFeeBps *
                                        tradeData.feeAccountedBps +
                                        tradeData.input.platformFeeBps *
                                        BPS));
                                tradeData.tradeWei = minOf(newTradeWei, tradeData.tradeWei);
                                // recalculate network and platform fees based on tradeWei
                                tradeData.networkFeeWei =
                                    (((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) /
                                    BPS;
                                tradeData.platformFeeWei = (tradeData.tradeWei * tradeData.input.platformFeeBps) / BPS;
                        
                                if (tradeData.input.src != ETH_TOKEN_ADDRESS) {
                                    actualSrcAmount = calcTradeSrcAmount(
                                        tradeData.input.srcAmount,
                                        tradeData.tokenToEth.decimals,
                                        ETH_DECIMALS,
                                        tradeData.tradeWei,
                                        tradeData.tokenToEth
                                    );
                                } else {
                                    actualSrcAmount = tradeData.tradeWei;
                                }
                        
                                assert(actualSrcAmount <= tradeData.input.srcAmount);
                            }
                        
                            /// @notice Recalculates srcAmounts and stores into tradingReserves, given the new destAmount.
                            ///     Uses the original proportion of srcAmounts and rates to determine new split destAmounts,
                            ///     then calculate the respective srcAmounts
                            /// @dev Due to small rounding errors, will fallback to current src amounts if new src amount is greater
                            function calcTradeSrcAmount(
                                uint256 srcAmount,
                                uint256 srcDecimals,
                                uint256 destDecimals,
                                uint256 destAmount,
                                ReservesData memory reservesData
                            ) internal pure returns (uint256 newSrcAmount) {
                                uint256 totalWeightedDestAmount;
                                for (uint256 i = 0; i < reservesData.srcAmounts.length; i++) {
                                    totalWeightedDestAmount += reservesData.srcAmounts[i] * reservesData.rates[i];
                                }
                        
                                uint256[] memory newSrcAmounts = new uint256[](reservesData.srcAmounts.length);
                                uint256 destAmountSoFar;
                                uint256 currentSrcAmount;
                                uint256 destAmountSplit;
                        
                                for (uint256 i = 0; i < reservesData.srcAmounts.length; i++) {
                                    currentSrcAmount = reservesData.srcAmounts[i];
                                    require(destAmount * currentSrcAmount * reservesData.rates[i] / destAmount == 
                                            currentSrcAmount * reservesData.rates[i], 
                                        "multiplication overflow");
                                    destAmountSplit = i == (reservesData.srcAmounts.length - 1)
                                        ? (destAmount - destAmountSoFar)
                                        : (destAmount * currentSrcAmount * reservesData.rates[i]) /
                                            totalWeightedDestAmount;
                                    destAmountSoFar += destAmountSplit;
                        
                                    newSrcAmounts[i] = calcSrcQty(
                                        destAmountSplit,
                                        srcDecimals,
                                        destDecimals,
                                        reservesData.rates[i]
                                    );
                                    if (newSrcAmounts[i] > currentSrcAmount) {
                                        // revert back to use current src amounts
                                        return srcAmount;
                                    }
                        
                                    newSrcAmount += newSrcAmounts[i];
                                }
                                // new src amounts are used only when all of them aren't greater then current srcAmounts
                                reservesData.srcAmounts = newSrcAmounts;
                            }
                        }

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

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

                        File 4 of 13: KyberFeeHandler
                        // File: contracts/sol6/IERC20.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        interface IERC20 {
                            event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                        
                            function approve(address _spender, uint256 _value) external returns (bool success);
                        
                            function transfer(address _to, uint256 _value) external returns (bool success);
                        
                            function transferFrom(
                                address _from,
                                address _to,
                                uint256 _value
                            ) external returns (bool success);
                        
                            function allowance(address _owner, address _spender) external view returns (uint256 remaining);
                        
                            function balanceOf(address _owner) external view returns (uint256 balance);
                        
                            function decimals() external view returns (uint8 digits);
                        
                            function totalSupply() external view returns (uint256 supply);
                        }
                        
                        
                        // to support backward compatible contract name -- so function signature remains same
                        abstract contract ERC20 is IERC20 {
                        
                        }
                        
                        // File: contracts/sol6/utils/Utils5.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        /**
                         * @title Kyber utility file
                         * mostly shared constants and rate calculation helpers
                         * inherited by most of kyber contracts.
                         * previous utils implementations are for previous solidity versions.
                         */
                        contract Utils5 {
                            IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
                                0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
                            );
                            uint256 internal constant PRECISION = (10**18);
                            uint256 internal constant MAX_QTY = (10**28); // 10B tokens
                            uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
                            uint256 internal constant MAX_DECIMALS = 18;
                            uint256 internal constant ETH_DECIMALS = 18;
                            uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
                            uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
                        
                            mapping(IERC20 => uint256) internal decimals;
                        
                            function getUpdateDecimals(IERC20 token) internal returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // moreover, very possible that old tokens have decimals 0
                                // these tokens will just have higher gas fees.
                                if (tokenDecimals == 0) {
                                    tokenDecimals = token.decimals();
                                    decimals[token] = tokenDecimals;
                                }
                        
                                return tokenDecimals;
                            }
                        
                            function setDecimals(IERC20 token) internal {
                                if (decimals[token] != 0) return; //already set
                        
                                if (token == ETH_TOKEN_ADDRESS) {
                                    decimals[token] = ETH_DECIMALS;
                                } else {
                                    decimals[token] = token.decimals();
                                }
                            }
                        
                            /// @dev get the balance of a user.
                            /// @param token The token type
                            /// @return The balance
                            function getBalance(IERC20 token, address user) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) {
                                    return user.balance;
                                } else {
                                    return token.balanceOf(user);
                                }
                            }
                        
                            function getDecimals(IERC20 token) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // 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 calcDestAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcSrcAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 destAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcDstQty(
                                uint256 srcQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                                }
                            }
                        
                            function calcSrcQty(
                                uint256 dstQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                //source quantity is rounded up. to avoid dest quantity being too low.
                                uint256 numerator;
                                uint256 denominator;
                                if (srcDecimals >= dstDecimals) {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                                    denominator = rate;
                                } else {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty);
                                    denominator = (rate * (10**(dstDecimals - srcDecimals)));
                                }
                                return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                            }
                        
                            function calcRateFromQty(
                                uint256 srcAmount,
                                uint256 destAmount,
                                uint256 srcDecimals,
                                uint256 dstDecimals
                            ) internal pure returns (uint256) {
                                require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
                                require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
                                }
                            }
                        
                            function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
                                return x > y ? y : x;
                            }
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/ReentrancyGuard.sol
                        
                        pragma solidity 0.6.6;
                        
                        /**
                         * @dev Contract module that helps prevent reentrant calls to a function.
                         *
                         * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
                         * available, which can be applied to functions to make sure there are no nested
                         * (reentrant) calls to them.
                         *
                         * Note that because there is a single `nonReentrant` guard, functions marked as
                         * `nonReentrant` may not call one another. This can be worked around by making
                         * those functions `private`, and then adding `external` `nonReentrant` entry
                         * points to them.
                         *
                         * TIP: If you would like to learn more about reentrancy and alternative ways
                         * to protect against it, check out our blog post
                         * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
                         */
                        contract ReentrancyGuard {
                            bool private _notEntered;
                        
                            constructor () internal {
                                // Storing an initial non-zero value makes deployment a bit more
                                // expensive, but in exchange the refund on every call to nonReentrant
                                // will be lower in amount. Since refunds are capped to a percetange of
                                // the total transaction's gas, it is best to keep them low in cases
                                // like this one, to increase the likelihood of the full refund coming
                                // into effect.
                                _notEntered = true;
                            }
                        
                            /**
                             * @dev Prevents a contract from calling itself, directly or indirectly.
                             * Calling a `nonReentrant` function from another `nonReentrant`
                             * function is not supported. It is possible to prevent this from happening
                             * by making the `nonReentrant` function external, and make it call a
                             * `private` function that does the actual work.
                             */
                            modifier nonReentrant() {
                                // On the first call to nonReentrant, _notEntered will be true
                                require(_notEntered, "ReentrancyGuard: reentrant call");
                        
                                // Any calls to nonReentrant after this point will fail
                                _notEntered = false;
                        
                                _;
                        
                                // By storing the original value once again, a refund is triggered (see
                                // https://eips.ethereum.org/EIPS/eip-2200)
                                _notEntered = true;
                            }
                        }
                        
                        // File: contracts/sol6/Dao/IEpochUtils.sol
                        
                        pragma solidity 0.6.6;
                        
                        interface IEpochUtils {
                            function epochPeriodInSeconds() external view returns (uint256);
                        
                            function firstEpochStartTimestamp() external view returns (uint256);
                        
                            function getCurrentEpochNumber() external view returns (uint256);
                        
                            function getEpochNumber(uint256 timestamp) external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberDao.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberDao is IEpochUtils {
                            event Voted(address indexed staker, uint indexed epoch, uint indexed campaignID, uint option);
                        
                            function getLatestNetworkFeeDataWithCache()
                                external
                                returns (uint256 feeInBps, uint256 expiryTimestamp);
                        
                            function getLatestBRRDataWithCache()
                                external
                                returns (
                                    uint256 burnInBps,
                                    uint256 rewardInBps,
                                    uint256 rebateInBps,
                                    uint256 epoch,
                                    uint256 expiryTimestamp
                                );
                        
                            function handleWithdrawal(address staker, uint256 penaltyAmount) external;
                        
                            function vote(uint256 campaignID, uint256 option) external;
                        
                            function getLatestNetworkFeeData()
                                external
                                view
                                returns (uint256 feeInBps, uint256 expiryTimestamp);
                        
                            function shouldBurnRewardForEpoch(uint256 epoch) external view returns (bool);
                        
                            /**
                             * @dev  return staker's reward percentage in precision for a past epoch only
                             *       fee handler should call this function when a staker wants to claim reward
                             *       return 0 if staker has no votes or stakes
                             */
                            function getPastEpochRewardPercentageInPrecision(address staker, uint256 epoch)
                                external
                                view
                                returns (uint256);
                        
                            /**
                             * @dev  return staker's reward percentage in precision for the current epoch
                             *       reward percentage is not finalized until the current epoch is ended
                             */
                            function getCurrentEpochRewardPercentageInPrecision(address staker)
                                external
                                view
                                returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberFeeHandler.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberFeeHandler {
                            event RewardPaid(address indexed staker, uint256 indexed epoch, IERC20 indexed token, uint256 amount);
                            event RebatePaid(address indexed rebateWallet, IERC20 indexed token, uint256 amount);
                            event PlatformFeePaid(address indexed platformWallet, IERC20 indexed token, uint256 amount);
                            event KncBurned(uint256 kncTWei, IERC20 indexed token, uint256 amount);
                        
                            function handleFees(
                                IERC20 token,
                                address[] calldata eligibleWallets,
                                uint256[] calldata rebatePercentages,
                                address platformWallet,
                                uint256 platformFee,
                                uint256 networkFee
                            ) external payable;
                        
                            function claimReserveRebate(address rebateWallet) external returns (uint256);
                        
                            function claimPlatformFee(address platformWallet) external returns (uint256);
                        
                            function claimStakerReward(
                                address staker,
                                uint256 epoch
                            ) external returns(uint amount);
                        }
                        
                        // File: contracts/sol6/IKyberNetworkProxy.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetworkProxy {
                        
                            event ExecuteTrade(
                                address indexed trader,
                                IERC20 src,
                                IERC20 dest,
                                address destAddress,
                                uint256 actualSrcAmount,
                                uint256 actualDestAmount,
                                address platformWallet,
                                uint256 platformFeeBps
                            );
                        
                            /// @notice backward compatible
                            function tradeWithHint(
                                ERC20 src,
                                uint256 srcAmount,
                                ERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable walletId,
                                bytes calldata hint
                            ) external payable returns (uint256);
                        
                            function tradeWithHintAndFee(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function trade(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet
                            ) external payable returns (uint256);
                        
                            /// @notice backward compatible
                            /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
                            function getExpectedRate(
                                ERC20 src,
                                ERC20 dest,
                                uint256 srcQty
                            ) external view returns (uint256 expectedRate, uint256 worstRate);
                        
                            function getExpectedRateAfterFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external view returns (uint256 expectedRate);
                        }
                        
                        // File: contracts/sol6/ISimpleKyberProxy.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        /*
                         * @title simple Kyber Network proxy interface
                         * add convenient functions to help with kyber proxy API
                         */
                        interface ISimpleKyberProxy {
                            function swapTokenToEther(
                                IERC20 token,
                                uint256 srcAmount,
                                uint256 minConversionRate
                            ) external returns (uint256 destAmount);
                        
                            function swapEtherToToken(IERC20 token, uint256 minConversionRate)
                                external
                                payable
                                returns (uint256 destAmount);
                        
                            function swapTokenToToken(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                uint256 minConversionRate
                            ) external returns (uint256 destAmount);
                        }
                        
                        // File: contracts/sol6/IBurnableToken.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        interface IBurnableToken {
                            function burn(uint256 _value) external returns (bool);
                        }
                        
                        // File: contracts/sol6/Dao/ISanityRate.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        /// @title Sanity Rate check to prevent burning knc with too expensive or cheap price
                        /// @dev Using ChainLink as the provider for current knc/eth price
                        interface ISanityRate {
                            // return latest rate of knc/eth
                            function latestAnswer() external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/SafeMath.sol
                        
                        pragma solidity 0.6.6;
                        
                        /**
                         * @dev Wrappers over Solidity's arithmetic operations with added overflow
                         * checks.
                         *
                         * Arithmetic operations in Solidity wrap on overflow. This can easily result
                         * in bugs, because programmers usually assume that an overflow raises an
                         * error, which is the standard behavior in high level programming languages.
                         * `SafeMath` restores this intuition by reverting the transaction when an
                         * operation overflows.
                         *
                         * Using this library instead of the unchecked operations eliminates an entire
                         * class of bugs, so it's recommended to use it always.
                         */
                        library SafeMath {
                            /**
                             * @dev Returns the addition of two unsigned integers, reverting on
                             * overflow.
                             *
                             * Counterpart to Solidity's `+` operator.
                             *
                             * Requirements:
                             * - Addition cannot overflow.
                             */
                            function add(uint256 a, uint256 b) internal pure returns (uint256) {
                                uint256 c = a + b;
                                require(c >= a, "SafeMath: addition overflow");
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the subtraction of two unsigned integers, reverting on
                             * overflow (when the result is negative).
                             *
                             * Counterpart to Solidity's `-` operator.
                             *
                             * Requirements:
                             * - Subtraction cannot overflow.
                             */
                            function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                                return sub(a, b, "SafeMath: subtraction overflow");
                            }
                        
                            /**
                             * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                             * overflow (when the result is negative).
                             *
                             * Counterpart to Solidity's `-` operator.
                             *
                             * Requirements:
                             * - Subtraction cannot overflow.
                             */
                            function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                require(b <= a, errorMessage);
                                uint256 c = a - b;
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the multiplication of two unsigned integers, reverting on
                             * overflow.
                             *
                             * Counterpart to Solidity's `*` operator.
                             *
                             * Requirements:
                             * - Multiplication cannot overflow.
                             */
                            function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                                // benefit is lost if 'b' is also tested.
                                // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                                if (a == 0) {
                                    return 0;
                                }
                        
                                uint256 c = a * b;
                                require(c / a == b, "SafeMath: multiplication overflow");
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the integer division of two unsigned integers. Reverts on
                             * division by zero. The result is rounded towards zero.
                             *
                             * Counterpart to Solidity's `/` operator. Note: this function uses a
                             * `revert` opcode (which leaves remaining gas untouched) while Solidity
                             * uses an invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function div(uint256 a, uint256 b) internal pure returns (uint256) {
                                return div(a, b, "SafeMath: division by zero");
                            }
                        
                            /**
                             * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                             * division by zero. The result is rounded towards zero.
                             *
                             * Counterpart to Solidity's `/` operator. Note: this function uses a
                             * `revert` opcode (which leaves remaining gas untouched) while Solidity
                             * uses an invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                // Solidity only automatically asserts when dividing by 0
                                require(b > 0, errorMessage);
                                uint256 c = a / b;
                                // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                             * Reverts when dividing by zero.
                             *
                             * Counterpart to Solidity's `%` operator. This function uses a `revert`
                             * opcode (which leaves remaining gas untouched) while Solidity uses an
                             * invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                                return mod(a, b, "SafeMath: modulo by zero");
                            }
                        
                            /**
                             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                             * Reverts with custom message when dividing by zero.
                             *
                             * Counterpart to Solidity's `%` operator. This function uses a `revert`
                             * opcode (which leaves remaining gas untouched) while Solidity uses an
                             * invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                require(b != 0, errorMessage);
                                return a % b;
                            }
                        
                            /**
                             * @dev Returns the smallest of two numbers.
                             */
                            function min(uint256 a, uint256 b) internal pure returns (uint256) {
                                return a < b ? a : b;
                            }
                        }
                        
                        // File: contracts/sol6/Dao/DaoOperator.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        contract DaoOperator {
                            address public daoOperator;
                        
                            constructor(address _daoOperator) public {
                                require(_daoOperator != address(0), "daoOperator is 0");
                                daoOperator = _daoOperator;
                            }
                        
                            modifier onlyDaoOperator() {
                                require(msg.sender == daoOperator, "only daoOperator");
                                _;
                            }
                        }
                        
                        // File: contracts/sol6/Dao/KyberFeeHandler.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        /**
                         * @title IKyberProxy
                         *  This interface combines two interfaces.
                         *  It is needed since we use one function from each of the interfaces.
                         *
                         */
                        interface IKyberProxy is IKyberNetworkProxy, ISimpleKyberProxy {
                            // empty block
                        }
                        
                        
                        /**
                         * @title kyberFeeHandler
                         *
                         * @dev kyberFeeHandler works tightly with contracts kyberNetwork and kyberDao.
                         *      Some events are moved to interface, for easier usage
                         * @dev Terminology:
                         *          Epoch - Voting campaign time frame in kyberDao.
                         *              kyberDao voting campaigns are in the scope of epochs.
                         *          BRR - Burn / Reward / Rebate. kyberNetwork fee is used for 3 purposes:
                         *              Burning KNC
                         *              Reward an address that staked knc in kyberStaking contract. AKA - stakers
                         *              Rebate reserves for supporting trades.
                         * @dev Code flow:
                         *      1. Accumulating && claiming Fees. Per trade on kyberNetwork, it calls handleFees() function which
                         *          internally accounts for network & platform fees from the trade. Fee distribution:
                         *              rewards: accumulated per epoch. can be claimed by the kyberDao after epoch is concluded.
                         *              rebates: accumulated per rebate wallet, can be claimed any time.
                         *              Burn: accumulated in the contract. Burned value and interval limited with safe check using
                                            sanity rate.
                         *              Platfrom fee: accumulated per platform wallet, can be claimed any time.
                         *      2. Network Fee distribution: Per epoch kyberFeeHandler contract reads BRR distribution percentage 
                         *          from kyberDao. When the data expires, kyberFeeHandler reads updated values.
                         */
                        contract KyberFeeHandler is IKyberFeeHandler, Utils5, DaoOperator, ReentrancyGuard {
                            using SafeMath for uint256;
                        
                            uint256 internal constant DEFAULT_REWARD_BPS = 3000;
                            uint256 internal constant DEFAULT_REBATE_BPS = 3000;
                            uint256 internal constant SANITY_RATE_DIFF_BPS = 1000; // 10%
                        
                            struct BRRData {
                                uint64 expiryTimestamp;
                                uint32 epoch;
                                uint16 rewardBps;
                                uint16 rebateBps;
                            }
                        
                            struct BRRWei {
                                uint256 rewardWei;
                                uint256 fullRebateWei;
                                uint256 paidRebateWei;
                                uint256 burnWei;
                            }
                        
                            IKyberDao public kyberDao;
                            IKyberProxy public kyberProxy;
                            address public kyberNetwork;
                            IERC20 public immutable knc;
                        
                            uint256 public immutable burnBlockInterval;
                            uint256 public lastBurnBlock;
                        
                            BRRData public brrAndEpochData;
                            address public daoSetter;
                        
                            /// @dev amount of eth to burn for each burn knc call
                            uint256 public weiToBurn = 2 ether;
                        
                            mapping(address => uint256) public feePerPlatformWallet;
                            mapping(address => uint256) public rebatePerWallet;
                            mapping(uint256 => uint256) public rewardsPerEpoch;
                            mapping(uint256 => uint256) public rewardsPaidPerEpoch;
                            // hasClaimedReward[staker][epoch]: true/false if the staker has/hasn't claimed the reward for an epoch
                            mapping(address => mapping (uint256 => bool)) public hasClaimedReward;
                            uint256 public totalPayoutBalance; // total balance in the contract that is for rebate, reward, platform fee
                        
                            /// @dev use to get rate of KNC/ETH to check if rate to burn knc is normal
                            /// @dev index 0 is currently used contract address, indexes > 0 are older versions
                            ISanityRate[] internal sanityRateContract;
                        
                            event FeeDistributed(
                                IERC20 indexed token,
                                address indexed platformWallet,
                                uint256 platformFeeWei,
                                uint256 rewardWei,
                                uint256 rebateWei,
                                address[] rebateWallets,
                                uint256[] rebatePercentBpsPerWallet,
                                uint256 burnAmtWei
                            );
                        
                            event BRRUpdated(
                                uint256 rewardBps,
                                uint256 rebateBps,
                                uint256 burnBps,
                                uint256 expiryTimestamp,
                                uint256 indexed epoch
                            );
                        
                            event EthReceived(uint256 amount);
                            event KyberDaoAddressSet(IKyberDao kyberDao);
                            event BurnConfigSet(ISanityRate sanityRate, uint256 weiToBurn);
                            event RewardsRemovedToBurn(uint256 indexed epoch, uint256 rewardsWei);
                            event KyberNetworkUpdated(address kyberNetwork);
                            event KyberProxyUpdated(IKyberProxy kyberProxy);
                        
                            constructor(
                                address _daoSetter,
                                IKyberProxy _kyberProxy,
                                address _kyberNetwork,
                                IERC20 _knc,
                                uint256 _burnBlockInterval,
                                address _daoOperator
                            ) public DaoOperator(_daoOperator) {
                                require(_daoSetter != address(0), "daoSetter 0");
                                require(_kyberProxy != IKyberProxy(0), "kyberNetworkProxy 0");
                                require(_kyberNetwork != address(0), "kyberNetwork 0");
                                require(_knc != IERC20(0), "knc 0");
                                require(_burnBlockInterval != 0, "_burnBlockInterval 0");
                        
                                daoSetter = _daoSetter;
                                kyberProxy = _kyberProxy;
                                kyberNetwork = _kyberNetwork;
                                knc = _knc;
                                burnBlockInterval = _burnBlockInterval;
                        
                                //start with epoch 0
                                updateBRRData(DEFAULT_REWARD_BPS, DEFAULT_REBATE_BPS, now, 0);
                            }
                        
                            modifier onlyKyberDao {
                                require(msg.sender == address(kyberDao), "only kyberDao");
                                _;
                            }
                        
                            modifier onlyKyberNetwork {
                                require(msg.sender == address(kyberNetwork), "only kyberNetwork");
                                _;
                            }
                        
                            modifier onlyNonContract {
                                require(tx.origin == msg.sender, "only non-contract");
                                _;
                            }
                        
                            receive() external payable {
                                emit EthReceived(msg.value);
                            }
                        
                            /// @dev handleFees function is called per trade on kyberNetwork. unless the trade is not involving any fees.
                            /// @param token Token currency of fees
                            /// @param rebateWallets a list of rebate wallets that will get rebate for this trade.
                            /// @param rebateBpsPerWallet percentage of rebate for each wallet, out of total rebate.
                            /// @param platformWallet Wallet address that will receive the platfrom fee.
                            /// @param platformFee Fee amount (in wei) the platfrom wallet is entitled to.
                            /// @param networkFee Fee amount (in wei) to be allocated for BRR
                            function handleFees(
                                IERC20 token,
                                address[] calldata rebateWallets,
                                uint256[] calldata rebateBpsPerWallet,
                                address platformWallet,
                                uint256 platformFee,
                                uint256 networkFee
                            ) external payable override onlyKyberNetwork nonReentrant {
                                require(token == ETH_TOKEN_ADDRESS, "token not eth");
                                require(msg.value == platformFee.add(networkFee), "msg.value not equal to total fees");
                        
                                // handle platform fee
                                feePerPlatformWallet[platformWallet] = feePerPlatformWallet[platformWallet].add(
                                    platformFee
                                );
                        
                                if (networkFee == 0) {
                                    // only platform fee paid
                                    totalPayoutBalance = totalPayoutBalance.add(platformFee);
                                    emit FeeDistributed(
                                        ETH_TOKEN_ADDRESS,
                                        platformWallet,
                                        platformFee,
                                        0,
                                        0,
                                        rebateWallets,
                                        rebateBpsPerWallet,
                                        0
                                    );
                                    return;
                                }
                        
                                BRRWei memory brrAmounts;
                                uint256 epoch;
                        
                                // Decoding BRR data
                                (brrAmounts.rewardWei, brrAmounts.fullRebateWei, epoch) = getRRWeiValues(networkFee);
                        
                                brrAmounts.paidRebateWei = updateRebateValues(
                                    brrAmounts.fullRebateWei, rebateWallets, rebateBpsPerWallet
                                );
                                brrAmounts.rewardWei = brrAmounts.rewardWei.add(
                                    brrAmounts.fullRebateWei.sub(brrAmounts.paidRebateWei)
                                );
                        
                                rewardsPerEpoch[epoch] = rewardsPerEpoch[epoch].add(brrAmounts.rewardWei);
                        
                                // update total balance of rewards, rebates, fee
                                totalPayoutBalance = totalPayoutBalance.add(
                                    platformFee).add(brrAmounts.rewardWei).add(brrAmounts.paidRebateWei
                                );
                        
                                brrAmounts.burnWei = networkFee.sub(brrAmounts.rewardWei).sub(brrAmounts.paidRebateWei);
                                emit FeeDistributed(
                                    ETH_TOKEN_ADDRESS,
                                    platformWallet,
                                    platformFee,
                                    brrAmounts.rewardWei,
                                    brrAmounts.paidRebateWei,
                                    rebateWallets,
                                    rebateBpsPerWallet,
                                    brrAmounts.burnWei
                                );
                            }
                        
                            /// @notice  WARNING When staker address is a contract,
                            ///          it should be able to receive claimed reward in ETH whenever anyone calls this function.
                            /// @dev not revert if already claimed or reward percentage is 0
                            ///      allow writing a wrapper to claim for multiple epochs
                            /// @param staker address.
                            /// @param epoch for which epoch the staker is claiming the reward
                            function claimStakerReward(
                                address staker,
                                uint256 epoch
                            ) external override nonReentrant returns(uint256 amountWei) {
                                if (hasClaimedReward[staker][epoch]) {
                                    // staker has already claimed reward for the epoch
                                    return 0;
                                }
                        
                                // the relative part of the reward the staker is entitled to for the epoch.
                                // units Precision: 10 ** 18 = 100%
                                // if the epoch is current or in the future, kyberDao will return 0 as result
                                uint256 percentageInPrecision = kyberDao.getPastEpochRewardPercentageInPrecision(staker, epoch);
                                if (percentageInPrecision == 0) {
                                    return 0; // not revert, in case a wrapper wants to claim reward for multiple epochs
                                }
                                require(percentageInPrecision <= PRECISION, "percentage too high");
                        
                                // Amount of reward to be sent to staker
                                amountWei = rewardsPerEpoch[epoch].mul(percentageInPrecision).div(PRECISION);
                        
                                // redundant check, can't happen
                                assert(totalPayoutBalance >= amountWei);
                                assert(rewardsPaidPerEpoch[epoch].add(amountWei) <= rewardsPerEpoch[epoch]);
                                
                                rewardsPaidPerEpoch[epoch] = rewardsPaidPerEpoch[epoch].add(amountWei);
                                totalPayoutBalance = totalPayoutBalance.sub(amountWei);
                        
                                hasClaimedReward[staker][epoch] = true;
                        
                                // send reward to staker
                                (bool success, ) = staker.call{value: amountWei}("");
                                require(success, "staker rewards transfer failed");
                        
                                emit RewardPaid(staker, epoch, ETH_TOKEN_ADDRESS, amountWei);
                            }
                        
                            /// @dev claim rebate per reserve wallet. called by any address
                            /// @param rebateWallet the wallet to claim rebates for. Total accumulated rebate sent to this wallet.
                            /// @return amountWei amount of rebate claimed
                            function claimReserveRebate(address rebateWallet) 
                                external 
                                override 
                                nonReentrant 
                                returns (uint256 amountWei) 
                            {
                                require(rebatePerWallet[rebateWallet] > 1, "no rebate to claim");
                                // Get total amount of rebate accumulated
                                amountWei = rebatePerWallet[rebateWallet].sub(1);
                        
                                // redundant check, can't happen
                                assert(totalPayoutBalance >= amountWei);
                                totalPayoutBalance = totalPayoutBalance.sub(amountWei);
                        
                                rebatePerWallet[rebateWallet] = 1; // avoid zero to non zero storage cost
                        
                                // send rebate to rebate wallet
                                (bool success, ) = rebateWallet.call{value: amountWei}("");
                                require(success, "rebate transfer failed");
                        
                                emit RebatePaid(rebateWallet, ETH_TOKEN_ADDRESS, amountWei);
                        
                                return amountWei;
                            }
                        
                            /// @dev claim accumulated fee per platform wallet. Called by any address
                            /// @param platformWallet the wallet to claim fee for. Total accumulated fee sent to this wallet.
                            /// @return amountWei amount of fee claimed
                            function claimPlatformFee(address platformWallet)
                                external
                                override
                                nonReentrant
                                returns (uint256 amountWei)
                            {
                                require(feePerPlatformWallet[platformWallet] > 1, "no fee to claim");
                                // Get total amount of fees accumulated
                                amountWei = feePerPlatformWallet[platformWallet].sub(1);
                        
                                // redundant check, can't happen
                                assert(totalPayoutBalance >= amountWei);
                                totalPayoutBalance = totalPayoutBalance.sub(amountWei);
                        
                                feePerPlatformWallet[platformWallet] = 1; // avoid zero to non zero storage cost
                        
                                (bool success, ) = platformWallet.call{value: amountWei}("");
                                require(success, "platform fee transfer failed");
                        
                                emit PlatformFeePaid(platformWallet, ETH_TOKEN_ADDRESS, amountWei);
                                return amountWei;
                            }
                        
                            /// @dev set kyberDao contract address once and set setter address to zero.
                            /// @param _kyberDao kyberDao address.
                            function setDaoContract(IKyberDao _kyberDao) external {
                                require(msg.sender == daoSetter, "only daoSetter");
                                require(_kyberDao != IKyberDao(0));
                                kyberDao = _kyberDao;
                                emit KyberDaoAddressSet(kyberDao);
                        
                                daoSetter = address(0);
                            }
                        
                            /// @dev set new kyberNetwork address by daoOperator
                            /// @param _kyberNetwork new kyberNetwork contract
                            function setNetworkContract(address _kyberNetwork) external onlyDaoOperator {
                                require(_kyberNetwork != address(0), "kyberNetwork 0");
                                if (_kyberNetwork != kyberNetwork) {
                                    kyberNetwork = _kyberNetwork;
                                    emit KyberNetworkUpdated(kyberNetwork);
                                }
                            }
                        
                            /// @dev Allow to set kyberNetworkProxy address by daoOperator
                            /// @param _newProxy new kyberNetworkProxy contract
                            function setKyberProxy(IKyberProxy _newProxy) external onlyDaoOperator {
                                require(_newProxy != IKyberProxy(0), "kyberNetworkProxy 0");
                                if (_newProxy != kyberProxy) {
                                    kyberProxy = _newProxy;
                                    emit KyberProxyUpdated(_newProxy);
                                }
                            }
                        
                            /// @dev set knc sanity rate contract and amount wei to burn
                            /// @param _sanityRate new sanity rate contract
                            /// @param _weiToBurn new amount of wei to burn
                            function setBurnConfigParams(ISanityRate _sanityRate, uint256 _weiToBurn)
                                external
                                onlyDaoOperator
                            {
                                require(_weiToBurn > 0, "_weiToBurn is 0");
                        
                                if (sanityRateContract.length == 0 || (_sanityRate != sanityRateContract[0])) {
                                    // it is a new sanity rate contract
                                    if (sanityRateContract.length == 0) {
                                        sanityRateContract.push(_sanityRate);
                                    } else {
                                        sanityRateContract.push(sanityRateContract[0]);
                                        sanityRateContract[0] = _sanityRate;
                                    }
                                }
                        
                                weiToBurn = _weiToBurn;
                        
                                emit BurnConfigSet(_sanityRate, _weiToBurn);
                            }
                        
                        
                            /// @dev Burn knc. The burn amount is limited. Forces block delay between burn calls.
                            /// @dev only none ontract can call this function
                            /// @return kncBurnAmount amount of knc burned
                            function burnKnc() external onlyNonContract returns (uint256 kncBurnAmount) {
                                // check if current block > last burn block number + num block interval
                                require(block.number > lastBurnBlock + burnBlockInterval, "wait more blocks to burn");
                        
                                // update last burn block number
                                lastBurnBlock = block.number;
                        
                                // Get amount to burn, if greater than weiToBurn, burn only weiToBurn per function call.
                                uint256 balance = address(this).balance;
                        
                                // redundant check, can't happen
                                assert(balance >= totalPayoutBalance);
                                uint256 srcAmount = balance.sub(totalPayoutBalance);
                                srcAmount = srcAmount > weiToBurn ? weiToBurn : srcAmount;
                        
                                // Get rate
                                uint256 kyberEthKncRate = kyberProxy.getExpectedRateAfterFee(
                                    ETH_TOKEN_ADDRESS,
                                    knc,
                                    srcAmount,
                                    0,
                                    ""
                                );
                                validateEthToKncRateToBurn(kyberEthKncRate);
                        
                                // Buy some knc and burn
                                kncBurnAmount = kyberProxy.swapEtherToToken{value: srcAmount}(
                                    knc,
                                    kyberEthKncRate
                                );
                        
                                require(IBurnableToken(address(knc)).burn(kncBurnAmount), "knc burn failed");
                        
                                emit KncBurned(kncBurnAmount, ETH_TOKEN_ADDRESS, srcAmount);
                                return kncBurnAmount;
                            }
                        
                            /// @dev if no one voted for an epoch (like epoch 0), no one gets rewards - should burn it.
                            ///         Will move the epoch reward amount to burn amount. So can later be burned.
                            ///         calls kyberDao contract to check if there were any votes for this epoch.
                            /// @param epoch epoch number to check.
                            function makeEpochRewardBurnable(uint256 epoch) external {
                                require(kyberDao != IKyberDao(0), "kyberDao not set");
                        
                                require(kyberDao.shouldBurnRewardForEpoch(epoch), "should not burn reward");
                        
                                uint256 rewardAmount = rewardsPerEpoch[epoch];
                                require(rewardAmount > 0, "reward is 0");
                        
                                // redundant check, can't happen
                                require(totalPayoutBalance >= rewardAmount, "total reward less than epoch reward");
                                totalPayoutBalance = totalPayoutBalance.sub(rewardAmount);
                        
                                rewardsPerEpoch[epoch] = 0;
                        
                                emit RewardsRemovedToBurn(epoch, rewardAmount);
                            }
                        
                            /// @notice should be called off chain
                            /// @dev returns list of sanity rate contracts
                            /// @dev index 0 is currently used contract address, indexes > 0 are older versions
                            function getSanityRateContracts() external view returns (ISanityRate[] memory sanityRates) {
                                sanityRates = sanityRateContract;
                            }
                        
                            /// @dev return latest knc/eth rate from sanity rate contract
                            function getLatestSanityRate() external view returns (uint256 kncToEthSanityRate) {
                                if (sanityRateContract.length > 0 && sanityRateContract[0] != ISanityRate(0)) {
                                    kncToEthSanityRate = sanityRateContract[0].latestAnswer();
                                } else {
                                    kncToEthSanityRate = 0; 
                                }
                            }
                        
                            function getBRR()
                                public
                                returns (
                                    uint256 rewardBps,
                                    uint256 rebateBps,
                                    uint256 epoch
                                )
                            {
                                uint256 expiryTimestamp;
                                (rewardBps, rebateBps, expiryTimestamp, epoch) = readBRRData();
                        
                                // Check current timestamp
                                if (now > expiryTimestamp && kyberDao != IKyberDao(0)) {
                                    uint256 burnBps;
                        
                                    (burnBps, rewardBps, rebateBps, epoch, expiryTimestamp) = kyberDao
                                        .getLatestBRRDataWithCache();
                                    require(burnBps.add(rewardBps).add(rebateBps) == BPS, "Bad BRR values");
                                    
                                    emit BRRUpdated(rewardBps, rebateBps, burnBps, expiryTimestamp, epoch);
                        
                                    // Update brrAndEpochData
                                    updateBRRData(rewardBps, rebateBps, expiryTimestamp, epoch);
                                }
                            }
                        
                            function readBRRData()
                                public
                                view
                                returns (
                                    uint256 rewardBps,
                                    uint256 rebateBps,
                                    uint256 expiryTimestamp,
                                    uint256 epoch
                                )
                            {
                                rewardBps = uint256(brrAndEpochData.rewardBps);
                                rebateBps = uint256(brrAndEpochData.rebateBps);
                                epoch = uint256(brrAndEpochData.epoch);
                                expiryTimestamp = uint256(brrAndEpochData.expiryTimestamp);
                            }
                        
                            function updateBRRData(
                                uint256 reward,
                                uint256 rebate,
                                uint256 expiryTimestamp,
                                uint256 epoch
                            ) internal {
                                // reward and rebate combined values <= BPS. Tested in getBRR.
                                require(expiryTimestamp < 2**64, "expiry timestamp overflow");
                                require(epoch < 2**32, "epoch overflow");
                        
                                brrAndEpochData.rewardBps = uint16(reward);
                                brrAndEpochData.rebateBps = uint16(rebate);
                                brrAndEpochData.expiryTimestamp = uint64(expiryTimestamp);
                                brrAndEpochData.epoch = uint32(epoch);
                            }
                        
                            function getRRWeiValues(uint256 RRAmountWei)
                                internal
                                returns (
                                    uint256 rewardWei,
                                    uint256 rebateWei,
                                    uint256 epoch
                                )
                            {
                                // Decoding BRR data
                                uint256 rewardInBps;
                                uint256 rebateInBps;
                                (rewardInBps, rebateInBps, epoch) = getBRR();
                        
                                rebateWei = RRAmountWei.mul(rebateInBps).div(BPS);
                                rewardWei = RRAmountWei.mul(rewardInBps).div(BPS);
                            }
                        
                            function updateRebateValues(
                                uint256 rebateWei,
                                address[] memory rebateWallets,
                                uint256[] memory rebateBpsPerWallet
                            ) internal returns (uint256 totalRebatePaidWei) {
                                uint256 totalRebateBps;
                                uint256 walletRebateWei;
                        
                                for (uint256 i = 0; i < rebateWallets.length; i++) {
                                    require(rebateWallets[i] != address(0), "rebate wallet address 0");
                        
                                    walletRebateWei = rebateWei.mul(rebateBpsPerWallet[i]).div(BPS);
                                    rebatePerWallet[rebateWallets[i]] = rebatePerWallet[rebateWallets[i]].add(
                                        walletRebateWei
                                    );
                        
                                    // a few wei could be left out due to rounding down. so count only paid wei
                                    totalRebatePaidWei = totalRebatePaidWei.add(walletRebateWei);
                                    totalRebateBps = totalRebateBps.add(rebateBpsPerWallet[i]);
                                }
                        
                                require(totalRebateBps <= BPS, "rebates more then 100%");
                            }
                        
                            function validateEthToKncRateToBurn(uint256 rateEthToKnc) internal view {
                                require(rateEthToKnc <= MAX_RATE, "ethToKnc rate out of bounds");
                                require(rateEthToKnc > 0, "ethToKnc rate is 0");
                                require(sanityRateContract.length > 0, "no sanity rate contract");
                                require(sanityRateContract[0] != ISanityRate(0), "sanity rate is 0x0, burning is blocked");
                        
                                // get latest knc/eth rate from sanity contract
                                uint256 kncToEthRate = sanityRateContract[0].latestAnswer();
                                require(kncToEthRate > 0, "sanity rate is 0");
                                require(kncToEthRate <= MAX_RATE, "sanity rate out of bounds");
                        
                                uint256 sanityEthToKncRate = PRECISION.mul(PRECISION).div(kncToEthRate);
                        
                                // rate shouldn't be SANITY_RATE_DIFF_BPS lower than sanity rate
                                require(
                                    rateEthToKnc.mul(BPS) >= sanityEthToKncRate.mul(BPS.sub(SANITY_RATE_DIFF_BPS)),
                                    "kyberNetwork eth to knc rate too low"
                                );
                            }
                        }

                        File 5 of 13: KyberNetworkProxy
                        // File: contracts/sol6/IERC20.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        interface IERC20 {
                            event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                        
                            function approve(address _spender, uint256 _value) external returns (bool success);
                        
                            function transfer(address _to, uint256 _value) external returns (bool success);
                        
                            function transferFrom(
                                address _from,
                                address _to,
                                uint256 _value
                            ) external returns (bool success);
                        
                            function allowance(address _owner, address _spender) external view returns (uint256 remaining);
                        
                            function balanceOf(address _owner) external view returns (uint256 balance);
                        
                            function decimals() external view returns (uint8 digits);
                        
                            function totalSupply() external view returns (uint256 supply);
                        }
                        
                        
                        // to support backward compatible contract name -- so function signature remains same
                        abstract contract ERC20 is IERC20 {
                        
                        }
                        
                        // File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        contract PermissionGroupsNoModifiers {
                            address public admin;
                            address public pendingAdmin;
                            mapping(address => bool) internal operators;
                            mapping(address => bool) internal alerters;
                            address[] internal operatorsGroup;
                            address[] internal alertersGroup;
                            uint256 internal constant MAX_GROUP_SIZE = 50;
                        
                            event AdminClaimed(address newAdmin, address previousAdmin);
                            event AlerterAdded(address newAlerter, bool isAdd);
                            event OperatorAdded(address newOperator, bool isAdd);
                            event TransferAdminPending(address pendingAdmin);
                        
                            constructor(address _admin) public {
                                require(_admin != address(0), "admin 0");
                                admin = _admin;
                            }
                        
                            function getOperators() external view returns (address[] memory) {
                                return operatorsGroup;
                            }
                        
                            function getAlerters() external view returns (address[] memory) {
                                return alertersGroup;
                            }
                        
                            function addAlerter(address newAlerter) public {
                                onlyAdmin();
                                require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
                                require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
                        
                                emit AlerterAdded(newAlerter, true);
                                alerters[newAlerter] = true;
                                alertersGroup.push(newAlerter);
                            }
                        
                            function addOperator(address newOperator) public {
                                onlyAdmin();
                                require(!operators[newOperator], "operator exists"); // prevent duplicates.
                                require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
                        
                                emit OperatorAdded(newOperator, true);
                                operators[newOperator] = true;
                                operatorsGroup.push(newOperator);
                            }
                        
                            /// @dev Allows the pendingAdmin address to finalize the change admin process.
                            function claimAdmin() public {
                                require(pendingAdmin == msg.sender, "not pending");
                                emit AdminClaimed(pendingAdmin, admin);
                                admin = pendingAdmin;
                                pendingAdmin = address(0);
                            }
                        
                            function removeAlerter(address alerter) public {
                                onlyAdmin();
                                require(alerters[alerter], "not alerter");
                                delete alerters[alerter];
                        
                                for (uint256 i = 0; i < alertersGroup.length; ++i) {
                                    if (alertersGroup[i] == alerter) {
                                        alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                        alertersGroup.pop();
                                        emit AlerterAdded(alerter, false);
                                        break;
                                    }
                                }
                            }
                        
                            function removeOperator(address operator) public {
                                onlyAdmin();
                                require(operators[operator], "not operator");
                                delete operators[operator];
                        
                                for (uint256 i = 0; i < operatorsGroup.length; ++i) {
                                    if (operatorsGroup[i] == operator) {
                                        operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                        operatorsGroup.pop();
                                        emit OperatorAdded(operator, false);
                                        break;
                                    }
                                }
                            }
                        
                            /// @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), "new admin 0");
                                emit TransferAdminPending(newAdmin);
                                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), "admin 0");
                                emit TransferAdminPending(newAdmin);
                                emit AdminClaimed(newAdmin, admin);
                                admin = newAdmin;
                            }
                        
                            function onlyAdmin() internal view {
                                require(msg.sender == admin, "only admin");
                            }
                        
                            function onlyAlerter() internal view {
                                require(alerters[msg.sender], "only alerter");
                            }
                        
                            function onlyOperator() internal view {
                                require(operators[msg.sender], "only operator");
                            }
                        }
                        
                        // File: contracts/sol6/utils/WithdrawableNoModifiers.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        contract WithdrawableNoModifiers is PermissionGroupsNoModifiers {
                            constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {}
                        
                            event EtherWithdraw(uint256 amount, address sendTo);
                            event TokenWithdraw(IERC20 token, uint256 amount, address sendTo);
                        
                            /// @dev Withdraw Ethers
                            function withdrawEther(uint256 amount, address payable sendTo) external {
                                onlyAdmin();
                                (bool success, ) = sendTo.call{value: amount}("");
                                require(success);
                                emit EtherWithdraw(amount, sendTo);
                            }
                        
                            /// @dev Withdraw all IERC20 compatible tokens
                            /// @param token IERC20 The address of the token contract
                            function withdrawToken(
                                IERC20 token,
                                uint256 amount,
                                address sendTo
                            ) external {
                                onlyAdmin();
                                token.transfer(sendTo, amount);
                                emit TokenWithdraw(token, amount, sendTo);
                            }
                        }
                        
                        // File: contracts/sol6/utils/Utils5.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        /**
                         * @title Kyber utility file
                         * mostly shared constants and rate calculation helpers
                         * inherited by most of kyber contracts.
                         * previous utils implementations are for previous solidity versions.
                         */
                        contract Utils5 {
                            IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
                                0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
                            );
                            uint256 internal constant PRECISION = (10**18);
                            uint256 internal constant MAX_QTY = (10**28); // 10B tokens
                            uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
                            uint256 internal constant MAX_DECIMALS = 18;
                            uint256 internal constant ETH_DECIMALS = 18;
                            uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
                            uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
                        
                            mapping(IERC20 => uint256) internal decimals;
                        
                            function getUpdateDecimals(IERC20 token) internal returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // moreover, very possible that old tokens have decimals 0
                                // these tokens will just have higher gas fees.
                                if (tokenDecimals == 0) {
                                    tokenDecimals = token.decimals();
                                    decimals[token] = tokenDecimals;
                                }
                        
                                return tokenDecimals;
                            }
                        
                            function setDecimals(IERC20 token) internal {
                                if (decimals[token] != 0) return; //already set
                        
                                if (token == ETH_TOKEN_ADDRESS) {
                                    decimals[token] = ETH_DECIMALS;
                                } else {
                                    decimals[token] = token.decimals();
                                }
                            }
                        
                            /// @dev get the balance of a user.
                            /// @param token The token type
                            /// @return The balance
                            function getBalance(IERC20 token, address user) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) {
                                    return user.balance;
                                } else {
                                    return token.balanceOf(user);
                                }
                            }
                        
                            function getDecimals(IERC20 token) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // 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 calcDestAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcSrcAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 destAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcDstQty(
                                uint256 srcQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                                }
                            }
                        
                            function calcSrcQty(
                                uint256 dstQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                //source quantity is rounded up. to avoid dest quantity being too low.
                                uint256 numerator;
                                uint256 denominator;
                                if (srcDecimals >= dstDecimals) {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                                    denominator = rate;
                                } else {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty);
                                    denominator = (rate * (10**(dstDecimals - srcDecimals)));
                                }
                                return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                            }
                        
                            function calcRateFromQty(
                                uint256 srcAmount,
                                uint256 destAmount,
                                uint256 srcDecimals,
                                uint256 dstDecimals
                            ) internal pure returns (uint256) {
                                require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
                                require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
                                }
                            }
                        
                            function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
                                return x > y ? y : x;
                            }
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/SafeMath.sol
                        
                        pragma solidity 0.6.6;
                        
                        /**
                         * @dev Wrappers over Solidity's arithmetic operations with added overflow
                         * checks.
                         *
                         * Arithmetic operations in Solidity wrap on overflow. This can easily result
                         * in bugs, because programmers usually assume that an overflow raises an
                         * error, which is the standard behavior in high level programming languages.
                         * `SafeMath` restores this intuition by reverting the transaction when an
                         * operation overflows.
                         *
                         * Using this library instead of the unchecked operations eliminates an entire
                         * class of bugs, so it's recommended to use it always.
                         */
                        library SafeMath {
                            /**
                             * @dev Returns the addition of two unsigned integers, reverting on
                             * overflow.
                             *
                             * Counterpart to Solidity's `+` operator.
                             *
                             * Requirements:
                             * - Addition cannot overflow.
                             */
                            function add(uint256 a, uint256 b) internal pure returns (uint256) {
                                uint256 c = a + b;
                                require(c >= a, "SafeMath: addition overflow");
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the subtraction of two unsigned integers, reverting on
                             * overflow (when the result is negative).
                             *
                             * Counterpart to Solidity's `-` operator.
                             *
                             * Requirements:
                             * - Subtraction cannot overflow.
                             */
                            function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                                return sub(a, b, "SafeMath: subtraction overflow");
                            }
                        
                            /**
                             * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                             * overflow (when the result is negative).
                             *
                             * Counterpart to Solidity's `-` operator.
                             *
                             * Requirements:
                             * - Subtraction cannot overflow.
                             */
                            function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                require(b <= a, errorMessage);
                                uint256 c = a - b;
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the multiplication of two unsigned integers, reverting on
                             * overflow.
                             *
                             * Counterpart to Solidity's `*` operator.
                             *
                             * Requirements:
                             * - Multiplication cannot overflow.
                             */
                            function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                                // benefit is lost if 'b' is also tested.
                                // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                                if (a == 0) {
                                    return 0;
                                }
                        
                                uint256 c = a * b;
                                require(c / a == b, "SafeMath: multiplication overflow");
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the integer division of two unsigned integers. Reverts on
                             * division by zero. The result is rounded towards zero.
                             *
                             * Counterpart to Solidity's `/` operator. Note: this function uses a
                             * `revert` opcode (which leaves remaining gas untouched) while Solidity
                             * uses an invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function div(uint256 a, uint256 b) internal pure returns (uint256) {
                                return div(a, b, "SafeMath: division by zero");
                            }
                        
                            /**
                             * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                             * division by zero. The result is rounded towards zero.
                             *
                             * Counterpart to Solidity's `/` operator. Note: this function uses a
                             * `revert` opcode (which leaves remaining gas untouched) while Solidity
                             * uses an invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                // Solidity only automatically asserts when dividing by 0
                                require(b > 0, errorMessage);
                                uint256 c = a / b;
                                // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                        
                                return c;
                            }
                        
                            /**
                             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                             * Reverts when dividing by zero.
                             *
                             * Counterpart to Solidity's `%` operator. This function uses a `revert`
                             * opcode (which leaves remaining gas untouched) while Solidity uses an
                             * invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                                return mod(a, b, "SafeMath: modulo by zero");
                            }
                        
                            /**
                             * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                             * Reverts with custom message when dividing by zero.
                             *
                             * Counterpart to Solidity's `%` operator. This function uses a `revert`
                             * opcode (which leaves remaining gas untouched) while Solidity uses an
                             * invalid opcode to revert (consuming all remaining gas).
                             *
                             * Requirements:
                             * - The divisor cannot be zero.
                             */
                            function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                                require(b != 0, errorMessage);
                                return a % b;
                            }
                        
                            /**
                             * @dev Returns the smallest of two numbers.
                             */
                            function min(uint256 a, uint256 b) internal pure returns (uint256) {
                                return a < b ? a : b;
                            }
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/Address.sol
                        
                        pragma solidity 0.6.6;
                        
                        /**
                         * @dev Collection of functions related to the address type
                         */
                        library Address {
                            /**
                             * @dev Returns true if `account` is a contract.
                             *
                             * [IMPORTANT]
                             * ====
                             * It is unsafe to assume that an address for which this function returns
                             * false is an externally-owned account (EOA) and not a contract.
                             *
                             * Among others, `isContract` will return false for the following
                             * types of addresses:
                             *
                             *  - an externally-owned account
                             *  - a contract in construction
                             *  - an address where a contract will be created
                             *  - an address where a contract lived, but was destroyed
                             * ====
                             */
                            function isContract(address account) internal view returns (bool) {
                                // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                                // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                                // for accounts without code, i.e. `keccak256('')`
                                bytes32 codehash;
                                bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                                // solhint-disable-next-line no-inline-assembly
                                assembly { codehash := extcodehash(account) }
                                return (codehash != accountHash && codehash != 0x0);
                            }
                        
                            /**
                             * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                             * `recipient`, forwarding all available gas and reverting on errors.
                             *
                             * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                             * of certain opcodes, possibly making contracts go over the 2300 gas limit
                             * imposed by `transfer`, making them unable to receive funds via
                             * `transfer`. {sendValue} removes this limitation.
                             *
                             * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                             *
                             * IMPORTANT: because control is transferred to `recipient`, care must be
                             * taken to not create reentrancy vulnerabilities. Consider using
                             * {ReentrancyGuard} or the
                             * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                             */
                            function sendValue(address payable recipient, uint256 amount) internal {
                                require(address(this).balance >= amount, "Address: insufficient balance");
                        
                                // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                                (bool success, ) = recipient.call{ value: amount }("");
                                require(success, "Address: unable to send value, recipient may have reverted");
                            }
                        }
                        
                        // File: contracts/sol6/utils/zeppelin/SafeERC20.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        /**
                         * @title SafeERC20
                         * @dev Wrappers around ERC20 operations that throw on failure (when the token
                         * contract returns false). Tokens that return no value (and instead revert or
                         * throw on failure) are also supported, non-reverting calls are assumed to be
                         * successful.
                         * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                         * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                         */
                        library SafeERC20 {
                            using SafeMath for uint256;
                            using Address for address;
                        
                            function safeTransfer(IERC20 token, address to, uint256 value) internal {
                                _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                            }
                        
                            function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                                _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                            }
                        
                            function safeApprove(IERC20 token, address spender, uint256 value) internal {
                                // safeApprove should only be called when setting an initial allowance,
                                // or when resetting it to zero. To increase and decrease it, use
                                // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                                // solhint-disable-next-line max-line-length
                                require((value == 0) || (token.allowance(address(this), spender) == 0),
                                    "SafeERC20: approve from non-zero to non-zero allowance"
                                );
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                            }
                        
                            function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                uint256 newAllowance = token.allowance(address(this), spender).add(value);
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                            }
                        
                            function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                                uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                            }
                        
                            /**
                             * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                             * on the return value: the return value is optional (but if data is returned, it must not be false).
                             * @param token The token targeted by the call.
                             * @param data The call data (encoded using abi.encode or one of its variants).
                             */
                            function _callOptionalReturn(IERC20 token, bytes memory data) private {
                                // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                                // we're implementing it ourselves.
                        
                                // A Solidity high level call has three parts:
                                //  1. The target address is checked to verify it contains contract code
                                //  2. The call itself is made, and success asserted
                                //  3. The return value is decoded, which in turn checks the size of the returned data.
                                // solhint-disable-next-line max-line-length
                                require(address(token).isContract(), "SafeERC20: call to non-contract");
                        
                                // solhint-disable-next-line avoid-low-level-calls
                                (bool success, bytes memory returndata) = address(token).call(data);
                                require(success, "SafeERC20: low-level call failed");
                        
                                if (returndata.length > 0) { // Return data is optional
                                    // solhint-disable-next-line max-line-length
                                    require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                                }
                            }
                        }
                        
                        // File: contracts/sol6/IKyberNetwork.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetwork {
                            event KyberTrade(
                                IERC20 indexed src,
                                IERC20 indexed dest,
                                uint256 ethWeiValue,
                                uint256 networkFeeWei,
                                uint256 customPlatformFeeWei,
                                bytes32[] t2eIds,
                                bytes32[] e2tIds,
                                uint256[] t2eSrcAmounts,
                                uint256[] e2tSrcAmounts,
                                uint256[] t2eRates,
                                uint256[] e2tRates
                            );
                        
                            function tradeWithHintAndFee(
                                address payable trader,
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function listTokenForReserve(
                                address reserve,
                                IERC20 token,
                                bool add
                            ) external;
                        
                            function enabled() external view returns (bool);
                        
                            function getExpectedRateWithHintAndFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            )
                                external
                                view
                                returns (
                                    uint256 expectedRateAfterNetworkFee,
                                    uint256 expectedRateAfterAllFees
                                );
                        
                            function getNetworkData()
                                external
                                view
                                returns (
                                    uint256 negligibleDiffBps,
                                    uint256 networkFeeBps,
                                    uint256 expiryTimestamp
                                );
                        
                            function maxGasPrice() external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberNetworkProxy.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetworkProxy {
                        
                            event ExecuteTrade(
                                address indexed trader,
                                IERC20 src,
                                IERC20 dest,
                                address destAddress,
                                uint256 actualSrcAmount,
                                uint256 actualDestAmount,
                                address platformWallet,
                                uint256 platformFeeBps
                            );
                        
                            /// @notice backward compatible
                            function tradeWithHint(
                                ERC20 src,
                                uint256 srcAmount,
                                ERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable walletId,
                                bytes calldata hint
                            ) external payable returns (uint256);
                        
                            function tradeWithHintAndFee(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function trade(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet
                            ) external payable returns (uint256);
                        
                            /// @notice backward compatible
                            /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
                            function getExpectedRate(
                                ERC20 src,
                                ERC20 dest,
                                uint256 srcQty
                            ) external view returns (uint256 expectedRate, uint256 worstRate);
                        
                            function getExpectedRateAfterFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external view returns (uint256 expectedRate);
                        }
                        
                        // File: contracts/sol6/ISimpleKyberProxy.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        /*
                         * @title simple Kyber Network proxy interface
                         * add convenient functions to help with kyber proxy API
                         */
                        interface ISimpleKyberProxy {
                            function swapTokenToEther(
                                IERC20 token,
                                uint256 srcAmount,
                                uint256 minConversionRate
                            ) external returns (uint256 destAmount);
                        
                            function swapEtherToToken(IERC20 token, uint256 minConversionRate)
                                external
                                payable
                                returns (uint256 destAmount);
                        
                            function swapTokenToToken(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                uint256 minConversionRate
                            ) external returns (uint256 destAmount);
                        }
                        
                        // File: contracts/sol6/IKyberReserve.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberReserve {
                            function trade(
                                IERC20 srcToken,
                                uint256 srcAmount,
                                IERC20 destToken,
                                address payable destAddress,
                                uint256 conversionRate,
                                bool validate
                            ) external payable returns (bool);
                        
                            function getConversionRate(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 blockNumber
                            ) external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberHint.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberHint {
                            enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
                            enum HintErrors {
                                NoError, // Hint is valid
                                NonEmptyDataError, // reserveIDs and splits must be empty for BestOfAll hint
                                ReserveIdDupError, // duplicate reserveID found
                                ReserveIdEmptyError, // reserveIDs array is empty for MaskIn and Split trade type
                                ReserveIdSplitsError, // reserveIDs and splitBpsValues arrays do not have the same length
                                ReserveIdSequenceError, // reserveID sequence in array is not in increasing order
                                ReserveIdNotFound, // reserveID isn't registered or doesn't exist
                                SplitsNotEmptyError, // splitBpsValues is not empty for MaskIn or MaskOut trade type
                                TokenListedError, // reserveID not listed for the token
                                TotalBPSError // total BPS for Split trade type is not 10000 (100%)
                            }
                        
                            function buildTokenToEthHint(
                                IERC20 tokenSrc,
                                TradeType tokenToEthType,
                                bytes32[] calldata tokenToEthReserveIds,
                                uint256[] calldata tokenToEthSplits
                            ) external view returns (bytes memory hint);
                        
                            function buildEthToTokenHint(
                                IERC20 tokenDest,
                                TradeType ethToTokenType,
                                bytes32[] calldata ethToTokenReserveIds,
                                uint256[] calldata ethToTokenSplits
                            ) external view returns (bytes memory hint);
                        
                            function buildTokenToTokenHint(
                                IERC20 tokenSrc,
                                TradeType tokenToEthType,
                                bytes32[] calldata tokenToEthReserveIds,
                                uint256[] calldata tokenToEthSplits,
                                IERC20 tokenDest,
                                TradeType ethToTokenType,
                                bytes32[] calldata ethToTokenReserveIds,
                                uint256[] calldata ethToTokenSplits
                            ) external view returns (bytes memory hint);
                        
                            function parseTokenToEthHint(IERC20 tokenSrc, bytes calldata hint)
                                external
                                view
                                returns (
                                    TradeType tokenToEthType,
                                    bytes32[] memory tokenToEthReserveIds,
                                    IKyberReserve[] memory tokenToEthAddresses,
                                    uint256[] memory tokenToEthSplits
                                );
                        
                            function parseEthToTokenHint(IERC20 tokenDest, bytes calldata hint)
                                external
                                view
                                returns (
                                    TradeType ethToTokenType,
                                    bytes32[] memory ethToTokenReserveIds,
                                    IKyberReserve[] memory ethToTokenAddresses,
                                    uint256[] memory ethToTokenSplits
                                );
                        
                            function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes calldata hint)
                                external
                                view
                                returns (
                                    TradeType tokenToEthType,
                                    bytes32[] memory tokenToEthReserveIds,
                                    IKyberReserve[] memory tokenToEthAddresses,
                                    uint256[] memory tokenToEthSplits,
                                    TradeType ethToTokenType,
                                    bytes32[] memory ethToTokenReserveIds,
                                    IKyberReserve[] memory ethToTokenAddresses,
                                    uint256[] memory ethToTokenSplits
                                );
                        }
                        
                        // File: contracts/sol6/KyberNetworkProxy.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        /**
                         *   @title kyberProxy for kyberNetwork contract
                         *   The contract provides the following functions:
                         *   - Get rates
                         *   - Trade execution
                         *   - Simple T2E, E2T and T2T trade APIs
                         *   - Has some checks in place to safeguard takers
                         */
                        contract KyberNetworkProxy is
                            IKyberNetworkProxy,
                            ISimpleKyberProxy,
                            WithdrawableNoModifiers,
                            Utils5
                        {
                            using SafeERC20 for IERC20;
                        
                            IKyberNetwork public kyberNetwork;
                            IKyberHint public kyberHintHandler; // kyberHintHhandler pointer for users.
                        
                            event KyberNetworkSet(IKyberNetwork newKyberNetwork, IKyberNetwork previousKyberNetwork);
                            event KyberHintHandlerSet(IKyberHint kyberHintHandler);
                        
                            constructor(address _admin) public WithdrawableNoModifiers(_admin) {
                                /*empty body*/
                            }
                        
                            /// @notice Backward compatible function
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Trade from src to dest token and sends dest token to destAddress
                            /// @param src Source token
                            /// @param srcAmount Amount of src tokens in twei
                            /// @param dest Destination token
                            /// @param destAddress Address to send tokens to
                            /// @param maxDestAmount A limit on the amount of dest tokens in twei
                            /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
                            /// @param platformWallet Platform wallet address for receiving fees
                            /// @return Amount of actual dest tokens in twei
                            function trade(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet
                            ) external payable override returns (uint256) {
                                bytes memory hint;
                        
                                return
                                    doTrade(
                                        src,
                                        srcAmount,
                                        dest,
                                        destAddress,
                                        maxDestAmount,
                                        minConversionRate,
                                        platformWallet,
                                        0,
                                        hint
                                    );
                            }
                        
                            /// @notice Backward compatible function
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Trade from src to dest token and sends dest token to destAddress
                            /// @param src Source token
                            /// @param srcAmount Amount of src tokens in twei
                            /// @param dest Destination token
                            /// @param destAddress Address to send tokens to
                            /// @param maxDestAmount A limit on the amount of dest tokens in twei
                            /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
                            /// @param walletId Platform wallet address for receiving fees
                            /// @param hint Advanced instructions for running the trade 
                            /// @return Amount of actual dest tokens in twei
                            function tradeWithHint(
                                ERC20 src,
                                uint256 srcAmount,
                                ERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable walletId,
                                bytes calldata hint
                            ) external payable override returns (uint256) {
                                return
                                    doTrade(
                                        src,
                                        srcAmount,
                                        dest,
                                        destAddress,
                                        maxDestAmount,
                                        minConversionRate,
                                        walletId,
                                        0,
                                        hint
                                    );
                            }
                        
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Trade from src to dest token and sends dest token to destAddress
                            /// @param src Source token
                            /// @param srcAmount Amount of src tokens in twei
                            /// @param dest Destination token
                            /// @param destAddress Address to send tokens to
                            /// @param maxDestAmount A limit on the amount of dest tokens in twei
                            /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
                            /// @param platformWallet Platform wallet address for receiving fees
                            /// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 10000 = 100%, 100 = 1%
                            /// @param hint Advanced instructions for running the trade 
                            /// @return destAmount Amount of actual dest tokens in twei
                            function tradeWithHintAndFee(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable override returns (uint256 destAmount) {
                                return
                                    doTrade(
                                        src,
                                        srcAmount,
                                        dest,
                                        destAddress,
                                        maxDestAmount,
                                        minConversionRate,
                                        platformWallet,
                                        platformFeeBps,
                                        hint
                                    );
                            }
                        
                            /// @dev Trade from src to dest token. Sends dest tokens to msg sender
                            /// @param src Source token
                            /// @param srcAmount Amount of src tokens in twei
                            /// @param dest Destination token
                            /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
                            /// @return Amount of actual dest tokens in twei
                            function swapTokenToToken(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                uint256 minConversionRate
                            ) external override returns (uint256) {
                                bytes memory hint;
                        
                                return
                                    doTrade(
                                        src,
                                        srcAmount,
                                        dest,
                                        msg.sender,
                                        MAX_QTY,
                                        minConversionRate,
                                        address(0),
                                        0,
                                        hint
                                    );
                            }
                        
                            /// @dev Trade from eth -> token. Sends token to msg sender
                            /// @param token Destination token
                            /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
                            /// @return Amount of actual dest tokens in twei
                            function swapEtherToToken(IERC20 token, uint256 minConversionRate)
                                external
                                payable
                                override
                                returns (uint256)
                            {
                                bytes memory hint;
                        
                                return
                                    doTrade(
                                        ETH_TOKEN_ADDRESS,
                                        msg.value,
                                        token,
                                        msg.sender,
                                        MAX_QTY,
                                        minConversionRate,
                                        address(0),
                                        0,
                                        hint
                                    );
                            }
                        
                            /// @dev Trade from token -> eth. Sends eth to msg sender
                            /// @param token Source token
                            /// @param srcAmount Amount of src tokens in twei
                            /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
                            /// @return Amount of actual dest tokens in twei
                            function swapTokenToEther(
                                IERC20 token,
                                uint256 srcAmount,
                                uint256 minConversionRate
                            ) external override returns (uint256) {
                                bytes memory hint;
                        
                                return
                                    doTrade(
                                        token,
                                        srcAmount,
                                        ETH_TOKEN_ADDRESS,
                                        msg.sender,
                                        MAX_QTY,
                                        minConversionRate,
                                        address(0),
                                        0,
                                        hint
                                    );
                            }
                        
                            function setKyberNetwork(IKyberNetwork _kyberNetwork) external {
                                onlyAdmin();
                                require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0");
                                emit KyberNetworkSet(_kyberNetwork, kyberNetwork);
                        
                                kyberNetwork = _kyberNetwork;
                            }
                        
                            function setHintHandler(IKyberHint _kyberHintHandler) external {
                                onlyAdmin();
                                require(_kyberHintHandler != IKyberHint(0), "kyberHintHandler 0");
                                emit KyberHintHandlerSet(_kyberHintHandler);
                        
                                kyberHintHandler = _kyberHintHandler;
                            }
                        
                            /// @notice Backward compatible function
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Get expected rate for a trade from src to dest tokens, with amount srcQty (no platform fee)
                            /// @param src Source token
                            /// @param dest Destination token
                            /// @param srcQty Amount of src tokens in twei
                            /// @return expectedRate for a trade after deducting network fee. Rate = destQty (twei) / srcQty (twei) * 10 ** 18
                            /// @return worstRate for a trade. Usually expectedRate * 97 / 100
                            ///             Use worstRate value as trade min conversion rate at your own risk
                            function getExpectedRate(
                                ERC20 src,
                                ERC20 dest,
                                uint256 srcQty
                            ) external view override returns (uint256 expectedRate, uint256 worstRate) {
                                bytes memory hint;
                                (expectedRate, ) = kyberNetwork.getExpectedRateWithHintAndFee(
                                    src,
                                    dest,
                                    srcQty,
                                    0,
                                    hint
                                );
                                // use simple backward compatible optoin.
                                worstRate = (expectedRate * 97) / 100;
                            }
                        
                            /// @notice Use token address ETH_TOKEN_ADDRESS for ether
                            /// @dev Get expected rate for a trade from src to dest tokens, amount srcQty and fees
                            /// @param src Source token
                            /// @param dest Destination token
                            /// @param srcQty Amount of src tokens in twei
                            /// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 10000 = 100%, 100 = 1%
                            /// @param hint Advanced instructions for running the trade 
                            /// @return expectedRate for a trade after deducting network + platform fee
                            ///             Rate = destQty (twei) / srcQty (twei) * 10 ** 18
                            function getExpectedRateAfterFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external view override returns (uint256 expectedRate) {
                                (, expectedRate) = kyberNetwork.getExpectedRateWithHintAndFee(
                                    src,
                                    dest,
                                    srcQty,
                                    platformFeeBps,
                                    hint
                                );
                            }
                        
                            function maxGasPrice() external view returns (uint256) {
                                return kyberNetwork.maxGasPrice();
                            }
                        
                            function enabled() external view returns (bool) {
                                return kyberNetwork.enabled();
                            }
                        
                            /// helper structure for function doTrade
                            struct UserBalance {
                                uint256 srcTok;
                                uint256 destTok;
                            }
                        
                            function doTrade(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes memory hint
                            ) internal returns (uint256) {
                                UserBalance memory balanceBefore = prepareTrade(src, dest, srcAmount, destAddress);
                        
                                uint256 reportedDestAmount = kyberNetwork.tradeWithHintAndFee{value: msg.value}(
                                    msg.sender,
                                    src,
                                    srcAmount,
                                    dest,
                                    destAddress,
                                    maxDestAmount,
                                    minConversionRate,
                                    platformWallet,
                                    platformFeeBps,
                                    hint
                                );
                                TradeOutcome memory tradeOutcome = calculateTradeOutcome(
                                    src,
                                    dest,
                                    destAddress,
                                    platformFeeBps,
                                    balanceBefore
                                );
                        
                                require(
                                    tradeOutcome.userDeltaDestToken == reportedDestAmount,
                                    "kyberNetwork returned wrong amount"
                                );
                                require(
                                    tradeOutcome.userDeltaDestToken <= maxDestAmount,
                                    "actual dest amount exceeds maxDestAmount"
                                );
                                require(tradeOutcome.actualRate >= minConversionRate, "rate below minConversionRate");
                        
                                emit ExecuteTrade(
                                    msg.sender,
                                    src,
                                    dest,
                                    destAddress,
                                    tradeOutcome.userDeltaSrcToken,
                                    tradeOutcome.userDeltaDestToken,
                                    platformWallet,
                                    platformFeeBps
                                );
                        
                                return tradeOutcome.userDeltaDestToken;
                            }
                        
                            /// helper structure for function prepareTrade
                            struct TradeOutcome {
                                uint256 userDeltaSrcToken;
                                uint256 userDeltaDestToken;
                                uint256 actualRate;
                            }
                        
                            function prepareTrade(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                address destAddress
                            ) internal returns (UserBalance memory balanceBefore) {
                                if (src == ETH_TOKEN_ADDRESS) {
                                    require(msg.value == srcAmount, "sent eth not equal to srcAmount");
                                } else {
                                    require(msg.value == 0, "sent eth not 0");
                                }
                        
                                balanceBefore.srcTok = getBalance(src, msg.sender);
                                balanceBefore.destTok = getBalance(dest, destAddress);
                        
                                if (src == ETH_TOKEN_ADDRESS) {
                                    balanceBefore.srcTok += msg.value;
                                } else {
                                    src.safeTransferFrom(msg.sender, address(kyberNetwork), srcAmount);
                                }
                            }
                        
                            function calculateTradeOutcome(
                                IERC20 src,
                                IERC20 dest,
                                address destAddress,
                                uint256 platformFeeBps,
                                UserBalance memory balanceBefore
                            ) internal returns (TradeOutcome memory outcome) {
                                uint256 srcTokenBalanceAfter;
                                uint256 destTokenBalanceAfter;
                        
                                srcTokenBalanceAfter = getBalance(src, msg.sender);
                                destTokenBalanceAfter = getBalance(dest, destAddress);
                        
                                //protect from underflow
                                require(
                                    destTokenBalanceAfter > balanceBefore.destTok,
                                    "wrong amount in destination address"
                                );
                                require(balanceBefore.srcTok > srcTokenBalanceAfter, "wrong amount in source address");
                        
                                outcome.userDeltaSrcToken = balanceBefore.srcTok - srcTokenBalanceAfter;
                                outcome.userDeltaDestToken = destTokenBalanceAfter - balanceBefore.destTok;
                        
                                // what would be the src amount after deducting platformFee
                                // not protecting from platform fee
                                uint256 srcTokenAmountAfterDeductingFee = (outcome.userDeltaSrcToken *
                                    (BPS - platformFeeBps)) / BPS;
                        
                                outcome.actualRate = calcRateFromQty(
                                    srcTokenAmountAfterDeductingFee,
                                    outcome.userDeltaDestToken,
                                    getUpdateDecimals(src),
                                    getUpdateDecimals(dest)
                                );
                            }
                        }

                        File 6 of 13: LiquidityPoolV1Converter
                        // File: contracts/utility/interfaces/IOwned.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Owned contract interface
                        */
                        contract IOwned {
                            // this function isn't abstract since the compiler emits automatically generated getter functions as external
                            function owner() public view returns (address) {this;}
                        
                            function transferOwnership(address _newOwner) public;
                            function acceptOwnership() public;
                        }
                        
                        // File: contracts/token/interfaces/IERC20Token.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            ERC20 Standard Token interface
                        */
                        contract IERC20Token {
                            // these functions aren't abstract since the compiler emits automatically generated getter functions as external
                            function name() public view returns (string) {this;}
                            function symbol() public view returns (string) {this;}
                            function decimals() public view returns (uint8) {this;}
                            function totalSupply() public view returns (uint256) {this;}
                            function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
                            function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
                        
                            function transfer(address _to, uint256 _value) public returns (bool success);
                            function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
                            function approve(address _spender, uint256 _value) public returns (bool success);
                        }
                        
                        // File: contracts/utility/interfaces/ITokenHolder.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        /*
                            Token Holder interface
                        */
                        contract ITokenHolder is IOwned {
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                        }
                        
                        // File: contracts/converter/interfaces/IConverterAnchor.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        /*
                            Converter Anchor interface
                        */
                        contract IConverterAnchor is IOwned, ITokenHolder {
                        }
                        
                        // File: contracts/utility/interfaces/IWhitelist.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Whitelist interface
                        */
                        contract IWhitelist {
                            function isWhitelisted(address _address) public view returns (bool);
                        }
                        
                        // File: contracts/converter/interfaces/IConverter.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        
                        /*
                            Converter interface
                        */
                        contract IConverter is IOwned {
                            function converterType() public pure returns (uint16);
                            function anchor() public view returns (IConverterAnchor) {this;}
                            function isActive() public view returns (bool);
                        
                            function targetAmountAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount) public view returns (uint256, uint256);
                            function convert(IERC20Token _sourceToken,
                                             IERC20Token _targetToken,
                                             uint256 _amount,
                                             address _trader,
                                             address _beneficiary) public payable returns (uint256);
                        
                            function conversionWhitelist() public view returns (IWhitelist) {this;}
                            function conversionFee() public view returns (uint32) {this;}
                            function maxConversionFee() public view returns (uint32) {this;}
                            function reserveBalance(IERC20Token _reserveToken) public view returns (uint256);
                            function() external payable;
                        
                            function transferAnchorOwnership(address _newOwner) public;
                            function acceptAnchorOwnership() public;
                            function setConversionFee(uint32 _conversionFee) public;
                            function setConversionWhitelist(IWhitelist _whitelist) public;
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                            function withdrawETH(address _to) public;
                            function addReserve(IERC20Token _token, uint32 _ratio) public;
                        
                            // deprecated, backward compatibility
                            function token() public view returns (IConverterAnchor);
                            function transferTokenOwnership(address _newOwner) public;
                            function acceptTokenOwnership() public;
                            function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool);
                            function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256);
                            function connectorTokens(uint256 _index) public view returns (IERC20Token);
                            function connectorTokenCount() public view returns (uint16);
                        }
                        
                        // File: contracts/converter/interfaces/IConverterUpgrader.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Converter Upgrader interface
                        */
                        contract IConverterUpgrader {
                            function upgrade(bytes32 _version) public;
                            function upgrade(uint16 _version) public;
                        }
                        
                        // File: contracts/converter/interfaces/IBancorFormula.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Bancor Formula interface
                        */
                        contract IBancorFormula {
                            function purchaseTargetAmount(uint256 _supply,
                                                          uint256 _reserveBalance,
                                                          uint32 _reserveWeight,
                                                          uint256 _amount)
                                                          public view returns (uint256);
                        
                            function saleTargetAmount(uint256 _supply,
                                                      uint256 _reserveBalance,
                                                      uint32 _reserveWeight,
                                                      uint256 _amount)
                                                      public view returns (uint256);
                        
                            function crossReserveTargetAmount(uint256 _sourceReserveBalance,
                                                              uint32 _sourceReserveWeight,
                                                              uint256 _targetReserveBalance,
                                                              uint32 _targetReserveWeight,
                                                              uint256 _amount)
                                                              public view returns (uint256);
                        
                            function fundCost(uint256 _supply,
                                              uint256 _reserveBalance,
                                              uint32 _reserveRatio,
                                              uint256 _amount)
                                              public view returns (uint256);
                        
                            function fundSupplyAmount(uint256 _supply,
                                                      uint256 _reserveBalance,
                                                      uint32 _reserveRatio,
                                                      uint256 _amount)
                                                      public view returns (uint256);
                        
                            function liquidateReserveAmount(uint256 _supply,
                                                            uint256 _reserveBalance,
                                                            uint32 _reserveRatio,
                                                            uint256 _amount)
                                                            public view returns (uint256);
                        }
                        
                        // File: contracts/IBancorNetwork.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        /*
                            Bancor Network interface
                        */
                        contract IBancorNetwork {
                            function convert2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) public payable returns (uint256);
                        
                            function claimAndConvert2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) public returns (uint256);
                        
                            function convertFor2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _for,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) public payable returns (uint256);
                        
                            function claimAndConvertFor2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _for,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) public returns (uint256);
                        
                            // deprecated, backward compatibility
                            function convert(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn
                            ) public payable returns (uint256);
                        
                            // deprecated, backward compatibility
                            function claimAndConvert(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn
                            ) public returns (uint256);
                        
                            // deprecated, backward compatibility
                            function convertFor(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _for
                            ) public payable returns (uint256);
                        
                            // deprecated, backward compatibility
                            function claimAndConvertFor(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _for
                            ) public returns (uint256);
                        }
                        
                        // File: contracts/utility/Owned.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        /**
                          * @dev Provides support and utilities for contract ownership
                        */
                        contract Owned is IOwned {
                            address public owner;
                            address public newOwner;
                        
                            /**
                              * @dev triggered when the owner is updated
                              *
                              * @param _prevOwner previous owner
                              * @param _newOwner  new owner
                            */
                            event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
                        
                            /**
                              * @dev initializes a new Owned instance
                            */
                            constructor() public {
                                owner = msg.sender;
                            }
                        
                            // allows execution by the owner only
                            modifier ownerOnly {
                                _ownerOnly();
                                _;
                            }
                        
                            // error message binary size optimization
                            function _ownerOnly() internal view {
                                require(msg.sender == owner, "ERR_ACCESS_DENIED");
                            }
                        
                            /**
                              * @dev allows transferring the contract ownership
                              * the new owner still needs to accept the transfer
                              * can only be called by the contract owner
                              *
                              * @param _newOwner    new contract owner
                            */
                            function transferOwnership(address _newOwner) public ownerOnly {
                                require(_newOwner != owner, "ERR_SAME_OWNER");
                                newOwner = _newOwner;
                            }
                        
                            /**
                              * @dev used by a new owner to accept an ownership transfer
                            */
                            function acceptOwnership() public {
                                require(msg.sender == newOwner, "ERR_ACCESS_DENIED");
                                emit OwnerUpdate(owner, newOwner);
                                owner = newOwner;
                                newOwner = address(0);
                            }
                        }
                        
                        // File: contracts/utility/Utils.sol
                        
                        pragma solidity 0.4.26;
                        
                        /**
                          * @dev Utilities & Common Modifiers
                        */
                        contract Utils {
                            // verifies that a value is greater than zero
                            modifier greaterThanZero(uint256 _value) {
                                _greaterThanZero(_value);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _greaterThanZero(uint256 _value) internal pure {
                                require(_value > 0, "ERR_ZERO_VALUE");
                            }
                        
                            // validates an address - currently only checks that it isn't null
                            modifier validAddress(address _address) {
                                _validAddress(_address);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _validAddress(address _address) internal pure {
                                require(_address != address(0), "ERR_INVALID_ADDRESS");
                            }
                        
                            // verifies that the address is different than this contract address
                            modifier notThis(address _address) {
                                _notThis(_address);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _notThis(address _address) internal view {
                                require(_address != address(this), "ERR_ADDRESS_IS_SELF");
                            }
                        }
                        
                        // File: contracts/utility/interfaces/IContractRegistry.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Contract Registry interface
                        */
                        contract IContractRegistry {
                            function addressOf(bytes32 _contractName) public view returns (address);
                        
                            // deprecated, backward compatibility
                            function getAddress(bytes32 _contractName) public view returns (address);
                        }
                        
                        // File: contracts/utility/ContractRegistryClient.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        /**
                          * @dev Base contract for ContractRegistry clients
                        */
                        contract ContractRegistryClient is Owned, Utils {
                            bytes32 internal constant CONTRACT_REGISTRY = "ContractRegistry";
                            bytes32 internal constant BANCOR_NETWORK = "BancorNetwork";
                            bytes32 internal constant BANCOR_FORMULA = "BancorFormula";
                            bytes32 internal constant CONVERTER_FACTORY = "ConverterFactory";
                            bytes32 internal constant CONVERSION_PATH_FINDER = "ConversionPathFinder";
                            bytes32 internal constant CONVERTER_UPGRADER = "BancorConverterUpgrader";
                            bytes32 internal constant CONVERTER_REGISTRY = "BancorConverterRegistry";
                            bytes32 internal constant CONVERTER_REGISTRY_DATA = "BancorConverterRegistryData";
                            bytes32 internal constant BNT_TOKEN = "BNTToken";
                            bytes32 internal constant BANCOR_X = "BancorX";
                            bytes32 internal constant BANCOR_X_UPGRADER = "BancorXUpgrader";
                        
                            IContractRegistry public registry;      // address of the current contract-registry
                            IContractRegistry public prevRegistry;  // address of the previous contract-registry
                            bool public onlyOwnerCanUpdateRegistry; // only an owner can update the contract-registry
                        
                            /**
                              * @dev verifies that the caller is mapped to the given contract name
                              *
                              * @param _contractName    contract name
                            */
                            modifier only(bytes32 _contractName) {
                                _only(_contractName);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _only(bytes32 _contractName) internal view {
                                require(msg.sender == addressOf(_contractName), "ERR_ACCESS_DENIED");
                            }
                        
                            /**
                              * @dev initializes a new ContractRegistryClient instance
                              *
                              * @param  _registry   address of a contract-registry contract
                            */
                            constructor(IContractRegistry _registry) internal validAddress(_registry) {
                                registry = IContractRegistry(_registry);
                                prevRegistry = IContractRegistry(_registry);
                            }
                        
                            /**
                              * @dev updates to the new contract-registry
                             */
                            function updateRegistry() public {
                                // verify that this function is permitted
                                require(msg.sender == owner || !onlyOwnerCanUpdateRegistry, "ERR_ACCESS_DENIED");
                        
                                // get the new contract-registry
                                IContractRegistry newRegistry = IContractRegistry(addressOf(CONTRACT_REGISTRY));
                        
                                // verify that the new contract-registry is different and not zero
                                require(newRegistry != address(registry) && newRegistry != address(0), "ERR_INVALID_REGISTRY");
                        
                                // verify that the new contract-registry is pointing to a non-zero contract-registry
                                require(newRegistry.addressOf(CONTRACT_REGISTRY) != address(0), "ERR_INVALID_REGISTRY");
                        
                                // save a backup of the current contract-registry before replacing it
                                prevRegistry = registry;
                        
                                // replace the current contract-registry with the new contract-registry
                                registry = newRegistry;
                            }
                        
                            /**
                              * @dev restores the previous contract-registry
                            */
                            function restoreRegistry() public ownerOnly {
                                // restore the previous contract-registry
                                registry = prevRegistry;
                            }
                        
                            /**
                              * @dev restricts the permission to update the contract-registry
                              *
                              * @param _onlyOwnerCanUpdateRegistry  indicates whether or not permission is restricted to owner only
                            */
                            function restrictRegistryUpdate(bool _onlyOwnerCanUpdateRegistry) public ownerOnly {
                                // change the permission to update the contract-registry
                                onlyOwnerCanUpdateRegistry = _onlyOwnerCanUpdateRegistry;
                            }
                        
                            /**
                              * @dev returns the address associated with the given contract name
                              *
                              * @param _contractName    contract name
                              *
                              * @return contract address
                            */
                            function addressOf(bytes32 _contractName) internal view returns (address) {
                                return registry.addressOf(_contractName);
                            }
                        }
                        
                        // File: contracts/utility/ReentrancyGuard.sol
                        
                        pragma solidity 0.4.26;
                        
                        /**
                          * @dev ReentrancyGuard
                          *
                          * The contract provides protection against re-entrancy - calling a function (directly or
                          * indirectly) from within itself.
                        */
                        contract ReentrancyGuard {
                            // true while protected code is being executed, false otherwise
                            bool private locked = false;
                        
                            /**
                              * @dev ensures instantiation only by sub-contracts
                            */
                            constructor() internal {}
                        
                            // protects a function against reentrancy attacks
                            modifier protected() {
                                _protected();
                                locked = true;
                                _;
                                locked = false;
                            }
                        
                            // error message binary size optimization
                            function _protected() internal view {
                                require(!locked, "ERR_REENTRANCY");
                            }
                        }
                        
                        // File: contracts/utility/SafeMath.sol
                        
                        pragma solidity 0.4.26;
                        
                        /**
                          * @dev Library for basic math operations with overflow/underflow protection
                        */
                        library SafeMath {
                            /**
                              * @dev returns the sum of _x and _y, reverts if the calculation overflows
                              *
                              * @param _x   value 1
                              * @param _y   value 2
                              *
                              * @return sum
                            */
                            function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                uint256 z = _x + _y;
                                require(z >= _x, "ERR_OVERFLOW");
                                return z;
                            }
                        
                            /**
                              * @dev returns the difference of _x minus _y, reverts if the calculation underflows
                              *
                              * @param _x   minuend
                              * @param _y   subtrahend
                              *
                              * @return difference
                            */
                            function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                require(_x >= _y, "ERR_UNDERFLOW");
                                return _x - _y;
                            }
                        
                            /**
                              * @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
                              *
                              * @param _x   factor 1
                              * @param _y   factor 2
                              *
                              * @return product
                            */
                            function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                // gas optimization
                                if (_x == 0)
                                    return 0;
                        
                                uint256 z = _x * _y;
                                require(z / _x == _y, "ERR_OVERFLOW");
                                return z;
                            }
                        
                            /**
                              * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
                              *
                              * @param _x   dividend
                              * @param _y   divisor
                              *
                              * @return quotient
                            */
                            function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                require(_y > 0, "ERR_DIVIDE_BY_ZERO");
                                uint256 c = _x / _y;
                                return c;
                            }
                        }
                        
                        // File: contracts/utility/TokenHandler.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        contract TokenHandler {
                            bytes4 private constant APPROVE_FUNC_SELECTOR = bytes4(keccak256("approve(address,uint256)"));
                            bytes4 private constant TRANSFER_FUNC_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
                            bytes4 private constant TRANSFER_FROM_FUNC_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)"));
                        
                            /**
                              * @dev executes the ERC20 token's `approve` function and reverts upon failure
                              * the main purpose of this function is to prevent a non standard ERC20 token
                              * from failing silently
                              *
                              * @param _token   ERC20 token address
                              * @param _spender approved address
                              * @param _value   allowance amount
                            */
                            function safeApprove(IERC20Token _token, address _spender, uint256 _value) internal {
                               execute(_token, abi.encodeWithSelector(APPROVE_FUNC_SELECTOR, _spender, _value));
                            }
                        
                            /**
                              * @dev executes the ERC20 token's `transfer` function and reverts upon failure
                              * the main purpose of this function is to prevent a non standard ERC20 token
                              * from failing silently
                              *
                              * @param _token   ERC20 token address
                              * @param _to      target address
                              * @param _value   transfer amount
                            */
                            function safeTransfer(IERC20Token _token, address _to, uint256 _value) internal {
                               execute(_token, abi.encodeWithSelector(TRANSFER_FUNC_SELECTOR, _to, _value));
                            }
                        
                            /**
                              * @dev executes the ERC20 token's `transferFrom` function and reverts upon failure
                              * the main purpose of this function is to prevent a non standard ERC20 token
                              * from failing silently
                              *
                              * @param _token   ERC20 token address
                              * @param _from    source address
                              * @param _to      target address
                              * @param _value   transfer amount
                            */
                            function safeTransferFrom(IERC20Token _token, address _from, address _to, uint256 _value) internal {
                               execute(_token, abi.encodeWithSelector(TRANSFER_FROM_FUNC_SELECTOR, _from, _to, _value));
                            }
                        
                            /**
                              * @dev executes a function on the ERC20 token and reverts upon failure
                              * the main purpose of this function is to prevent a non standard ERC20 token
                              * from failing silently
                              *
                              * @param _token   ERC20 token address
                              * @param _data    data to pass in to the token's contract for execution
                            */
                            function execute(IERC20Token _token, bytes memory _data) private {
                                uint256[1] memory ret = [uint256(1)];
                        
                                assembly {
                                    let success := call(
                                        gas,            // gas remaining
                                        _token,         // destination address
                                        0,              // no ether
                                        add(_data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
                                        mload(_data),   // input length (loaded from the first 32 bytes in the `data` array)
                                        ret,            // output buffer
                                        32              // output length
                                    )
                                    if iszero(success) {
                                        revert(0, 0)
                                    }
                                }
                        
                                require(ret[0] != 0, "ERR_TRANSFER_FAILED");
                            }
                        }
                        
                        // File: contracts/utility/TokenHolder.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        
                        
                        /**
                          * @dev We consider every contract to be a 'token holder' since it's currently not possible
                          * for a contract to deny receiving tokens.
                          *
                          * The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
                          * the owner to send tokens that were sent to the contract by mistake back to their sender.
                          *
                          * Note that we use the non standard ERC-20 interface which has no return value for transfer
                          * in order to support both non standard as well as standard token contracts.
                          * see https://github.com/ethereum/solidity/issues/4116
                        */
                        contract TokenHolder is ITokenHolder, TokenHandler, Owned, Utils {
                            /**
                              * @dev withdraws tokens held by the contract and sends them to an account
                              * can only be called by the owner
                              *
                              * @param _token   ERC20 token contract address
                              * @param _to      account to receive the new amount
                              * @param _amount  amount to withdraw
                            */
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
                                public
                                ownerOnly
                                validAddress(_token)
                                validAddress(_to)
                                notThis(_to)
                            {
                                safeTransfer(_token, _to, _amount);
                            }
                        }
                        
                        // File: contracts/token/interfaces/IEtherToken.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        /*
                            Ether Token interface
                        */
                        contract IEtherToken is IERC20Token {
                            function deposit() public payable;
                            function withdraw(uint256 _amount) public;
                            function depositTo(address _to) public payable;
                            function withdrawTo(address _to, uint256 _amount) public;
                        }
                        
                        // File: contracts/bancorx/interfaces/IBancorX.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        contract IBancorX {
                            function token() public view returns (IERC20Token) {this;}
                            function xTransfer(bytes32 _toBlockchain, bytes32 _to, uint256 _amount, uint256 _id) public;
                            function getXTransferAmount(uint256 _xTransferId, address _for) public view returns (uint256);
                        }
                        
                        // File: contracts/converter/ConverterBase.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        /**
                          * @dev ConverterBase
                          *
                          * The converter contains the main logic for conversions between different ERC20 tokens.
                          *
                          * It is also the upgradable part of the mechanism (note that upgrades are opt-in).
                          *
                          * The anchor must be set on construction and cannot be changed afterwards.
                          * Wrappers are provided for some of the anchor's functions, for easier access.
                          *
                          * Once the converter accepts ownership of the anchor, it becomes the anchor's sole controller
                          * and can execute any of its functions.
                          *
                          * To upgrade the converter, anchor ownership must be transferred to a new converter, along with
                          * any relevant data.
                          *
                          * Note that the converter can transfer anchor ownership to a new converter that
                          * doesn't allow upgrades anymore, for finalizing the relationship between the converter
                          * and the anchor.
                          *
                          * Converter types (defined as uint16 type) -
                          * 0 = liquid token converter
                          * 1 = liquidity pool v1 converter
                          * 2 = liquidity pool v2 converter
                          *
                          * Note that converters don't currently support tokens with transfer fees.
                        */
                        contract ConverterBase is IConverter, TokenHandler, TokenHolder, ContractRegistryClient, ReentrancyGuard {
                            using SafeMath for uint256;
                        
                            uint32 internal constant WEIGHT_RESOLUTION = 1000000;
                            uint64 internal constant CONVERSION_FEE_RESOLUTION = 1000000;
                            address internal constant ETH_RESERVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                        
                            struct Reserve {
                                uint256 balance;    // reserve balance
                                uint32 weight;      // reserve weight, represented in ppm, 1-1000000
                                bool deprecated1;   // deprecated
                                bool deprecated2;   // deprecated
                                bool isSet;         // true if the reserve is valid, false otherwise
                            }
                        
                            /**
                              * @dev version number
                            */
                            uint16 public constant version = 30;
                        
                            IConverterAnchor public anchor;                 // converter anchor contract
                            IWhitelist public conversionWhitelist;          // whitelist contract with list of addresses that are allowed to use the converter
                            IERC20Token[] public reserveTokens;             // ERC20 standard token addresses (prior version 17, use 'connectorTokens' instead)
                            mapping (address => Reserve) public reserves;   // reserve token addresses -> reserve data (prior version 17, use 'connectors' instead)
                            uint32 public reserveRatio = 0;                 // ratio between the reserves and the market cap, equal to the total reserve weights
                            uint32 public maxConversionFee = 0;             // maximum conversion fee for the lifetime of the contract,
                                                                            // represented in ppm, 0...1000000 (0 = no fee, 100 = 0.01%, 1000000 = 100%)
                            uint32 public conversionFee = 0;                // current conversion fee, represented in ppm, 0...maxConversionFee
                            bool public constant conversionsEnabled = true; // deprecated, backward compatibility
                        
                            /**
                              * @dev triggered when the converter is activated
                              *
                              * @param _anchor      converter anchor
                              * @param _activated   true if the converter was activated, false if it was deactivated
                            */
                            event Activation(IConverterAnchor _anchor, bool _activated);
                        
                            /**
                              * @dev triggered when a conversion between two tokens occurs
                              *
                              * @param _fromToken       source ERC20 token
                              * @param _toToken         target ERC20 token
                              * @param _trader          wallet that initiated the trade
                              * @param _amount          amount converted, in the source token
                              * @param _return          amount returned, minus conversion fee
                              * @param _conversionFee   conversion fee
                            */
                            event Conversion(
                                address indexed _fromToken,
                                address indexed _toToken,
                                address indexed _trader,
                                uint256 _amount,
                                uint256 _return,
                                int256 _conversionFee
                            );
                        
                            /**
                              * @dev triggered when the rate between two tokens in the converter changes
                              * note that the event might be dispatched for rate updates between any two tokens in the converter
                              * note that prior to version 28, you should use the 'PriceDataUpdate' event instead
                              *
                              * @param  _token1 address of the first token
                              * @param  _token2 address of the second token
                              * @param  _rateN  rate of 1 unit of `_token1` in `_token2` (numerator)
                              * @param  _rateD  rate of 1 unit of `_token1` in `_token2` (denominator)
                            */
                            event TokenRateUpdate(
                                address indexed _token1,
                                address indexed _token2,
                                uint256 _rateN,
                                uint256 _rateD
                            );
                        
                            /**
                              * @dev triggered when the conversion fee is updated
                              *
                              * @param  _prevFee    previous fee percentage, represented in ppm
                              * @param  _newFee     new fee percentage, represented in ppm
                            */
                            event ConversionFeeUpdate(uint32 _prevFee, uint32 _newFee);
                        
                            /**
                              * @dev used by sub-contracts to initialize a new converter
                              *
                              * @param  _anchor             anchor governed by the converter
                              * @param  _registry           address of a contract registry contract
                              * @param  _maxConversionFee   maximum conversion fee, represented in ppm
                            */
                            constructor(
                                IConverterAnchor _anchor,
                                IContractRegistry _registry,
                                uint32 _maxConversionFee
                            )
                                validAddress(_anchor)
                                ContractRegistryClient(_registry)
                                internal
                                validConversionFee(_maxConversionFee)
                            {
                                anchor = _anchor;
                                maxConversionFee = _maxConversionFee;
                            }
                        
                            // ensures that the converter is active
                            modifier active() {
                                _active();
                                _;
                            }
                        
                            // error message binary size optimization
                            function _active() internal view {
                                require(isActive(), "ERR_INACTIVE");
                            }
                        
                            // ensures that the converter is not active
                            modifier inactive() {
                                _inactive();
                                _;
                            }
                        
                            // error message binary size optimization
                            function _inactive() internal view {
                                require(!isActive(), "ERR_ACTIVE");
                            }
                        
                            // validates a reserve token address - verifies that the address belongs to one of the reserve tokens
                            modifier validReserve(IERC20Token _address) {
                                _validReserve(_address);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _validReserve(IERC20Token _address) internal view {
                                require(reserves[_address].isSet, "ERR_INVALID_RESERVE");
                            }
                        
                            // validates conversion fee
                            modifier validConversionFee(uint32 _conversionFee) {
                                _validConversionFee(_conversionFee);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _validConversionFee(uint32 _conversionFee) internal pure {
                                require(_conversionFee <= CONVERSION_FEE_RESOLUTION, "ERR_INVALID_CONVERSION_FEE");
                            }
                        
                            // validates reserve weight
                            modifier validReserveWeight(uint32 _weight) {
                                _validReserveWeight(_weight);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _validReserveWeight(uint32 _weight) internal pure {
                                require(_weight > 0 && _weight <= WEIGHT_RESOLUTION, "ERR_INVALID_RESERVE_WEIGHT");
                            }
                        
                            /**
                              * @dev deposits ether
                              * can only be called if the converter has an ETH reserve
                            */
                            function() external payable {
                                require(reserves[ETH_RESERVE_ADDRESS].isSet, "ERR_INVALID_RESERVE"); // require(hasETHReserve(), "ERR_INVALID_RESERVE");
                                // a workaround for a problem when running solidity-coverage
                                // see https://github.com/sc-forks/solidity-coverage/issues/487
                            }
                        
                            /**
                              * @dev withdraws ether
                              * can only be called by the owner if the converter is inactive or by upgrader contract
                              * can only be called after the upgrader contract has accepted the ownership of this contract
                              * can only be called if the converter has an ETH reserve
                              *
                              * @param _to  address to send the ETH to
                            */
                            function withdrawETH(address _to)
                                public
                                protected
                                ownerOnly
                                validReserve(IERC20Token(ETH_RESERVE_ADDRESS))
                            {
                                address converterUpgrader = addressOf(CONVERTER_UPGRADER);
                        
                                // verify that the converter is inactive or that the owner is the upgrader contract
                                require(!isActive() || owner == converterUpgrader, "ERR_ACCESS_DENIED");
                                _to.transfer(address(this).balance);
                        
                                // sync the ETH reserve balance
                                syncReserveBalance(IERC20Token(ETH_RESERVE_ADDRESS));
                            }
                        
                            /**
                              * @dev checks whether or not the converter version is 28 or higher
                              *
                              * @return true, since the converter version is 28 or higher
                            */
                            function isV28OrHigher() public pure returns (bool) {
                                return true;
                            }
                        
                            /**
                              * @dev allows the owner to update & enable the conversion whitelist contract address
                              * when set, only addresses that are whitelisted are actually allowed to use the converter
                              * note that the whitelist check is actually done by the BancorNetwork contract
                              *
                              * @param _whitelist    address of a whitelist contract
                            */
                            function setConversionWhitelist(IWhitelist _whitelist)
                                public
                                ownerOnly
                                notThis(_whitelist)
                            {
                                conversionWhitelist = _whitelist;
                            }
                        
                            /**
                              * @dev returns true if the converter is active, false otherwise
                            */
                            function isActive() public view returns (bool) {
                                return anchor.owner() == address(this);
                            }
                        
                            /**
                              * @dev transfers the anchor ownership
                              * the new owner needs to accept the transfer
                              * can only be called by the converter upgrder while the upgrader is the owner
                              * note that prior to version 28, you should use 'transferAnchorOwnership' instead
                              *
                              * @param _newOwner    new token owner
                            */
                            function transferAnchorOwnership(address _newOwner)
                                public
                                ownerOnly
                                only(CONVERTER_UPGRADER)
                            {
                                anchor.transferOwnership(_newOwner);
                            }
                        
                            /**
                              * @dev accepts ownership of the anchor after an ownership transfer
                              * most converters are also activated as soon as they accept the anchor ownership
                              * can only be called by the contract owner
                              * note that prior to version 28, you should use 'acceptTokenOwnership' instead
                            */
                            function acceptAnchorOwnership() public ownerOnly {
                                // verify the the converter has at least one reserve
                                require(reserveTokenCount() > 0, "ERR_INVALID_RESERVE_COUNT");
                                anchor.acceptOwnership();
                                syncReserveBalances();
                            }
                        
                            /**
                              * @dev withdraws tokens held by the anchor and sends them to an account
                              * can only be called by the owner
                              *
                              * @param _token   ERC20 token contract address
                              * @param _to      account to receive the new amount
                              * @param _amount  amount to withdraw
                            */
                            function withdrawFromAnchor(IERC20Token _token, address _to, uint256 _amount) public ownerOnly {
                                anchor.withdrawTokens(_token, _to, _amount);
                            }
                        
                            /**
                              * @dev updates the current conversion fee
                              * can only be called by the contract owner
                              *
                              * @param _conversionFee new conversion fee, represented in ppm
                            */
                            function setConversionFee(uint32 _conversionFee) public ownerOnly {
                                require(_conversionFee <= maxConversionFee, "ERR_INVALID_CONVERSION_FEE");
                                emit ConversionFeeUpdate(conversionFee, _conversionFee);
                                conversionFee = _conversionFee;
                            }
                        
                            /**
                              * @dev withdraws tokens held by the converter and sends them to an account
                              * can only be called by the owner
                              * note that reserve tokens can only be withdrawn by the owner while the converter is inactive
                              * unless the owner is the converter upgrader contract
                              *
                              * @param _token   ERC20 token contract address
                              * @param _to      account to receive the new amount
                              * @param _amount  amount to withdraw
                            */
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public protected ownerOnly {
                                address converterUpgrader = addressOf(CONVERTER_UPGRADER);
                        
                                // if the token is not a reserve token, allow withdrawal
                                // otherwise verify that the converter is inactive or that the owner is the upgrader contract
                                require(!reserves[_token].isSet || !isActive() || owner == converterUpgrader, "ERR_ACCESS_DENIED");
                                super.withdrawTokens(_token, _to, _amount);
                        
                                // if the token is a reserve token, sync the reserve balance
                                if (reserves[_token].isSet)
                                    syncReserveBalance(_token);
                            }
                        
                            /**
                              * @dev upgrades the converter to the latest version
                              * can only be called by the owner
                              * note that the owner needs to call acceptOwnership on the new converter after the upgrade
                            */
                            function upgrade() public ownerOnly {
                                IConverterUpgrader converterUpgrader = IConverterUpgrader(addressOf(CONVERTER_UPGRADER));
                        
                                transferOwnership(converterUpgrader);
                                converterUpgrader.upgrade(version);
                                acceptOwnership();
                            }
                        
                            /**
                              * @dev returns the number of reserve tokens defined
                              * note that prior to version 17, you should use 'connectorTokenCount' instead
                              *
                              * @return number of reserve tokens
                            */
                            function reserveTokenCount() public view returns (uint16) {
                                return uint16(reserveTokens.length);
                            }
                        
                            /**
                              * @dev defines a new reserve token for the converter
                              * can only be called by the owner while the converter is inactive
                              *
                              * @param _token   address of the reserve token
                              * @param _weight  reserve weight, represented in ppm, 1-1000000
                            */
                            function addReserve(IERC20Token _token, uint32 _weight)
                                public
                                ownerOnly
                                inactive
                                validAddress(_token)
                                notThis(_token)
                                validReserveWeight(_weight)
                            {
                                // validate input
                                require(_token != address(anchor) && !reserves[_token].isSet, "ERR_INVALID_RESERVE");
                                require(_weight <= WEIGHT_RESOLUTION - reserveRatio, "ERR_INVALID_RESERVE_WEIGHT");
                                require(reserveTokenCount() < uint16(-1), "ERR_INVALID_RESERVE_COUNT");
                        
                                Reserve storage newReserve = reserves[_token];
                                newReserve.balance = 0;
                                newReserve.weight = _weight;
                                newReserve.isSet = true;
                                reserveTokens.push(_token);
                                reserveRatio += _weight;
                            }
                        
                            /**
                              * @dev returns the reserve's weight
                              * added in version 28
                              *
                              * @param _reserveToken    reserve token contract address
                              *
                              * @return reserve weight
                            */
                            function reserveWeight(IERC20Token _reserveToken)
                                public
                                view
                                validReserve(_reserveToken)
                                returns (uint32)
                            {
                                return reserves[_reserveToken].weight;
                            }
                        
                            /**
                              * @dev returns the reserve's balance
                              * note that prior to version 17, you should use 'getConnectorBalance' instead
                              *
                              * @param _reserveToken    reserve token contract address
                              *
                              * @return reserve balance
                            */
                            function reserveBalance(IERC20Token _reserveToken)
                                public
                                view
                                validReserve(_reserveToken)
                                returns (uint256)
                            {
                                return reserves[_reserveToken].balance;
                            }
                        
                            /**
                              * @dev checks whether or not the converter has an ETH reserve
                              *
                              * @return true if the converter has an ETH reserve, false otherwise
                            */
                            function hasETHReserve() public view returns (bool) {
                                return reserves[ETH_RESERVE_ADDRESS].isSet;
                            }
                        
                            /**
                              * @dev converts a specific amount of source tokens to target tokens
                              * can only be called by the bancor network contract
                              *
                              * @param _sourceToken source ERC20 token
                              * @param _targetToken target ERC20 token
                              * @param _amount      amount of tokens to convert (in units of the source token)
                              * @param _trader      address of the caller who executed the conversion
                              * @param _beneficiary wallet to receive the conversion result
                              *
                              * @return amount of tokens received (in units of the target token)
                            */
                            function convert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary)
                                public
                                payable
                                protected
                                only(BANCOR_NETWORK)
                                returns (uint256)
                            {
                                // validate input
                                require(_sourceToken != _targetToken, "ERR_SAME_SOURCE_TARGET");
                        
                                // if a whitelist is set, verify that both and trader and the beneficiary are whitelisted
                                require(conversionWhitelist == address(0) ||
                                        (conversionWhitelist.isWhitelisted(_trader) && conversionWhitelist.isWhitelisted(_beneficiary)),
                                        "ERR_NOT_WHITELISTED");
                        
                                return doConvert(_sourceToken, _targetToken, _amount, _trader, _beneficiary);
                            }
                        
                            /**
                              * @dev converts a specific amount of source tokens to target tokens
                              * called by ConverterBase and allows the inherited contracts to implement custom conversion logic
                              *
                              * @param _sourceToken source ERC20 token
                              * @param _targetToken target ERC20 token
                              * @param _amount      amount of tokens to convert (in units of the source token)
                              * @param _trader      address of the caller who executed the conversion
                              * @param _beneficiary wallet to receive the conversion result
                              *
                              * @return amount of tokens received (in units of the target token)
                            */
                            function doConvert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary) internal returns (uint256);
                        
                            /**
                              * @dev returns the conversion fee for a given return amount
                              *
                              * @param _amount  return amount
                              *
                              * @return conversion fee
                            */
                            function calculateFee(uint256 _amount) internal view returns (uint256) {
                                return _amount.mul(conversionFee).div(CONVERSION_FEE_RESOLUTION);
                            }
                        
                            /**
                              * @dev syncs the stored reserve balance for a given reserve with the real reserve balance
                              *
                              * @param _reserveToken    address of the reserve token
                            */
                            function syncReserveBalance(IERC20Token _reserveToken) internal validReserve(_reserveToken) {
                                if (_reserveToken == ETH_RESERVE_ADDRESS)
                                    reserves[_reserveToken].balance = address(this).balance;
                                else
                                    reserves[_reserveToken].balance = _reserveToken.balanceOf(this);
                            }
                        
                            /**
                              * @dev syncs all stored reserve balances
                            */
                            function syncReserveBalances() internal {
                                uint256 reserveCount = reserveTokens.length;
                                for (uint256 i = 0; i < reserveCount; i++)
                                    syncReserveBalance(reserveTokens[i]);
                            }
                        
                            /**
                              * @dev helper, dispatches the Conversion event
                              *
                              * @param _sourceToken     source ERC20 token
                              * @param _targetToken     target ERC20 token
                              * @param _trader          address of the caller who executed the conversion
                              * @param _amount          amount purchased/sold (in the source token)
                              * @param _returnAmount    amount returned (in the target token)
                            */
                            function dispatchConversionEvent(
                                IERC20Token _sourceToken,
                                IERC20Token _targetToken,
                                address _trader,
                                uint256 _amount,
                                uint256 _returnAmount,
                                uint256 _feeAmount)
                                internal
                            {
                                // fee amount is converted to 255 bits -
                                // negative amount means the fee is taken from the source token, positive amount means its taken from the target token
                                // currently the fee is always taken from the target token
                                // since we convert it to a signed number, we first ensure that it's capped at 255 bits to prevent overflow
                                assert(_feeAmount < 2 ** 255);
                                emit Conversion(_sourceToken, _targetToken, _trader, _amount, _returnAmount, int256(_feeAmount));
                            }
                        
                            /**
                              * @dev deprecated since version 28, backward compatibility - use only for earlier versions
                            */
                            function token() public view returns (IConverterAnchor) {
                                return anchor;
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function transferTokenOwnership(address _newOwner) public ownerOnly {
                                transferAnchorOwnership(_newOwner);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function acceptTokenOwnership() public ownerOnly {
                                acceptAnchorOwnership();
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool) {
                                Reserve memory reserve = reserves[_address];
                                return(reserve.balance, reserve.weight, false, false, reserve.isSet);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function connectorTokens(uint256 _index) public view returns (IERC20Token) {
                                return ConverterBase.reserveTokens[_index];
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function connectorTokenCount() public view returns (uint16) {
                                return reserveTokenCount();
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256) {
                                return reserveBalance(_connectorToken);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function getReturn(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount) public view returns (uint256, uint256) {
                                return targetAmountAndFee(_sourceToken, _targetToken, _amount);
                            }
                        }
                        
                        // File: contracts/converter/LiquidityPoolConverter.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        /**
                          * @dev Liquidity Pool Converter
                          *
                          * The liquidity pool converter is the base contract for specific types of converters that
                          * manage liquidity pools.
                          *
                          * Liquidity pools have 2 reserves or more and they allow converting between them.
                          *
                          * Note that TokenRateUpdate events are dispatched for pool tokens as well.
                          * The pool token is the first token in the event in that case.
                        */
                        contract LiquidityPoolConverter is ConverterBase {
                            /**
                              * @dev triggered after liquidity is added
                              *
                              * @param  _provider       liquidity provider
                              * @param  _reserveToken   reserve token address
                              * @param  _amount         reserve token amount
                              * @param  _newBalance     reserve token new balance
                              * @param  _newSupply      pool token new supply
                            */
                            event LiquidityAdded(
                                address indexed _provider,
                                address indexed _reserveToken,
                                uint256 _amount,
                                uint256 _newBalance,
                                uint256 _newSupply
                            );
                        
                            /**
                              * @dev triggered after liquidity is removed
                              *
                              * @param  _provider       liquidity provider
                              * @param  _reserveToken   reserve token address
                              * @param  _amount         reserve token amount
                              * @param  _newBalance     reserve token new balance
                              * @param  _newSupply      pool token new supply
                            */
                            event LiquidityRemoved(
                                address indexed _provider,
                                address indexed _reserveToken,
                                uint256 _amount,
                                uint256 _newBalance,
                                uint256 _newSupply
                            );
                        
                            /**
                              * @dev initializes a new LiquidityPoolConverter instance
                              *
                              * @param  _anchor             anchor governed by the converter
                              * @param  _registry           address of a contract registry contract
                              * @param  _maxConversionFee   maximum conversion fee, represented in ppm
                            */
                            constructor(
                                IConverterAnchor _anchor,
                                IContractRegistry _registry,
                                uint32 _maxConversionFee
                            )
                                ConverterBase(_anchor, _registry, _maxConversionFee)
                                internal
                            {
                            }
                        
                            /**
                              * @dev accepts ownership of the anchor after an ownership transfer
                              * also activates the converter
                              * can only be called by the contract owner
                              * note that prior to version 28, you should use 'acceptTokenOwnership' instead
                            */
                            function acceptTokenOwnership() public {
                                // verify that the converter has at least 2 reserves
                                require(reserveTokenCount() > 1, "ERR_INVALID_RESERVE_COUNT");
                                super.acceptTokenOwnership();
                            }
                        }
                        
                        // File: contracts/converter/interfaces/ITypedConverterFactory.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        /*
                            Typed Converter Factory interface
                        */
                        contract ITypedConverterFactory {
                            function converterType() public pure returns (uint16);
                            function createConverter(IConverterAnchor _anchor, IContractRegistry _registry, uint32 _maxConversionFee) public returns (IConverter);
                        }
                        
                        // File: contracts/token/interfaces/ISmartToken.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        /*
                            Smart Token interface
                        */
                        contract ISmartToken is IConverterAnchor, IERC20Token {
                            function disableTransfers(bool _disable) public;
                            function issue(address _to, uint256 _amount) public;
                            function destroy(address _from, uint256 _amount) public;
                        }
                        
                        // File: contracts/converter/LiquidityPoolV1Converter.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        /*
                            LiquidityPoolV1Converter Factory
                        */
                        contract LiquidityPoolV1ConverterFactory is ITypedConverterFactory {
                            /**
                              * @dev returns the converter type the factory is associated with
                              *
                              * @return converter type
                            */
                            function converterType() public pure returns (uint16) {
                                return 1;
                            }
                        
                            /**
                              * @dev creates a new converter with the given arguments and transfers
                              * the ownership to the caller
                              *
                              * @param _anchor            anchor governed by the converter
                              * @param _registry          address of a contract registry contract
                              * @param _maxConversionFee  maximum conversion fee, represented in ppm
                              *
                              * @return a new converter
                            */
                            function createConverter(IConverterAnchor _anchor, IContractRegistry _registry, uint32 _maxConversionFee) public returns (IConverter) {
                                ConverterBase converter = new LiquidityPoolV1Converter(ISmartToken(_anchor), _registry, _maxConversionFee);
                                converter.transferOwnership(msg.sender);
                                return converter;
                            }
                        }
                        
                        /**
                          * @dev Liquidity Pool v1 Converter
                          *
                          * The liquidity pool v1 converter is a specialized version of a converter that manages
                          * a classic bancor liquidity pool.
                          *
                          * Even though classic pools can have many reserves, the most common configuration of
                          * the pool has 2 reserves with 50%/50% weights.
                        */
                        contract LiquidityPoolV1Converter is LiquidityPoolConverter {
                            IEtherToken internal etherToken = IEtherToken(0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315);
                        
                            /**
                              * @dev triggered after a conversion with new price data
                              * deprecated, use `TokenRateUpdate` from version 28 and up
                              *
                              * @param  _connectorToken     reserve token
                              * @param  _tokenSupply        smart token supply
                              * @param  _connectorBalance   reserve balance
                              * @param  _connectorWeight    reserve weight
                            */
                            event PriceDataUpdate(
                                address indexed _connectorToken,
                                uint256 _tokenSupply,
                                uint256 _connectorBalance,
                                uint32 _connectorWeight
                            );
                        
                            /**
                              * @dev initializes a new LiquidityPoolV1Converter instance
                              *
                              * @param  _token              pool token governed by the converter
                              * @param  _registry           address of a contract registry contract
                              * @param  _maxConversionFee   maximum conversion fee, represented in ppm
                            */
                            constructor(
                                ISmartToken _token,
                                IContractRegistry _registry,
                                uint32 _maxConversionFee
                            )
                                LiquidityPoolConverter(_token, _registry, _maxConversionFee)
                                public
                            {
                            }
                        
                            /**
                              * @dev returns the converter type
                              *
                              * @return see the converter types in the the main contract doc
                            */
                            function converterType() public pure returns (uint16) {
                                return 1;
                            }
                        
                            /**
                              * @dev accepts ownership of the anchor after an ownership transfer
                              * also activates the converter
                              * can only be called by the contract owner
                              * note that prior to version 28, you should use 'acceptTokenOwnership' instead
                            */
                            function acceptAnchorOwnership() public ownerOnly {
                                super.acceptAnchorOwnership();
                        
                                emit Activation(anchor, true);
                            }
                        
                            /**
                              * @dev returns the expected target amount of converting one reserve to another along with the fee
                              *
                              * @param _sourceToken contract address of the source reserve token
                              * @param _targetToken contract address of the target reserve token
                              * @param _amount      amount of tokens received from the user
                              *
                              * @return expected target amount
                              * @return expected fee
                            */
                            function targetAmountAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount)
                                public
                                view
                                active
                                validReserve(_sourceToken)
                                validReserve(_targetToken)
                                returns (uint256, uint256)
                            {
                                // validate input
                                require(_sourceToken != _targetToken, "ERR_SAME_SOURCE_TARGET");
                        
                                uint256 amount = IBancorFormula(addressOf(BANCOR_FORMULA)).crossReserveTargetAmount(
                                    reserveBalance(_sourceToken),
                                    reserves[_sourceToken].weight,
                                    reserveBalance(_targetToken),
                                    reserves[_targetToken].weight,
                                    _amount
                                );
                        
                                // return the amount minus the conversion fee and the conversion fee
                                uint256 fee = calculateFee(amount);
                                return (amount - fee, fee);
                            }
                        
                            /**
                              * @dev converts a specific amount of source tokens to target tokens
                              * can only be called by the bancor network contract
                              *
                              * @param _sourceToken source ERC20 token
                              * @param _targetToken target ERC20 token
                              * @param _amount      amount of tokens to convert (in units of the source token)
                              * @param _trader      address of the caller who executed the conversion
                              * @param _beneficiary wallet to receive the conversion result
                              *
                              * @return amount of tokens received (in units of the target token)
                            */
                            function doConvert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary)
                                internal
                                returns (uint256)
                            {
                                // get expected target amount and fee
                                (uint256 amount, uint256 fee) = targetAmountAndFee(_sourceToken, _targetToken, _amount);
                        
                                // ensure that the trade gives something in return
                                require(amount != 0, "ERR_ZERO_TARGET_AMOUNT");
                        
                                // ensure that the trade won't deplete the reserve balance
                                uint256 targetReserveBalance = reserveBalance(_targetToken);
                                assert(amount < targetReserveBalance);
                        
                                // ensure that the input amount was already deposited
                                if (_sourceToken == ETH_RESERVE_ADDRESS)
                                    require(msg.value == _amount, "ERR_ETH_AMOUNT_MISMATCH");
                                else
                                    require(msg.value == 0 && _sourceToken.balanceOf(this).sub(reserveBalance(_sourceToken)) >= _amount, "ERR_INVALID_AMOUNT");
                        
                                // sync the reserve balances
                                syncReserveBalance(_sourceToken);
                                reserves[_targetToken].balance = reserves[_targetToken].balance.sub(amount);
                        
                                // transfer funds to the beneficiary in the to reserve token
                                if (_targetToken == ETH_RESERVE_ADDRESS)
                                    _beneficiary.transfer(amount);
                                else
                                    safeTransfer(_targetToken, _beneficiary, amount);
                        
                                // dispatch the conversion event
                                dispatchConversionEvent(_sourceToken, _targetToken, _trader, _amount, amount, fee);
                        
                                // dispatch rate updates
                                dispatchRateEvents(_sourceToken, _targetToken);
                        
                                return amount;
                            }
                        
                            /**
                              * @dev increases the pool's liquidity and mints new shares in the pool to the caller
                              * note that prior to version 28, you should use 'fund' instead
                              *
                              * @param _reserveTokens   address of each reserve token
                              * @param _reserveAmounts  amount of each reserve token
                              * @param _minReturn       token minimum return-amount
                            */
                            function addLiquidity(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _minReturn)
                                public
                                payable
                                protected
                                active
                            {
                                // verify the user input
                                verifyLiquidityInput(_reserveTokens, _reserveAmounts, _minReturn);
                        
                                // if one of the reserves is ETH, then verify that the input amount of ETH is equal to the input value of ETH
                                for (uint256 i = 0; i < _reserveTokens.length; i++)
                                    if (_reserveTokens[i] == ETH_RESERVE_ADDRESS)
                                        require(_reserveAmounts[i] == msg.value, "ERR_ETH_AMOUNT_MISMATCH");
                        
                                // if the input value of ETH is larger than zero, then verify that one of the reserves is ETH
                                if (msg.value > 0)
                                    require(reserves[ETH_RESERVE_ADDRESS].isSet, "ERR_NO_ETH_RESERVE");
                        
                                // get the total supply
                                uint256 totalSupply = ISmartToken(anchor).totalSupply();
                        
                                // transfer from the user an equally-worth amount of each one of the reserve tokens
                                uint256 amount = addLiquidityToPool(_reserveTokens, _reserveAmounts, totalSupply);
                        
                                // verify that the equivalent amount of tokens is equal to or larger than the user's expectation
                                require(amount >= _minReturn, "ERR_RETURN_TOO_LOW");
                        
                                // issue the tokens to the user
                                ISmartToken(anchor).issue(msg.sender, amount);
                            }
                        
                            /**
                              * @dev decreases the pool's liquidity and burns the caller's shares in the pool
                              * note that prior to version 28, you should use 'liquidate' instead
                              *
                              * @param _amount                  token amount
                              * @param _reserveTokens           address of each reserve token
                              * @param _reserveMinReturnAmounts minimum return-amount of each reserve token
                            */
                            function removeLiquidity(uint256 _amount, IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts)
                                public
                                protected
                                active
                            {
                                // verify the user input
                                verifyLiquidityInput(_reserveTokens, _reserveMinReturnAmounts, _amount);
                        
                                // get the total supply BEFORE destroying the user tokens
                                uint256 totalSupply = ISmartToken(anchor).totalSupply();
                        
                                // destroy the user tokens
                                ISmartToken(anchor).destroy(msg.sender, _amount);
                        
                                // transfer to the user an equivalent amount of each one of the reserve tokens
                                removeLiquidityFromPool(_reserveTokens, _reserveMinReturnAmounts, totalSupply, _amount);
                            }
                        
                            /**
                              * @dev increases the pool's liquidity and mints new shares in the pool to the caller
                              * for example, if the caller increases the supply by 10%,
                              * then it will cost an amount equal to 10% of each reserve token balance
                              * note that starting from version 28, you should use 'addLiquidity' instead
                              *
                              * @param _amount  amount to increase the supply by (in the pool token)
                            */
                            function fund(uint256 _amount) public payable protected {
                                syncReserveBalances();
                                reserves[ETH_RESERVE_ADDRESS].balance = reserves[ETH_RESERVE_ADDRESS].balance.sub(msg.value);
                        
                                uint256 supply = ISmartToken(anchor).totalSupply();
                                IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
                        
                                // iterate through the reserve tokens and transfer a percentage equal to the weight between
                                // _amount and the total supply in each reserve from the caller to the converter
                                uint256 reserveCount = reserveTokens.length;
                                for (uint256 i = 0; i < reserveCount; i++) {
                                    IERC20Token reserveToken = reserveTokens[i];
                                    uint256 rsvBalance = reserves[reserveToken].balance;
                                    uint256 reserveAmount = formula.fundCost(supply, rsvBalance, reserveRatio, _amount);
                        
                                    // transfer funds from the caller in the reserve token
                                    if (reserveToken == ETH_RESERVE_ADDRESS) {
                                        if (msg.value > reserveAmount) {
                                            msg.sender.transfer(msg.value - reserveAmount);
                                        }
                                        else if (msg.value < reserveAmount) {
                                            require(msg.value == 0, "ERR_INVALID_ETH_VALUE");
                                            safeTransferFrom(etherToken, msg.sender, this, reserveAmount);
                                            etherToken.withdraw(reserveAmount);
                                        }
                                    }
                                    else {
                                        safeTransferFrom(reserveToken, msg.sender, this, reserveAmount);
                                    }
                        
                                    // sync the reserve balance
                                    uint256 newReserveBalance = rsvBalance.add(reserveAmount);
                                    reserves[reserveToken].balance = newReserveBalance;
                        
                                    uint256 newPoolTokenSupply = supply.add(_amount);
                        
                                    // dispatch liquidity update for the pool token/reserve
                                    emit LiquidityAdded(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
                        
                                    // dispatch the `TokenRateUpdate` event for the pool token
                                    uint32 reserveWeight = reserves[reserveToken].weight;
                                    dispatchPoolTokenRateEvent(newPoolTokenSupply, reserveToken, newReserveBalance, reserveWeight);
                                }
                        
                                // issue new funds to the caller in the pool token
                                ISmartToken(anchor).issue(msg.sender, _amount);
                            }
                        
                            /**
                              * @dev decreases the pool's liquidity and burns the caller's shares in the pool
                              * for example, if the holder sells 10% of the supply,
                              * then they will receive 10% of each reserve token balance in return
                              * note that starting from version 28, you should use 'removeLiquidity' instead
                              *
                              * @param _amount  amount to liquidate (in the pool token)
                            */
                            function liquidate(uint256 _amount) public protected {
                                require(_amount > 0, "ERR_ZERO_AMOUNT");
                        
                                uint256 totalSupply = ISmartToken(anchor).totalSupply();
                                ISmartToken(anchor).destroy(msg.sender, _amount);
                        
                                uint256[] memory reserveMinReturnAmounts = new uint256[](reserveTokens.length);
                                for (uint256 i = 0; i < reserveMinReturnAmounts.length; i++)
                                    reserveMinReturnAmounts[i] = 1;
                        
                                removeLiquidityFromPool(reserveTokens, reserveMinReturnAmounts, totalSupply, _amount);
                            }
                        
                            /**
                              * @dev verifies that a given array of tokens is identical to the converter's array of reserve tokens
                              * we take this input in order to allow specifying the corresponding reserve amounts in any order
                              *
                              * @param _reserveTokens   array of reserve tokens
                              * @param _reserveAmounts  array of reserve amounts
                              * @param _amount          token amount
                            */
                            function verifyLiquidityInput(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _amount) private view {
                                uint256 i;
                                uint256 j;
                        
                                uint256 length = reserveTokens.length;
                                require(length == _reserveTokens.length, "ERR_INVALID_RESERVE");
                                require(length == _reserveAmounts.length, "ERR_INVALID_AMOUNT");
                        
                                for (i = 0; i < length; i++) {
                                    // verify that every input reserve token is included in the reserve tokens
                                    require(reserves[_reserveTokens[i]].isSet, "ERR_INVALID_RESERVE");
                                    for (j = 0; j < length; j++) {
                                        if (reserveTokens[i] == _reserveTokens[j])
                                            break;
                                    }
                                    // verify that every reserve token is included in the input reserve tokens
                                    require(j < length, "ERR_INVALID_RESERVE");
                                    // verify that every input reserve token amount is larger than zero
                                    require(_reserveAmounts[i] > 0, "ERR_INVALID_AMOUNT");
                                }
                        
                                // verify that the input token amount is larger than zero
                                require(_amount > 0, "ERR_ZERO_AMOUNT");
                            }
                        
                            /**
                              * @dev adds liquidity (reserve) to the pool
                              *
                              * @param _reserveTokens   address of each reserve token
                              * @param _reserveAmounts  amount of each reserve token
                              * @param _totalSupply     token total supply
                            */
                            function addLiquidityToPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _totalSupply)
                                private
                                returns (uint256)
                            {
                                if (_totalSupply == 0)
                                    return addLiquidityToEmptyPool(_reserveTokens, _reserveAmounts);
                                return addLiquidityToNonEmptyPool(_reserveTokens, _reserveAmounts, _totalSupply);
                            }
                        
                            /**
                              * @dev adds liquidity (reserve) to the pool when it's empty
                              *
                              * @param _reserveTokens   address of each reserve token
                              * @param _reserveAmounts  amount of each reserve token
                            */
                            function addLiquidityToEmptyPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts)
                                private
                                returns (uint256)
                            {
                                // calculate the geometric-mean of the reserve amounts approved by the user
                                uint256 amount = geometricMean(_reserveAmounts);
                        
                                // transfer each one of the reserve amounts from the user to the pool
                                for (uint256 i = 0; i < _reserveTokens.length; i++) {
                                    if (_reserveTokens[i] != ETH_RESERVE_ADDRESS) // ETH has already been transferred as part of the transaction
                                        safeTransferFrom(_reserveTokens[i], msg.sender, this, _reserveAmounts[i]);
                        
                                    reserves[_reserveTokens[i]].balance = _reserveAmounts[i];
                        
                                    emit LiquidityAdded(msg.sender, _reserveTokens[i], _reserveAmounts[i], _reserveAmounts[i], amount);
                        
                                    // dispatch the `TokenRateUpdate` event for the pool token
                                    uint32 reserveWeight = reserves[_reserveTokens[i]].weight;
                                    dispatchPoolTokenRateEvent(amount, _reserveTokens[i], _reserveAmounts[i], reserveWeight);
                                }
                        
                                return amount;
                            }
                        
                            /**
                              * @dev adds liquidity (reserve) to the pool when it's not empty
                              *
                              * @param _reserveTokens   address of each reserve token
                              * @param _reserveAmounts  amount of each reserve token
                              * @param _totalSupply     token total supply
                            */
                            function addLiquidityToNonEmptyPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _totalSupply)
                                private
                                returns (uint256)
                            {
                                syncReserveBalances();
                                reserves[ETH_RESERVE_ADDRESS].balance = reserves[ETH_RESERVE_ADDRESS].balance.sub(msg.value);
                        
                                IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
                                uint256 amount = getMinShare(formula, _totalSupply, _reserveTokens, _reserveAmounts);
                                uint256 newPoolTokenSupply = _totalSupply.add(amount);
                        
                                for (uint256 i = 0; i < _reserveTokens.length; i++) {
                                    IERC20Token reserveToken = _reserveTokens[i];
                                    uint256 rsvBalance = reserves[reserveToken].balance;
                                    uint256 reserveAmount = formula.fundCost(_totalSupply, rsvBalance, reserveRatio, amount);
                                    require(reserveAmount > 0, "ERR_ZERO_TARGET_AMOUNT");
                                    assert(reserveAmount <= _reserveAmounts[i]);
                        
                                    // transfer each one of the reserve amounts from the user to the pool
                                    if (reserveToken != ETH_RESERVE_ADDRESS) // ETH has already been transferred as part of the transaction
                                        safeTransferFrom(reserveToken, msg.sender, this, reserveAmount);
                                    else if (_reserveAmounts[i] > reserveAmount) // transfer the extra amount of ETH back to the user
                                        msg.sender.transfer(_reserveAmounts[i] - reserveAmount);
                        
                                    uint256 newReserveBalance = rsvBalance.add(reserveAmount);
                                    reserves[reserveToken].balance = newReserveBalance;
                        
                                    emit LiquidityAdded(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
                        
                                    // dispatch the `TokenRateUpdate` event for the pool token
                                    uint32 reserveWeight = reserves[_reserveTokens[i]].weight;
                                    dispatchPoolTokenRateEvent(newPoolTokenSupply, _reserveTokens[i], newReserveBalance, reserveWeight);
                                }
                        
                                return amount;
                            }
                        
                            /**
                              * @dev removes liquidity (reserve) from the pool
                              *
                              * @param _reserveTokens           address of each reserve token
                              * @param _reserveMinReturnAmounts minimum return-amount of each reserve token
                              * @param _totalSupply             token total supply
                              * @param _amount                  token amount
                            */
                            function removeLiquidityFromPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts, uint256 _totalSupply, uint256 _amount)
                                private
                            {
                                syncReserveBalances();
                        
                                IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
                                uint256 newPoolTokenSupply = _totalSupply.sub(_amount);
                        
                                for (uint256 i = 0; i < _reserveTokens.length; i++) {
                                    IERC20Token reserveToken = _reserveTokens[i];
                                    uint256 rsvBalance = reserves[reserveToken].balance;
                                    uint256 reserveAmount = formula.liquidateReserveAmount(_totalSupply, rsvBalance, reserveRatio, _amount);
                                    require(reserveAmount >= _reserveMinReturnAmounts[i], "ERR_ZERO_TARGET_AMOUNT");
                        
                                    uint256 newReserveBalance = rsvBalance.sub(reserveAmount);
                                    reserves[reserveToken].balance = newReserveBalance;
                        
                                    // transfer each one of the reserve amounts from the pool to the user
                                    if (reserveToken == ETH_RESERVE_ADDRESS)
                                        msg.sender.transfer(reserveAmount);
                                    else
                                        safeTransfer(reserveToken, msg.sender, reserveAmount);
                        
                                    emit LiquidityRemoved(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
                        
                                    // dispatch the `TokenRateUpdate` event for the pool token
                                    uint32 reserveWeight = reserves[reserveToken].weight;
                                    dispatchPoolTokenRateEvent(newPoolTokenSupply, reserveToken, newReserveBalance, reserveWeight);
                                }
                            }
                        
                            function getMinShare(IBancorFormula formula, uint256 _totalSupply, IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts) private view returns (uint256) {
                                uint256 minIndex = 0;
                                for (uint256 i = 1; i < _reserveTokens.length; i++) {
                                    if (_reserveAmounts[i].mul(reserves[_reserveTokens[minIndex]].balance) < _reserveAmounts[minIndex].mul(reserves[_reserveTokens[i]].balance))
                                        minIndex = i;
                                }
                                return formula.fundSupplyAmount(_totalSupply, reserves[_reserveTokens[minIndex]].balance, reserveRatio, _reserveAmounts[minIndex]);
                            }
                        
                            /**
                              * @dev calculates the number of decimal digits in a given value
                              *
                              * @param _x   value (assumed positive)
                              * @return the number of decimal digits in the given value
                            */
                            function decimalLength(uint256 _x) public pure returns (uint256) {
                                uint256 y = 0;
                                for (uint256 x = _x; x > 0; x /= 10)
                                    y++;
                                return y;
                            }
                        
                            /**
                              * @dev calculates the nearest integer to a given quotient
                              *
                              * @param _n   quotient numerator
                              * @param _d   quotient denominator
                              * @return the nearest integer to the given quotient
                            */
                            function roundDiv(uint256 _n, uint256 _d) public pure returns (uint256) {
                                return (_n + _d / 2) / _d;
                            }
                        
                            /**
                              * @dev calculates the average number of decimal digits in a given list of values
                              *
                              * @param _values  list of values (each of which assumed positive)
                              * @return the average number of decimal digits in the given list of values
                            */
                            function geometricMean(uint256[] memory _values) public pure returns (uint256) {
                                uint256 numOfDigits = 0;
                                uint256 length = _values.length;
                                for (uint256 i = 0; i < length; i++)
                                    numOfDigits += decimalLength(_values[i]);
                                return uint256(10) ** (roundDiv(numOfDigits, length) - 1);
                            }
                        
                             /**
                              * @dev dispatches rate events for both reserves / pool tokens
                              * only used to circumvent the `stack too deep` compiler error
                              *
                              * @param _sourceToken address of the source reserve token
                              * @param _targetToken address of the target reserve token
                            */
                            function dispatchRateEvents(IERC20Token _sourceToken, IERC20Token _targetToken) private {
                                uint256 poolTokenSupply = ISmartToken(anchor).totalSupply();
                                uint256 sourceReserveBalance = reserveBalance(_sourceToken);
                                uint256 targetReserveBalance = reserveBalance(_targetToken);
                                uint32 sourceReserveWeight = reserves[_sourceToken].weight;
                                uint32 targetReserveWeight = reserves[_targetToken].weight;
                        
                                // dispatch token rate update event
                                uint256 rateN = targetReserveBalance.mul(sourceReserveWeight);
                                uint256 rateD = sourceReserveBalance.mul(targetReserveWeight);
                                emit TokenRateUpdate(_sourceToken, _targetToken, rateN, rateD);
                        
                                // dispatch the `TokenRateUpdate` event for the pool token
                                dispatchPoolTokenRateEvent(poolTokenSupply, _sourceToken, sourceReserveBalance, sourceReserveWeight);
                                dispatchPoolTokenRateEvent(poolTokenSupply, _targetToken, targetReserveBalance, targetReserveWeight);
                        
                                // dispatch price data update events (deprecated events)
                                emit PriceDataUpdate(_sourceToken, poolTokenSupply, sourceReserveBalance, sourceReserveWeight);
                                emit PriceDataUpdate(_targetToken, poolTokenSupply, targetReserveBalance, targetReserveWeight);
                            }
                        
                            /**
                              * @dev dispatches the `TokenRateUpdate` for the pool token
                              * only used to circumvent the `stack too deep` compiler error
                              *
                              * @param _poolTokenSupply total pool token supply
                              * @param _reserveToken    address of the reserve token
                              * @param _reserveBalance  reserve balance
                              * @param _reserveWeight   reserve weight
                            */
                            function dispatchPoolTokenRateEvent(uint256 _poolTokenSupply, IERC20Token _reserveToken, uint256 _reserveBalance, uint32 _reserveWeight) private {
                                emit TokenRateUpdate(anchor, _reserveToken, _reserveBalance.mul(WEIGHT_RESOLUTION), _poolTokenSupply.mul(_reserveWeight));
                            }
                        }

                        File 7 of 13: BancorNetwork
                        // File: contracts/token/interfaces/IERC20Token.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            ERC20 Standard Token interface
                        */
                        contract IERC20Token {
                            // these functions aren't abstract since the compiler emits automatically generated getter functions as external
                            function name() public view returns (string) {this;}
                            function symbol() public view returns (string) {this;}
                            function decimals() public view returns (uint8) {this;}
                            function totalSupply() public view returns (uint256) {this;}
                            function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
                            function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
                        
                            function transfer(address _to, uint256 _value) public returns (bool success);
                            function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
                            function approve(address _spender, uint256 _value) public returns (bool success);
                        }
                        
                        // File: contracts/IBancorNetwork.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        /*
                            Bancor Network interface
                        */
                        contract IBancorNetwork {
                            function convert2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) public payable returns (uint256);
                        
                            function claimAndConvert2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) public returns (uint256);
                        
                            function convertFor2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _for,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) public payable returns (uint256);
                        
                            function claimAndConvertFor2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _for,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) public returns (uint256);
                        
                            // deprecated, backward compatibility
                            function convert(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn
                            ) public payable returns (uint256);
                        
                            // deprecated, backward compatibility
                            function claimAndConvert(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn
                            ) public returns (uint256);
                        
                            // deprecated, backward compatibility
                            function convertFor(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _for
                            ) public payable returns (uint256);
                        
                            // deprecated, backward compatibility
                            function claimAndConvertFor(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _for
                            ) public returns (uint256);
                        }
                        
                        // File: contracts/IConversionPathFinder.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        /*
                            Conversion Path Finder interface
                        */
                        contract IConversionPathFinder {
                            function findPath(address _sourceToken, address _targetToken) public view returns (address[] memory);
                        }
                        
                        // File: contracts/utility/interfaces/IOwned.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Owned contract interface
                        */
                        contract IOwned {
                            // this function isn't abstract since the compiler emits automatically generated getter functions as external
                            function owner() public view returns (address) {this;}
                        
                            function transferOwnership(address _newOwner) public;
                            function acceptOwnership() public;
                        }
                        
                        // File: contracts/utility/interfaces/ITokenHolder.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        /*
                            Token Holder interface
                        */
                        contract ITokenHolder is IOwned {
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                        }
                        
                        // File: contracts/converter/interfaces/IConverterAnchor.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        /*
                            Converter Anchor interface
                        */
                        contract IConverterAnchor is IOwned, ITokenHolder {
                        }
                        
                        // File: contracts/utility/interfaces/IWhitelist.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Whitelist interface
                        */
                        contract IWhitelist {
                            function isWhitelisted(address _address) public view returns (bool);
                        }
                        
                        // File: contracts/converter/interfaces/IConverter.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        
                        /*
                            Converter interface
                        */
                        contract IConverter is IOwned {
                            function converterType() public pure returns (uint16);
                            function anchor() public view returns (IConverterAnchor) {this;}
                            function isActive() public view returns (bool);
                        
                            function rateAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount) public view returns (uint256, uint256);
                            function convert(IERC20Token _sourceToken,
                                             IERC20Token _targetToken,
                                             uint256 _amount,
                                             address _trader,
                                             address _beneficiary) public payable returns (uint256);
                        
                            function conversionWhitelist() public view returns (IWhitelist) {this;}
                            function conversionFee() public view returns (uint32) {this;}
                            function maxConversionFee() public view returns (uint32) {this;}
                            function reserveBalance(IERC20Token _reserveToken) public view returns (uint256);
                            function() external payable;
                        
                            function transferAnchorOwnership(address _newOwner) public;
                            function acceptAnchorOwnership() public;
                            function setConversionFee(uint32 _conversionFee) public;
                            function setConversionWhitelist(IWhitelist _whitelist) public;
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                            function withdrawETH(address _to) public;
                            function addReserve(IERC20Token _token, uint32 _ratio) public;
                        
                            // deprecated, backward compatibility
                            function token() public view returns (IConverterAnchor);
                            function transferTokenOwnership(address _newOwner) public;
                            function acceptTokenOwnership() public;
                            function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool);
                            function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256);
                            function connectorTokens(uint256 _index) public view returns (IERC20Token);
                            function connectorTokenCount() public view returns (uint16);
                        }
                        
                        // File: contracts/converter/interfaces/IBancorFormula.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Bancor Formula interface
                        */
                        contract IBancorFormula {
                            function purchaseRate(uint256 _supply,
                                                  uint256 _reserveBalance,
                                                  uint32 _reserveWeight,
                                                  uint256 _amount)
                                                  public view returns (uint256);
                        
                            function saleRate(uint256 _supply,
                                              uint256 _reserveBalance,
                                              uint32 _reserveWeight,
                                              uint256 _amount)
                                              public view returns (uint256);
                        
                            function crossReserveRate(uint256 _sourceReserveBalance,
                                                      uint32 _sourceReserveWeight,
                                                      uint256 _targetReserveBalance,
                                                      uint32 _targetReserveWeight,
                                                      uint256 _amount)
                                                      public view returns (uint256);
                        
                            function fundCost(uint256 _supply,
                                              uint256 _reserveBalance,
                                              uint32 _reserveRatio,
                                              uint256 _amount)
                                              public view returns (uint256);
                        
                            function liquidateRate(uint256 _supply,
                                                   uint256 _reserveBalance,
                                                   uint32 _reserveRatio,
                                                   uint256 _amount)
                                                   public view returns (uint256);
                        }
                        
                        // File: contracts/utility/Owned.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        /**
                          * @dev Provides support and utilities for contract ownership
                        */
                        contract Owned is IOwned {
                            address public owner;
                            address public newOwner;
                        
                            /**
                              * @dev triggered when the owner is updated
                              *
                              * @param _prevOwner previous owner
                              * @param _newOwner  new owner
                            */
                            event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
                        
                            /**
                              * @dev initializes a new Owned instance
                            */
                            constructor() public {
                                owner = msg.sender;
                            }
                        
                            // allows execution by the owner only
                            modifier ownerOnly {
                                _ownerOnly();
                                _;
                            }
                        
                            // error message binary size optimization
                            function _ownerOnly() internal view {
                                require(msg.sender == owner, "ERR_ACCESS_DENIED");
                            }
                        
                            /**
                              * @dev allows transferring the contract ownership
                              * the new owner still needs to accept the transfer
                              * can only be called by the contract owner
                              *
                              * @param _newOwner    new contract owner
                            */
                            function transferOwnership(address _newOwner) public ownerOnly {
                                require(_newOwner != owner, "ERR_SAME_OWNER");
                                newOwner = _newOwner;
                            }
                        
                            /**
                              * @dev used by a new owner to accept an ownership transfer
                            */
                            function acceptOwnership() public {
                                require(msg.sender == newOwner, "ERR_ACCESS_DENIED");
                                emit OwnerUpdate(owner, newOwner);
                                owner = newOwner;
                                newOwner = address(0);
                            }
                        }
                        
                        // File: contracts/utility/Utils.sol
                        
                        pragma solidity 0.4.26;
                        
                        /**
                          * @dev Utilities & Common Modifiers
                        */
                        contract Utils {
                            // verifies that a value is greater than zero
                            modifier greaterThanZero(uint256 _value) {
                                _greaterThanZero(_value);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _greaterThanZero(uint256 _value) internal pure {
                                require(_value > 0, "ERR_ZERO_VALUE");
                            }
                        
                            // validates an address - currently only checks that it isn't null
                            modifier validAddress(address _address) {
                                _validAddress(_address);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _validAddress(address _address) internal pure {
                                require(_address != address(0), "ERR_INVALID_ADDRESS");
                            }
                        
                            // verifies that the address is different than this contract address
                            modifier notThis(address _address) {
                                _notThis(_address);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _notThis(address _address) internal view {
                                require(_address != address(this), "ERR_ADDRESS_IS_SELF");
                            }
                        }
                        
                        // File: contracts/utility/interfaces/IContractRegistry.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Contract Registry interface
                        */
                        contract IContractRegistry {
                            function addressOf(bytes32 _contractName) public view returns (address);
                        
                            // deprecated, backward compatibility
                            function getAddress(bytes32 _contractName) public view returns (address);
                        }
                        
                        // File: contracts/utility/ContractRegistryClient.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        /**
                          * @dev Base contract for ContractRegistry clients
                        */
                        contract ContractRegistryClient is Owned, Utils {
                            bytes32 internal constant CONTRACT_REGISTRY = "ContractRegistry";
                            bytes32 internal constant BANCOR_NETWORK = "BancorNetwork";
                            bytes32 internal constant BANCOR_FORMULA = "BancorFormula";
                            bytes32 internal constant CONVERTER_FACTORY = "ConverterFactory";
                            bytes32 internal constant CONVERSION_PATH_FINDER = "ConversionPathFinder";
                            bytes32 internal constant CONVERTER_UPGRADER = "BancorConverterUpgrader";
                            bytes32 internal constant CONVERTER_REGISTRY = "BancorConverterRegistry";
                            bytes32 internal constant CONVERTER_REGISTRY_DATA = "BancorConverterRegistryData";
                            bytes32 internal constant BNT_TOKEN = "BNTToken";
                            bytes32 internal constant BANCOR_X = "BancorX";
                            bytes32 internal constant BANCOR_X_UPGRADER = "BancorXUpgrader";
                        
                            IContractRegistry public registry;      // address of the current contract-registry
                            IContractRegistry public prevRegistry;  // address of the previous contract-registry
                            bool public onlyOwnerCanUpdateRegistry; // only an owner can update the contract-registry
                        
                            /**
                              * @dev verifies that the caller is mapped to the given contract name
                              *
                              * @param _contractName    contract name
                            */
                            modifier only(bytes32 _contractName) {
                                _only(_contractName);
                                _;
                            }
                        
                            // error message binary size optimization
                            function _only(bytes32 _contractName) internal view {
                                require(msg.sender == addressOf(_contractName), "ERR_ACCESS_DENIED");
                            }
                        
                            /**
                              * @dev initializes a new ContractRegistryClient instance
                              *
                              * @param  _registry   address of a contract-registry contract
                            */
                            constructor(IContractRegistry _registry) internal validAddress(_registry) {
                                registry = IContractRegistry(_registry);
                                prevRegistry = IContractRegistry(_registry);
                            }
                        
                            /**
                              * @dev updates to the new contract-registry
                             */
                            function updateRegistry() public {
                                // verify that this function is permitted
                                require(msg.sender == owner || !onlyOwnerCanUpdateRegistry, "ERR_ACCESS_DENIED");
                        
                                // get the new contract-registry
                                IContractRegistry newRegistry = IContractRegistry(addressOf(CONTRACT_REGISTRY));
                        
                                // verify that the new contract-registry is different and not zero
                                require(newRegistry != address(registry) && newRegistry != address(0), "ERR_INVALID_REGISTRY");
                        
                                // verify that the new contract-registry is pointing to a non-zero contract-registry
                                require(newRegistry.addressOf(CONTRACT_REGISTRY) != address(0), "ERR_INVALID_REGISTRY");
                        
                                // save a backup of the current contract-registry before replacing it
                                prevRegistry = registry;
                        
                                // replace the current contract-registry with the new contract-registry
                                registry = newRegistry;
                            }
                        
                            /**
                              * @dev restores the previous contract-registry
                            */
                            function restoreRegistry() public ownerOnly {
                                // restore the previous contract-registry
                                registry = prevRegistry;
                            }
                        
                            /**
                              * @dev restricts the permission to update the contract-registry
                              *
                              * @param _onlyOwnerCanUpdateRegistry  indicates whether or not permission is restricted to owner only
                            */
                            function restrictRegistryUpdate(bool _onlyOwnerCanUpdateRegistry) public ownerOnly {
                                // change the permission to update the contract-registry
                                onlyOwnerCanUpdateRegistry = _onlyOwnerCanUpdateRegistry;
                            }
                        
                            /**
                              * @dev returns the address associated with the given contract name
                              *
                              * @param _contractName    contract name
                              *
                              * @return contract address
                            */
                            function addressOf(bytes32 _contractName) internal view returns (address) {
                                return registry.addressOf(_contractName);
                            }
                        }
                        
                        // File: contracts/utility/ReentrancyGuard.sol
                        
                        pragma solidity 0.4.26;
                        
                        /**
                          * @dev ReentrancyGuard
                          *
                          * The contract provides protection against re-entrancy - calling a function (directly or
                          * indirectly) from within itself.
                        */
                        contract ReentrancyGuard {
                            // true while protected code is being executed, false otherwise
                            bool private locked = false;
                        
                            /**
                              * @dev ensures instantiation only by sub-contracts
                            */
                            constructor() internal {}
                        
                            // protects a function against reentrancy attacks
                            modifier protected() {
                                _protected();
                                locked = true;
                                _;
                                locked = false;
                            }
                        
                            // error message binary size optimization
                            function _protected() internal view {
                                require(!locked, "ERR_REENTRANCY");
                            }
                        }
                        
                        // File: contracts/utility/TokenHandler.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        contract TokenHandler {
                            bytes4 private constant APPROVE_FUNC_SELECTOR = bytes4(keccak256("approve(address,uint256)"));
                            bytes4 private constant TRANSFER_FUNC_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
                            bytes4 private constant TRANSFER_FROM_FUNC_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)"));
                        
                            /**
                              * @dev executes the ERC20 token's `approve` function and reverts upon failure
                              * the main purpose of this function is to prevent a non standard ERC20 token
                              * from failing silently
                              *
                              * @param _token   ERC20 token address
                              * @param _spender approved address
                              * @param _value   allowance amount
                            */
                            function safeApprove(IERC20Token _token, address _spender, uint256 _value) internal {
                               execute(_token, abi.encodeWithSelector(APPROVE_FUNC_SELECTOR, _spender, _value));
                            }
                        
                            /**
                              * @dev executes the ERC20 token's `transfer` function and reverts upon failure
                              * the main purpose of this function is to prevent a non standard ERC20 token
                              * from failing silently
                              *
                              * @param _token   ERC20 token address
                              * @param _to      target address
                              * @param _value   transfer amount
                            */
                            function safeTransfer(IERC20Token _token, address _to, uint256 _value) internal {
                               execute(_token, abi.encodeWithSelector(TRANSFER_FUNC_SELECTOR, _to, _value));
                            }
                        
                            /**
                              * @dev executes the ERC20 token's `transferFrom` function and reverts upon failure
                              * the main purpose of this function is to prevent a non standard ERC20 token
                              * from failing silently
                              *
                              * @param _token   ERC20 token address
                              * @param _from    source address
                              * @param _to      target address
                              * @param _value   transfer amount
                            */
                            function safeTransferFrom(IERC20Token _token, address _from, address _to, uint256 _value) internal {
                               execute(_token, abi.encodeWithSelector(TRANSFER_FROM_FUNC_SELECTOR, _from, _to, _value));
                            }
                        
                            /**
                              * @dev executes a function on the ERC20 token and reverts upon failure
                              * the main purpose of this function is to prevent a non standard ERC20 token
                              * from failing silently
                              *
                              * @param _token   ERC20 token address
                              * @param _data    data to pass in to the token's contract for execution
                            */
                            function execute(IERC20Token _token, bytes memory _data) private {
                                uint256[1] memory ret = [uint256(1)];
                        
                                assembly {
                                    let success := call(
                                        gas,            // gas remaining
                                        _token,         // destination address
                                        0,              // no ether
                                        add(_data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
                                        mload(_data),   // input length (loaded from the first 32 bytes in the `data` array)
                                        ret,            // output buffer
                                        32              // output length
                                    )
                                    if iszero(success) {
                                        revert(0, 0)
                                    }
                                }
                        
                                require(ret[0] != 0, "ERR_TRANSFER_FAILED");
                            }
                        }
                        
                        // File: contracts/utility/TokenHolder.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        
                        
                        /**
                          * @dev We consider every contract to be a 'token holder' since it's currently not possible
                          * for a contract to deny receiving tokens.
                          *
                          * The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
                          * the owner to send tokens that were sent to the contract by mistake back to their sender.
                          *
                          * Note that we use the non standard ERC-20 interface which has no return value for transfer
                          * in order to support both non standard as well as standard token contracts.
                          * see https://github.com/ethereum/solidity/issues/4116
                        */
                        contract TokenHolder is ITokenHolder, TokenHandler, Owned, Utils {
                            /**
                              * @dev withdraws tokens held by the contract and sends them to an account
                              * can only be called by the owner
                              *
                              * @param _token   ERC20 token contract address
                              * @param _to      account to receive the new amount
                              * @param _amount  amount to withdraw
                            */
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
                                public
                                ownerOnly
                                validAddress(_token)
                                validAddress(_to)
                                notThis(_to)
                            {
                                safeTransfer(_token, _to, _amount);
                            }
                        }
                        
                        // File: contracts/utility/SafeMath.sol
                        
                        pragma solidity 0.4.26;
                        
                        /**
                          * @dev Library for basic math operations with overflow/underflow protection
                        */
                        library SafeMath {
                            /**
                              * @dev returns the sum of _x and _y, reverts if the calculation overflows
                              *
                              * @param _x   value 1
                              * @param _y   value 2
                              *
                              * @return sum
                            */
                            function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                uint256 z = _x + _y;
                                require(z >= _x, "ERR_OVERFLOW");
                                return z;
                            }
                        
                            /**
                              * @dev returns the difference of _x minus _y, reverts if the calculation underflows
                              *
                              * @param _x   minuend
                              * @param _y   subtrahend
                              *
                              * @return difference
                            */
                            function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                require(_x >= _y, "ERR_UNDERFLOW");
                                return _x - _y;
                            }
                        
                            /**
                              * @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
                              *
                              * @param _x   factor 1
                              * @param _y   factor 2
                              *
                              * @return product
                            */
                            function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                // gas optimization
                                if (_x == 0)
                                    return 0;
                        
                                uint256 z = _x * _y;
                                require(z / _x == _y, "ERR_OVERFLOW");
                                return z;
                            }
                        
                            /**
                              * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
                              *
                              * @param _x   dividend
                              * @param _y   divisor
                              *
                              * @return quotient
                            */
                            function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                require(_y > 0, "ERR_DIVIDE_BY_ZERO");
                                uint256 c = _x / _y;
                                return c;
                            }
                        }
                        
                        // File: contracts/token/interfaces/IEtherToken.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        /*
                            Ether Token interface
                        */
                        contract IEtherToken is IERC20Token {
                            function deposit() public payable;
                            function withdraw(uint256 _amount) public;
                            function depositTo(address _to) public payable;
                            function withdrawTo(address _to, uint256 _amount) public;
                        }
                        
                        // File: contracts/token/interfaces/ISmartToken.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        /*
                            Smart Token interface
                        */
                        contract ISmartToken is IConverterAnchor, IERC20Token {
                            function disableTransfers(bool _disable) public;
                            function issue(address _to, uint256 _amount) public;
                            function destroy(address _from, uint256 _amount) public;
                        }
                        
                        // File: contracts/bancorx/interfaces/IBancorX.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        contract IBancorX {
                            function token() public view returns (IERC20Token) {this;}
                            function xTransfer(bytes32 _toBlockchain, bytes32 _to, uint256 _amount, uint256 _id) public;
                            function getXTransferAmount(uint256 _xTransferId, address _for) public view returns (uint256);
                        }
                        
                        // File: contracts/BancorNetwork.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        // interface of older converters for backward compatibility
                        contract ILegacyConverter {
                            function change(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, uint256 _minReturn) public returns (uint256);
                        }
                        
                        /**
                          * @dev The BancorNetwork contract is the main entry point for Bancor token conversions.
                          * It also allows for the conversion of any token in the Bancor Network to any other token in a single
                          * transaction by providing a conversion path.
                          *
                          * A note on Conversion Path: Conversion path is a data structure that is used when converting a token
                          * to another token in the Bancor Network, when the conversion cannot necessarily be done by a single
                          * converter and might require multiple 'hops'.
                          * The path defines which converters should be used and what kind of conversion should be done in each step.
                          *
                          * The path format doesn't include complex structure; instead, it is represented by a single array
                          * in which each 'hop' is represented by a 2-tuple - converter anchor & target token.
                          * In addition, the first element is always the source token.
                          * The converter anchor is only used as a pointer to a converter (since converter addresses are more
                          * likely to change as opposed to anchor addresses).
                          *
                          * Format:
                          * [source token, converter anchor, target token, converter anchor, target token...]
                        */
                        contract BancorNetwork is IBancorNetwork, TokenHolder, ContractRegistryClient, ReentrancyGuard {
                            using SafeMath for uint256;
                        
                            uint256 private constant CONVERSION_FEE_RESOLUTION = 1000000;
                            uint256 private constant AFFILIATE_FEE_RESOLUTION = 1000000;
                            address private constant ETH_RESERVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                        
                            struct ConversionStep {
                                IConverter converter;
                                IConverterAnchor anchor;
                                IERC20Token sourceToken;
                                IERC20Token targetToken;
                                address beneficiary;
                                bool isV28OrHigherConverter;
                                bool processAffiliateFee;
                            }
                        
                            uint256 public maxAffiliateFee = 30000;     // maximum affiliate-fee
                        
                            mapping (address => bool) public etherTokens;       // list of all supported ether tokens
                        
                            /**
                              * @dev triggered when a conversion between two tokens occurs
                              *
                              * @param _smartToken  anchor governed by the converter
                              * @param _fromToken   source ERC20 token
                              * @param _toToken     target ERC20 token
                              * @param _fromAmount  amount converted, in the source token
                              * @param _toAmount    amount returned, minus conversion fee
                              * @param _trader      wallet that initiated the trade
                            */
                            event Conversion(
                                address indexed _smartToken,
                                address indexed _fromToken,
                                address indexed _toToken,
                                uint256 _fromAmount,
                                uint256 _toAmount,
                                address _trader
                            );
                        
                            /**
                              * @dev initializes a new BancorNetwork instance
                              *
                              * @param _registry    address of a contract registry contract
                            */
                            constructor(IContractRegistry _registry) ContractRegistryClient(_registry) public {
                                etherTokens[ETH_RESERVE_ADDRESS] = true;
                            }
                        
                            /**
                              * @dev allows the owner to update the maximum affiliate-fee
                              *
                              * @param _maxAffiliateFee   maximum affiliate-fee
                            */
                            function setMaxAffiliateFee(uint256 _maxAffiliateFee)
                                public
                                ownerOnly
                            {
                                require(_maxAffiliateFee <= AFFILIATE_FEE_RESOLUTION, "ERR_INVALID_AFFILIATE_FEE");
                                maxAffiliateFee = _maxAffiliateFee;
                            }
                        
                            /**
                              * @dev allows the owner to register/unregister ether tokens
                              *
                              * @param _token       ether token contract address
                              * @param _register    true to register, false to unregister
                            */
                            function registerEtherToken(IEtherToken _token, bool _register)
                                public
                                ownerOnly
                                validAddress(_token)
                                notThis(_token)
                            {
                                etherTokens[_token] = _register;
                            }
                        
                            /**
                              * @dev returns the conversion path between two tokens in the network
                              * note that this method is quite expensive in terms of gas and should generally be called off-chain
                              *
                              * @param _sourceToken source token address
                              * @param _targetToken target token address
                              *
                              * @return conversion path between the two tokens
                            */
                            function conversionPath(IERC20Token _sourceToken, IERC20Token _targetToken) public view returns (address[]) {
                                IConversionPathFinder pathFinder = IConversionPathFinder(addressOf(CONVERSION_PATH_FINDER));
                                return pathFinder.findPath(_sourceToken, _targetToken);
                            }
                        
                            /**
                              * @dev returns the expected rate of converting a given amount on a given path
                              * note that there is no support for circular paths
                              *
                              * @param _path        conversion path (see conversion path format above)
                              * @param _amount      amount of _path[0] tokens received from the sender
                              *
                              * @return expected rate
                            */
                            function rateByPath(IERC20Token[] _path, uint256 _amount) public view returns (uint256) {
                                uint256 amount;
                                uint256 fee;
                                uint256 supply;
                                uint256 balance;
                                uint32 weight;
                                IConverter converter;
                                IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
                        
                                amount = _amount;
                        
                                // verify that the number of elements is larger than 2 and odd
                                require(_path.length > 2 && _path.length % 2 == 1, "ERR_INVALID_PATH");
                        
                                // iterate over the conversion path
                                for (uint256 i = 2; i < _path.length; i += 2) {
                                    IERC20Token sourceToken = _path[i - 2];
                                    IERC20Token anchor = _path[i - 1];
                                    IERC20Token targetToken = _path[i];
                        
                                    converter = IConverter(IConverterAnchor(anchor).owner());
                        
                                    // backward compatibility
                                    sourceToken = getConverterTokenAddress(converter, sourceToken);
                                    targetToken = getConverterTokenAddress(converter, targetToken);
                        
                                    if (targetToken == anchor) { // buy the smart token
                                        // check if the current smart token has changed
                                        if (i < 3 || anchor != _path[i - 3])
                                            supply = ISmartToken(anchor).totalSupply();
                        
                                        // get the amount & the conversion fee
                                        balance = converter.getConnectorBalance(sourceToken);
                                        (, weight, , , ) = converter.connectors(sourceToken);
                                        amount = formula.purchaseRate(supply, balance, weight, amount);
                                        fee = amount.mul(converter.conversionFee()).div(CONVERSION_FEE_RESOLUTION);
                                        amount -= fee;
                        
                                        // update the smart token supply for the next iteration
                                        supply = supply.add(amount);
                                    }
                                    else if (sourceToken == anchor) { // sell the smart token
                                        // check if the current smart token has changed
                                        if (i < 3 || anchor != _path[i - 3])
                                            supply = ISmartToken(anchor).totalSupply();
                        
                                        // get the amount & the conversion fee
                                        balance = converter.getConnectorBalance(targetToken);
                                        (, weight, , , ) = converter.connectors(targetToken);
                                        amount = formula.saleRate(supply, balance, weight, amount);
                                        fee = amount.mul(converter.conversionFee()).div(CONVERSION_FEE_RESOLUTION);
                                        amount -= fee;
                        
                                        // update the smart token supply for the next iteration
                                        supply = supply.sub(amount);
                                    }
                                    else { // cross reserve conversion
                                        (amount, fee) = getReturn(converter, sourceToken, targetToken, amount);
                                    }
                                }
                        
                                return amount;
                            }
                        
                            /**
                              * @dev converts the token to any other token in the bancor network by following
                              * a predefined conversion path and transfers the result tokens to a target account
                              * affiliate account/fee can also be passed in to receive a conversion fee (on top of the liquidity provider fees)
                              * note that the network should already have been given allowance of the source token (if not ETH)
                              *
                              * @param _path                conversion path, see conversion path format above
                              * @param _amount              amount to convert from, in the source token
                              * @param _minReturn           if the conversion results in an amount smaller than the minimum return - it is cancelled, must be greater than zero
                              * @param _beneficiary         account that will receive the conversion result or 0x0 to send the result to the sender account
                              * @param _affiliateAccount    wallet address to receive the affiliate fee or 0x0 to disable affiliate fee
                              * @param _affiliateFee        affiliate fee in PPM or 0 to disable affiliate fee
                              *
                              * @return amount of tokens received from the conversion
                            */
                            function convertByPath(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, address _beneficiary, address _affiliateAccount, uint256 _affiliateFee)
                                public
                                payable
                                protected
                                greaterThanZero(_minReturn)
                                returns (uint256)
                            {
                                // verify that the path contrains at least a single 'hop' and that the number of elements is odd
                                require(_path.length > 2 && _path.length % 2 == 1, "ERR_INVALID_PATH");
                        
                                // validate msg.value and prepare the source token for the conversion
                                handleSourceToken(_path[0], IConverterAnchor(_path[1]), _amount);
                        
                                // check if affiliate fee is enabled
                                bool affiliateFeeEnabled = false;
                                if (address(_affiliateAccount) == 0) {
                                    require(_affiliateFee == 0, "ERR_INVALID_AFFILIATE_FEE");
                                }
                                else {
                                    require(0 < _affiliateFee && _affiliateFee <= maxAffiliateFee, "ERR_INVALID_AFFILIATE_FEE");
                                    affiliateFeeEnabled = true;
                                }
                        
                                // check if beneficiary is set
                                address beneficiary = msg.sender;
                                if (_beneficiary != address(0))
                                    beneficiary = _beneficiary;
                        
                                // convert and get the resulting amount
                                ConversionStep[] memory data = createConversionData(_path, beneficiary, affiliateFeeEnabled);
                                uint256 amount = doConversion(data, _amount, _minReturn, _affiliateAccount, _affiliateFee);
                        
                                // handle the conversion target tokens
                                handleTargetToken(data, amount, beneficiary);
                        
                                return amount;
                            }
                        
                            /**
                              * @dev converts any other token to BNT in the bancor network by following
                              a predefined conversion path and transfers the result to an account on a different blockchain
                              * note that the network should already have been given allowance of the source token (if not ETH)
                              *
                              * @param _path                conversion path, see conversion path format above
                              * @param _amount              amount to convert from, in the source token
                              * @param _minReturn           if the conversion results in an amount smaller than the minimum return - it is cancelled, must be greater than zero
                              * @param _targetBlockchain    blockchain BNT will be issued on
                              * @param _targetAccount       address/account on the target blockchain to send the BNT to
                              * @param _conversionId        pre-determined unique (if non zero) id which refers to this transaction
                              *
                              * @return the amount of BNT received from this conversion
                            */
                            function xConvert(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                bytes32 _targetBlockchain,
                                bytes32 _targetAccount,
                                uint256 _conversionId
                            )
                                public
                                payable
                                returns (uint256)
                            {
                                return xConvert2(_path, _amount, _minReturn, _targetBlockchain, _targetAccount, _conversionId, address(0), 0);
                            }
                        
                            /**
                              * @dev converts any other token to BNT in the bancor network by following
                              a predefined conversion path and transfers the result to an account on a different blockchain
                              * note that the network should already have been given allowance of the source token (if not ETH)
                              *
                              * @param _path                conversion path, see conversion path format above
                              * @param _amount              amount to convert from, in the source token
                              * @param _minReturn           if the conversion results in an amount smaller than the minimum return - it is cancelled, must be greater than zero
                              * @param _targetBlockchain    blockchain BNT will be issued on
                              * @param _targetAccount       address/account on the target blockchain to send the BNT to
                              * @param _conversionId        pre-determined unique (if non zero) id which refers to this transaction
                              * @param _affiliateAccount    affiliate account
                              * @param _affiliateFee        affiliate fee in PPM
                              *
                              * @return the amount of BNT received from this conversion
                            */
                            function xConvert2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                bytes32 _targetBlockchain,
                                bytes32 _targetAccount,
                                uint256 _conversionId,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            )
                                public
                                payable
                                greaterThanZero(_minReturn)
                                returns (uint256)
                            {
                                IERC20Token targetToken = _path[_path.length - 1];
                                IBancorX bancorX = IBancorX(addressOf(BANCOR_X));
                        
                                // verify that the destination token is BNT
                                require(targetToken == addressOf(BNT_TOKEN), "ERR_INVALID_TARGET_TOKEN");
                        
                                // convert and get the resulting amount
                                uint256 amount = convertByPath(_path, _amount, _minReturn, this, _affiliateAccount, _affiliateFee);
                        
                                // grant BancorX allowance
                                ensureAllowance(targetToken, bancorX, amount);
                        
                                // transfer the resulting amount to BancorX
                                bancorX.xTransfer(_targetBlockchain, _targetAccount, amount, _conversionId);
                        
                                return amount;
                            }
                        
                            /**
                              * @dev allows a user to convert a token that was sent from another blockchain into any other
                              * token on the BancorNetwork
                              * ideally this transaction is created before the previous conversion is even complete, so
                              * so the input amount isn't known at that point - the amount is actually take from the
                              * BancorX contract directly by specifying the conversion id
                              *
                              * @param _path            conversion path
                              * @param _bancorX         address of the BancorX contract for the source token
                              * @param _conversionId    pre-determined unique (if non zero) id which refers to this conversion
                              * @param _minReturn       if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
                              * @param _beneficiary     wallet to receive the conversion result
                              *
                              * @return amount of tokens received from the conversion
                            */
                            function completeXConversion(IERC20Token[] _path, IBancorX _bancorX, uint256 _conversionId, uint256 _minReturn, address _beneficiary)
                                public returns (uint256)
                            {
                                // verify that the source token is the BancorX token
                                require(_path[0] == _bancorX.token(), "ERR_INVALID_SOURCE_TOKEN");
                        
                                // get conversion amount from BancorX contract
                                uint256 amount = _bancorX.getXTransferAmount(_conversionId, msg.sender);
                        
                                // perform the conversion
                                return convertByPath(_path, amount, _minReturn, _beneficiary, address(0), 0);
                            }
                        
                            /**
                              * @dev executes the actual conversion by following the conversion path
                              *
                              * @param _data                conversion data, see ConversionStep struct above
                              * @param _amount              amount to convert from, in the source token
                              * @param _minReturn           if the conversion results in an amount smaller than the minimum return - it is cancelled, must be greater than zero
                              * @param _affiliateAccount    affiliate account
                              * @param _affiliateFee        affiliate fee in PPM
                              *
                              * @return amount of tokens received from the conversion
                            */
                            function doConversion(
                                ConversionStep[] _data,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            ) private returns (uint256) {
                                uint256 toAmount;
                                uint256 fromAmount = _amount;
                        
                                // iterate over the conversion data
                                for (uint256 i = 0; i < _data.length; i++) {
                                    ConversionStep memory stepData = _data[i];
                        
                                    // newer converter
                                    if (stepData.isV28OrHigherConverter) {
                                        // transfer the tokens to the converter only if the network contract currently holds the tokens
                                        // not needed with ETH or if it's the first conversion step
                                        if (i != 0 && _data[i - 1].beneficiary == address(this) && !etherTokens[stepData.sourceToken])
                                            safeTransfer(stepData.sourceToken, stepData.converter, fromAmount);
                                    }
                                    // older converter
                                    // if the source token is the smart token, no need to do any transfers as the converter controls it
                                    else if (stepData.sourceToken != ISmartToken(stepData.anchor)) {
                                        // grant allowance for it to transfer the tokens from the network contract
                                        ensureAllowance(stepData.sourceToken, stepData.converter, fromAmount);
                                    }
                        
                                    // do the conversion
                                    if (!stepData.isV28OrHigherConverter)
                                        toAmount = ILegacyConverter(stepData.converter).change(stepData.sourceToken, stepData.targetToken, fromAmount, 1);
                                    else if (etherTokens[stepData.sourceToken])
                                        toAmount = stepData.converter.convert.value(msg.value)(stepData.sourceToken, stepData.targetToken, fromAmount, msg.sender, stepData.beneficiary);
                                    else
                                        toAmount = stepData.converter.convert(stepData.sourceToken, stepData.targetToken, fromAmount, msg.sender, stepData.beneficiary);
                        
                                    // pay affiliate-fee if needed
                                    if (stepData.processAffiliateFee) {
                                        uint256 affiliateAmount = toAmount.mul(_affiliateFee).div(AFFILIATE_FEE_RESOLUTION);
                                        require(stepData.targetToken.transfer(_affiliateAccount, affiliateAmount), "ERR_FEE_TRANSFER_FAILED");
                                        toAmount -= affiliateAmount;
                                    }
                        
                                    emit Conversion(stepData.anchor, stepData.sourceToken, stepData.targetToken, fromAmount, toAmount, msg.sender);
                                    fromAmount = toAmount;
                                }
                        
                                // ensure the trade meets the minimum requested amount
                                require(toAmount >= _minReturn, "ERR_RETURN_TOO_LOW");
                        
                                return toAmount;
                            }
                        
                            /**
                              * @dev validates msg.value and prepares the conversion source token for the conversion
                              *
                              * @param _sourceToken source token of the first conversion step
                              * @param _anchor      converter anchor of the first conversion step
                              * @param _amount      amount to convert from, in the source token
                            */
                            function handleSourceToken(IERC20Token _sourceToken, IConverterAnchor _anchor, uint256 _amount) private {
                                IConverter firstConverter = IConverter(_anchor.owner());
                                bool isNewerConverter = isV28OrHigherConverter(firstConverter);
                        
                                // ETH
                                if (msg.value > 0) {
                                    // validate msg.value
                                    require(msg.value == _amount, "ERR_ETH_AMOUNT_MISMATCH");
                        
                                    // EtherToken converter - deposit the ETH into the EtherToken
                                    // note that it can still be a non ETH converter if the path is wrong
                                    // but such conversion will simply revert
                                    if (!isNewerConverter)
                                        IEtherToken(getConverterEtherTokenAddress(firstConverter)).deposit.value(msg.value)();
                                }
                                // EtherToken
                                else if (etherTokens[_sourceToken]) {
                                    // claim the tokens - if the source token is ETH reserve, this call will fail
                                    // since in that case the transaction must be sent with msg.value
                                    safeTransferFrom(_sourceToken, msg.sender, this, _amount);
                        
                                    // ETH converter - withdraw the ETH
                                    if (isNewerConverter)
                                        IEtherToken(_sourceToken).withdraw(_amount);
                                }
                                // other ERC20 token
                                else {
                                    // newer converter - transfer the tokens from the sender directly to the converter
                                    // otherwise claim the tokens
                                    if (isNewerConverter)
                                        safeTransferFrom(_sourceToken, msg.sender, firstConverter, _amount);
                                    else
                                        safeTransferFrom(_sourceToken, msg.sender, this, _amount);
                                }
                            }
                        
                            /**
                              * @dev handles the conversion target token if the network still holds it at the end of the conversion
                              *
                              * @param _data        conversion data, see ConversionStep struct above
                              * @param _amount      conversion return amount, in the target token
                              * @param _beneficiary wallet to receive the conversion result
                            */
                            function handleTargetToken(ConversionStep[] _data, uint256 _amount, address _beneficiary) private {
                                ConversionStep memory stepData = _data[_data.length - 1];
                        
                                // network contract doesn't hold the tokens, do nothing
                                if (stepData.beneficiary != address(this))
                                    return;
                        
                                IERC20Token targetToken = stepData.targetToken;
                        
                                // ETH / EtherToken
                                if (etherTokens[targetToken]) {
                                    // newer converter should send ETH directly to the beneficiary
                                    assert(!stepData.isV28OrHigherConverter);
                        
                                    // EtherToken converter - withdraw the ETH and transfer to the beneficiary
                                    IEtherToken(targetToken).withdrawTo(_beneficiary, _amount);
                                }
                                // other ERC20 token
                                else {
                                    safeTransfer(targetToken, _beneficiary, _amount);
                                }
                            }
                        
                            /**
                              * @dev creates a memory cache of all conversion steps data to minimize logic and external calls during conversions
                              *
                              * @param _conversionPath      conversion path, see conversion path format above
                              * @param _beneficiary         wallet to receive the conversion result
                              * @param _affiliateFeeEnabled true if affiliate fee was requested by the sender, false if not
                              *
                              * @return cached conversion data to be ingested later on by the conversion flow
                            */
                            function createConversionData(IERC20Token[] _conversionPath, address _beneficiary, bool _affiliateFeeEnabled) private view returns (ConversionStep[]) {
                                ConversionStep[] memory data = new ConversionStep[](_conversionPath.length / 2);
                        
                                bool affiliateFeeProcessed = false;
                                address bntToken = addressOf(BNT_TOKEN);
                                // iterate the conversion path and create the conversion data for each step
                                uint256 i;
                                for (i = 0; i < _conversionPath.length - 1; i += 2) {
                                    IConverterAnchor anchor = IConverterAnchor(_conversionPath[i + 1]);
                                    IConverter converter = IConverter(anchor.owner());
                                    IERC20Token targetToken = _conversionPath[i + 2];
                        
                                    // check if the affiliate fee should be processed in this step
                                    bool processAffiliateFee = _affiliateFeeEnabled && !affiliateFeeProcessed && targetToken == bntToken;
                                    if (processAffiliateFee)
                                        affiliateFeeProcessed = true;
                        
                                    data[i / 2] = ConversionStep({
                                        // set the converter anchor
                                        anchor: anchor,
                        
                                        // set the converter
                                        converter: converter,
                        
                                        // set the source/target tokens
                                        sourceToken: _conversionPath[i],
                                        targetToken: targetToken,
                        
                                        // requires knowledge about the next step, so initialize in the next phase
                                        beneficiary: address(0),
                        
                                        // set flags
                                        isV28OrHigherConverter: isV28OrHigherConverter(converter),
                                        processAffiliateFee: processAffiliateFee
                                    });
                                }
                        
                                // ETH support
                                // source is ETH
                                ConversionStep memory stepData = data[0];
                                if (etherTokens[stepData.sourceToken]) {
                                    // newer converter - replace the source token address with ETH reserve address
                                    if (stepData.isV28OrHigherConverter)
                                        stepData.sourceToken = IERC20Token(ETH_RESERVE_ADDRESS);
                                    // older converter - replace the source token with the EtherToken address used by the converter
                                    else
                                        stepData.sourceToken = IERC20Token(getConverterEtherTokenAddress(stepData.converter));
                                }
                        
                                // target is ETH
                                stepData = data[data.length - 1];
                                if (etherTokens[stepData.targetToken]) {
                                    // newer converter - replace the target token address with ETH reserve address
                                    if (stepData.isV28OrHigherConverter)
                                        stepData.targetToken = IERC20Token(ETH_RESERVE_ADDRESS);
                                    // older converter - replace the target token with the EtherToken address used by the converter
                                    else
                                        stepData.targetToken = IERC20Token(getConverterEtherTokenAddress(stepData.converter));
                                }
                        
                                // set the beneficiary for each step
                                for (i = 0; i < data.length; i++) {
                                    stepData = data[i];
                        
                                    // first check if the converter in this step is newer as older converters don't even support the beneficiary argument
                                    if (stepData.isV28OrHigherConverter) {
                                        // if affiliate fee is processed in this step, beneficiary is the network contract
                                        if (stepData.processAffiliateFee)
                                            stepData.beneficiary = this;
                                        // if it's the last step, beneficiary is the final beneficiary
                                        else if (i == data.length - 1)
                                            stepData.beneficiary = _beneficiary;
                                        // if the converter in the next step is newer, beneficiary is the next converter
                                        else if (data[i + 1].isV28OrHigherConverter)
                                            stepData.beneficiary = data[i + 1].converter;
                                        // the converter in the next step is older, beneficiary is the network contract
                                        else
                                            stepData.beneficiary = this;
                                    }
                                    else {
                                        // converter in this step is older, beneficiary is the network contract
                                        stepData.beneficiary = this;
                                    }
                                }
                        
                                return data;
                            }
                        
                            /**
                              * @dev utility, checks whether allowance for the given spender exists and approves one if it doesn't.
                              * Note that we use the non standard erc-20 interface in which `approve` has no return value so that
                              * this function will work for both standard and non standard tokens
                              *
                              * @param _token   token to check the allowance in
                              * @param _spender approved address
                              * @param _value   allowance amount
                            */
                            function ensureAllowance(IERC20Token _token, address _spender, uint256 _value) private {
                                uint256 allowance = _token.allowance(this, _spender);
                                if (allowance < _value) {
                                    if (allowance > 0)
                                        safeApprove(_token, _spender, 0);
                                    safeApprove(_token, _spender, _value);
                                }
                            }
                        
                            // legacy - returns the address of an EtherToken used by the converter
                            function getConverterEtherTokenAddress(IConverter _converter) private view returns (address) {
                                uint256 reserveCount = _converter.connectorTokenCount();
                                for (uint256 i = 0; i < reserveCount; i++) {
                                    address reserveTokenAddress = _converter.connectorTokens(i);
                                    if (etherTokens[reserveTokenAddress])
                                        return reserveTokenAddress;
                                }
                        
                                return ETH_RESERVE_ADDRESS;
                            }
                        
                            // legacy - if the token is an ether token, returns the ETH reserve address
                            // used by the converter, otherwise returns the input token address
                            function getConverterTokenAddress(IConverter _converter, IERC20Token _token) private view returns (IERC20Token) {
                                if (!etherTokens[_token])
                                    return _token;
                        
                                if (isV28OrHigherConverter(_converter))
                                    return IERC20Token(ETH_RESERVE_ADDRESS);
                        
                                return IERC20Token(getConverterEtherTokenAddress(_converter));
                            }
                        
                            bytes4 private constant GET_RETURN_FUNC_SELECTOR = bytes4(keccak256("getReturn(address,address,uint256)"));
                        
                            // using assembly code since older converter versions have different return values
                            function getReturn(address _dest, address _sourceToken, address _targetToken, uint256 _amount) internal view returns (uint256, uint256) {
                                uint256[2] memory ret;
                                bytes memory data = abi.encodeWithSelector(GET_RETURN_FUNC_SELECTOR, _sourceToken, _targetToken, _amount);
                        
                                assembly {
                                    let success := staticcall(
                                        gas,           // gas remaining
                                        _dest,         // destination address
                                        add(data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
                                        mload(data),   // input length (loaded from the first 32 bytes in the `data` array)
                                        ret,           // output buffer
                                        64             // output length
                                    )
                                    if iszero(success) {
                                        revert(0, 0)
                                    }
                                }
                        
                                return (ret[0], ret[1]);
                            }
                        
                            bytes4 private constant IS_V28_OR_HIGHER_FUNC_SELECTOR = bytes4(keccak256("isV28OrHigher()"));
                        
                            // using assembly code to identify converter version
                            // can't rely on the version number since the function had a different signature in older converters
                            function isV28OrHigherConverter(IConverter _converter) internal view returns (bool) {
                                bool success;
                                uint256[1] memory ret;
                                bytes memory data = abi.encodeWithSelector(IS_V28_OR_HIGHER_FUNC_SELECTOR);
                        
                                assembly {
                                    success := staticcall(
                                        5000,          // isV28OrHigher consumes 190 gas, but just for extra safety
                                        _converter,    // destination address
                                        add(data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
                                        mload(data),   // input length (loaded from the first 32 bytes in the `data` array)
                                        ret,           // output buffer
                                        32             // output length
                                    )
                                }
                        
                                return success && ret[0] != 0;
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function getReturnByPath(IERC20Token[] _path, uint256 _amount) public view returns (uint256, uint256) {
                                return (rateByPath(_path, _amount), 0);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function convert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn) public payable returns (uint256) {
                                return convertByPath(_path, _amount, _minReturn, address(0), address(0), 0);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function convert2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            )
                                public
                                payable
                                returns (uint256)
                            {
                                return convertByPath(_path, _amount, _minReturn, address(0), _affiliateAccount, _affiliateFee);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function convertFor(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, address _beneficiary) public payable returns (uint256) {
                                return convertByPath(_path, _amount, _minReturn, _beneficiary, address(0), 0);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function convertFor2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _beneficiary,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            )
                                public
                                payable
                                greaterThanZero(_minReturn)
                                returns (uint256)
                            {
                                return convertByPath(_path, _amount, _minReturn, _beneficiary, _affiliateAccount, _affiliateFee);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function claimAndConvert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn) public returns (uint256) {
                                return convertByPath(_path, _amount, _minReturn, address(0), address(0), 0);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function claimAndConvert2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            )
                                public
                                returns (uint256)
                            {
                                return convertByPath(_path, _amount, _minReturn, address(0), _affiliateAccount, _affiliateFee);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function claimAndConvertFor(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, address _beneficiary) public returns (uint256) {
                                return convertByPath(_path, _amount, _minReturn, _beneficiary, address(0), 0);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function claimAndConvertFor2(
                                IERC20Token[] _path,
                                uint256 _amount,
                                uint256 _minReturn,
                                address _beneficiary,
                                address _affiliateAccount,
                                uint256 _affiliateFee
                            )
                                public
                                returns (uint256)
                            {
                                return convertByPath(_path, _amount, _minReturn, _beneficiary, _affiliateAccount, _affiliateFee);
                            }
                        }

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

                        File 9 of 13: KyberMatchingEngine
                        // File: contracts/sol6/IERC20.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        interface IERC20 {
                            event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                        
                            function approve(address _spender, uint256 _value) external returns (bool success);
                        
                            function transfer(address _to, uint256 _value) external returns (bool success);
                        
                            function transferFrom(
                                address _from,
                                address _to,
                                uint256 _value
                            ) external returns (bool success);
                        
                            function allowance(address _owner, address _spender) external view returns (uint256 remaining);
                        
                            function balanceOf(address _owner) external view returns (uint256 balance);
                        
                            function decimals() external view returns (uint8 digits);
                        
                            function totalSupply() external view returns (uint256 supply);
                        }
                        
                        
                        // to support backward compatible contract name -- so function signature remains same
                        abstract contract ERC20 is IERC20 {
                        
                        }
                        
                        // File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        contract PermissionGroupsNoModifiers {
                            address public admin;
                            address public pendingAdmin;
                            mapping(address => bool) internal operators;
                            mapping(address => bool) internal alerters;
                            address[] internal operatorsGroup;
                            address[] internal alertersGroup;
                            uint256 internal constant MAX_GROUP_SIZE = 50;
                        
                            event AdminClaimed(address newAdmin, address previousAdmin);
                            event AlerterAdded(address newAlerter, bool isAdd);
                            event OperatorAdded(address newOperator, bool isAdd);
                            event TransferAdminPending(address pendingAdmin);
                        
                            constructor(address _admin) public {
                                require(_admin != address(0), "admin 0");
                                admin = _admin;
                            }
                        
                            function getOperators() external view returns (address[] memory) {
                                return operatorsGroup;
                            }
                        
                            function getAlerters() external view returns (address[] memory) {
                                return alertersGroup;
                            }
                        
                            function addAlerter(address newAlerter) public {
                                onlyAdmin();
                                require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
                                require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
                        
                                emit AlerterAdded(newAlerter, true);
                                alerters[newAlerter] = true;
                                alertersGroup.push(newAlerter);
                            }
                        
                            function addOperator(address newOperator) public {
                                onlyAdmin();
                                require(!operators[newOperator], "operator exists"); // prevent duplicates.
                                require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
                        
                                emit OperatorAdded(newOperator, true);
                                operators[newOperator] = true;
                                operatorsGroup.push(newOperator);
                            }
                        
                            /// @dev Allows the pendingAdmin address to finalize the change admin process.
                            function claimAdmin() public {
                                require(pendingAdmin == msg.sender, "not pending");
                                emit AdminClaimed(pendingAdmin, admin);
                                admin = pendingAdmin;
                                pendingAdmin = address(0);
                            }
                        
                            function removeAlerter(address alerter) public {
                                onlyAdmin();
                                require(alerters[alerter], "not alerter");
                                delete alerters[alerter];
                        
                                for (uint256 i = 0; i < alertersGroup.length; ++i) {
                                    if (alertersGroup[i] == alerter) {
                                        alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                        alertersGroup.pop();
                                        emit AlerterAdded(alerter, false);
                                        break;
                                    }
                                }
                            }
                        
                            function removeOperator(address operator) public {
                                onlyAdmin();
                                require(operators[operator], "not operator");
                                delete operators[operator];
                        
                                for (uint256 i = 0; i < operatorsGroup.length; ++i) {
                                    if (operatorsGroup[i] == operator) {
                                        operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                        operatorsGroup.pop();
                                        emit OperatorAdded(operator, false);
                                        break;
                                    }
                                }
                            }
                        
                            /// @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), "new admin 0");
                                emit TransferAdminPending(newAdmin);
                                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), "admin 0");
                                emit TransferAdminPending(newAdmin);
                                emit AdminClaimed(newAdmin, admin);
                                admin = newAdmin;
                            }
                        
                            function onlyAdmin() internal view {
                                require(msg.sender == admin, "only admin");
                            }
                        
                            function onlyAlerter() internal view {
                                require(alerters[msg.sender], "only alerter");
                            }
                        
                            function onlyOperator() internal view {
                                require(operators[msg.sender], "only operator");
                            }
                        }
                        
                        // File: contracts/sol6/utils/WithdrawableNoModifiers.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        contract WithdrawableNoModifiers is PermissionGroupsNoModifiers {
                            constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {}
                        
                            event EtherWithdraw(uint256 amount, address sendTo);
                            event TokenWithdraw(IERC20 token, uint256 amount, address sendTo);
                        
                            /// @dev Withdraw Ethers
                            function withdrawEther(uint256 amount, address payable sendTo) external {
                                onlyAdmin();
                                (bool success, ) = sendTo.call{value: amount}("");
                                require(success);
                                emit EtherWithdraw(amount, sendTo);
                            }
                        
                            /// @dev Withdraw all IERC20 compatible tokens
                            /// @param token IERC20 The address of the token contract
                            function withdrawToken(
                                IERC20 token,
                                uint256 amount,
                                address sendTo
                            ) external {
                                onlyAdmin();
                                token.transfer(sendTo, amount);
                                emit TokenWithdraw(token, amount, sendTo);
                            }
                        }
                        
                        // File: contracts/sol6/IKyberReserve.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberReserve {
                            function trade(
                                IERC20 srcToken,
                                uint256 srcAmount,
                                IERC20 destToken,
                                address payable destAddress,
                                uint256 conversionRate,
                                bool validate
                            ) external payable returns (bool);
                        
                            function getConversionRate(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 blockNumber
                            ) external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberNetwork.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetwork {
                            event KyberTrade(
                                IERC20 indexed src,
                                IERC20 indexed dest,
                                uint256 ethWeiValue,
                                uint256 networkFeeWei,
                                uint256 customPlatformFeeWei,
                                bytes32[] t2eIds,
                                bytes32[] e2tIds,
                                uint256[] t2eSrcAmounts,
                                uint256[] e2tSrcAmounts,
                                uint256[] t2eRates,
                                uint256[] e2tRates
                            );
                        
                            function tradeWithHintAndFee(
                                address payable trader,
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function listTokenForReserve(
                                address reserve,
                                IERC20 token,
                                bool add
                            ) external;
                        
                            function enabled() external view returns (bool);
                        
                            function getExpectedRateWithHintAndFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            )
                                external
                                view
                                returns (
                                    uint256 expectedRateAfterNetworkFee,
                                    uint256 expectedRateAfterAllFees
                                );
                        
                            function getNetworkData()
                                external
                                view
                                returns (
                                    uint256 negligibleDiffBps,
                                    uint256 networkFeeBps,
                                    uint256 expiryTimestamp
                                );
                        
                            function maxGasPrice() external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberNetworkProxy.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetworkProxy {
                        
                            event ExecuteTrade(
                                address indexed trader,
                                IERC20 src,
                                IERC20 dest,
                                address destAddress,
                                uint256 actualSrcAmount,
                                uint256 actualDestAmount,
                                address platformWallet,
                                uint256 platformFeeBps
                            );
                        
                            /// @notice backward compatible
                            function tradeWithHint(
                                ERC20 src,
                                uint256 srcAmount,
                                ERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable walletId,
                                bytes calldata hint
                            ) external payable returns (uint256);
                        
                            function tradeWithHintAndFee(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function trade(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet
                            ) external payable returns (uint256);
                        
                            /// @notice backward compatible
                            /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
                            function getExpectedRate(
                                ERC20 src,
                                ERC20 dest,
                                uint256 srcQty
                            ) external view returns (uint256 expectedRate, uint256 worstRate);
                        
                            function getExpectedRateAfterFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external view returns (uint256 expectedRate);
                        }
                        
                        // File: contracts/sol6/IKyberStorage.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        interface IKyberStorage {
                            enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST}
                        
                            function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies)
                                external;
                        
                            function removeKyberProxy(address kyberProxy) external;
                        
                            function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external;
                        
                            function setKyberDaoContract(address _kyberDao) external;
                        
                            function getReserveId(address reserve) external view returns (bytes32 reserveId);
                        
                            function getReserveIdsFromAddresses(address[] calldata reserveAddresses)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesFromIds(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getReserveIdsPerTokenSrc(IERC20 token)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getReserveIdsPerTokenDest(IERC20 token)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesByReserveId(bytes32 reserveId)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getRebateWalletsFromIds(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (address[] memory rebateWallets);
                        
                            function getKyberProxies() external view returns (IKyberNetworkProxy[] memory);
                        
                            function getReserveDetailsByAddress(address reserve)
                                external
                                view
                                returns (
                                    bytes32 reserveId,
                                    address rebateWallet,
                                    ReserveType resType,
                                    bool isFeeAccountedFlag,
                                    bool isEntitledRebateFlag
                                );
                        
                            function getReserveDetailsById(bytes32 reserveId)
                                external
                                view
                                returns (
                                    address reserveAddress,
                                    address rebateWallet,
                                    ReserveType resType,
                                    bool isFeeAccountedFlag,
                                    bool isEntitledRebateFlag
                                );
                        
                            function getFeeAccountedData(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (bool[] memory feeAccountedArr);
                        
                            function getEntitledRebateData(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (bool[] memory entitledRebateArr);
                        
                            function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest)
                                external
                                view
                                returns (
                                    bool areAllReservesListed,
                                    bool[] memory feeAccountedArr,
                                    bool[] memory entitledRebateArr,
                                    IKyberReserve[] memory reserveAddresses);
                        
                            function isKyberProxyAdded() external view returns (bool);
                        }
                        
                        // File: contracts/sol6/IKyberMatchingEngine.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        
                        interface IKyberMatchingEngine {
                            enum ProcessWithRate {NotRequired, Required}
                        
                            function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps) external;
                        
                            function setKyberStorage(IKyberStorage _kyberStorage) external;
                        
                            function getNegligibleRateDiffBps() external view returns (uint256);
                        
                            function getTradingReserves(
                                IERC20 src,
                                IERC20 dest,
                                bool isTokenToToken,
                                bytes calldata hint
                            )
                                external
                                view
                                returns (
                                    bytes32[] memory reserveIds,
                                    uint256[] memory splitValuesBps,
                                    ProcessWithRate processWithRate
                                );
                        
                            function doMatch(
                                IERC20 src,
                                IERC20 dest,
                                uint256[] calldata srcAmounts,
                                uint256[] calldata feesAccountedDestBps,
                                uint256[] calldata rates
                            ) external view returns (uint256[] memory reserveIndexes);
                        }
                        
                        // File: contracts/sol6/utils/Utils5.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        /**
                         * @title Kyber utility file
                         * mostly shared constants and rate calculation helpers
                         * inherited by most of kyber contracts.
                         * previous utils implementations are for previous solidity versions.
                         */
                        contract Utils5 {
                            IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
                                0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
                            );
                            uint256 internal constant PRECISION = (10**18);
                            uint256 internal constant MAX_QTY = (10**28); // 10B tokens
                            uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
                            uint256 internal constant MAX_DECIMALS = 18;
                            uint256 internal constant ETH_DECIMALS = 18;
                            uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
                            uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
                        
                            mapping(IERC20 => uint256) internal decimals;
                        
                            function getUpdateDecimals(IERC20 token) internal returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // moreover, very possible that old tokens have decimals 0
                                // these tokens will just have higher gas fees.
                                if (tokenDecimals == 0) {
                                    tokenDecimals = token.decimals();
                                    decimals[token] = tokenDecimals;
                                }
                        
                                return tokenDecimals;
                            }
                        
                            function setDecimals(IERC20 token) internal {
                                if (decimals[token] != 0) return; //already set
                        
                                if (token == ETH_TOKEN_ADDRESS) {
                                    decimals[token] = ETH_DECIMALS;
                                } else {
                                    decimals[token] = token.decimals();
                                }
                            }
                        
                            /// @dev get the balance of a user.
                            /// @param token The token type
                            /// @return The balance
                            function getBalance(IERC20 token, address user) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) {
                                    return user.balance;
                                } else {
                                    return token.balanceOf(user);
                                }
                            }
                        
                            function getDecimals(IERC20 token) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // 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 calcDestAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcSrcAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 destAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcDstQty(
                                uint256 srcQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                                }
                            }
                        
                            function calcSrcQty(
                                uint256 dstQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                //source quantity is rounded up. to avoid dest quantity being too low.
                                uint256 numerator;
                                uint256 denominator;
                                if (srcDecimals >= dstDecimals) {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                                    denominator = rate;
                                } else {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty);
                                    denominator = (rate * (10**(dstDecimals - srcDecimals)));
                                }
                                return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                            }
                        
                            function calcRateFromQty(
                                uint256 srcAmount,
                                uint256 destAmount,
                                uint256 srcDecimals,
                                uint256 dstDecimals
                            ) internal pure returns (uint256) {
                                require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
                                require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
                                }
                            }
                        
                            function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
                                return x > y ? y : x;
                            }
                        }
                        
                        // File: contracts/sol6/IKyberHint.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberHint {
                            enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
                            enum HintErrors {
                                NoError, // Hint is valid
                                NonEmptyDataError, // reserveIDs and splits must be empty for BestOfAll hint
                                ReserveIdDupError, // duplicate reserveID found
                                ReserveIdEmptyError, // reserveIDs array is empty for MaskIn and Split trade type
                                ReserveIdSplitsError, // reserveIDs and splitBpsValues arrays do not have the same length
                                ReserveIdSequenceError, // reserveID sequence in array is not in increasing order
                                ReserveIdNotFound, // reserveID isn't registered or doesn't exist
                                SplitsNotEmptyError, // splitBpsValues is not empty for MaskIn or MaskOut trade type
                                TokenListedError, // reserveID not listed for the token
                                TotalBPSError // total BPS for Split trade type is not 10000 (100%)
                            }
                        
                            function buildTokenToEthHint(
                                IERC20 tokenSrc,
                                TradeType tokenToEthType,
                                bytes32[] calldata tokenToEthReserveIds,
                                uint256[] calldata tokenToEthSplits
                            ) external view returns (bytes memory hint);
                        
                            function buildEthToTokenHint(
                                IERC20 tokenDest,
                                TradeType ethToTokenType,
                                bytes32[] calldata ethToTokenReserveIds,
                                uint256[] calldata ethToTokenSplits
                            ) external view returns (bytes memory hint);
                        
                            function buildTokenToTokenHint(
                                IERC20 tokenSrc,
                                TradeType tokenToEthType,
                                bytes32[] calldata tokenToEthReserveIds,
                                uint256[] calldata tokenToEthSplits,
                                IERC20 tokenDest,
                                TradeType ethToTokenType,
                                bytes32[] calldata ethToTokenReserveIds,
                                uint256[] calldata ethToTokenSplits
                            ) external view returns (bytes memory hint);
                        
                            function parseTokenToEthHint(IERC20 tokenSrc, bytes calldata hint)
                                external
                                view
                                returns (
                                    TradeType tokenToEthType,
                                    bytes32[] memory tokenToEthReserveIds,
                                    IKyberReserve[] memory tokenToEthAddresses,
                                    uint256[] memory tokenToEthSplits
                                );
                        
                            function parseEthToTokenHint(IERC20 tokenDest, bytes calldata hint)
                                external
                                view
                                returns (
                                    TradeType ethToTokenType,
                                    bytes32[] memory ethToTokenReserveIds,
                                    IKyberReserve[] memory ethToTokenAddresses,
                                    uint256[] memory ethToTokenSplits
                                );
                        
                            function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes calldata hint)
                                external
                                view
                                returns (
                                    TradeType tokenToEthType,
                                    bytes32[] memory tokenToEthReserveIds,
                                    IKyberReserve[] memory tokenToEthAddresses,
                                    uint256[] memory tokenToEthSplits,
                                    TradeType ethToTokenType,
                                    bytes32[] memory ethToTokenReserveIds,
                                    IKyberReserve[] memory ethToTokenAddresses,
                                    uint256[] memory ethToTokenSplits
                                );
                        }
                        
                        // File: contracts/sol6/KyberHintHandler.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        /**
                         *   @title kyberHintHandler contract
                         *   The contract provides the following functionality:
                         *       - building hints
                         *       - parsing hints
                         *
                         *       All external functions, build*Hint() and parse*Hint:
                         *           - Will revert with error message if an error is found
                         *           - parse*Hint() returns both reserveIds and reserveAddresses
                         *       Internal functions unpackT2THint() and parseHint():
                         *           - Are part of get rate && trade flow
                         *           - Don't revert if an error is found
                         *           - If an error is found, return no data such that the trade flow
                         *             returns 0 rate for bad hint values
                         */
                        abstract contract KyberHintHandler is IKyberHint, Utils5 {
                            /// @notice Parses the hint for a token -> eth trade
                            /// @param tokenSrc source token to trade
                            /// @param hint The ABI encoded hint, built using the build*Hint functions
                            /// @return tokenToEthType Decoded hint type
                            /// @return tokenToEthReserveIds Decoded reserve IDs
                            /// @return tokenToEthAddresses Reserve addresses corresponding to reserve IDs
                            /// @return tokenToEthSplits Decoded splits
                            function parseTokenToEthHint(IERC20 tokenSrc, bytes memory hint)
                                public
                                view
                                override
                                returns (
                                    TradeType tokenToEthType,
                                    bytes32[] memory tokenToEthReserveIds,
                                    IKyberReserve[] memory tokenToEthAddresses,
                                    uint256[] memory tokenToEthSplits
                                )
                            {
                                HintErrors error;
                        
                                (tokenToEthType, tokenToEthReserveIds, tokenToEthSplits, error) = parseHint(hint);
                                if (error != HintErrors.NoError) throwHintError(error);
                        
                                if (tokenToEthType == TradeType.MaskIn || tokenToEthType == TradeType.Split) {
                                    checkTokenListedForReserve(tokenSrc, tokenToEthReserveIds, true);
                                }
                        
                                tokenToEthAddresses = new IKyberReserve[](tokenToEthReserveIds.length);
                        
                                for (uint256 i = 0; i < tokenToEthReserveIds.length; i++) {
                                    checkReserveIdsExists(tokenToEthReserveIds[i]);
                                    checkDuplicateReserveIds(tokenToEthReserveIds, i);
                        
                                    if (i > 0 && tokenToEthType == TradeType.Split) {
                                        checkSplitReserveIdSeq(tokenToEthReserveIds[i], tokenToEthReserveIds[i - 1]);
                                    }
                        
                                    tokenToEthAddresses[i] = IKyberReserve(
                                        getReserveAddress(tokenToEthReserveIds[i])
                                    );
                                }
                            }
                        
                            /// @notice Parses the hint for a eth -> token trade
                            /// @param tokenDest destination token to trade
                            /// @param hint The ABI encoded hint, built using the build*Hint functions
                            /// @return ethToTokenType Decoded hint type
                            /// @return ethToTokenReserveIds Decoded reserve IDs
                            /// @return ethToTokenAddresses Reserve addresses corresponding to reserve IDs
                            /// @return ethToTokenSplits Decoded splits
                            function parseEthToTokenHint(IERC20 tokenDest, bytes memory hint)
                                public
                                view
                                override
                                returns (
                                    TradeType ethToTokenType,
                                    bytes32[] memory ethToTokenReserveIds,
                                    IKyberReserve[] memory ethToTokenAddresses,
                                    uint256[] memory ethToTokenSplits
                                )
                            {
                                HintErrors error;
                        
                                (ethToTokenType, ethToTokenReserveIds, ethToTokenSplits, error) = parseHint(hint);
                                if (error != HintErrors.NoError) throwHintError(error);
                        
                                if (ethToTokenType == TradeType.MaskIn || ethToTokenType == TradeType.Split) {
                                    checkTokenListedForReserve(tokenDest, ethToTokenReserveIds, false);
                                }
                        
                                ethToTokenAddresses = new IKyberReserve[](ethToTokenReserveIds.length);
                        
                                for (uint256 i = 0; i < ethToTokenReserveIds.length; i++) {
                                    checkReserveIdsExists(ethToTokenReserveIds[i]);
                                    checkDuplicateReserveIds(ethToTokenReserveIds, i);
                        
                                    if (i > 0 && ethToTokenType == TradeType.Split) {
                                        checkSplitReserveIdSeq(ethToTokenReserveIds[i], ethToTokenReserveIds[i - 1]);
                                    }
                        
                                    ethToTokenAddresses[i] = IKyberReserve(
                                        getReserveAddress(ethToTokenReserveIds[i])
                                    );
                                }
                            }
                        
                            /// @notice Parses the hint for a token to token trade
                            /// @param tokenSrc source token to trade
                            /// @param tokenDest destination token to trade
                            /// @param hint The ABI encoded hint, built using the build*Hint functions
                            /// @return tokenToEthType Decoded hint type
                            /// @return tokenToEthReserveIds Decoded reserve IDs
                            /// @return tokenToEthAddresses Reserve addresses corresponding to reserve IDs
                            /// @return tokenToEthSplits Decoded splits
                            /// @return ethToTokenType Decoded hint type
                            /// @return ethToTokenReserveIds Decoded reserve IDs
                            /// @return ethToTokenAddresses Reserve addresses corresponding to reserve IDs
                            /// @return ethToTokenSplits Decoded splits
                            function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes memory hint)
                                public
                                view
                                override
                                returns (
                                    TradeType tokenToEthType,
                                    bytes32[] memory tokenToEthReserveIds,
                                    IKyberReserve[] memory tokenToEthAddresses,
                                    uint256[] memory tokenToEthSplits,
                                    TradeType ethToTokenType,
                                    bytes32[] memory ethToTokenReserveIds,
                                    IKyberReserve[] memory ethToTokenAddresses,
                                    uint256[] memory ethToTokenSplits
                                )
                            {
                                bytes memory t2eHint;
                                bytes memory e2tHint;
                        
                                (t2eHint, e2tHint) = unpackT2THint(hint);
                        
                                (
                                    tokenToEthType,
                                    tokenToEthReserveIds,
                                    tokenToEthAddresses,
                                    tokenToEthSplits
                                ) = parseTokenToEthHint(tokenSrc, t2eHint);
                        
                                (
                                    ethToTokenType,
                                    ethToTokenReserveIds,
                                    ethToTokenAddresses,
                                    ethToTokenSplits
                                ) = parseEthToTokenHint(tokenDest, e2tHint);
                            }
                        
                            /// @notice Builds the hint for a token -> eth trade
                            /// @param tokenSrc source token to trade
                            /// @param tokenToEthType token -> eth trade hint type
                            /// @param tokenToEthReserveIds token -> eth reserve IDs
                            /// @param tokenToEthSplits token -> eth reserve splits
                            /// @return hint The ABI encoded hint
                            function buildTokenToEthHint(
                                IERC20 tokenSrc,
                                TradeType tokenToEthType,
                                bytes32[] memory tokenToEthReserveIds,
                                uint256[] memory tokenToEthSplits
                            ) public view override returns (bytes memory hint) {
                                for (uint256 i = 0; i < tokenToEthReserveIds.length; i++) {
                                    checkReserveIdsExists(tokenToEthReserveIds[i]);
                                }
                        
                                HintErrors valid = verifyData(
                                    tokenToEthType,
                                    tokenToEthReserveIds,
                                    tokenToEthSplits
                                );
                                if (valid != HintErrors.NoError) throwHintError(valid);
                        
                                if (tokenToEthType == TradeType.MaskIn || tokenToEthType == TradeType.Split) {
                                    checkTokenListedForReserve(tokenSrc, tokenToEthReserveIds, true);
                                }
                        
                                if (tokenToEthType == TradeType.Split) {
                                    bytes32[] memory seqT2EReserveIds;
                                    uint256[] memory seqT2ESplits;
                        
                                    (seqT2EReserveIds, seqT2ESplits) = ensureSplitSeq(
                                        tokenToEthReserveIds,
                                        tokenToEthSplits
                                    );
                        
                                    hint = abi.encode(tokenToEthType, seqT2EReserveIds, seqT2ESplits);
                                } else {
                                    hint = abi.encode(tokenToEthType, tokenToEthReserveIds, tokenToEthSplits);
                                }
                            }
                        
                            /// @notice Builds the hint for a eth -> token trade
                            /// @param tokenDest destination token to trade
                            /// @param ethToTokenType eth -> token trade hint type
                            /// @param ethToTokenReserveIds eth -> token reserve IDs
                            /// @param ethToTokenSplits eth -> token reserve splits
                            /// @return hint The ABI encoded hint
                            function buildEthToTokenHint(
                                IERC20 tokenDest,
                                TradeType ethToTokenType,
                                bytes32[] memory ethToTokenReserveIds,
                                uint256[] memory ethToTokenSplits
                            ) public view override returns (bytes memory hint) {
                                for (uint256 i = 0; i < ethToTokenReserveIds.length; i++) {
                                    checkReserveIdsExists(ethToTokenReserveIds[i]);
                                }
                        
                                HintErrors valid = verifyData(
                                    ethToTokenType,
                                    ethToTokenReserveIds,
                                    ethToTokenSplits
                                );
                                if (valid != HintErrors.NoError) throwHintError(valid);
                        
                                if (ethToTokenType == TradeType.MaskIn || ethToTokenType == TradeType.Split) {
                                    checkTokenListedForReserve(tokenDest, ethToTokenReserveIds, false);
                                }
                        
                                if (ethToTokenType == TradeType.Split) {
                                    bytes32[] memory seqE2TReserveIds;
                                    uint256[] memory seqE2TSplits;
                        
                                    (seqE2TReserveIds, seqE2TSplits) = ensureSplitSeq(
                                        ethToTokenReserveIds,
                                        ethToTokenSplits
                                    );
                        
                                    hint = abi.encode(ethToTokenType, seqE2TReserveIds, seqE2TSplits);
                                } else {
                                    hint = abi.encode(ethToTokenType, ethToTokenReserveIds, ethToTokenSplits);
                                }
                            }
                        
                            /// @notice Builds the hint for a token to token trade
                            /// @param tokenSrc source token to trade
                            /// @param tokenToEthType token -> eth trade hint type
                            /// @param tokenToEthReserveIds token -> eth reserve IDs
                            /// @param tokenToEthSplits token -> eth reserve splits
                            /// @param tokenDest destination token to trade
                            /// @param ethToTokenType eth -> token trade hint type
                            /// @param ethToTokenReserveIds eth -> token reserve IDs
                            /// @param ethToTokenSplits eth -> token reserve splits
                            /// @return hint The ABI encoded hint
                            function buildTokenToTokenHint(
                                IERC20 tokenSrc,
                                TradeType tokenToEthType,
                                bytes32[] memory tokenToEthReserveIds,
                                uint256[] memory tokenToEthSplits,
                                IERC20 tokenDest,
                                TradeType ethToTokenType,
                                bytes32[] memory ethToTokenReserveIds,
                                uint256[] memory ethToTokenSplits
                            ) public view override returns (bytes memory hint) {
                                bytes memory t2eHint = buildTokenToEthHint(
                                    tokenSrc,
                                    tokenToEthType,
                                    tokenToEthReserveIds,
                                    tokenToEthSplits
                                );
                        
                                bytes memory e2tHint = buildEthToTokenHint(
                                    tokenDest,
                                    ethToTokenType,
                                    ethToTokenReserveIds,
                                    ethToTokenSplits
                                );
                        
                                hint = abi.encode(t2eHint, e2tHint);
                            }
                        
                            /// @notice Parses or decodes the token -> eth or eth -> token bytes hint
                            /// @param hint token -> eth or eth -> token trade hint
                            /// @return tradeType Decoded hint type
                            /// @return reserveIds Decoded reserve IDs
                            /// @return splits Reserve addresses corresponding to reserve IDs
                            /// @return valid Whether the decoded is valid
                            function parseHint(bytes memory hint)
                                internal
                                pure
                                returns (
                                    TradeType tradeType,
                                    bytes32[] memory reserveIds,
                                    uint256[] memory splits,
                                    HintErrors valid
                                )
                            {
                                (tradeType, reserveIds, splits) = abi.decode(hint, (TradeType, bytes32[], uint256[])); // solhint-disable
                                valid = verifyData(tradeType, reserveIds, splits);
                        
                                if (valid != HintErrors.NoError) {
                                    reserveIds = new bytes32[](0);
                                    splits = new uint256[](0);
                                }
                            }
                        
                            /// @notice Unpacks the token to token hint to token -> eth and eth -> token hints
                            /// @param hint token to token trade hint
                            /// @return t2eHint The ABI encoded token -> eth hint
                            /// @return e2tHint The ABI encoded eth -> token hint
                            function unpackT2THint(bytes memory hint)
                                internal
                                pure
                                returns (bytes memory t2eHint, bytes memory e2tHint)
                            {
                                (t2eHint, e2tHint) = abi.decode(hint, (bytes, bytes));
                            }
                        
                            /// @notice Checks if the reserveId exists
                            /// @param reserveId Reserve ID to check
                            function checkReserveIdsExists(bytes32 reserveId)
                                internal
                                view
                            {
                                if (getReserveAddress(reserveId) == address(0))
                                    throwHintError(HintErrors.ReserveIdNotFound);
                            }
                        
                            /// @notice Checks that the token is listed for the reserves
                            /// @param token ERC20 token
                            /// @param reserveIds Reserve IDs
                            /// @param isTokenToEth Flag to indicate token -> eth or eth -> token
                            function checkTokenListedForReserve(
                                IERC20 token,
                                bytes32[] memory reserveIds,
                                bool isTokenToEth
                            ) internal view {
                                IERC20 src = (isTokenToEth) ? token : ETH_TOKEN_ADDRESS;
                                IERC20 dest = (isTokenToEth) ? ETH_TOKEN_ADDRESS : token;
                        
                                if (!areAllReservesListed(reserveIds, src, dest))
                                    throwHintError(HintErrors.TokenListedError);
                            }
                        
                            /// @notice Ensures that the reserveIds in the hint to be parsed has no duplicates
                            /// and applies to all trade types
                            /// @param reserveIds Array of reserve IDs
                            /// @param i Starting index from outer loop
                            function checkDuplicateReserveIds(bytes32[] memory reserveIds, uint256 i)
                                internal
                                pure
                            {
                                for (uint256 j = i + 1; j < reserveIds.length; j++) {
                                    if (uint256(reserveIds[i]) == uint256(reserveIds[j])) {
                                        throwHintError(HintErrors.ReserveIdDupError);
                                    }
                                }
                            }
                        
                            /// @notice Ensures that the reserveIds in the hint to be parsed is in
                            /// sequence for and applies to only Split trade type
                            /// @param reserveId Current index Reserve ID in array
                            /// @param prevReserveId Previous index Reserve ID in array
                            function checkSplitReserveIdSeq(bytes32 reserveId, bytes32 prevReserveId)
                                internal
                                pure
                            {
                                if (uint256(reserveId) <= uint256(prevReserveId)) {
                                    throwHintError(HintErrors.ReserveIdSequenceError);
                                }
                            }
                        
                            /// @notice Ensures that the reserveIds and splits passed when building Split hints are in increasing sequence
                            /// @param reserveIds Reserve IDs
                            /// @param splits Reserve splits
                            /// @return Returns a bytes32[] with reserveIds in increasing sequence and respective arranged splits
                            function ensureSplitSeq(
                                bytes32[] memory reserveIds,
                                uint256[] memory splits
                            )
                                internal
                                pure
                                returns (bytes32[] memory, uint256[] memory)
                            {
                                for (uint256 i = 0; i < reserveIds.length; i++) {
                                    for (uint256 j = i + 1; j < reserveIds.length; j++) {
                                        if (uint256(reserveIds[i]) > (uint256(reserveIds[j]))) {
                                            bytes32 tempId = reserveIds[i];
                                            uint256 tempSplit = splits[i];
                        
                                            reserveIds[i] = reserveIds[j];
                                            reserveIds[j] = tempId;
                                            splits[i] = splits[j];
                                            splits[j] = tempSplit;
                                        } else if (reserveIds[i] == reserveIds[j]) {
                                            throwHintError(HintErrors.ReserveIdDupError);
                                        }
                                    }
                                }
                        
                                return (reserveIds, splits);
                            }
                        
                            /// @notice Ensures that the data passed when building/parsing hints is valid
                            /// @param tradeType Trade hint type
                            /// @param reserveIds Reserve IDs
                            /// @param splits Reserve splits
                            /// @return Returns a HintError enum to indicate valid or invalid hint data
                            function verifyData(
                                TradeType tradeType,
                                bytes32[] memory reserveIds,
                                uint256[] memory splits
                            ) internal pure returns (HintErrors) {
                                if (tradeType == TradeType.BestOfAll) {
                                    if (reserveIds.length != 0 || splits.length != 0) return HintErrors.NonEmptyDataError;
                                }
                        
                                if (
                                    (tradeType == TradeType.MaskIn || tradeType == TradeType.Split) &&
                                    reserveIds.length == 0
                                ) return HintErrors.ReserveIdEmptyError;
                        
                                if (tradeType == TradeType.Split) {
                                    if (reserveIds.length != splits.length) return HintErrors.ReserveIdSplitsError;
                        
                                    uint256 bpsSoFar;
                                    for (uint256 i = 0; i < splits.length; i++) {
                                        bpsSoFar += splits[i];
                                    }
                        
                                    if (bpsSoFar != BPS) return HintErrors.TotalBPSError;
                                } else {
                                    if (splits.length != 0) return HintErrors.SplitsNotEmptyError;
                                }
                        
                                return HintErrors.NoError;
                            }
                        
                            /// @notice Throws error message to user to indicate error on hint
                            /// @param error Error type from HintErrors enum
                            function throwHintError(HintErrors error) internal pure {
                                if (error == HintErrors.NonEmptyDataError) revert("reserveIds and splits must be empty");
                                if (error == HintErrors.ReserveIdDupError) revert("duplicate reserveId");
                                if (error == HintErrors.ReserveIdEmptyError) revert("reserveIds cannot be empty");
                                if (error == HintErrors.ReserveIdSplitsError) revert("reserveIds.length != splits.length");
                                if (error == HintErrors.ReserveIdSequenceError) revert("reserveIds not in increasing order");
                                if (error == HintErrors.ReserveIdNotFound) revert("reserveId not found");
                                if (error == HintErrors.SplitsNotEmptyError) revert("splits must be empty");
                                if (error == HintErrors.TokenListedError) revert("token is not listed for reserveId");
                                if (error == HintErrors.TotalBPSError) revert("total BPS != 10000");
                            }
                        
                            function getReserveAddress(bytes32 reserveId) internal view virtual returns (address);
                        
                            function areAllReservesListed(
                                bytes32[] memory reserveIds,
                                IERC20 src,
                                IERC20 dest
                            ) internal virtual view returns (bool);
                        }
                        
                        // File: contracts/sol6/KyberMatchingEngine.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        
                        
                        
                        /**
                         *   @title kyberMatchingEngine contract
                         *   During getExpectedRate flow and trade flow this contract is called for:
                         *       - parsing hint and returning reserve list (function getTradingReserves)
                         *       - matching best reserves to trade with (function doMatch)
                         */
                        contract KyberMatchingEngine is KyberHintHandler, IKyberMatchingEngine, WithdrawableNoModifiers {
                            struct BestReserveInfo {
                                uint256 index;
                                uint256 destAmount;
                                uint256 numRelevantReserves;
                            }
                            IKyberNetwork public kyberNetwork;
                            IKyberStorage public kyberStorage;
                        
                            uint256 negligibleRateDiffBps = 5; // 1 bps is 0.01%
                        
                            event KyberStorageUpdated(IKyberStorage newKyberStorage);
                            event KyberNetworkUpdated(IKyberNetwork newKyberNetwork);
                        
                            constructor(address _admin) public WithdrawableNoModifiers(_admin) {
                                /* empty body */
                            }
                        
                            function setKyberStorage(IKyberStorage _kyberStorage) external virtual override {
                                onlyAdmin();
                                emit KyberStorageUpdated(_kyberStorage);
                                kyberStorage = _kyberStorage;
                            }
                        
                            function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps)
                                external
                                virtual
                                override
                            {
                                onlyNetwork();
                                require(_negligibleRateDiffBps <= BPS, "rateDiffBps exceed BPS"); // at most 100%
                                negligibleRateDiffBps = _negligibleRateDiffBps;
                            }
                        
                            function setNetworkContract(IKyberNetwork _kyberNetwork) external {
                                onlyAdmin();
                                require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0");
                                emit KyberNetworkUpdated(_kyberNetwork);
                                kyberNetwork = _kyberNetwork;
                            }
                        
                            /// @dev Returns trading reserves info for a trade
                            /// @param src Source token
                            /// @param dest Destination token
                            /// @param isTokenToToken Whether the trade is token -> token
                            /// @param hint Advanced instructions for running the trade
                            /// @return reserveIds Array of reserve IDs for the trade, each being 32 bytes
                            /// @return splitValuesBps Array of split values (in basis points) for the trade
                            /// @return processWithRate Enum ProcessWithRate, whether extra processing is required or not
                            function getTradingReserves(
                                IERC20 src,
                                IERC20 dest,
                                bool isTokenToToken,
                                bytes calldata hint
                            )
                                external
                                view
                                override
                                returns (
                                    bytes32[] memory reserveIds,
                                    uint256[] memory splitValuesBps,
                                    ProcessWithRate processWithRate
                                )
                            {
                                HintErrors error;
                                if (hint.length == 0 || hint.length == 4) {
                                    reserveIds = (dest == ETH_TOKEN_ADDRESS)
                                        ? kyberStorage.getReserveIdsPerTokenSrc(src)
                                        : kyberStorage.getReserveIdsPerTokenDest(dest);
                        
                                    splitValuesBps = populateSplitValuesBps(reserveIds.length);
                                    processWithRate = ProcessWithRate.Required;
                                    return (reserveIds, splitValuesBps, processWithRate);
                                }
                        
                                TradeType tradeType;
                        
                                if (isTokenToToken) {
                                    bytes memory unpackedHint;
                                    if (src == ETH_TOKEN_ADDRESS) {
                                        (, unpackedHint) = unpackT2THint(hint);
                                        (tradeType, reserveIds, splitValuesBps, error) = parseHint(unpackedHint);
                                    }
                                    if (dest == ETH_TOKEN_ADDRESS) {
                                        (unpackedHint, ) = unpackT2THint(hint);
                                        (tradeType, reserveIds, splitValuesBps, error) = parseHint(unpackedHint);
                                    }
                                } else {
                                    (tradeType, reserveIds, splitValuesBps, error) = parseHint(hint);
                                }
                        
                                if (error != HintErrors.NoError)
                                    return (new bytes32[](0), new uint256[](0), ProcessWithRate.NotRequired);
                        
                                if (tradeType == TradeType.MaskIn) {
                                    splitValuesBps = populateSplitValuesBps(reserveIds.length);
                                } else if (tradeType == TradeType.BestOfAll || tradeType == TradeType.MaskOut) {
                                    bytes32[] memory allReserves = (dest == ETH_TOKEN_ADDRESS)
                                        ? kyberStorage.getReserveIdsPerTokenSrc(src)
                                        : kyberStorage.getReserveIdsPerTokenDest(dest);
                        
                                    // if bestOfAll, reserveIds = allReserves
                                    // if mask out, apply masking out logic
                                    reserveIds = (tradeType == TradeType.BestOfAll) ?
                                        allReserves :
                                        maskOutReserves(allReserves, reserveIds);
                                    splitValuesBps = populateSplitValuesBps(reserveIds.length);
                                }
                        
                                // for split no need to search for best rate. User defines full trade details in advance.
                                processWithRate = (tradeType == TradeType.Split)
                                    ? ProcessWithRate.NotRequired
                                    : ProcessWithRate.Required;
                            }
                        
                            function getNegligibleRateDiffBps() external view override returns (uint256) {
                                return negligibleRateDiffBps;
                            }
                        
                            /// @dev Returns the indexes of the best rate from the rates array
                            ///     for token -> eth or eth -> token side of trade
                            /// @param src Source token (not needed in this kyberMatchingEngine version)
                            /// @param dest Destination token (not needed in this kyberMatchingEngine version)
                            /// @param srcAmounts Array of srcAmounts after deducting fees.
                            /// @param feesAccountedDestBps Fees charged in BPS, to be deducted from calculated destAmount
                            /// @param rates Rates queried from reserves
                            /// @return reserveIndexes An array of the indexes most suited for the trade
                            function doMatch(
                                IERC20 src,
                                IERC20 dest,
                                uint256[] calldata srcAmounts,
                                uint256[] calldata feesAccountedDestBps, // 0 for no fee, networkFeeBps when has fee
                                uint256[] calldata rates
                            ) external view override returns (uint256[] memory reserveIndexes) {
                                src;
                                dest;
                                reserveIndexes = new uint256[](1);
                        
                                // use destAmounts for comparison, but return the best rate
                                BestReserveInfo memory bestReserve;
                                bestReserve.numRelevantReserves = 1; // assume always best reserve will be relevant
                        
                                // return empty array for unlisted tokens
                                if (rates.length == 0) {
                                    reserveIndexes = new uint256[](0);
                                    return reserveIndexes;
                                }
                        
                                uint256[] memory reserveCandidates = new uint256[](rates.length);
                                uint256[] memory destAmounts = new uint256[](rates.length);
                                uint256 destAmount;
                        
                                for (uint256 i = 0; i < rates.length; i++) {
                                    // if fee is accounted on dest amount of this reserve, should deduct it
                                    destAmount = (srcAmounts[i] * rates[i] * (BPS - feesAccountedDestBps[i])) / BPS;
                                    if (destAmount > bestReserve.destAmount) {
                                        // best rate is highest rate
                                        bestReserve.destAmount = destAmount;
                                        bestReserve.index = i;
                                    }
                        
                                    destAmounts[i] = destAmount;
                                }
                        
                                if (bestReserve.destAmount == 0) {
                                    reserveIndexes[0] = bestReserve.index;
                                    return reserveIndexes;
                                }
                        
                                reserveCandidates[0] = bestReserve.index;
                        
                                // update best reserve destAmount to be its destAmount after deducting negligible diff.
                                // if any reserve has better or equal dest amount it can be considred to be chosen as best
                                bestReserve.destAmount = (bestReserve.destAmount * BPS) / (BPS + negligibleRateDiffBps);
                        
                                for (uint256 i = 0; i < rates.length; i++) {
                                    if (i == bestReserve.index) continue;
                                    if (destAmounts[i] > bestReserve.destAmount) {
                                        reserveCandidates[bestReserve.numRelevantReserves++] = i;
                                    }
                                }
                        
                                if (bestReserve.numRelevantReserves > 1) {
                                    // when encountering small rate diff from bestRate. draw from relevant reserves
                                    bestReserve.index = reserveCandidates[uint256(blockhash(block.number - 1)) %
                                        bestReserve.numRelevantReserves];
                                } else {
                                    bestReserve.index = reserveCandidates[0];
                                }
                        
                                reserveIndexes[0] = bestReserve.index;
                            }
                        
                            function getReserveAddress(bytes32 reserveId) internal view override returns (address reserveAddress) {
                                (reserveAddress, , , ,) = kyberStorage.getReserveDetailsById(reserveId);
                            }
                        
                            function areAllReservesListed(
                                bytes32[] memory reserveIds,
                                IERC20 src,
                                IERC20 dest
                            ) internal override view returns (bool allReservesListed) {
                                (allReservesListed, , ,) = kyberStorage.getReservesData(reserveIds, src, dest);
                            }
                        
                            /// @notice Logic for masking out reserves
                            /// @param allReservesPerToken Array of reserveIds that support
                            ///     the token -> eth or eth -> token side of the trade
                            /// @param maskedOutReserves Array of reserveIds to be excluded from allReservesPerToken
                            /// @return filteredReserves An array of reserveIds that can be used for the trade
                            function maskOutReserves(
                                bytes32[] memory allReservesPerToken,
                                bytes32[] memory maskedOutReserves
                            ) internal pure returns (bytes32[] memory filteredReserves) {
                                require(
                                    allReservesPerToken.length >= maskedOutReserves.length,
                                    "mask out exceeds available reserves"
                                );
                                filteredReserves = new bytes32[](allReservesPerToken.length - maskedOutReserves.length);
                                uint256 currentResultIndex = 0;
                        
                                for (uint256 i = 0; i < allReservesPerToken.length; i++) {
                                    bytes32 reserveId = allReservesPerToken[i];
                                    bool notMaskedOut = true;
                        
                                    for (uint256 j = 0; j < maskedOutReserves.length; j++) {
                                        bytes32 maskedOutReserveId = maskedOutReserves[j];
                                        if (reserveId == maskedOutReserveId) {
                                            notMaskedOut = false;
                                            break;
                                        }
                                    }
                        
                                    if (notMaskedOut) filteredReserves[currentResultIndex++] = reserveId;
                                }
                            }
                        
                            function onlyNetwork() internal view {
                                require(msg.sender == address(kyberNetwork), "only kyberNetwork");
                            }
                        
                            function populateSplitValuesBps(uint256 length)
                                internal
                                pure
                                returns (uint256[] memory splitValuesBps)
                            {
                                splitValuesBps = new uint256[](length);
                                for (uint256 i = 0; i < length; i++) {
                                    splitValuesBps[i] = BPS;
                                }
                            }
                        }

                        File 10 of 13: KyberStorage
                        // File: contracts/sol6/IKyberHistory.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        interface IKyberHistory {
                            function saveContract(address _contract) external;
                            function getContracts() external view returns (address[] memory);
                        }
                        
                        // File: contracts/sol6/IERC20.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        interface IERC20 {
                            event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                        
                            function approve(address _spender, uint256 _value) external returns (bool success);
                        
                            function transfer(address _to, uint256 _value) external returns (bool success);
                        
                            function transferFrom(
                                address _from,
                                address _to,
                                uint256 _value
                            ) external returns (bool success);
                        
                            function allowance(address _owner, address _spender) external view returns (uint256 remaining);
                        
                            function balanceOf(address _owner) external view returns (uint256 balance);
                        
                            function decimals() external view returns (uint8 digits);
                        
                            function totalSupply() external view returns (uint256 supply);
                        }
                        
                        
                        // to support backward compatible contract name -- so function signature remains same
                        abstract contract ERC20 is IERC20 {
                        
                        }
                        
                        // File: contracts/sol6/IKyberNetworkProxy.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetworkProxy {
                        
                            event ExecuteTrade(
                                address indexed trader,
                                IERC20 src,
                                IERC20 dest,
                                address destAddress,
                                uint256 actualSrcAmount,
                                uint256 actualDestAmount,
                                address platformWallet,
                                uint256 platformFeeBps
                            );
                        
                            /// @notice backward compatible
                            function tradeWithHint(
                                ERC20 src,
                                uint256 srcAmount,
                                ERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable walletId,
                                bytes calldata hint
                            ) external payable returns (uint256);
                        
                            function tradeWithHintAndFee(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function trade(
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet
                            ) external payable returns (uint256);
                        
                            /// @notice backward compatible
                            /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
                            function getExpectedRate(
                                ERC20 src,
                                ERC20 dest,
                                uint256 srcQty
                            ) external view returns (uint256 expectedRate, uint256 worstRate);
                        
                            function getExpectedRateAfterFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external view returns (uint256 expectedRate);
                        }
                        
                        // File: contracts/sol6/IKyberReserve.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberReserve {
                            function trade(
                                IERC20 srcToken,
                                uint256 srcAmount,
                                IERC20 destToken,
                                address payable destAddress,
                                uint256 conversionRate,
                                bool validate
                            ) external payable returns (bool);
                        
                            function getConversionRate(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 blockNumber
                            ) external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/IKyberStorage.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        interface IKyberStorage {
                            enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST}
                        
                            function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies)
                                external;
                        
                            function removeKyberProxy(address kyberProxy) external;
                        
                            function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external;
                        
                            function setKyberDaoContract(address _kyberDao) external;
                        
                            function getReserveId(address reserve) external view returns (bytes32 reserveId);
                        
                            function getReserveIdsFromAddresses(address[] calldata reserveAddresses)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesFromIds(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getReserveIdsPerTokenSrc(IERC20 token)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getReserveIdsPerTokenDest(IERC20 token)
                                external
                                view
                                returns (bytes32[] memory reserveIds);
                        
                            function getReserveAddressesByReserveId(bytes32 reserveId)
                                external
                                view
                                returns (address[] memory reserveAddresses);
                        
                            function getRebateWalletsFromIds(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (address[] memory rebateWallets);
                        
                            function getKyberProxies() external view returns (IKyberNetworkProxy[] memory);
                        
                            function getReserveDetailsByAddress(address reserve)
                                external
                                view
                                returns (
                                    bytes32 reserveId,
                                    address rebateWallet,
                                    ReserveType resType,
                                    bool isFeeAccountedFlag,
                                    bool isEntitledRebateFlag
                                );
                        
                            function getReserveDetailsById(bytes32 reserveId)
                                external
                                view
                                returns (
                                    address reserveAddress,
                                    address rebateWallet,
                                    ReserveType resType,
                                    bool isFeeAccountedFlag,
                                    bool isEntitledRebateFlag
                                );
                        
                            function getFeeAccountedData(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (bool[] memory feeAccountedArr);
                        
                            function getEntitledRebateData(bytes32[] calldata reserveIds)
                                external
                                view
                                returns (bool[] memory entitledRebateArr);
                        
                            function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest)
                                external
                                view
                                returns (
                                    bool areAllReservesListed,
                                    bool[] memory feeAccountedArr,
                                    bool[] memory entitledRebateArr,
                                    IKyberReserve[] memory reserveAddresses);
                        
                            function isKyberProxyAdded() external view returns (bool);
                        }
                        
                        // File: contracts/sol6/IKyberNetwork.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        interface IKyberNetwork {
                            event KyberTrade(
                                IERC20 indexed src,
                                IERC20 indexed dest,
                                uint256 ethWeiValue,
                                uint256 networkFeeWei,
                                uint256 customPlatformFeeWei,
                                bytes32[] t2eIds,
                                bytes32[] e2tIds,
                                uint256[] t2eSrcAmounts,
                                uint256[] e2tSrcAmounts,
                                uint256[] t2eRates,
                                uint256[] e2tRates
                            );
                        
                            function tradeWithHintAndFee(
                                address payable trader,
                                IERC20 src,
                                uint256 srcAmount,
                                IERC20 dest,
                                address payable destAddress,
                                uint256 maxDestAmount,
                                uint256 minConversionRate,
                                address payable platformWallet,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            ) external payable returns (uint256 destAmount);
                        
                            function listTokenForReserve(
                                address reserve,
                                IERC20 token,
                                bool add
                            ) external;
                        
                            function enabled() external view returns (bool);
                        
                            function getExpectedRateWithHintAndFee(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcQty,
                                uint256 platformFeeBps,
                                bytes calldata hint
                            )
                                external
                                view
                                returns (
                                    uint256 expectedRateAfterNetworkFee,
                                    uint256 expectedRateAfterAllFees
                                );
                        
                            function getNetworkData()
                                external
                                view
                                returns (
                                    uint256 negligibleDiffBps,
                                    uint256 networkFeeBps,
                                    uint256 expiryTimestamp
                                );
                        
                            function maxGasPrice() external view returns (uint256);
                        }
                        
                        // File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        contract PermissionGroupsNoModifiers {
                            address public admin;
                            address public pendingAdmin;
                            mapping(address => bool) internal operators;
                            mapping(address => bool) internal alerters;
                            address[] internal operatorsGroup;
                            address[] internal alertersGroup;
                            uint256 internal constant MAX_GROUP_SIZE = 50;
                        
                            event AdminClaimed(address newAdmin, address previousAdmin);
                            event AlerterAdded(address newAlerter, bool isAdd);
                            event OperatorAdded(address newOperator, bool isAdd);
                            event TransferAdminPending(address pendingAdmin);
                        
                            constructor(address _admin) public {
                                require(_admin != address(0), "admin 0");
                                admin = _admin;
                            }
                        
                            function getOperators() external view returns (address[] memory) {
                                return operatorsGroup;
                            }
                        
                            function getAlerters() external view returns (address[] memory) {
                                return alertersGroup;
                            }
                        
                            function addAlerter(address newAlerter) public {
                                onlyAdmin();
                                require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
                                require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
                        
                                emit AlerterAdded(newAlerter, true);
                                alerters[newAlerter] = true;
                                alertersGroup.push(newAlerter);
                            }
                        
                            function addOperator(address newOperator) public {
                                onlyAdmin();
                                require(!operators[newOperator], "operator exists"); // prevent duplicates.
                                require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
                        
                                emit OperatorAdded(newOperator, true);
                                operators[newOperator] = true;
                                operatorsGroup.push(newOperator);
                            }
                        
                            /// @dev Allows the pendingAdmin address to finalize the change admin process.
                            function claimAdmin() public {
                                require(pendingAdmin == msg.sender, "not pending");
                                emit AdminClaimed(pendingAdmin, admin);
                                admin = pendingAdmin;
                                pendingAdmin = address(0);
                            }
                        
                            function removeAlerter(address alerter) public {
                                onlyAdmin();
                                require(alerters[alerter], "not alerter");
                                delete alerters[alerter];
                        
                                for (uint256 i = 0; i < alertersGroup.length; ++i) {
                                    if (alertersGroup[i] == alerter) {
                                        alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                        alertersGroup.pop();
                                        emit AlerterAdded(alerter, false);
                                        break;
                                    }
                                }
                            }
                        
                            function removeOperator(address operator) public {
                                onlyAdmin();
                                require(operators[operator], "not operator");
                                delete operators[operator];
                        
                                for (uint256 i = 0; i < operatorsGroup.length; ++i) {
                                    if (operatorsGroup[i] == operator) {
                                        operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                        operatorsGroup.pop();
                                        emit OperatorAdded(operator, false);
                                        break;
                                    }
                                }
                            }
                        
                            /// @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), "new admin 0");
                                emit TransferAdminPending(newAdmin);
                                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), "admin 0");
                                emit TransferAdminPending(newAdmin);
                                emit AdminClaimed(newAdmin, admin);
                                admin = newAdmin;
                            }
                        
                            function onlyAdmin() internal view {
                                require(msg.sender == admin, "only admin");
                            }
                        
                            function onlyAlerter() internal view {
                                require(alerters[msg.sender], "only alerter");
                            }
                        
                            function onlyOperator() internal view {
                                require(operators[msg.sender], "only operator");
                            }
                        }
                        
                        // File: contracts/sol6/utils/Utils5.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        /**
                         * @title Kyber utility file
                         * mostly shared constants and rate calculation helpers
                         * inherited by most of kyber contracts.
                         * previous utils implementations are for previous solidity versions.
                         */
                        contract Utils5 {
                            IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
                                0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
                            );
                            uint256 internal constant PRECISION = (10**18);
                            uint256 internal constant MAX_QTY = (10**28); // 10B tokens
                            uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
                            uint256 internal constant MAX_DECIMALS = 18;
                            uint256 internal constant ETH_DECIMALS = 18;
                            uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
                            uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
                        
                            mapping(IERC20 => uint256) internal decimals;
                        
                            function getUpdateDecimals(IERC20 token) internal returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // moreover, very possible that old tokens have decimals 0
                                // these tokens will just have higher gas fees.
                                if (tokenDecimals == 0) {
                                    tokenDecimals = token.decimals();
                                    decimals[token] = tokenDecimals;
                                }
                        
                                return tokenDecimals;
                            }
                        
                            function setDecimals(IERC20 token) internal {
                                if (decimals[token] != 0) return; //already set
                        
                                if (token == ETH_TOKEN_ADDRESS) {
                                    decimals[token] = ETH_DECIMALS;
                                } else {
                                    decimals[token] = token.decimals();
                                }
                            }
                        
                            /// @dev get the balance of a user.
                            /// @param token The token type
                            /// @return The balance
                            function getBalance(IERC20 token, address user) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) {
                                    return user.balance;
                                } else {
                                    return token.balanceOf(user);
                                }
                            }
                        
                            function getDecimals(IERC20 token) internal view returns (uint256) {
                                if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                                uint256 tokenDecimals = decimals[token];
                                // 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 calcDestAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 srcAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcSrcAmount(
                                IERC20 src,
                                IERC20 dest,
                                uint256 destAmount,
                                uint256 rate
                            ) internal view returns (uint256) {
                                return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
                            }
                        
                            function calcDstQty(
                                uint256 srcQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                                }
                            }
                        
                            function calcSrcQty(
                                uint256 dstQty,
                                uint256 srcDecimals,
                                uint256 dstDecimals,
                                uint256 rate
                            ) internal pure returns (uint256) {
                                require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
                                require(rate <= MAX_RATE, "rate > MAX_RATE");
                        
                                //source quantity is rounded up. to avoid dest quantity being too low.
                                uint256 numerator;
                                uint256 denominator;
                                if (srcDecimals >= dstDecimals) {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                                    denominator = rate;
                                } else {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    numerator = (PRECISION * dstQty);
                                    denominator = (rate * (10**(dstDecimals - srcDecimals)));
                                }
                                return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                            }
                        
                            function calcRateFromQty(
                                uint256 srcAmount,
                                uint256 destAmount,
                                uint256 srcDecimals,
                                uint256 dstDecimals
                            ) internal pure returns (uint256) {
                                require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
                                require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
                        
                                if (dstDecimals >= srcDecimals) {
                                    require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
                                    return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
                                } else {
                                    require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
                                    return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
                                }
                            }
                        
                            function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
                                return x > y ? y : x;
                            }
                        }
                        
                        // File: contracts/sol6/KyberStorage.sol
                        
                        pragma solidity 0.6.6;
                        
                        
                        
                        
                        
                        
                        
                        /**
                         *   @title kyberStorage contract
                         *   The contract provides the following functions for kyberNetwork contract:
                         *   - Stores reserve and token listing information by the kyberNetwork
                         *   - Stores feeAccounted data for reserve types
                         *   - Record contract changes for reserves and kyberProxies
                         *   - Points to historical contracts that record contract changes for kyberNetwork,
                         *        kyberFeeHandler, kyberDao and kyberMatchingEngine
                         */
                        contract KyberStorage is IKyberStorage, PermissionGroupsNoModifiers, Utils5 {
                            // store current and previous contracts
                            IKyberHistory public kyberNetworkHistory;
                            IKyberHistory public kyberFeeHandlerHistory;
                            IKyberHistory public kyberDaoHistory;
                            IKyberHistory public kyberMatchingEngineHistory;
                        
                            IKyberReserve[] internal reserves;
                            IKyberNetworkProxy[] internal kyberProxyArray;
                        
                            mapping(bytes32 => address[]) internal reserveIdToAddresses;
                            mapping(bytes32 => address) internal reserveRebateWallet;
                            mapping(address => bytes32) internal reserveAddressToId;
                            mapping(IERC20 => bytes32[]) internal reservesPerTokenSrc; // reserves supporting token to eth
                            mapping(IERC20 => bytes32[]) internal reservesPerTokenDest; // reserves support eth to token
                            mapping(bytes32 => IERC20[]) internal srcTokensPerReserve;
                            mapping(bytes32 => IERC20[]) internal destTokensPerReserve;
                        
                            mapping(IERC20 => mapping(bytes32 => bool)) internal isListedReserveWithTokenSrc;
                            mapping(IERC20 => mapping(bytes32 => bool)) internal isListedReserveWithTokenDest;
                        
                            uint256 internal feeAccountedPerType = 0xffffffff;
                            uint256 internal entitledRebatePerType = 0xffffffff;
                            mapping(bytes32 => uint256) internal reserveType; // type from enum ReserveType
                            mapping(ReserveType => bytes32[]) internal reservesPerType;
                        
                            IKyberNetwork public kyberNetwork;
                        
                            constructor(
                                address _admin,
                                IKyberHistory _kyberNetworkHistory,
                                IKyberHistory _kyberFeeHandlerHistory,
                                IKyberHistory _kyberDaoHistory,
                                IKyberHistory _kyberMatchingEngineHistory
                            ) public PermissionGroupsNoModifiers(_admin) {
                                require(_kyberNetworkHistory != IKyberHistory(0), "kyberNetworkHistory 0");
                                require(_kyberFeeHandlerHistory != IKyberHistory(0), "kyberFeeHandlerHistory 0");
                                require(_kyberDaoHistory != IKyberHistory(0), "kyberDaoHistory 0");
                                require(_kyberMatchingEngineHistory != IKyberHistory(0), "kyberMatchingEngineHistory 0");
                        
                                kyberNetworkHistory = _kyberNetworkHistory;
                                kyberFeeHandlerHistory = _kyberFeeHandlerHistory;
                                kyberDaoHistory = _kyberDaoHistory;
                                kyberMatchingEngineHistory = _kyberMatchingEngineHistory;
                            }
                        
                            event KyberNetworkUpdated(IKyberNetwork newKyberNetwork);
                            event RemoveReserveFromStorage(address indexed reserve, bytes32 indexed reserveId);
                        
                            event AddReserveToStorage(
                                address indexed reserve,
                                bytes32 indexed reserveId,
                                IKyberStorage.ReserveType reserveType,
                                address indexed rebateWallet
                            );
                        
                            event ReserveRebateWalletSet(
                                bytes32 indexed reserveId,
                                address indexed rebateWallet
                            );
                        
                            event ListReservePairs(
                                bytes32 indexed reserveId,
                                address reserve,
                                IERC20 indexed src,
                                IERC20 indexed dest,
                                bool add
                            );
                        
                            function setNetworkContract(IKyberNetwork _kyberNetwork) external {
                                onlyAdmin();
                                require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0");
                                emit KyberNetworkUpdated(_kyberNetwork);
                                kyberNetworkHistory.saveContract(address(_kyberNetwork));
                                kyberNetwork = _kyberNetwork;
                            }
                        
                            function setRebateWallet(bytes32 reserveId, address rebateWallet) external {
                                onlyOperator();
                                require(rebateWallet != address(0), "rebate wallet is 0");
                                require(reserveId != bytes32(0), "reserveId = 0");
                                require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found");
                                require(reserveIdToAddresses[reserveId][0] != address(0), "no reserve associated");
                        
                                reserveRebateWallet[reserveId] = rebateWallet;
                                emit ReserveRebateWalletSet(reserveId, rebateWallet);
                            }
                        
                            function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine)
                                external
                                override
                            {
                                onlyNetwork();
                                require(_kyberFeeHandler != address(0), "kyberFeeHandler 0");
                                require(_kyberMatchingEngine != address(0), "kyberMatchingEngine 0");
                        
                                kyberFeeHandlerHistory.saveContract(_kyberFeeHandler);
                                kyberMatchingEngineHistory.saveContract(_kyberMatchingEngine);
                            }
                        
                            function setKyberDaoContract(address _kyberDao) external override {
                                onlyNetwork();
                        
                                kyberDaoHistory.saveContract(_kyberDao);
                            }
                        
                            /// @notice Can be called only by operator
                            /// @dev Adds a reserve to the storage
                            /// @param reserve The reserve address
                            /// @param reserveId The reserve ID in 32 bytes.
                            /// @param resType Type of the reserve out of enum ReserveType
                            /// @param rebateWallet Rebate wallet address for this reserve
                            function addReserve(
                                address reserve,
                                bytes32 reserveId,
                                ReserveType resType,
                                address payable rebateWallet
                            ) external {
                                onlyOperator();
                                require(reserveAddressToId[reserve] == bytes32(0), "reserve has id");
                                require(reserveId != bytes32(0), "reserveId = 0");
                                require(
                                    (resType != ReserveType.NONE) && (uint256(resType) < uint256(ReserveType.LAST)),
                                    "bad reserve type"
                                );
                                require(feeAccountedPerType != 0xffffffff, "fee accounted data not set");
                                require(entitledRebatePerType != 0xffffffff, "entitled rebate data not set");
                                require(rebateWallet != address(0), "rebate wallet is 0");
                        
                                reserveRebateWallet[reserveId] = rebateWallet;
                        
                                if (reserveIdToAddresses[reserveId].length == 0) {
                                    reserveIdToAddresses[reserveId].push(reserve);
                                } else {
                                    require(reserveIdToAddresses[reserveId][0] == address(0), "reserveId taken");
                                    reserveIdToAddresses[reserveId][0] = reserve;
                                }
                        
                                reserves.push(IKyberReserve(reserve));
                                reservesPerType[resType].push(reserveId);
                                reserveAddressToId[reserve] = reserveId;
                                reserveType[reserveId] = uint256(resType);
                        
                                emit AddReserveToStorage(reserve, reserveId, resType, rebateWallet);
                                emit ReserveRebateWalletSet(reserveId, rebateWallet);
                            }
                        
                            /// @notice Can be called only by operator
                            /// @dev Removes a reserve from the storage
                            /// @param reserveId The reserve id
                            /// @param startIndex Index to start searching from in reserve array
                            function removeReserve(bytes32 reserveId, uint256 startIndex)
                                external
                            {
                                onlyOperator();
                                require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found");
                                address reserve = reserveIdToAddresses[reserveId][0];
                        
                                // delist all token pairs for reserve
                                delistTokensOfReserve(reserveId);
                        
                                uint256 reserveIndex = 2**255;
                                for (uint256 i = startIndex; i < reserves.length; i++) {
                                    if (reserves[i] == IKyberReserve(reserve)) {
                                        reserveIndex = i;
                                        break;
                                    }
                                }
                                require(reserveIndex != 2**255, "reserve not found");
                                reserves[reserveIndex] = reserves[reserves.length - 1];
                                reserves.pop();
                                // remove reserve from mapping to address
                                require(reserveAddressToId[reserve] != bytes32(0), "reserve's existing reserveId is 0");
                                reserveId = reserveAddressToId[reserve];
                        
                                // update reserve mappings
                                reserveIdToAddresses[reserveId].push(reserveIdToAddresses[reserveId][0]);
                                reserveIdToAddresses[reserveId][0] = address(0);
                        
                                // remove reserveId from reservesPerType
                                bytes32[] storage reservesOfType = reservesPerType[ReserveType(reserveType[reserveId])];
                                for (uint256 i = 0; i < reservesOfType.length; i++) {
                                    if (reserveId == reservesOfType[i]) {
                                        reservesOfType[i] = reservesOfType[reservesOfType.length - 1];
                                        reservesOfType.pop();
                                        break;
                                    }
                                }
                        
                                delete reserveAddressToId[reserve];
                                delete reserveType[reserveId];
                                delete reserveRebateWallet[reserveId];
                        
                                emit RemoveReserveFromStorage(reserve, reserveId);
                            }
                        
                            /// @notice Can be called only by operator
                            /// @dev Allow or prevent a specific reserve to trade a pair of tokens
                            /// @param reserveId The reserve id
                            /// @param token Token address
                            /// @param ethToToken Will it support ether to token trade
                            /// @param tokenToEth Will it support token to ether trade
                            /// @param add If true then list this pair, otherwise unlist it
                            function listPairForReserve(
                                bytes32 reserveId,
                                IERC20 token,
                                bool ethToToken,
                                bool tokenToEth,
                                bool add
                            ) public {
                                onlyOperator();
                        
                                require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found");
                                address reserve = reserveIdToAddresses[reserveId][0];
                                require(reserve != address(0), "reserve = 0");
                        
                                if (ethToToken) {
                                    listPairs(reserveId, token, false, add);
                                    emit ListReservePairs(reserveId, reserve, ETH_TOKEN_ADDRESS, token, add);
                                }
                        
                                if (tokenToEth) {
                                    kyberNetwork.listTokenForReserve(reserve, token, add);
                                    listPairs(reserveId, token, true, add);
                                    emit ListReservePairs(reserveId, reserve, token, ETH_TOKEN_ADDRESS, add);
                                }
                            }
                        
                            /// @dev No. of kyberProxies are capped
                            function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies)
                                external
                                override
                            {
                                onlyNetwork();
                                require(kyberProxy != address(0), "kyberProxy 0");
                                require(kyberProxyArray.length < maxApprovedProxies, "max kyberProxies limit reached");
                        
                                kyberProxyArray.push(IKyberNetworkProxy(kyberProxy));
                            }
                        
                            function removeKyberProxy(address kyberProxy) external override {
                                onlyNetwork();
                                uint256 proxyIndex = 2**255;
                        
                                for (uint256 i = 0; i < kyberProxyArray.length; i++) {
                                    if (kyberProxyArray[i] == IKyberNetworkProxy(kyberProxy)) {
                                        proxyIndex = i;
                                        break;
                                    }
                                }
                        
                                require(proxyIndex != 2**255, "kyberProxy not found");
                                kyberProxyArray[proxyIndex] = kyberProxyArray[kyberProxyArray.length - 1];
                                kyberProxyArray.pop();
                            }
                        
                            function setFeeAccountedPerReserveType(
                                bool fpr,
                                bool apr,
                                bool bridge,
                                bool utility,
                                bool custom,
                                bool orderbook
                            ) external {
                                onlyAdmin();
                                uint256 feeAccountedData;
                        
                                if (fpr) feeAccountedData |= 1 << uint256(ReserveType.FPR);
                                if (apr) feeAccountedData |= 1 << uint256(ReserveType.APR);
                                if (bridge) feeAccountedData |= 1 << uint256(ReserveType.BRIDGE);
                                if (utility) feeAccountedData |= 1 << uint256(ReserveType.UTILITY);
                                if (custom) feeAccountedData |= 1 << uint256(ReserveType.CUSTOM);
                                if (orderbook) feeAccountedData |= 1 << uint256(ReserveType.ORDERBOOK);
                        
                                feeAccountedPerType = feeAccountedData;
                            }
                        
                            function setEntitledRebatePerReserveType(
                                bool fpr,
                                bool apr,
                                bool bridge,
                                bool utility,
                                bool custom,
                                bool orderbook
                            ) external {
                                onlyAdmin();
                                require(feeAccountedPerType != 0xffffffff, "fee accounted data not set");
                                uint256 entitledRebateData;
                        
                                if (fpr) {
                                    require(feeAccountedPerType & (1 << uint256(ReserveType.FPR)) > 0, "fpr not fee accounted");
                                    entitledRebateData |= 1 << uint256(ReserveType.FPR);
                                }
                        
                                if (apr) {
                                    require(feeAccountedPerType & (1 << uint256(ReserveType.APR)) > 0, "apr not fee accounted");
                                    entitledRebateData |= 1 << uint256(ReserveType.APR);
                                }
                        
                                if (bridge) {
                                    require(feeAccountedPerType & (1 << uint256(ReserveType.BRIDGE)) > 0, "bridge not fee accounted");
                                    entitledRebateData |= 1 << uint256(ReserveType.BRIDGE);
                                }
                        
                                if (utility) {
                                    require(feeAccountedPerType & (1 << uint256(ReserveType.UTILITY)) > 0, "utility not fee accounted");
                                    entitledRebateData |= 1 << uint256(ReserveType.UTILITY);
                                }
                        
                                if (custom) {
                                    require(feeAccountedPerType & (1 << uint256(ReserveType.CUSTOM)) > 0, "custom not fee accounted");
                                    entitledRebateData |= 1 << uint256(ReserveType.CUSTOM);
                                }
                        
                                if (orderbook) {
                                    require(feeAccountedPerType & (1 << uint256(ReserveType.ORDERBOOK)) > 0, "orderbook not fee accounted");
                                    entitledRebateData |= 1 << uint256(ReserveType.ORDERBOOK);
                                }
                        
                                entitledRebatePerType = entitledRebateData;
                            }
                        
                            /// @notice Should be called off chain
                            /// @return An array of all reserves
                            function getReserves() external view returns (IKyberReserve[] memory) {
                                return reserves;
                            }
                        
                            function getReservesPerType(ReserveType resType) external view returns (bytes32[] memory) {
                                return reservesPerType[resType];
                            }
                        
                            function getReserveId(address reserve) external view override returns (bytes32) {
                                return reserveAddressToId[reserve];
                            }
                        
                            function getReserveIdsFromAddresses(address[] calldata reserveAddresses)
                                external
                                override
                                view
                                returns (bytes32[] memory reserveIds)
                            {
                                reserveIds = new bytes32[](reserveAddresses.length);
                                for (uint256 i = 0; i < reserveAddresses.length; i++) {
                                    reserveIds[i] = reserveAddressToId[reserveAddresses[i]];
                                }
                            }
                        
                            function getReserveAddressesFromIds(bytes32[] calldata reserveIds)
                                external
                                view
                                override
                                returns (address[] memory reserveAddresses)
                            {
                                reserveAddresses = new address[](reserveIds.length);
                                for (uint256 i = 0; i < reserveIds.length; i++) {
                                    reserveAddresses[i] = reserveIdToAddresses[reserveIds[i]][0];
                                }
                            }
                        
                            function getRebateWalletsFromIds(bytes32[] calldata reserveIds)
                                external
                                view
                                override
                                returns (address[] memory rebateWallets)
                            {
                                rebateWallets = new address[](reserveIds.length);
                                for (uint256 i = 0; i < rebateWallets.length; i++) {
                                    rebateWallets[i] = reserveRebateWallet[reserveIds[i]];
                                }
                            }
                        
                            function getReserveIdsPerTokenSrc(IERC20 token)
                                external
                                view
                                override
                                returns (bytes32[] memory reserveIds)
                            {
                                reserveIds = reservesPerTokenSrc[token];
                            }
                        
                            /// @dev kyberNetwork is calling this function to approve (allowance) for list of reserves for a token
                            ///      in case we have a long list of reserves, approving all of them could run out of gas
                            ///      using startIndex and endIndex to prevent above scenario
                            ///      also enable us to approve reserve one by one
                            function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex)
                                external
                                view
                                override
                                returns (address[] memory reserveAddresses)
                            {
                                bytes32[] memory reserveIds = reservesPerTokenSrc[token];
                                if (reserveIds.length == 0) {
                                    return reserveAddresses;
                                }
                                uint256 endId = (endIndex >= reserveIds.length) ? (reserveIds.length - 1) : endIndex;
                                if (endId < startIndex) {
                                    return reserveAddresses;
                                }
                                reserveAddresses = new address[](endId - startIndex + 1);
                                for(uint256 i = startIndex; i <= endId; i++) {
                                    reserveAddresses[i - startIndex] = reserveIdToAddresses[reserveIds[i]][0];
                                }
                            }
                        
                            function getReserveIdsPerTokenDest(IERC20 token)
                                external
                                view
                                override
                                returns (bytes32[] memory reserveIds)
                            {
                                reserveIds = reservesPerTokenDest[token];
                            }
                        
                            function getReserveAddressesByReserveId(bytes32 reserveId)
                                external
                                view
                                override
                                returns (address[] memory reserveAddresses)
                            {
                                reserveAddresses = reserveIdToAddresses[reserveId];
                            }
                        
                            /// @notice Should be called off chain
                            /// @dev Returns list of kyberDao, kyberFeeHandler, kyberMatchingEngine and kyberNetwork contracts
                            /// @dev Index 0 is currently used contract address, indexes > 0 are older versions
                            function getContracts()
                                external
                                view
                                returns (
                                    address[] memory kyberDaoAddresses,
                                    address[] memory kyberFeeHandlerAddresses,
                                    address[] memory kyberMatchingEngineAddresses,
                                    address[] memory kyberNetworkAddresses
                                )
                            {
                                kyberDaoAddresses = kyberDaoHistory.getContracts();
                                kyberFeeHandlerAddresses = kyberFeeHandlerHistory.getContracts();
                                kyberMatchingEngineAddresses = kyberMatchingEngineHistory.getContracts();
                                kyberNetworkAddresses = kyberNetworkHistory.getContracts();
                            }
                        
                            /// @notice Should be called off chain
                            /// @return An array of KyberNetworkProxies
                            function getKyberProxies() external view override returns (IKyberNetworkProxy[] memory) {
                                return kyberProxyArray;
                            }
                        
                            function isKyberProxyAdded() external view override returns (bool) {
                                return (kyberProxyArray.length > 0);
                            }
                        
                            /// @notice Returns information about a reserve given its reserve ID
                            /// @return reserveAddress Address of the reserve
                            /// @return rebateWallet address of rebate wallet of this reserve
                            /// @return resType Reserve type from enum ReserveType
                            /// @return isFeeAccountedFlag Whether fees are to be charged for the trade for this reserve
                            /// @return isEntitledRebateFlag Whether reserve is entitled rebate from the trade fees
                            function getReserveDetailsById(bytes32 reserveId)
                                external
                                view
                                override
                                returns (
                                    address reserveAddress,
                                    address rebateWallet,
                                    ReserveType resType,
                                    bool isFeeAccountedFlag,
                                    bool isEntitledRebateFlag
                                )
                            {
                                address[] memory reserveAddresses = reserveIdToAddresses[reserveId];
                        
                                if (reserveAddresses.length != 0) {
                                    reserveAddress = reserveIdToAddresses[reserveId][0];
                                    rebateWallet = reserveRebateWallet[reserveId];
                                    uint256 resTypeUint = reserveType[reserveId];
                                    resType = ReserveType(resTypeUint);
                                    isFeeAccountedFlag = (feeAccountedPerType & (1 << resTypeUint)) > 0;
                                    isEntitledRebateFlag = (entitledRebatePerType & (1 << resTypeUint)) > 0;
                                }
                            }
                        
                            /// @notice Returns information about a reserve given its reserve ID
                            /// @return reserveId The reserve ID in 32 bytes.
                            /// @return rebateWallet address of rebate wallet of this reserve
                            /// @return resType Reserve type from enum ReserveType
                            /// @return isFeeAccountedFlag Whether fees are to be charged for the trade for this reserve
                            /// @return isEntitledRebateFlag Whether reserve is entitled rebate from the trade fees
                            function getReserveDetailsByAddress(address reserve)
                                external
                                view
                                override
                                returns (
                                    bytes32 reserveId,
                                    address rebateWallet,
                                    ReserveType resType,
                                    bool isFeeAccountedFlag,
                                    bool isEntitledRebateFlag
                                )
                            {
                                reserveId = reserveAddressToId[reserve];
                                rebateWallet = reserveRebateWallet[reserveId];
                                uint256 resTypeUint = reserveType[reserveId];
                                resType = ReserveType(resTypeUint);
                                isFeeAccountedFlag = (feeAccountedPerType & (1 << resTypeUint)) > 0;
                                isEntitledRebateFlag = (entitledRebatePerType & (1 << resTypeUint)) > 0;
                            }
                        
                            function getListedTokensByReserveId(bytes32 reserveId)
                                external
                                view
                                returns (
                                    IERC20[] memory srcTokens,
                                    IERC20[] memory destTokens
                                )
                            {
                                srcTokens = srcTokensPerReserve[reserveId];
                                destTokens = destTokensPerReserve[reserveId];
                            }
                        
                            function getFeeAccountedData(bytes32[] calldata reserveIds)
                                external
                                view
                                override
                                returns (bool[] memory feeAccountedArr)
                            {
                                feeAccountedArr = new bool[](reserveIds.length);
                        
                                uint256 feeAccountedData = feeAccountedPerType;
                        
                                for (uint256 i = 0; i < reserveIds.length; i++) {
                                    feeAccountedArr[i] = (feeAccountedData & (1 << reserveType[reserveIds[i]]) > 0);
                                }
                            }
                        
                            function getEntitledRebateData(bytes32[] calldata reserveIds)
                                external
                                view
                                override
                                returns (bool[] memory entitledRebateArr)
                            {
                                entitledRebateArr = new bool[](reserveIds.length);
                        
                                uint256 entitledRebateData = entitledRebatePerType;
                        
                                for (uint256 i = 0; i < reserveIds.length; i++) {
                                    entitledRebateArr[i] = (entitledRebateData & (1 << reserveType[reserveIds[i]]) > 0);
                                }
                            }
                        
                            /// @dev Returns information about reserves given their reserve IDs
                            ///      Also check if these reserve IDs are listed for token
                            ///      Network calls this function to retrive information about fee, address and rebate information
                            function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest)
                                external
                                view
                                override
                                returns (
                                    bool areAllReservesListed,
                                    bool[] memory feeAccountedArr,
                                    bool[] memory entitledRebateArr,
                                    IKyberReserve[] memory reserveAddresses)
                            {
                                feeAccountedArr = new bool[](reserveIds.length);
                                entitledRebateArr = new bool[](reserveIds.length);
                                reserveAddresses = new IKyberReserve[](reserveIds.length);
                                areAllReservesListed = true;
                        
                                uint256 entitledRebateData = entitledRebatePerType;
                                uint256 feeAccountedData = feeAccountedPerType;
                        
                                mapping(bytes32 => bool) storage isListedReserveWithToken = (dest == ETH_TOKEN_ADDRESS) ?
                                    isListedReserveWithTokenSrc[src]:
                                    isListedReserveWithTokenDest[dest];
                        
                                for (uint256 i = 0; i < reserveIds.length; i++) {
                                    uint256 resType = reserveType[reserveIds[i]];
                                    entitledRebateArr[i] = (entitledRebateData & (1 << resType) > 0);
                                    feeAccountedArr[i] = (feeAccountedData & (1 << resType) > 0);
                                    reserveAddresses[i] = IKyberReserve(reserveIdToAddresses[reserveIds[i]][0]);
                        
                                    if (!isListedReserveWithToken[reserveIds[i]]){
                                        areAllReservesListed = false;
                                        break;
                                    }
                                }
                            }
                        
                            function delistTokensOfReserve(bytes32 reserveId) internal {
                                // token to ether
                                // memory declaration instead of storage because we are modifying the storage array
                                IERC20[] memory tokensArr = srcTokensPerReserve[reserveId];
                                for (uint256 i = 0; i < tokensArr.length; i++) {
                                    listPairForReserve(reserveId, tokensArr[i], false, true, false);
                                }
                        
                                // ether to token
                                tokensArr = destTokensPerReserve[reserveId];
                                for (uint256 i = 0; i < tokensArr.length; i++) {
                                    listPairForReserve(reserveId, tokensArr[i], true, false, false);
                                }
                            }
                        
                            function listPairs(
                                bytes32 reserveId,
                                IERC20 token,
                                bool isTokenToEth,
                                bool add
                            ) internal {
                                uint256 i;
                                bytes32[] storage reserveArr = reservesPerTokenDest[token];
                                IERC20[] storage tokensArr = destTokensPerReserve[reserveId];
                                mapping(bytes32 => bool) storage isListedReserveWithToken = isListedReserveWithTokenDest[token];
                        
                                if (isTokenToEth) {
                                    reserveArr = reservesPerTokenSrc[token];
                                    tokensArr = srcTokensPerReserve[reserveId];
                                    isListedReserveWithToken = isListedReserveWithTokenSrc[token];
                                }
                        
                                for (i = 0; i < reserveArr.length; i++) {
                                    if (reserveId == reserveArr[i]) {
                                        if (add) {
                                            return; // reserve already added, no further action needed
                                        } else {
                                            // remove reserve from reserveArr
                                            reserveArr[i] = reserveArr[reserveArr.length - 1];
                                            reserveArr.pop();
                        
                                            break;
                                        }
                                    }
                                }
                        
                                if (add) {
                                    // add reserve and token to reserveArr and tokensArr respectively
                                    reserveArr.push(reserveId);
                                    tokensArr.push(token);
                                    isListedReserveWithToken[reserveId] = true;
                                } else {
                                    // remove token from tokenArr
                                    for (i = 0; i < tokensArr.length; i++) {
                                        if (token == tokensArr[i]) {
                                            tokensArr[i] = tokensArr[tokensArr.length - 1];
                                            tokensArr.pop();
                                            break;
                                        }
                                    }
                                    delete isListedReserveWithToken[reserveId];
                                }
                            }
                        
                            function onlyNetwork() internal view {
                                require(msg.sender == address(kyberNetwork), "only kyberNetwork");
                            }
                        }

                        File 11 of 13: SmartToken
                        pragma solidity 0.4.26;
                        
                        // File: contracts/token/interfaces/IERC20Token.sol
                        
                        /*
                            ERC20 Standard Token interface
                        */
                        contract IERC20Token {
                            // these functions aren't abstract since the compiler emits automatically generated getter functions as external
                            function name() public view returns (string) {this;}
                            function symbol() public view returns (string) {this;}
                            function decimals() public view returns (uint8) {this;}
                            function totalSupply() public view returns (uint256) {this;}
                            function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
                            function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
                        
                            function transfer(address _to, uint256 _value) public returns (bool success);
                            function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
                            function approve(address _spender, uint256 _value) public returns (bool success);
                        }
                        
                        // File: contracts/utility/Utils.sol
                        
                        /**
                          * @dev Utilities & Common Modifiers
                        */
                        contract Utils {
                            /**
                              * constructor
                            */
                            constructor() public {
                            }
                        
                            // verifies that an amount is greater than zero
                            modifier greaterThanZero(uint256 _amount) {
                                require(_amount > 0);
                                _;
                            }
                        
                            // validates an address - currently only checks that it isn't null
                            modifier validAddress(address _address) {
                                require(_address != address(0));
                                _;
                            }
                        
                            // verifies that the address is different than this contract address
                            modifier notThis(address _address) {
                                require(_address != address(this));
                                _;
                            }
                        
                        }
                        
                        // File: contracts/utility/SafeMath.sol
                        
                        /**
                          * @dev Library for basic math operations with overflow/underflow protection
                        */
                        library SafeMath {
                            /**
                              * @dev returns the sum of _x and _y, reverts if the calculation overflows
                              * 
                              * @param _x   value 1
                              * @param _y   value 2
                              * 
                              * @return sum
                            */
                            function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                uint256 z = _x + _y;
                                require(z >= _x);
                                return z;
                            }
                        
                            /**
                              * @dev returns the difference of _x minus _y, reverts if the calculation underflows
                              * 
                              * @param _x   minuend
                              * @param _y   subtrahend
                              * 
                              * @return difference
                            */
                            function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                require(_x >= _y);
                                return _x - _y;
                            }
                        
                            /**
                              * @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
                              * 
                              * @param _x   factor 1
                              * @param _y   factor 2
                              * 
                              * @return product
                            */
                            function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                // gas optimization
                                if (_x == 0)
                                    return 0;
                        
                                uint256 z = _x * _y;
                                require(z / _x == _y);
                                return z;
                            }
                        
                              /**
                                * ev Integer division of two numbers truncating the quotient, reverts on division by zero.
                                * 
                                * aram _x   dividend
                                * aram _y   divisor
                                * 
                                * eturn quotient
                            */
                            function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                require(_y > 0);
                                uint256 c = _x / _y;
                        
                                return c;
                            }
                        }
                        
                        // File: contracts/token/ERC20Token.sol
                        
                        /**
                          * @dev ERC20 Standard Token implementation
                        */
                        contract ERC20Token is IERC20Token, Utils {
                            using SafeMath for uint256;
                        
                        
                            string public name;
                            string public symbol;
                            uint8 public decimals;
                            uint256 public totalSupply;
                            mapping (address => uint256) public balanceOf;
                            mapping (address => mapping (address => uint256)) public allowance;
                        
                            /**
                              * @dev triggered when tokens are transferred between wallets
                              * 
                              * @param _from    source address
                              * @param _to      target address
                              * @param _value   transfer amount
                            */
                            event Transfer(address indexed _from, address indexed _to, uint256 _value);
                        
                            /**
                              * @dev triggered when a wallet allows another wallet to transfer tokens from on its behalf
                              * 
                              * @param _owner   wallet that approves the allowance
                              * @param _spender wallet that receives the allowance
                              * @param _value   allowance amount
                            */
                            event Approval(address indexed _owner, address indexed _spender, uint256 _value);
                        
                            /**
                              * @dev initializes a new ERC20Token instance
                              * 
                              * @param _name        token name
                              * @param _symbol      token symbol
                              * @param _decimals    decimal points, for display purposes
                              * @param _totalSupply total supply of token units
                            */
                            constructor(string _name, string _symbol, uint8 _decimals, uint256 _totalSupply) public {
                                require(bytes(_name).length > 0 && bytes(_symbol).length > 0); // validate input
                        
                                name = _name;
                                symbol = _symbol;
                                decimals = _decimals;
                                totalSupply = _totalSupply;
                                balanceOf[msg.sender] = _totalSupply;
                            }
                        
                            /**
                              * @dev send coins
                              * throws on any error rather then return a false flag to minimize user errors
                              * 
                              * @param _to      target address
                              * @param _value   transfer amount
                              * 
                              * @return true if the transfer was successful, false if it wasn't
                            */
                            function transfer(address _to, uint256 _value)
                                public
                                validAddress(_to)
                                returns (bool success)
                            {
                                balanceOf[msg.sender] = balanceOf[msg.sender].sub(_value);
                                balanceOf[_to] = balanceOf[_to].add(_value);
                                emit Transfer(msg.sender, _to, _value);
                                return true;
                            }
                        
                            /**
                              * @dev an account/contract attempts to get the coins
                              * throws on any error rather then return a false flag to minimize user errors
                              * 
                              * @param _from    source address
                              * @param _to      target address
                              * @param _value   transfer amount
                              * 
                              * @return true if the transfer was successful, false if it wasn't
                            */
                            function transferFrom(address _from, address _to, uint256 _value)
                                public
                                validAddress(_from)
                                validAddress(_to)
                                returns (bool success)
                            {
                                allowance[_from][msg.sender] = allowance[_from][msg.sender].sub(_value);
                                balanceOf[_from] = balanceOf[_from].sub(_value);
                                balanceOf[_to] = balanceOf[_to].add(_value);
                                emit Transfer(_from, _to, _value);
                                return true;
                            }
                        
                            /**
                              * @dev allow another account/contract to spend some tokens on your behalf
                              * throws on any error rather then return a false flag to minimize user errors
                              * 
                              * also, to minimize the risk of the approve/transferFrom attack vector
                              * (see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), approve has to be called twice
                              * in 2 separate transactions - once to change the allowance to 0 and secondly to change it to the new allowance value
                              * 
                              * @param _spender approved address
                              * @param _value   allowance amount
                              * 
                              * @return true if the approval was successful, false if it wasn't
                            */
                            function approve(address _spender, uint256 _value)
                                public
                                validAddress(_spender)
                                returns (bool success)
                            {
                                // if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal
                                require(_value == 0 || allowance[msg.sender][_spender] == 0);
                        
                                allowance[msg.sender][_spender] = _value;
                                emit Approval(msg.sender, _spender, _value);
                                return true;
                            }
                        }
                        
                        // File: contracts/utility/interfaces/IOwned.sol
                        
                        /*
                            Owned contract interface
                        */
                        contract IOwned {
                            // this function isn't abstract since the compiler emits automatically generated getter functions as external
                            function owner() public view returns (address) {this;}
                        
                            function transferOwnership(address _newOwner) public;
                            function acceptOwnership() public;
                        }
                        
                        // File: contracts/token/interfaces/ISmartToken.sol
                        
                        /*
                            Smart Token interface
                        */
                        contract ISmartToken is IOwned, IERC20Token {
                            function disableTransfers(bool _disable) public;
                            function issue(address _to, uint256 _amount) public;
                            function destroy(address _from, uint256 _amount) public;
                        }
                        
                        // File: contracts/utility/Owned.sol
                        
                        /**
                          * @dev Provides support and utilities for contract ownership
                        */
                        contract Owned is IOwned {
                            address public owner;
                            address public newOwner;
                        
                            /**
                              * @dev triggered when the owner is updated
                              * 
                              * @param _prevOwner previous owner
                              * @param _newOwner  new owner
                            */
                            event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
                        
                            /**
                              * @dev initializes a new Owned instance
                            */
                            constructor() public {
                                owner = msg.sender;
                            }
                        
                            // allows execution by the owner only
                            modifier ownerOnly {
                                require(msg.sender == owner);
                                _;
                            }
                        
                            /**
                              * @dev allows transferring the contract ownership
                              * the new owner still needs to accept the transfer
                              * can only be called by the contract owner
                              * 
                              * @param _newOwner    new contract owner
                            */
                            function transferOwnership(address _newOwner) public ownerOnly {
                                require(_newOwner != owner);
                                newOwner = _newOwner;
                            }
                        
                            /**
                              * @dev used by a new owner to accept an ownership transfer
                            */
                            function acceptOwnership() public {
                                require(msg.sender == newOwner);
                                emit OwnerUpdate(owner, newOwner);
                                owner = newOwner;
                                newOwner = address(0);
                            }
                        }
                        
                        // File: contracts/utility/interfaces/ITokenHolder.sol
                        
                        /*
                            Token Holder interface
                        */
                        contract ITokenHolder is IOwned {
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
                        }
                        
                        // File: contracts/token/interfaces/INonStandardERC20.sol
                        
                        /*
                            ERC20 Standard Token interface which doesn't return true/false for transfer, transferFrom and approve
                        */
                        contract INonStandardERC20 {
                            // these functions aren't abstract since the compiler emits automatically generated getter functions as external
                            function name() public view returns (string) {this;}
                            function symbol() public view returns (string) {this;}
                            function decimals() public view returns (uint8) {this;}
                            function totalSupply() public view returns (uint256) {this;}
                            function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
                            function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
                        
                            function transfer(address _to, uint256 _value) public;
                            function transferFrom(address _from, address _to, uint256 _value) public;
                            function approve(address _spender, uint256 _value) public;
                        }
                        
                        // File: contracts/utility/TokenHolder.sol
                        
                        /**
                          * @dev We consider every contract to be a 'token holder' since it's currently not possible
                          * for a contract to deny receiving tokens.
                          * 
                          * The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
                          * the owner to send tokens that were sent to the contract by mistake back to their sender.
                          * 
                          * Note that we use the non standard ERC-20 interface which has no return value for transfer
                          * in order to support both non standard as well as standard token contracts.
                          * see https://github.com/ethereum/solidity/issues/4116
                        */
                        contract TokenHolder is ITokenHolder, Owned, Utils {
                            /**
                              * @dev initializes a new TokenHolder instance
                            */
                            constructor() public {
                            }
                        
                            /**
                              * @dev withdraws tokens held by the contract and sends them to an account
                              * can only be called by the owner
                              * 
                              * @param _token   ERC20 token contract address
                              * @param _to      account to receive the new amount
                              * @param _amount  amount to withdraw
                            */
                            function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
                                public
                                ownerOnly
                                validAddress(_token)
                                validAddress(_to)
                                notThis(_to)
                            {
                                INonStandardERC20(_token).transfer(_to, _amount);
                            }
                        }
                        
                        // File: contracts/token/SmartToken.sol
                        
                        /**
                          * @dev Smart Token
                          * 
                          * 'Owned' is specified here for readability reasons
                        */
                        contract SmartToken is ISmartToken, Owned, ERC20Token, TokenHolder {
                            using SafeMath for uint256;
                        
                        
                            string public version = '0.3';
                        
                            bool public transfersEnabled = true;    // true if transfer/transferFrom are enabled, false if not
                        
                            /**
                              * @dev triggered when a smart token is deployed
                              * the _token address is defined for forward compatibility, in case the event is trigger by a factory
                              * 
                              * @param _token  new smart token address
                            */
                            event NewSmartToken(address _token);
                        
                            /**
                              * @dev triggered when the total supply is increased
                              * 
                              * @param _amount  amount that gets added to the supply
                            */
                            event Issuance(uint256 _amount);
                        
                            /**
                              * @dev triggered when the total supply is decreased
                              * 
                              * @param _amount  amount that gets removed from the supply
                            */
                            event Destruction(uint256 _amount);
                        
                            /**
                              * @dev initializes a new SmartToken instance
                              * 
                              * @param _name       token name
                              * @param _symbol     token short symbol, minimum 1 character
                              * @param _decimals   for display purposes only
                            */
                            constructor(string _name, string _symbol, uint8 _decimals)
                                public
                                ERC20Token(_name, _symbol, _decimals, 0)
                            {
                                emit NewSmartToken(address(this));
                            }
                        
                            // allows execution only when transfers aren't disabled
                            modifier transfersAllowed {
                                assert(transfersEnabled);
                                _;
                            }
                        
                            /**
                              * @dev disables/enables transfers
                              * can only be called by the contract owner
                              * 
                              * @param _disable    true to disable transfers, false to enable them
                            */
                            function disableTransfers(bool _disable) public ownerOnly {
                                transfersEnabled = !_disable;
                            }
                        
                            /**
                              * @dev increases the token supply and sends the new tokens to an account
                              * can only be called by the contract owner
                              * 
                              * @param _to         account to receive the new amount
                              * @param _amount     amount to increase the supply by
                            */
                            function issue(address _to, uint256 _amount)
                                public
                                ownerOnly
                                validAddress(_to)
                                notThis(_to)
                            {
                                totalSupply = totalSupply.add(_amount);
                                balanceOf[_to] = balanceOf[_to].add(_amount);
                        
                                emit Issuance(_amount);
                                emit Transfer(this, _to, _amount);
                            }
                        
                            /**
                              * @dev removes tokens from an account and decreases the token supply
                              * can be called by the contract owner to destroy tokens from any account or by any holder to destroy tokens from his/her own account
                              * 
                              * @param _from       account to remove the amount from
                              * @param _amount     amount to decrease the supply by
                            */
                            function destroy(address _from, uint256 _amount) public {
                                require(msg.sender == _from || msg.sender == owner); // validate input
                        
                                balanceOf[_from] = balanceOf[_from].sub(_amount);
                                totalSupply = totalSupply.sub(_amount);
                        
                                emit Transfer(_from, this, _amount);
                                emit Destruction(_amount);
                            }
                        
                            // ERC20 standard method overrides with some extra functionality
                        
                            /**
                              * @dev send coins
                              * throws on any error rather then return a false flag to minimize user errors
                              * in addition to the standard checks, the function throws if transfers are disabled
                              * 
                              * @param _to      target address
                              * @param _value   transfer amount
                              * 
                              * @return true if the transfer was successful, false if it wasn't
                            */
                            function transfer(address _to, uint256 _value) public transfersAllowed returns (bool success) {
                                assert(super.transfer(_to, _value));
                                return true;
                            }
                        
                            /**
                              * @dev an account/contract attempts to get the coins
                              * throws on any error rather then return a false flag to minimize user errors
                              * in addition to the standard checks, the function throws if transfers are disabled
                              * 
                              * @param _from    source address
                              * @param _to      target address
                              * @param _value   transfer amount
                              * 
                              * @return true if the transfer was successful, false if it wasn't
                            */
                            function transferFrom(address _from, address _to, uint256 _value) public transfersAllowed returns (bool success) {
                                assert(super.transferFrom(_from, _to, _value));
                                return true;
                            }
                        }
                        

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

                        File 13 of 13: BancorFormula
                        // File: solidity/contracts/converter/interfaces/IBancorFormula.sol
                        
                        pragma solidity 0.4.26;
                        
                        /*
                            Bancor Formula interface
                        */
                        contract IBancorFormula {
                            function purchaseTargetAmount(uint256 _supply,
                                                          uint256 _reserveBalance,
                                                          uint32 _reserveWeight,
                                                          uint256 _amount)
                                                          public view returns (uint256);
                        
                            function saleTargetAmount(uint256 _supply,
                                                      uint256 _reserveBalance,
                                                      uint32 _reserveWeight,
                                                      uint256 _amount)
                                                      public view returns (uint256);
                        
                            function crossReserveTargetAmount(uint256 _sourceReserveBalance,
                                                              uint32 _sourceReserveWeight,
                                                              uint256 _targetReserveBalance,
                                                              uint32 _targetReserveWeight,
                                                              uint256 _amount)
                                                              public view returns (uint256);
                        
                            function fundCost(uint256 _supply,
                                              uint256 _reserveBalance,
                                              uint32 _reserveRatio,
                                              uint256 _amount)
                                              public view returns (uint256);
                        
                            function fundSupplyAmount(uint256 _supply,
                                                      uint256 _reserveBalance,
                                                      uint32 _reserveRatio,
                                                      uint256 _amount)
                                                      public view returns (uint256);
                        
                            function liquidateReserveAmount(uint256 _supply,
                                                            uint256 _reserveBalance,
                                                            uint32 _reserveRatio,
                                                            uint256 _amount)
                                                            public view returns (uint256);
                        
                            function balancedWeights(uint256 _primaryReserveStakedBalance,
                                                     uint256 _primaryReserveBalance,
                                                     uint256 _secondaryReserveBalance,
                                                     uint256 _reserveRateNumerator,
                                                     uint256 _reserveRateDenominator)
                                                     public view returns (uint32, uint32);
                        }
                        
                        // File: solidity/contracts/utility/SafeMath.sol
                        
                        pragma solidity 0.4.26;
                        
                        /**
                          * @dev Library for basic math operations with overflow/underflow protection
                        */
                        library SafeMath {
                            /**
                              * @dev returns the sum of _x and _y, reverts if the calculation overflows
                              *
                              * @param _x   value 1
                              * @param _y   value 2
                              *
                              * @return sum
                            */
                            function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                uint256 z = _x + _y;
                                require(z >= _x, "ERR_OVERFLOW");
                                return z;
                            }
                        
                            /**
                              * @dev returns the difference of _x minus _y, reverts if the calculation underflows
                              *
                              * @param _x   minuend
                              * @param _y   subtrahend
                              *
                              * @return difference
                            */
                            function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                require(_x >= _y, "ERR_UNDERFLOW");
                                return _x - _y;
                            }
                        
                            /**
                              * @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
                              *
                              * @param _x   factor 1
                              * @param _y   factor 2
                              *
                              * @return product
                            */
                            function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                // gas optimization
                                if (_x == 0)
                                    return 0;
                        
                                uint256 z = _x * _y;
                                require(z / _x == _y, "ERR_OVERFLOW");
                                return z;
                            }
                        
                            /**
                              * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
                              *
                              * @param _x   dividend
                              * @param _y   divisor
                              *
                              * @return quotient
                            */
                            function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
                                require(_y > 0, "ERR_DIVIDE_BY_ZERO");
                                uint256 c = _x / _y;
                                return c;
                            }
                        }
                        
                        // File: solidity/contracts/converter/BancorFormula.sol
                        
                        pragma solidity 0.4.26;
                        
                        
                        
                        contract BancorFormula is IBancorFormula {
                            using SafeMath for uint256;
                        
                            uint16 public constant version = 8;
                        
                            uint256 private constant ONE = 1;
                            uint32 private constant MAX_WEIGHT = 1000000;
                            uint8 private constant MIN_PRECISION = 32;
                            uint8 private constant MAX_PRECISION = 127;
                        
                            /**
                              * Auto-generated via 'PrintIntScalingFactors.py'
                            */
                            uint256 private constant FIXED_1 = 0x080000000000000000000000000000000;
                            uint256 private constant FIXED_2 = 0x100000000000000000000000000000000;
                            uint256 private constant MAX_NUM = 0x200000000000000000000000000000000;
                        
                            /**
                              * Auto-generated via 'PrintLn2ScalingFactors.py'
                            */
                            uint256 private constant LN2_NUMERATOR   = 0x3f80fe03f80fe03f80fe03f80fe03f8;
                            uint256 private constant LN2_DENOMINATOR = 0x5b9de1d10bf4103d647b0955897ba80;
                        
                            /**
                              * Auto-generated via 'PrintFunctionOptimalLog.py' and 'PrintFunctionOptimalExp.py'
                            */
                            uint256 private constant OPT_LOG_MAX_VAL = 0x15bf0a8b1457695355fb8ac404e7a79e3;
                            uint256 private constant OPT_EXP_MAX_VAL = 0x800000000000000000000000000000000;
                        
                            /**
                              * Auto-generated via 'PrintLambertFactors.py'
                            */
                            uint256 private constant LAMBERT_CONV_RADIUS = 0x002f16ac6c59de6f8d5d6f63c1482a7c86;
                            uint256 private constant LAMBERT_POS2_SAMPLE = 0x0003060c183060c183060c183060c18306;
                            uint256 private constant LAMBERT_POS2_MAXVAL = 0x01af16ac6c59de6f8d5d6f63c1482a7c80;
                            uint256 private constant LAMBERT_POS3_MAXVAL = 0x6b22d43e72c326539cceeef8bb48f255ff;
                        
                            /**
                              * Auto-generated via 'PrintWeightFactors.py'
                            */
                            uint256 private constant MAX_UNF_WEIGHT = 0x10c6f7a0b5ed8d36b4c7f34938583621fafc8b0079a2834d26fa3fcc9ea9;
                        
                            /**
                              * Auto-generated via 'PrintMaxExpArray.py'
                            */
                            uint256[128] private maxExpArray;
                            function initMaxExpArray() private {
                            //  maxExpArray[  0] = 0x6bffffffffffffffffffffffffffffffff;
                            //  maxExpArray[  1] = 0x67ffffffffffffffffffffffffffffffff;
                            //  maxExpArray[  2] = 0x637fffffffffffffffffffffffffffffff;
                            //  maxExpArray[  3] = 0x5f6fffffffffffffffffffffffffffffff;
                            //  maxExpArray[  4] = 0x5b77ffffffffffffffffffffffffffffff;
                            //  maxExpArray[  5] = 0x57b3ffffffffffffffffffffffffffffff;
                            //  maxExpArray[  6] = 0x5419ffffffffffffffffffffffffffffff;
                            //  maxExpArray[  7] = 0x50a2ffffffffffffffffffffffffffffff;
                            //  maxExpArray[  8] = 0x4d517fffffffffffffffffffffffffffff;
                            //  maxExpArray[  9] = 0x4a233fffffffffffffffffffffffffffff;
                            //  maxExpArray[ 10] = 0x47165fffffffffffffffffffffffffffff;
                            //  maxExpArray[ 11] = 0x4429afffffffffffffffffffffffffffff;
                            //  maxExpArray[ 12] = 0x415bc7ffffffffffffffffffffffffffff;
                            //  maxExpArray[ 13] = 0x3eab73ffffffffffffffffffffffffffff;
                            //  maxExpArray[ 14] = 0x3c1771ffffffffffffffffffffffffffff;
                            //  maxExpArray[ 15] = 0x399e96ffffffffffffffffffffffffffff;
                            //  maxExpArray[ 16] = 0x373fc47fffffffffffffffffffffffffff;
                            //  maxExpArray[ 17] = 0x34f9e8ffffffffffffffffffffffffffff;
                            //  maxExpArray[ 18] = 0x32cbfd5fffffffffffffffffffffffffff;
                            //  maxExpArray[ 19] = 0x30b5057fffffffffffffffffffffffffff;
                            //  maxExpArray[ 20] = 0x2eb40f9fffffffffffffffffffffffffff;
                            //  maxExpArray[ 21] = 0x2cc8340fffffffffffffffffffffffffff;
                            //  maxExpArray[ 22] = 0x2af09481ffffffffffffffffffffffffff;
                            //  maxExpArray[ 23] = 0x292c5bddffffffffffffffffffffffffff;
                            //  maxExpArray[ 24] = 0x277abdcdffffffffffffffffffffffffff;
                            //  maxExpArray[ 25] = 0x25daf6657fffffffffffffffffffffffff;
                            //  maxExpArray[ 26] = 0x244c49c65fffffffffffffffffffffffff;
                            //  maxExpArray[ 27] = 0x22ce03cd5fffffffffffffffffffffffff;
                            //  maxExpArray[ 28] = 0x215f77c047ffffffffffffffffffffffff;
                            //  maxExpArray[ 29] = 0x1fffffffffffffffffffffffffffffffff;
                            //  maxExpArray[ 30] = 0x1eaefdbdabffffffffffffffffffffffff;
                            //  maxExpArray[ 31] = 0x1d6bd8b2ebffffffffffffffffffffffff;
                                maxExpArray[ 32] = 0x1c35fedd14ffffffffffffffffffffffff;
                                maxExpArray[ 33] = 0x1b0ce43b323fffffffffffffffffffffff;
                                maxExpArray[ 34] = 0x19f0028ec1ffffffffffffffffffffffff;
                                maxExpArray[ 35] = 0x18ded91f0e7fffffffffffffffffffffff;
                                maxExpArray[ 36] = 0x17d8ec7f0417ffffffffffffffffffffff;
                                maxExpArray[ 37] = 0x16ddc6556cdbffffffffffffffffffffff;
                                maxExpArray[ 38] = 0x15ecf52776a1ffffffffffffffffffffff;
                                maxExpArray[ 39] = 0x15060c256cb2ffffffffffffffffffffff;
                                maxExpArray[ 40] = 0x1428a2f98d72ffffffffffffffffffffff;
                                maxExpArray[ 41] = 0x13545598e5c23fffffffffffffffffffff;
                                maxExpArray[ 42] = 0x1288c4161ce1dfffffffffffffffffffff;
                                maxExpArray[ 43] = 0x11c592761c666fffffffffffffffffffff;
                                maxExpArray[ 44] = 0x110a688680a757ffffffffffffffffffff;
                                maxExpArray[ 45] = 0x1056f1b5bedf77ffffffffffffffffffff;
                                maxExpArray[ 46] = 0x0faadceceeff8bffffffffffffffffffff;
                                maxExpArray[ 47] = 0x0f05dc6b27edadffffffffffffffffffff;
                                maxExpArray[ 48] = 0x0e67a5a25da4107fffffffffffffffffff;
                                maxExpArray[ 49] = 0x0dcff115b14eedffffffffffffffffffff;
                                maxExpArray[ 50] = 0x0d3e7a392431239fffffffffffffffffff;
                                maxExpArray[ 51] = 0x0cb2ff529eb71e4fffffffffffffffffff;
                                maxExpArray[ 52] = 0x0c2d415c3db974afffffffffffffffffff;
                                maxExpArray[ 53] = 0x0bad03e7d883f69bffffffffffffffffff;
                                maxExpArray[ 54] = 0x0b320d03b2c343d5ffffffffffffffffff;
                                maxExpArray[ 55] = 0x0abc25204e02828dffffffffffffffffff;
                                maxExpArray[ 56] = 0x0a4b16f74ee4bb207fffffffffffffffff;
                                maxExpArray[ 57] = 0x09deaf736ac1f569ffffffffffffffffff;
                                maxExpArray[ 58] = 0x0976bd9952c7aa957fffffffffffffffff;
                                maxExpArray[ 59] = 0x09131271922eaa606fffffffffffffffff;
                                maxExpArray[ 60] = 0x08b380f3558668c46fffffffffffffffff;
                                maxExpArray[ 61] = 0x0857ddf0117efa215bffffffffffffffff;
                                maxExpArray[ 62] = 0x07ffffffffffffffffffffffffffffffff;
                                maxExpArray[ 63] = 0x07abbf6f6abb9d087fffffffffffffffff;
                                maxExpArray[ 64] = 0x075af62cbac95f7dfa7fffffffffffffff;
                                maxExpArray[ 65] = 0x070d7fb7452e187ac13fffffffffffffff;
                                maxExpArray[ 66] = 0x06c3390ecc8af379295fffffffffffffff;
                                maxExpArray[ 67] = 0x067c00a3b07ffc01fd6fffffffffffffff;
                                maxExpArray[ 68] = 0x0637b647c39cbb9d3d27ffffffffffffff;
                                maxExpArray[ 69] = 0x05f63b1fc104dbd39587ffffffffffffff;
                                maxExpArray[ 70] = 0x05b771955b36e12f7235ffffffffffffff;
                                maxExpArray[ 71] = 0x057b3d49dda84556d6f6ffffffffffffff;
                                maxExpArray[ 72] = 0x054183095b2c8ececf30ffffffffffffff;
                                maxExpArray[ 73] = 0x050a28be635ca2b888f77fffffffffffff;
                                maxExpArray[ 74] = 0x04d5156639708c9db33c3fffffffffffff;
                                maxExpArray[ 75] = 0x04a23105873875bd52dfdfffffffffffff;
                                maxExpArray[ 76] = 0x0471649d87199aa990756fffffffffffff;
                                maxExpArray[ 77] = 0x04429a21a029d4c1457cfbffffffffffff;
                                maxExpArray[ 78] = 0x0415bc6d6fb7dd71af2cb3ffffffffffff;
                                maxExpArray[ 79] = 0x03eab73b3bbfe282243ce1ffffffffffff;
                                maxExpArray[ 80] = 0x03c1771ac9fb6b4c18e229ffffffffffff;
                                maxExpArray[ 81] = 0x0399e96897690418f785257fffffffffff;
                                maxExpArray[ 82] = 0x0373fc456c53bb779bf0ea9fffffffffff;
                                maxExpArray[ 83] = 0x034f9e8e490c48e67e6ab8bfffffffffff;
                                maxExpArray[ 84] = 0x032cbfd4a7adc790560b3337ffffffffff;
                                maxExpArray[ 85] = 0x030b50570f6e5d2acca94613ffffffffff;
                                maxExpArray[ 86] = 0x02eb40f9f620fda6b56c2861ffffffffff;
                                maxExpArray[ 87] = 0x02cc8340ecb0d0f520a6af58ffffffffff;
                                maxExpArray[ 88] = 0x02af09481380a0a35cf1ba02ffffffffff;
                                maxExpArray[ 89] = 0x0292c5bdd3b92ec810287b1b3fffffffff;
                                maxExpArray[ 90] = 0x0277abdcdab07d5a77ac6d6b9fffffffff;
                                maxExpArray[ 91] = 0x025daf6654b1eaa55fd64df5efffffffff;
                                maxExpArray[ 92] = 0x0244c49c648baa98192dce88b7ffffffff;
                                maxExpArray[ 93] = 0x022ce03cd5619a311b2471268bffffffff;
                                maxExpArray[ 94] = 0x0215f77c045fbe885654a44a0fffffffff;
                                maxExpArray[ 95] = 0x01ffffffffffffffffffffffffffffffff;
                                maxExpArray[ 96] = 0x01eaefdbdaaee7421fc4d3ede5ffffffff;
                                maxExpArray[ 97] = 0x01d6bd8b2eb257df7e8ca57b09bfffffff;
                                maxExpArray[ 98] = 0x01c35fedd14b861eb0443f7f133fffffff;
                                maxExpArray[ 99] = 0x01b0ce43b322bcde4a56e8ada5afffffff;
                                maxExpArray[100] = 0x019f0028ec1fff007f5a195a39dfffffff;
                                maxExpArray[101] = 0x018ded91f0e72ee74f49b15ba527ffffff;
                                maxExpArray[102] = 0x017d8ec7f04136f4e5615fd41a63ffffff;
                                maxExpArray[103] = 0x016ddc6556cdb84bdc8d12d22e6fffffff;
                                maxExpArray[104] = 0x015ecf52776a1155b5bd8395814f7fffff;
                                maxExpArray[105] = 0x015060c256cb23b3b3cc3754cf40ffffff;
                                maxExpArray[106] = 0x01428a2f98d728ae223ddab715be3fffff;
                                maxExpArray[107] = 0x013545598e5c23276ccf0ede68034fffff;
                                maxExpArray[108] = 0x01288c4161ce1d6f54b7f61081194fffff;
                                maxExpArray[109] = 0x011c592761c666aa641d5a01a40f17ffff;
                                maxExpArray[110] = 0x0110a688680a7530515f3e6e6cfdcdffff;
                                maxExpArray[111] = 0x01056f1b5bedf75c6bcb2ce8aed428ffff;
                                maxExpArray[112] = 0x00faadceceeff8a0890f3875f008277fff;
                                maxExpArray[113] = 0x00f05dc6b27edad306388a600f6ba0bfff;
                                maxExpArray[114] = 0x00e67a5a25da41063de1495d5b18cdbfff;
                                maxExpArray[115] = 0x00dcff115b14eedde6fc3aa5353f2e4fff;
                                maxExpArray[116] = 0x00d3e7a3924312399f9aae2e0f868f8fff;
                                maxExpArray[117] = 0x00cb2ff529eb71e41582cccd5a1ee26fff;
                                maxExpArray[118] = 0x00c2d415c3db974ab32a51840c0b67edff;
                                maxExpArray[119] = 0x00bad03e7d883f69ad5b0a186184e06bff;
                                maxExpArray[120] = 0x00b320d03b2c343d4829abd6075f0cc5ff;
                                maxExpArray[121] = 0x00abc25204e02828d73c6e80bcdb1a95bf;
                                maxExpArray[122] = 0x00a4b16f74ee4bb2040a1ec6c15fbbf2df;
                                maxExpArray[123] = 0x009deaf736ac1f569deb1b5ae3f36c130f;
                                maxExpArray[124] = 0x00976bd9952c7aa957f5937d790ef65037;
                                maxExpArray[125] = 0x009131271922eaa6064b73a22d0bd4f2bf;
                                maxExpArray[126] = 0x008b380f3558668c46c91c49a2f8e967b9;
                                maxExpArray[127] = 0x00857ddf0117efa215952912839f6473e6;
                            }
                        
                            /**
                              * Auto-generated via 'PrintLambertArray.py'
                            */
                            uint256[128] private lambertArray;
                            function initLambertArray() private {
                                lambertArray[  0] = 0x60e393c68d20b1bd09deaabc0373b9c5;
                                lambertArray[  1] = 0x5f8f46e4854120989ed94719fb4c2011;
                                lambertArray[  2] = 0x5e479ebb9129fb1b7e72a648f992b606;
                                lambertArray[  3] = 0x5d0bd23fe42dfedde2e9586be12b85fe;
                                lambertArray[  4] = 0x5bdb29ddee979308ddfca81aeeb8095a;
                                lambertArray[  5] = 0x5ab4fd8a260d2c7e2c0d2afcf0009dad;
                                lambertArray[  6] = 0x5998b31359a55d48724c65cf09001221;
                                lambertArray[  7] = 0x5885bcad2b322dfc43e8860f9c018cf5;
                                lambertArray[  8] = 0x577b97aa1fe222bb452fdf111b1f0be2;
                                lambertArray[  9] = 0x5679cb5e3575632e5baa27e2b949f704;
                                lambertArray[ 10] = 0x557fe8241b3a31c83c732f1cdff4a1c5;
                                lambertArray[ 11] = 0x548d868026504875d6e59bbe95fc2a6b;
                                lambertArray[ 12] = 0x53a2465ce347cf34d05a867c17dd3088;
                                lambertArray[ 13] = 0x52bdce5dcd4faed59c7f5511cf8f8acc;
                                lambertArray[ 14] = 0x51dfcb453c07f8da817606e7885f7c3e;
                                lambertArray[ 15] = 0x5107ef6b0a5a2be8f8ff15590daa3cce;
                                lambertArray[ 16] = 0x5035f241d6eae0cd7bacba119993de7b;
                                lambertArray[ 17] = 0x4f698fe90d5b53d532171e1210164c66;
                                lambertArray[ 18] = 0x4ea288ca297a0e6a09a0eee240e16c85;
                                lambertArray[ 19] = 0x4de0a13fdcf5d4213fc398ba6e3becde;
                                lambertArray[ 20] = 0x4d23a145eef91fec06b06140804c4808;
                                lambertArray[ 21] = 0x4c6b5430d4c1ee5526473db4ae0f11de;
                                lambertArray[ 22] = 0x4bb7886c240562eba11f4963a53b4240;
                                lambertArray[ 23] = 0x4b080f3f1cb491d2d521e0ea4583521e;
                                lambertArray[ 24] = 0x4a5cbc96a05589cb4d86be1db3168364;
                                lambertArray[ 25] = 0x49b566d40243517658d78c33162d6ece;
                                lambertArray[ 26] = 0x4911e6a02e5507a30f947383fd9a3276;
                                lambertArray[ 27] = 0x487216c2b31be4adc41db8a8d5cc0c88;
                                lambertArray[ 28] = 0x47d5d3fc4a7a1b188cd3d788b5c5e9fc;
                                lambertArray[ 29] = 0x473cfce4871a2c40bc4f9e1c32b955d0;
                                lambertArray[ 30] = 0x46a771ca578ab878485810e285e31c67;
                                lambertArray[ 31] = 0x4615149718aed4c258c373dc676aa72d;
                                lambertArray[ 32] = 0x4585c8b3f8fe489c6e1833ca47871384;
                                lambertArray[ 33] = 0x44f972f174e41e5efb7e9d63c29ce735;
                                lambertArray[ 34] = 0x446ff970ba86d8b00beb05ecebf3c4dc;
                                lambertArray[ 35] = 0x43e9438ec88971812d6f198b5ccaad96;
                                lambertArray[ 36] = 0x436539d11ff7bea657aeddb394e809ef;
                                lambertArray[ 37] = 0x42e3c5d3e5a913401d86f66db5d81c2c;
                                lambertArray[ 38] = 0x4264d2395303070ea726cbe98df62174;
                                lambertArray[ 39] = 0x41e84a9a593bb7194c3a6349ecae4eea;
                                lambertArray[ 40] = 0x416e1b785d13eba07a08f3f18876a5ab;
                                lambertArray[ 41] = 0x40f6322ff389d423ba9dd7e7e7b7e809;
                                lambertArray[ 42] = 0x40807cec8a466880ecf4184545d240a4;
                                lambertArray[ 43] = 0x400cea9ce88a8d3ae668e8ea0d9bf07f;
                                lambertArray[ 44] = 0x3f9b6ae8772d4c55091e0ed7dfea0ac1;
                                lambertArray[ 45] = 0x3f2bee253fd84594f54bcaafac383a13;
                                lambertArray[ 46] = 0x3ebe654e95208bb9210c575c081c5958;
                                lambertArray[ 47] = 0x3e52c1fc5665635b78ce1f05ad53c086;
                                lambertArray[ 48] = 0x3de8f65ac388101ddf718a6f5c1eff65;
                                lambertArray[ 49] = 0x3d80f522d59bd0b328ca012df4cd2d49;
                                lambertArray[ 50] = 0x3d1ab193129ea72b23648a161163a85a;
                                lambertArray[ 51] = 0x3cb61f68d32576c135b95cfb53f76d75;
                                lambertArray[ 52] = 0x3c5332d9f1aae851a3619e77e4cc8473;
                                lambertArray[ 53] = 0x3bf1e08edbe2aa109e1525f65759ef73;
                                lambertArray[ 54] = 0x3b921d9cff13fa2c197746a3dfc4918f;
                                lambertArray[ 55] = 0x3b33df818910bfc1a5aefb8f63ae2ac4;
                                lambertArray[ 56] = 0x3ad71c1c77e34fa32a9f184967eccbf6;
                                lambertArray[ 57] = 0x3a7bc9abf2c5bb53e2f7384a8a16521a;
                                lambertArray[ 58] = 0x3a21dec7e76369783a68a0c6385a1c57;
                                lambertArray[ 59] = 0x39c9525de6c9cdf7c1c157ca4a7a6ee3;
                                lambertArray[ 60] = 0x39721bad3dc85d1240ff0190e0adaac3;
                                lambertArray[ 61] = 0x391c324344d3248f0469eb28dd3d77e0;
                                lambertArray[ 62] = 0x38c78df7e3c796279fb4ff84394ab3da;
                                lambertArray[ 63] = 0x387426ea4638ae9aae08049d3554c20a;
                                lambertArray[ 64] = 0x3821f57dbd2763256c1a99bbd2051378;
                                lambertArray[ 65] = 0x37d0f256cb46a8c92ff62fbbef289698;
                                lambertArray[ 66] = 0x37811658591ffc7abdd1feaf3cef9b73;
                                lambertArray[ 67] = 0x37325aa10e9e82f7df0f380f7997154b;
                                lambertArray[ 68] = 0x36e4b888cfb408d873b9a80d439311c6;
                                lambertArray[ 69] = 0x3698299e59f4bb9de645fc9b08c64cca;
                                lambertArray[ 70] = 0x364ca7a5012cb603023b57dd3ebfd50d;
                                lambertArray[ 71] = 0x36022c928915b778ab1b06aaee7e61d4;
                                lambertArray[ 72] = 0x35b8b28d1a73dc27500ffe35559cc028;
                                lambertArray[ 73] = 0x357033e951fe250ec5eb4e60955132d7;
                                lambertArray[ 74] = 0x3528ab2867934e3a21b5412e4c4f8881;
                                lambertArray[ 75] = 0x34e212f66c55057f9676c80094a61d59;
                                lambertArray[ 76] = 0x349c66289e5b3c4b540c24f42fa4b9bb;
                                lambertArray[ 77] = 0x34579fbbd0c733a9c8d6af6b0f7d00f7;
                                lambertArray[ 78] = 0x3413bad2e712288b924b5882b5b369bf;
                                lambertArray[ 79] = 0x33d0b2b56286510ef730e213f71f12e9;
                                lambertArray[ 80] = 0x338e82ce00e2496262c64457535ba1a1;
                                lambertArray[ 81] = 0x334d26a96b373bb7c2f8ea1827f27a92;
                                lambertArray[ 82] = 0x330c99f4f4211469e00b3e18c31475ea;
                                lambertArray[ 83] = 0x32ccd87d6486094999c7d5e6f33237d8;
                                lambertArray[ 84] = 0x328dde2dd617b6665a2e8556f250c1af;
                                lambertArray[ 85] = 0x324fa70e9adc270f8262755af5a99af9;
                                lambertArray[ 86] = 0x32122f443110611ca51040f41fa6e1e3;
                                lambertArray[ 87] = 0x31d5730e42c0831482f0f1485c4263d8;
                                lambertArray[ 88] = 0x31996ec6b07b4a83421b5ebc4ab4e1f1;
                                lambertArray[ 89] = 0x315e1ee0a68ff46bb43ec2b85032e876;
                                lambertArray[ 90] = 0x31237fe7bc4deacf6775b9efa1a145f8;
                                lambertArray[ 91] = 0x30e98e7f1cc5a356e44627a6972ea2ff;
                                lambertArray[ 92] = 0x30b04760b8917ec74205a3002650ec05;
                                lambertArray[ 93] = 0x3077a75c803468e9132ce0cf3224241d;
                                lambertArray[ 94] = 0x303fab57a6a275c36f19cda9bace667a;
                                lambertArray[ 95] = 0x3008504beb8dcbd2cf3bc1f6d5a064f0;
                                lambertArray[ 96] = 0x2fd19346ed17dac61219ce0c2c5ac4b0;
                                lambertArray[ 97] = 0x2f9b7169808c324b5852fd3d54ba9714;
                                lambertArray[ 98] = 0x2f65e7e711cf4b064eea9c08cbdad574;
                                lambertArray[ 99] = 0x2f30f405093042ddff8a251b6bf6d103;
                                lambertArray[100] = 0x2efc931a3750f2e8bfe323edfe037574;
                                lambertArray[101] = 0x2ec8c28e46dbe56d98685278339400cb;
                                lambertArray[102] = 0x2e957fd933c3926d8a599b602379b851;
                                lambertArray[103] = 0x2e62c882c7c9ed4473412702f08ba0e5;
                                lambertArray[104] = 0x2e309a221c12ba361e3ed695167feee2;
                                lambertArray[105] = 0x2dfef25d1f865ae18dd07cfea4bcea10;
                                lambertArray[106] = 0x2dcdcee821cdc80decc02c44344aeb31;
                                lambertArray[107] = 0x2d9d2d8562b34944d0b201bb87260c83;
                                lambertArray[108] = 0x2d6d0c04a5b62a2c42636308669b729a;
                                lambertArray[109] = 0x2d3d6842c9a235517fc5a0332691528f;
                                lambertArray[110] = 0x2d0e402963fe1ea2834abc408c437c10;
                                lambertArray[111] = 0x2cdf91ae602647908aff975e4d6a2a8c;
                                lambertArray[112] = 0x2cb15ad3a1eb65f6d74a75da09a1b6c5;
                                lambertArray[113] = 0x2c8399a6ab8e9774d6fcff373d210727;
                                lambertArray[114] = 0x2c564c4046f64edba6883ca06bbc4535;
                                lambertArray[115] = 0x2c2970c431f952641e05cb493e23eed3;
                                lambertArray[116] = 0x2bfd0560cd9eb14563bc7c0732856c18;
                                lambertArray[117] = 0x2bd1084ed0332f7ff4150f9d0ef41a2c;
                                lambertArray[118] = 0x2ba577d0fa1628b76d040b12a82492fb;
                                lambertArray[119] = 0x2b7a5233cd21581e855e89dc2f1e8a92;
                                lambertArray[120] = 0x2b4f95cd46904d05d72bdcde337d9cc7;
                                lambertArray[121] = 0x2b2540fc9b4d9abba3faca6691914675;
                                lambertArray[122] = 0x2afb5229f68d0830d8be8adb0a0db70f;
                                lambertArray[123] = 0x2ad1c7c63a9b294c5bc73a3ba3ab7a2b;
                                lambertArray[124] = 0x2aa8a04ac3cbe1ee1c9c86361465dbb8;
                                lambertArray[125] = 0x2a7fda392d725a44a2c8aeb9ab35430d;
                                lambertArray[126] = 0x2a57741b18cde618717792b4faa216db;
                                lambertArray[127] = 0x2a2f6c81f5d84dd950a35626d6d5503a;
                            }
                        
                            /**
                              * @dev should be executed after construction (too large for the constructor)
                            */
                            function init() public {
                                initMaxExpArray();
                                initLambertArray();
                            }
                        
                            /**
                              * @dev given a token supply, reserve balance, weight and a deposit amount (in the reserve token),
                              * calculates the target amount for a given conversion (in the main token)
                              *
                              * Formula:
                              * return = _supply * ((1 + _amount / _reserveBalance) ^ (_reserveWeight / 1000000) - 1)
                              *
                              * @param _supply          smart token supply
                              * @param _reserveBalance  reserve balance
                              * @param _reserveWeight   reserve weight, represented in ppm (1-1000000)
                              * @param _amount          amount of reserve tokens to get the target amount for
                              *
                              * @return smart token amount
                            */
                            function purchaseTargetAmount(uint256 _supply,
                                                          uint256 _reserveBalance,
                                                          uint32 _reserveWeight,
                                                          uint256 _amount)
                                                          public view returns (uint256)
                            {
                                // validate input
                                require(_supply > 0, "ERR_INVALID_SUPPLY");
                                require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
                                require(_reserveWeight > 0 && _reserveWeight <= MAX_WEIGHT, "ERR_INVALID_RESERVE_WEIGHT");
                        
                                // special case for 0 deposit amount
                                if (_amount == 0)
                                    return 0;
                        
                                // special case if the weight = 100%
                                if (_reserveWeight == MAX_WEIGHT)
                                    return _supply.mul(_amount) / _reserveBalance;
                        
                                uint256 result;
                                uint8 precision;
                                uint256 baseN = _amount.add(_reserveBalance);
                                (result, precision) = power(baseN, _reserveBalance, _reserveWeight, MAX_WEIGHT);
                                uint256 temp = _supply.mul(result) >> precision;
                                return temp - _supply;
                            }
                        
                            /**
                              * @dev given a token supply, reserve balance, weight and a sell amount (in the main token),
                              * calculates the target amount for a given conversion (in the reserve token)
                              *
                              * Formula:
                              * return = _reserveBalance * (1 - (1 - _amount / _supply) ^ (1000000 / _reserveWeight))
                              *
                              * @param _supply          smart token supply
                              * @param _reserveBalance  reserve balance
                              * @param _reserveWeight   reserve weight, represented in ppm (1-1000000)
                              * @param _amount          amount of smart tokens to get the target amount for
                              *
                              * @return reserve token amount
                            */
                            function saleTargetAmount(uint256 _supply,
                                                      uint256 _reserveBalance,
                                                      uint32 _reserveWeight,
                                                      uint256 _amount)
                                                      public view returns (uint256)
                            {
                                // validate input
                                require(_supply > 0, "ERR_INVALID_SUPPLY");
                                require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
                                require(_reserveWeight > 0 && _reserveWeight <= MAX_WEIGHT, "ERR_INVALID_RESERVE_WEIGHT");
                                require(_amount <= _supply, "ERR_INVALID_AMOUNT");
                        
                                // special case for 0 sell amount
                                if (_amount == 0)
                                    return 0;
                        
                                // special case for selling the entire supply
                                if (_amount == _supply)
                                    return _reserveBalance;
                        
                                // special case if the weight = 100%
                                if (_reserveWeight == MAX_WEIGHT)
                                    return _reserveBalance.mul(_amount) / _supply;
                        
                                uint256 result;
                                uint8 precision;
                                uint256 baseD = _supply - _amount;
                                (result, precision) = power(_supply, baseD, MAX_WEIGHT, _reserveWeight);
                                uint256 temp1 = _reserveBalance.mul(result);
                                uint256 temp2 = _reserveBalance << precision;
                                return (temp1 - temp2) / result;
                            }
                        
                            /**
                              * @dev given two reserve balances/weights and a sell amount (in the first reserve token),
                              * calculates the target amount for a conversion from the source reserve token to the target reserve token
                              *
                              * Formula:
                              * return = _targetReserveBalance * (1 - (_sourceReserveBalance / (_sourceReserveBalance + _amount)) ^ (_sourceReserveWeight / _targetReserveWeight))
                              *
                              * @param _sourceReserveBalance    source reserve balance
                              * @param _sourceReserveWeight     source reserve weight, represented in ppm (1-1000000)
                              * @param _targetReserveBalance    target reserve balance
                              * @param _targetReserveWeight     target reserve weight, represented in ppm (1-1000000)
                              * @param _amount                  source reserve amount
                              *
                              * @return target reserve amount
                            */
                            function crossReserveTargetAmount(uint256 _sourceReserveBalance,
                                                              uint32 _sourceReserveWeight,
                                                              uint256 _targetReserveBalance,
                                                              uint32 _targetReserveWeight,
                                                              uint256 _amount)
                                                              public view returns (uint256)
                            {
                                // validate input
                                require(_sourceReserveBalance > 0 && _targetReserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
                                require(_sourceReserveWeight > 0 && _sourceReserveWeight <= MAX_WEIGHT &&
                                        _targetReserveWeight > 0 && _targetReserveWeight <= MAX_WEIGHT, "ERR_INVALID_RESERVE_WEIGHT");
                        
                                // special case for equal weights
                                if (_sourceReserveWeight == _targetReserveWeight)
                                    return _targetReserveBalance.mul(_amount) / _sourceReserveBalance.add(_amount);
                        
                                uint256 result;
                                uint8 precision;
                                uint256 baseN = _sourceReserveBalance.add(_amount);
                                (result, precision) = power(baseN, _sourceReserveBalance, _sourceReserveWeight, _targetReserveWeight);
                                uint256 temp1 = _targetReserveBalance.mul(result);
                                uint256 temp2 = _targetReserveBalance << precision;
                                return (temp1 - temp2) / result;
                            }
                        
                            /**
                              * @dev given a smart token supply, reserve balance, reserve ratio and an amount of requested smart tokens,
                              * calculates the amount of reserve tokens required for purchasing the given amount of smart tokens
                              *
                              * Formula:
                              * return = _reserveBalance * (((_supply + _amount) / _supply) ^ (MAX_WEIGHT / _reserveRatio) - 1)
                              *
                              * @param _supply          smart token supply
                              * @param _reserveBalance  reserve balance
                              * @param _reserveRatio    reserve ratio, represented in ppm (2-2000000)
                              * @param _amount          requested amount of smart tokens
                              *
                              * @return reserve token amount
                            */
                            function fundCost(uint256 _supply,
                                              uint256 _reserveBalance,
                                              uint32 _reserveRatio,
                                              uint256 _amount)
                                              public view returns (uint256)
                            {
                                // validate input
                                require(_supply > 0, "ERR_INVALID_SUPPLY");
                                require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
                                require(_reserveRatio > 1 && _reserveRatio <= MAX_WEIGHT * 2, "ERR_INVALID_RESERVE_RATIO");
                        
                                // special case for 0 amount
                                if (_amount == 0)
                                    return 0;
                        
                                // special case if the reserve ratio = 100%
                                if (_reserveRatio == MAX_WEIGHT)
                                    return (_amount.mul(_reserveBalance) - 1) / _supply + 1;
                        
                                uint256 result;
                                uint8 precision;
                                uint256 baseN = _supply.add(_amount);
                                (result, precision) = power(baseN, _supply, MAX_WEIGHT, _reserveRatio);
                                uint256 temp = ((_reserveBalance.mul(result) - 1) >> precision) + 1;
                                return temp - _reserveBalance;
                            }
                        
                            /**
                              * @dev given a smart token supply, reserve balance, reserve ratio and an amount of reserve tokens to fund with,
                              * calculates the amount of smart tokens received for purchasing with the given amount of reserve tokens
                              *
                              * Formula:
                              * return = _supply * ((_amount / _reserveBalance + 1) ^ (_reserveRatio / MAX_WEIGHT) - 1)
                              *
                              * @param _supply          smart token supply
                              * @param _reserveBalance  reserve balance
                              * @param _reserveRatio    reserve ratio, represented in ppm (2-2000000)
                              * @param _amount          amount of reserve tokens to fund with
                              *
                              * @return smart token amount
                            */
                            function fundSupplyAmount(uint256 _supply,
                                                      uint256 _reserveBalance,
                                                      uint32 _reserveRatio,
                                                      uint256 _amount)
                                                      public view returns (uint256)
                            {
                                // validate input
                                require(_supply > 0, "ERR_INVALID_SUPPLY");
                                require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
                                require(_reserveRatio > 1 && _reserveRatio <= MAX_WEIGHT * 2, "ERR_INVALID_RESERVE_RATIO");
                        
                                // special case for 0 amount
                                if (_amount == 0)
                                    return 0;
                        
                                // special case if the reserve ratio = 100%
                                if (_reserveRatio == MAX_WEIGHT)
                                    return _amount.mul(_supply) / _reserveBalance;
                        
                                uint256 result;
                                uint8 precision;
                                uint256 baseN = _reserveBalance.add(_amount);
                                (result, precision) = power(baseN, _reserveBalance, _reserveRatio, MAX_WEIGHT);
                                uint256 temp = _supply.mul(result) >> precision;
                                return temp - _supply;
                            }
                        
                            /**
                              * @dev given a smart token supply, reserve balance, reserve ratio and an amount of smart tokens to liquidate,
                              * calculates the amount of reserve tokens received for selling the given amount of smart tokens
                              *
                              * Formula:
                              * return = _reserveBalance * (1 - ((_supply - _amount) / _supply) ^ (MAX_WEIGHT / _reserveRatio))
                              *
                              * @param _supply          smart token supply
                              * @param _reserveBalance  reserve balance
                              * @param _reserveRatio    reserve ratio, represented in ppm (2-2000000)
                              * @param _amount          amount of smart tokens to liquidate
                              *
                              * @return reserve token amount
                            */
                            function liquidateReserveAmount(uint256 _supply,
                                                            uint256 _reserveBalance,
                                                            uint32 _reserveRatio,
                                                            uint256 _amount)
                                                            public view returns (uint256)
                            {
                                // validate input
                                require(_supply > 0, "ERR_INVALID_SUPPLY");
                                require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
                                require(_reserveRatio > 1 && _reserveRatio <= MAX_WEIGHT * 2, "ERR_INVALID_RESERVE_RATIO");
                                require(_amount <= _supply, "ERR_INVALID_AMOUNT");
                        
                                // special case for 0 amount
                                if (_amount == 0)
                                    return 0;
                        
                                // special case for liquidating the entire supply
                                if (_amount == _supply)
                                    return _reserveBalance;
                        
                                // special case if the reserve ratio = 100%
                                if (_reserveRatio == MAX_WEIGHT)
                                    return _amount.mul(_reserveBalance) / _supply;
                        
                                uint256 result;
                                uint8 precision;
                                uint256 baseD = _supply - _amount;
                                (result, precision) = power(_supply, baseD, MAX_WEIGHT, _reserveRatio);
                                uint256 temp1 = _reserveBalance.mul(result);
                                uint256 temp2 = _reserveBalance << precision;
                                return (temp1 - temp2) / result;
                            }
                        
                            /**
                              * @dev The arbitrage incentive is to convert to the point where the on-chain price is equal to the off-chain price.
                              * We want this operation to also impact the primary reserve balance becoming equal to the primary reserve staked balance.
                              * In other words, we want the arbitrager to convert the difference between the reserve balance and the reserve staked balance.
                              *
                              * Formula input:
                              * - let t denote the primary reserve token staked balance
                              * - let s denote the primary reserve token balance
                              * - let r denote the secondary reserve token balance
                              * - let q denote the numerator of the rate between the tokens
                              * - let p denote the denominator of the rate between the tokens
                              * Where p primary tokens are equal to q secondary tokens
                              *
                              * Formula output:
                              * - compute x = W(t / r * q / p * log(s / t)) / log(s / t)
                              * - return x / (1 + x) as the weight of the primary reserve token
                              * - return 1 / (1 + x) as the weight of the secondary reserve token
                              * Where W is the Lambert W Function
                              *
                              * If the rate-provider provides the rates for a common unit, for example:
                              * - P = 2 ==> 2 primary reserve tokens = 1 ether
                              * - Q = 3 ==> 3 secondary reserve tokens = 1 ether
                              * Then you can simply use p = P and q = Q
                              *
                              * If the rate-provider provides the rates for a single unit, for example:
                              * - P = 2 ==> 1 primary reserve token = 2 ethers
                              * - Q = 3 ==> 1 secondary reserve token = 3 ethers
                              * Then you can simply use p = Q and q = P
                              *
                              * @param _primaryReserveStakedBalance the primary reserve token staked balance
                              * @param _primaryReserveBalance       the primary reserve token balance
                              * @param _secondaryReserveBalance     the secondary reserve token balance
                              * @param _reserveRateNumerator        the numerator of the rate between the tokens
                              * @param _reserveRateDenominator      the denominator of the rate between the tokens
                              *
                              * Note that `numerator / denominator` should represent the amount of secondary tokens equal to one primary token
                              *
                              * @return the weight of the primary reserve token and the weight of the secondary reserve token, both in ppm (0-1000000)
                            */
                            function balancedWeights(uint256 _primaryReserveStakedBalance,
                                                     uint256 _primaryReserveBalance,
                                                     uint256 _secondaryReserveBalance,
                                                     uint256 _reserveRateNumerator,
                                                     uint256 _reserveRateDenominator)
                                                     public view returns (uint32, uint32)
                            {
                                if (_primaryReserveStakedBalance == _primaryReserveBalance)
                                    require(_primaryReserveStakedBalance > 0 || _secondaryReserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
                                else
                                    require(_primaryReserveStakedBalance > 0 && _primaryReserveBalance > 0 && _secondaryReserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
                                require(_reserveRateNumerator > 0 && _reserveRateDenominator > 0, "ERR_INVALID_RESERVE_RATE");
                        
                                uint256 tq = _primaryReserveStakedBalance.mul(_reserveRateNumerator);
                                uint256 rp = _secondaryReserveBalance.mul(_reserveRateDenominator);
                        
                                if (_primaryReserveStakedBalance < _primaryReserveBalance)
                                    return balancedWeightsByStake(_primaryReserveBalance, _primaryReserveStakedBalance, tq, rp, true);
                        
                                if (_primaryReserveStakedBalance > _primaryReserveBalance)
                                    return balancedWeightsByStake(_primaryReserveStakedBalance, _primaryReserveBalance, tq, rp, false);
                        
                                return normalizedWeights(tq, rp);
                            }
                        
                            /**
                              * @dev General Description:
                              *     Determine a value of precision.
                              *     Calculate an integer approximation of (_baseN / _baseD) ^ (_expN / _expD) * 2 ^ precision.
                              *     Return the result along with the precision used.
                              *
                              * Detailed Description:
                              *     Instead of calculating "base ^ exp", we calculate "e ^ (log(base) * exp)".
                              *     The value of "log(base)" is represented with an integer slightly smaller than "log(base) * 2 ^ precision".
                              *     The larger "precision" is, the more accurately this value represents the real value.
                              *     However, the larger "precision" is, the more bits are required in order to store this value.
                              *     And the exponentiation function, which takes "x" and calculates "e ^ x", is limited to a maximum exponent (maximum value of "x").
                              *     This maximum exponent depends on the "precision" used, and it is given by "maxExpArray[precision] >> (MAX_PRECISION - precision)".
                              *     Hence we need to determine the highest precision which can be used for the given input, before calling the exponentiation function.
                              *     This allows us to compute "base ^ exp" with maximum accuracy and without exceeding 256 bits in any of the intermediate computations.
                              *     This functions assumes that "_expN < 2 ^ 256 / log(MAX_NUM - 1)", otherwise the multiplication should be replaced with a "safeMul".
                              *     Since we rely on unsigned-integer arithmetic and "base < 1" ==> "log(base) < 0", this function does not support "_baseN < _baseD".
                            */
                            function power(uint256 _baseN, uint256 _baseD, uint32 _expN, uint32 _expD) internal view returns (uint256, uint8) {
                                require(_baseN < MAX_NUM);
                        
                                uint256 baseLog;
                                uint256 base = _baseN * FIXED_1 / _baseD;
                                if (base < OPT_LOG_MAX_VAL) {
                                    baseLog = optimalLog(base);
                                }
                                else {
                                    baseLog = generalLog(base);
                                }
                        
                                uint256 baseLogTimesExp = baseLog * _expN / _expD;
                                if (baseLogTimesExp < OPT_EXP_MAX_VAL) {
                                    return (optimalExp(baseLogTimesExp), MAX_PRECISION);
                                }
                                else {
                                    uint8 precision = findPositionInMaxExpArray(baseLogTimesExp);
                                    return (generalExp(baseLogTimesExp >> (MAX_PRECISION - precision), precision), precision);
                                }
                            }
                        
                            /**
                              * @dev computes log(x / FIXED_1) * FIXED_1.
                              * This functions assumes that "x >= FIXED_1", because the output would be negative otherwise.
                            */
                            function generalLog(uint256 x) internal pure returns (uint256) {
                                uint256 res = 0;
                        
                                // If x >= 2, then we compute the integer part of log2(x), which is larger than 0.
                                if (x >= FIXED_2) {
                                    uint8 count = floorLog2(x / FIXED_1);
                                    x >>= count; // now x < 2
                                    res = count * FIXED_1;
                                }
                        
                                // If x > 1, then we compute the fraction part of log2(x), which is larger than 0.
                                if (x > FIXED_1) {
                                    for (uint8 i = MAX_PRECISION; i > 0; --i) {
                                        x = (x * x) / FIXED_1; // now 1 < x < 4
                                        if (x >= FIXED_2) {
                                            x >>= 1; // now 1 < x < 2
                                            res += ONE << (i - 1);
                                        }
                                    }
                                }
                        
                                return res * LN2_NUMERATOR / LN2_DENOMINATOR;
                            }
                        
                            /**
                              * @dev computes the largest integer smaller than or equal to the binary logarithm of the input.
                            */
                            function floorLog2(uint256 _n) internal pure returns (uint8) {
                                uint8 res = 0;
                        
                                if (_n < 256) {
                                    // At most 8 iterations
                                    while (_n > 1) {
                                        _n >>= 1;
                                        res += 1;
                                    }
                                }
                                else {
                                    // Exactly 8 iterations
                                    for (uint8 s = 128; s > 0; s >>= 1) {
                                        if (_n >= (ONE << s)) {
                                            _n >>= s;
                                            res |= s;
                                        }
                                    }
                                }
                        
                                return res;
                            }
                        
                            /**
                              * @dev the global "maxExpArray" is sorted in descending order, and therefore the following statements are equivalent:
                              * - This function finds the position of [the smallest value in "maxExpArray" larger than or equal to "x"]
                              * - This function finds the highest position of [a value in "maxExpArray" larger than or equal to "x"]
                            */
                            function findPositionInMaxExpArray(uint256 _x) internal view returns (uint8) {
                                uint8 lo = MIN_PRECISION;
                                uint8 hi = MAX_PRECISION;
                        
                                while (lo + 1 < hi) {
                                    uint8 mid = (lo + hi) / 2;
                                    if (maxExpArray[mid] >= _x)
                                        lo = mid;
                                    else
                                        hi = mid;
                                }
                        
                                if (maxExpArray[hi] >= _x)
                                    return hi;
                                if (maxExpArray[lo] >= _x)
                                    return lo;
                        
                                require(false);
                            }
                        
                            /**
                              * @dev this function can be auto-generated by the script 'PrintFunctionGeneralExp.py'.
                              * it approximates "e ^ x" via maclaurin summation: "(x^0)/0! + (x^1)/1! + ... + (x^n)/n!".
                              * it returns "e ^ (x / 2 ^ precision) * 2 ^ precision", that is, the result is upshifted for accuracy.
                              * the global "maxExpArray" maps each "precision" to "((maximumExponent + 1) << (MAX_PRECISION - precision)) - 1".
                              * the maximum permitted value for "x" is therefore given by "maxExpArray[precision] >> (MAX_PRECISION - precision)".
                            */
                            function generalExp(uint256 _x, uint8 _precision) internal pure returns (uint256) {
                                uint256 xi = _x;
                                uint256 res = 0;
                        
                                xi = (xi * _x) >> _precision; res += xi * 0x3442c4e6074a82f1797f72ac0000000; // add x^02 * (33! / 02!)
                                xi = (xi * _x) >> _precision; res += xi * 0x116b96f757c380fb287fd0e40000000; // add x^03 * (33! / 03!)
                                xi = (xi * _x) >> _precision; res += xi * 0x045ae5bdd5f0e03eca1ff4390000000; // add x^04 * (33! / 04!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00defabf91302cd95b9ffda50000000; // add x^05 * (33! / 05!)
                                xi = (xi * _x) >> _precision; res += xi * 0x002529ca9832b22439efff9b8000000; // add x^06 * (33! / 06!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00054f1cf12bd04e516b6da88000000; // add x^07 * (33! / 07!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000a9e39e257a09ca2d6db51000000; // add x^08 * (33! / 08!)
                                xi = (xi * _x) >> _precision; res += xi * 0x000012e066e7b839fa050c309000000; // add x^09 * (33! / 09!)
                                xi = (xi * _x) >> _precision; res += xi * 0x000001e33d7d926c329a1ad1a800000; // add x^10 * (33! / 10!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000002bee513bdb4a6b19b5f800000; // add x^11 * (33! / 11!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00000003a9316fa79b88eccf2a00000; // add x^12 * (33! / 12!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000048177ebe1fa812375200000; // add x^13 * (33! / 13!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000005263fe90242dcbacf00000; // add x^14 * (33! / 14!)
                                xi = (xi * _x) >> _precision; res += xi * 0x000000000057e22099c030d94100000; // add x^15 * (33! / 15!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000000057e22099c030d9410000; // add x^16 * (33! / 16!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00000000000052b6b54569976310000; // add x^17 * (33! / 17!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00000000000004985f67696bf748000; // add x^18 * (33! / 18!)
                                xi = (xi * _x) >> _precision; res += xi * 0x000000000000003dea12ea99e498000; // add x^19 * (33! / 19!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00000000000000031880f2214b6e000; // add x^20 * (33! / 20!)
                                xi = (xi * _x) >> _precision; res += xi * 0x000000000000000025bcff56eb36000; // add x^21 * (33! / 21!)
                                xi = (xi * _x) >> _precision; res += xi * 0x000000000000000001b722e10ab1000; // add x^22 * (33! / 22!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000001317c70077000; // add x^23 * (33! / 23!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000cba84aafa00; // add x^24 * (33! / 24!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000082573a0a00; // add x^25 * (33! / 25!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000005035ad900; // add x^26 * (33! / 26!)
                                xi = (xi * _x) >> _precision; res += xi * 0x000000000000000000000002f881b00; // add x^27 * (33! / 27!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000001b29340; // add x^28 * (33! / 28!)
                                xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000000000efc40; // add x^29 * (33! / 29!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000007fe0; // add x^30 * (33! / 30!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000420; // add x^31 * (33! / 31!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000021; // add x^32 * (33! / 32!)
                                xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000001; // add x^33 * (33! / 33!)
                        
                                return res / 0x688589cc0e9505e2f2fee5580000000 + _x + (ONE << _precision); // divide by 33! and then add x^1 / 1! + x^0 / 0!
                            }
                        
                            /**
                              * @dev computes log(x / FIXED_1) * FIXED_1
                              * Input range: FIXED_1 <= x <= OPT_LOG_MAX_VAL - 1
                              * Auto-generated via 'PrintFunctionOptimalLog.py'
                              * Detailed description:
                              * - Rewrite the input as a product of natural exponents and a single residual r, such that 1 < r < 2
                              * - The natural logarithm of each (pre-calculated) exponent is the degree of the exponent
                              * - The natural logarithm of r is calculated via Taylor series for log(1 + x), where x = r - 1
                              * - The natural logarithm of the input is calculated by summing up the intermediate results above
                              * - For example: log(250) = log(e^4 * e^1 * e^0.5 * 1.021692859) = 4 + 1 + 0.5 + log(1 + 0.021692859)
                            */
                            function optimalLog(uint256 x) internal pure returns (uint256) {
                                uint256 res = 0;
                        
                                uint256 y;
                                uint256 z;
                                uint256 w;
                        
                                if (x >= 0xd3094c70f034de4b96ff7d5b6f99fcd8) {res += 0x40000000000000000000000000000000; x = x * FIXED_1 / 0xd3094c70f034de4b96ff7d5b6f99fcd8;} // add 1 / 2^1
                                if (x >= 0xa45af1e1f40c333b3de1db4dd55f29a7) {res += 0x20000000000000000000000000000000; x = x * FIXED_1 / 0xa45af1e1f40c333b3de1db4dd55f29a7;} // add 1 / 2^2
                                if (x >= 0x910b022db7ae67ce76b441c27035c6a1) {res += 0x10000000000000000000000000000000; x = x * FIXED_1 / 0x910b022db7ae67ce76b441c27035c6a1;} // add 1 / 2^3
                                if (x >= 0x88415abbe9a76bead8d00cf112e4d4a8) {res += 0x08000000000000000000000000000000; x = x * FIXED_1 / 0x88415abbe9a76bead8d00cf112e4d4a8;} // add 1 / 2^4
                                if (x >= 0x84102b00893f64c705e841d5d4064bd3) {res += 0x04000000000000000000000000000000; x = x * FIXED_1 / 0x84102b00893f64c705e841d5d4064bd3;} // add 1 / 2^5
                                if (x >= 0x8204055aaef1c8bd5c3259f4822735a2) {res += 0x02000000000000000000000000000000; x = x * FIXED_1 / 0x8204055aaef1c8bd5c3259f4822735a2;} // add 1 / 2^6
                                if (x >= 0x810100ab00222d861931c15e39b44e99) {res += 0x01000000000000000000000000000000; x = x * FIXED_1 / 0x810100ab00222d861931c15e39b44e99;} // add 1 / 2^7
                                if (x >= 0x808040155aabbbe9451521693554f733) {res += 0x00800000000000000000000000000000; x = x * FIXED_1 / 0x808040155aabbbe9451521693554f733;} // add 1 / 2^8
                        
                                z = y = x - FIXED_1;
                                w = y * y / FIXED_1;
                                res += z * (0x100000000000000000000000000000000 - y) / 0x100000000000000000000000000000000; z = z * w / FIXED_1; // add y^01 / 01 - y^02 / 02
                                res += z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y) / 0x200000000000000000000000000000000; z = z * w / FIXED_1; // add y^03 / 03 - y^04 / 04
                                res += z * (0x099999999999999999999999999999999 - y) / 0x300000000000000000000000000000000; z = z * w / FIXED_1; // add y^05 / 05 - y^06 / 06
                                res += z * (0x092492492492492492492492492492492 - y) / 0x400000000000000000000000000000000; z = z * w / FIXED_1; // add y^07 / 07 - y^08 / 08
                                res += z * (0x08e38e38e38e38e38e38e38e38e38e38e - y) / 0x500000000000000000000000000000000; z = z * w / FIXED_1; // add y^09 / 09 - y^10 / 10
                                res += z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y) / 0x600000000000000000000000000000000; z = z * w / FIXED_1; // add y^11 / 11 - y^12 / 12
                                res += z * (0x089d89d89d89d89d89d89d89d89d89d89 - y) / 0x700000000000000000000000000000000; z = z * w / FIXED_1; // add y^13 / 13 - y^14 / 14
                                res += z * (0x088888888888888888888888888888888 - y) / 0x800000000000000000000000000000000;                      // add y^15 / 15 - y^16 / 16
                        
                                return res;
                            }
                        
                            /**
                              * @dev computes e ^ (x / FIXED_1) * FIXED_1
                              * input range: 0 <= x <= OPT_EXP_MAX_VAL - 1
                              * auto-generated via 'PrintFunctionOptimalExp.py'
                              * Detailed description:
                              * - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible
                              * - The exponentiation of each binary exponent is given (pre-calculated)
                              * - The exponentiation of r is calculated via Taylor series for e^x, where x = r
                              * - The exponentiation of the input is calculated by multiplying the intermediate results above
                              * - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859
                            */
                            function optimalExp(uint256 x) internal pure returns (uint256) {
                                uint256 res = 0;
                        
                                uint256 y;
                                uint256 z;
                        
                                z = y = x % 0x10000000000000000000000000000000; // get the input modulo 2^(-3)
                                z = z * y / FIXED_1; res += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
                                z = z * y / FIXED_1; res += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
                                z = z * y / FIXED_1; res += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
                                z = z * y / FIXED_1; res += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
                                z = z * y / FIXED_1; res += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
                                z = z * y / FIXED_1; res += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
                                z = z * y / FIXED_1; res += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
                                z = z * y / FIXED_1; res += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
                                z = z * y / FIXED_1; res += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
                                z = z * y / FIXED_1; res += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
                                z = z * y / FIXED_1; res += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
                                z = z * y / FIXED_1; res += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
                                z = z * y / FIXED_1; res += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
                                z = z * y / FIXED_1; res += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
                                z = z * y / FIXED_1; res += z * 0x000000000001c638; // add y^16 * (20! / 16!)
                                z = z * y / FIXED_1; res += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
                                z = z * y / FIXED_1; res += z * 0x000000000000017c; // add y^18 * (20! / 18!)
                                z = z * y / FIXED_1; res += z * 0x0000000000000014; // add y^19 * (20! / 19!)
                                z = z * y / FIXED_1; res += z * 0x0000000000000001; // add y^20 * (20! / 20!)
                                res = res / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0!
                        
                                if ((x & 0x010000000000000000000000000000000) != 0) res = res * 0x1c3d6a24ed82218787d624d3e5eba95f9 / 0x18ebef9eac820ae8682b9793ac6d1e776; // multiply by e^2^(-3)
                                if ((x & 0x020000000000000000000000000000000) != 0) res = res * 0x18ebef9eac820ae8682b9793ac6d1e778 / 0x1368b2fc6f9609fe7aceb46aa619baed4; // multiply by e^2^(-2)
                                if ((x & 0x040000000000000000000000000000000) != 0) res = res * 0x1368b2fc6f9609fe7aceb46aa619baed5 / 0x0bc5ab1b16779be3575bd8f0520a9f21f; // multiply by e^2^(-1)
                                if ((x & 0x080000000000000000000000000000000) != 0) res = res * 0x0bc5ab1b16779be3575bd8f0520a9f21e / 0x0454aaa8efe072e7f6ddbab84b40a55c9; // multiply by e^2^(+0)
                                if ((x & 0x100000000000000000000000000000000) != 0) res = res * 0x0454aaa8efe072e7f6ddbab84b40a55c5 / 0x00960aadc109e7a3bf4578099615711ea; // multiply by e^2^(+1)
                                if ((x & 0x200000000000000000000000000000000) != 0) res = res * 0x00960aadc109e7a3bf4578099615711d7 / 0x0002bf84208204f5977f9a8cf01fdce3d; // multiply by e^2^(+2)
                                if ((x & 0x400000000000000000000000000000000) != 0) res = res * 0x0002bf84208204f5977f9a8cf01fdc307 / 0x0000003c6ab775dd0b95b4cbee7e65d11; // multiply by e^2^(+3)
                        
                                return res;
                            }
                        
                            /**
                              * @dev computes W(x / FIXED_1) / (x / FIXED_1) * FIXED_1
                            */
                            function lowerStake(uint256 _x) internal view returns (uint256) {
                                if (_x <= LAMBERT_CONV_RADIUS)
                                    return lambertPos1(_x);
                                if (_x <= LAMBERT_POS2_MAXVAL)
                                    return lambertPos2(_x);
                                if (_x <= LAMBERT_POS3_MAXVAL)
                                    return lambertPos3(_x);
                                require(false);
                            }
                        
                            /**
                              * @dev computes W(-x / FIXED_1) / (-x / FIXED_1) * FIXED_1
                            */
                            function higherStake(uint256 _x) internal pure returns (uint256) {
                                if (_x <= LAMBERT_CONV_RADIUS)
                                    return lambertNeg1(_x);
                                return FIXED_1 * FIXED_1 / _x;
                            }
                        
                            /**
                              * @dev computes W(x / FIXED_1) / (x / FIXED_1) * FIXED_1
                              * input range: 1 <= x <= 1 / e * FIXED_1
                              * auto-generated via 'PrintFunctionLambertPos1.py'
                            */
                            function lambertPos1(uint256 _x) internal pure returns (uint256) {
                                uint256 xi = _x;
                                uint256 res = (FIXED_1 - _x) * 0xde1bc4d19efcac82445da75b00000000; // x^(1-1) * (34! * 1^(1-1) / 1!) - x^(2-1) * (34! * 2^(2-1) / 2!)
                        
                                xi = (xi * _x) / FIXED_1; res += xi * 0x00000000014d29a73a6e7b02c3668c7b0880000000; // add x^(03-1) * (34! * 03^(03-1) / 03!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x0000000002504a0cd9a7f7215b60f9be4800000000; // sub x^(04-1) * (34! * 04^(04-1) / 04!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000000000484d0a1191c0ead267967c7a4a0000000; // add x^(05-1) * (34! * 05^(05-1) / 05!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x00000000095ec580d7e8427a4baf26a90a00000000; // sub x^(06-1) * (34! * 06^(06-1) / 06!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000000001440b0be1615a47dba6e5b3b1f10000000; // add x^(07-1) * (34! * 07^(07-1) / 07!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x000000002d207601f46a99b4112418400000000000; // sub x^(08-1) * (34! * 08^(08-1) / 08!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000000066ebaac4c37c622dd8288a7eb1b2000000; // add x^(09-1) * (34! * 09^(09-1) / 09!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x00000000ef17240135f7dbd43a1ba10cf200000000; // sub x^(10-1) * (34! * 10^(10-1) / 10!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000000233c33c676a5eb2416094a87b3657000000; // add x^(11-1) * (34! * 11^(11-1) / 11!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x0000000541cde48bc0254bed49a9f8700000000000; // sub x^(12-1) * (34! * 12^(12-1) / 12!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000000cae1fad2cdd4d4cb8d73abca0d19a400000; // add x^(13-1) * (34! * 13^(13-1) / 13!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x0000001edb2aa2f760d15c41ceedba956400000000; // sub x^(14-1) * (34! * 14^(14-1) / 14!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000004ba8d20d2dabd386c9529659841a2e200000; // add x^(15-1) * (34! * 15^(15-1) / 15!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x000000bac08546b867cdaa20000000000000000000; // sub x^(16-1) * (34! * 16^(16-1) / 16!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000001cfa8e70c03625b9db76c8ebf5bbf24820000; // add x^(17-1) * (34! * 17^(17-1) / 17!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x000004851d99f82060df265f3309b26f8200000000; // sub x^(18-1) * (34! * 18^(18-1) / 18!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x00000b550d19b129d270c44f6f55f027723cbb0000; // add x^(19-1) * (34! * 19^(19-1) / 19!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x00001c877dadc761dc272deb65d4b0000000000000; // sub x^(20-1) * (34! * 20^(20-1) / 20!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000048178ece97479f33a77f2ad22a81b64406c000; // add x^(21-1) * (34! * 21^(21-1) / 21!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x0000b6ca8268b9d810fedf6695ef2f8a6c00000000; // sub x^(22-1) * (34! * 22^(22-1) / 22!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0001d0e76631a5b05d007b8cb72a7c7f11ec36e000; // add x^(23-1) * (34! * 23^(23-1) / 23!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x0004a1c37bd9f85fd9c6c780000000000000000000; // sub x^(24-1) * (34! * 24^(24-1) / 24!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000bd8369f1b702bf491e2ebfcee08250313b65400; // add x^(25-1) * (34! * 25^(25-1) / 25!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x001e5c7c32a9f6c70ab2cb59d9225764d400000000; // sub x^(26-1) * (34! * 26^(26-1) / 26!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x004dff5820e165e910f95120a708e742496221e600; // add x^(27-1) * (34! * 27^(27-1) / 27!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x00c8c8f66db1fced378ee50e536000000000000000; // sub x^(28-1) * (34! * 28^(28-1) / 28!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0205db8dffff45bfa2938f128f599dbf16eb11d880; // add x^(29-1) * (34! * 29^(29-1) / 29!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x053a044ebd984351493e1786af38d39a0800000000; // sub x^(30-1) * (34! * 30^(30-1) / 30!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0d86dae2a4cc0f47633a544479735869b487b59c40; // add x^(31-1) * (34! * 31^(31-1) / 31!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0x231000000000000000000000000000000000000000; // sub x^(32-1) * (34! * 32^(32-1) / 32!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x5b0485a76f6646c2039db1507cdd51b08649680822; // add x^(33-1) * (34! * 33^(33-1) / 33!)
                                xi = (xi * _x) / FIXED_1; res -= xi * 0xec983c46c49545bc17efa6b5b0055e242200000000; // sub x^(34-1) * (34! * 34^(34-1) / 34!)
                        
                                return res / 0xde1bc4d19efcac82445da75b00000000; // divide by 34!
                            }
                        
                            /**
                              * @dev computes W(x / FIXED_1) / (x / FIXED_1) * FIXED_1
                              * input range: LAMBERT_CONV_RADIUS + 1 <= x <= LAMBERT_POS2_MAXVAL
                            */
                            function lambertPos2(uint256 _x) internal view returns (uint256) {
                                uint256 x = _x - LAMBERT_CONV_RADIUS - 1;
                                uint256 i = x / LAMBERT_POS2_SAMPLE;
                                uint256 a = LAMBERT_POS2_SAMPLE * i;
                                uint256 b = LAMBERT_POS2_SAMPLE * (i + 1);
                                uint256 c = lambertArray[i];
                                uint256 d = lambertArray[i + 1];
                                return (c * (b - x) + d * (x - a)) / LAMBERT_POS2_SAMPLE;
                            }
                        
                            /**
                              * @dev computes W(x / FIXED_1) / (x / FIXED_1) * FIXED_1
                              * input range: LAMBERT_POS2_MAXVAL + 1 <= x <= LAMBERT_POS3_MAXVAL
                            */
                            function lambertPos3(uint256 _x) internal pure returns (uint256) {
                                uint256 l1 = _x < OPT_LOG_MAX_VAL ? optimalLog(_x) : generalLog(_x);
                                uint256 l2 = l1 < OPT_LOG_MAX_VAL ? optimalLog(l1) : generalLog(l1);
                                return (l1 - l2 + l2 * FIXED_1 / l1) * FIXED_1 / _x;
                            }
                        
                            /**
                              * @dev computes W(-x / FIXED_1) / (-x / FIXED_1) * FIXED_1
                              * input range: 1 <= x <= 1 / e * FIXED_1
                              * auto-generated via 'PrintFunctionLambertNeg1.py'
                            */
                            function lambertNeg1(uint256 _x) internal pure returns (uint256) {
                                uint256 xi = _x;
                                uint256 res = 0;
                        
                                xi = (xi * _x) / FIXED_1; res += xi * 0x00000000014d29a73a6e7b02c3668c7b0880000000; // add x^(03-1) * (34! * 03^(03-1) / 03!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000000002504a0cd9a7f7215b60f9be4800000000; // add x^(04-1) * (34! * 04^(04-1) / 04!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000000000484d0a1191c0ead267967c7a4a0000000; // add x^(05-1) * (34! * 05^(05-1) / 05!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x00000000095ec580d7e8427a4baf26a90a00000000; // add x^(06-1) * (34! * 06^(06-1) / 06!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000000001440b0be1615a47dba6e5b3b1f10000000; // add x^(07-1) * (34! * 07^(07-1) / 07!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000000002d207601f46a99b4112418400000000000; // add x^(08-1) * (34! * 08^(08-1) / 08!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000000066ebaac4c37c622dd8288a7eb1b2000000; // add x^(09-1) * (34! * 09^(09-1) / 09!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x00000000ef17240135f7dbd43a1ba10cf200000000; // add x^(10-1) * (34! * 10^(10-1) / 10!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000000233c33c676a5eb2416094a87b3657000000; // add x^(11-1) * (34! * 11^(11-1) / 11!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000000541cde48bc0254bed49a9f8700000000000; // add x^(12-1) * (34! * 12^(12-1) / 12!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000000cae1fad2cdd4d4cb8d73abca0d19a400000; // add x^(13-1) * (34! * 13^(13-1) / 13!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000001edb2aa2f760d15c41ceedba956400000000; // add x^(14-1) * (34! * 14^(14-1) / 14!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000004ba8d20d2dabd386c9529659841a2e200000; // add x^(15-1) * (34! * 15^(15-1) / 15!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000000bac08546b867cdaa20000000000000000000; // add x^(16-1) * (34! * 16^(16-1) / 16!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000001cfa8e70c03625b9db76c8ebf5bbf24820000; // add x^(17-1) * (34! * 17^(17-1) / 17!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000004851d99f82060df265f3309b26f8200000000; // add x^(18-1) * (34! * 18^(18-1) / 18!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x00000b550d19b129d270c44f6f55f027723cbb0000; // add x^(19-1) * (34! * 19^(19-1) / 19!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x00001c877dadc761dc272deb65d4b0000000000000; // add x^(20-1) * (34! * 20^(20-1) / 20!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000048178ece97479f33a77f2ad22a81b64406c000; // add x^(21-1) * (34! * 21^(21-1) / 21!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0000b6ca8268b9d810fedf6695ef2f8a6c00000000; // add x^(22-1) * (34! * 22^(22-1) / 22!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0001d0e76631a5b05d007b8cb72a7c7f11ec36e000; // add x^(23-1) * (34! * 23^(23-1) / 23!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0004a1c37bd9f85fd9c6c780000000000000000000; // add x^(24-1) * (34! * 24^(24-1) / 24!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x000bd8369f1b702bf491e2ebfcee08250313b65400; // add x^(25-1) * (34! * 25^(25-1) / 25!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x001e5c7c32a9f6c70ab2cb59d9225764d400000000; // add x^(26-1) * (34! * 26^(26-1) / 26!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x004dff5820e165e910f95120a708e742496221e600; // add x^(27-1) * (34! * 27^(27-1) / 27!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x00c8c8f66db1fced378ee50e536000000000000000; // add x^(28-1) * (34! * 28^(28-1) / 28!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0205db8dffff45bfa2938f128f599dbf16eb11d880; // add x^(29-1) * (34! * 29^(29-1) / 29!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x053a044ebd984351493e1786af38d39a0800000000; // add x^(30-1) * (34! * 30^(30-1) / 30!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x0d86dae2a4cc0f47633a544479735869b487b59c40; // add x^(31-1) * (34! * 31^(31-1) / 31!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x231000000000000000000000000000000000000000; // add x^(32-1) * (34! * 32^(32-1) / 32!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0x5b0485a76f6646c2039db1507cdd51b08649680822; // add x^(33-1) * (34! * 33^(33-1) / 33!)
                                xi = (xi * _x) / FIXED_1; res += xi * 0xec983c46c49545bc17efa6b5b0055e242200000000; // add x^(34-1) * (34! * 34^(34-1) / 34!)
                        
                                return res / 0xde1bc4d19efcac82445da75b00000000 + _x + FIXED_1; // divide by 34! and then add x^(2-1) * (34! * 2^(2-1) / 2!) + x^(1-1) * (34! * 1^(1-1) / 1!)
                            }
                        
                            /**
                              * @dev computes the weights based on "W(log(hi / lo) * tq / rp) * tq / rp", where "W" is a variation of the Lambert W function.
                            */
                            function balancedWeightsByStake(uint256 _hi, uint256 _lo, uint256 _tq, uint256 _rp, bool _lowerStake) internal view returns (uint32, uint32) {
                                (_tq, _rp) = safeFactors(_tq, _rp);
                                uint256 f = _hi.mul(FIXED_1) / _lo;
                                uint256 g = f < OPT_LOG_MAX_VAL ? optimalLog(f) : generalLog(f);
                                uint256 x = g.mul(_tq) / _rp;
                                uint256 y = _lowerStake ? lowerStake(x) : higherStake(x);
                                return normalizedWeights(y.mul(_tq), _rp.mul(FIXED_1));
                            }
                        
                            /**
                              * @dev reduces "a" and "b" while maintaining their ratio.
                            */
                            function safeFactors(uint256 _a, uint256 _b) internal pure returns (uint256, uint256) {
                                if (_a <= FIXED_2 && _b <= FIXED_2)
                                    return (_a, _b);
                                if (_a < FIXED_2)
                                    return (_a * FIXED_2 / _b, FIXED_2);
                                if (_b < FIXED_2)
                                    return (FIXED_2, _b * FIXED_2 / _a);
                                uint256 c = _a > _b ? _a : _b;
                                uint256 n = floorLog2(c / FIXED_1);
                                return (_a >> n, _b >> n);
                            }
                        
                            /**
                              * @dev computes "MAX_WEIGHT * a / (a + b)" and "MAX_WEIGHT * b / (a + b)".
                            */
                            function normalizedWeights(uint256 _a, uint256 _b) internal pure returns (uint32, uint32) {
                                if (_a <= _b)
                                    return accurateWeights(_a, _b);
                                (uint32 y, uint32 x) = accurateWeights(_b, _a);
                                return (x, y);
                            }
                        
                            /**
                              * @dev computes "MAX_WEIGHT * a / (a + b)" and "MAX_WEIGHT * b / (a + b)", assuming that "a <= b".
                            */
                            function accurateWeights(uint256 _a, uint256 _b) internal pure returns (uint32, uint32) {
                                if (_a > MAX_UNF_WEIGHT) {
                                    uint256 c = _a / (MAX_UNF_WEIGHT + 1) + 1;
                                    _a /= c;
                                    _b /= c;
                                }
                                uint256 x = roundDiv(_a * MAX_WEIGHT, _a.add(_b));
                                uint256 y = MAX_WEIGHT - x;
                                return (uint32(x), uint32(y));
                            }
                        
                            /**
                              * @dev computes the nearest integer to a given quotient without overflowing or underflowing.
                            */
                            function roundDiv(uint256 _n, uint256 _d) internal pure returns (uint256) {
                                return _n / _d + _n % _d / (_d - _d / 2);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function calculatePurchaseReturn(uint256 _supply,
                                                             uint256 _reserveBalance,
                                                             uint32 _reserveWeight,
                                                             uint256 _amount)
                                                             public view returns (uint256)
                            {
                                return purchaseTargetAmount(_supply, _reserveBalance, _reserveWeight, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function calculateSaleReturn(uint256 _supply,
                                                         uint256 _reserveBalance,
                                                         uint32 _reserveWeight,
                                                         uint256 _amount)
                                                         public view returns (uint256)
                            {
                                return saleTargetAmount(_supply, _reserveBalance, _reserveWeight, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function calculateCrossReserveReturn(uint256 _sourceReserveBalance,
                                                                 uint32 _sourceReserveWeight,
                                                                 uint256 _targetReserveBalance,
                                                                 uint32 _targetReserveWeight,
                                                                 uint256 _amount)
                                                                 public view returns (uint256)
                            {
                                return crossReserveTargetAmount(_sourceReserveBalance, _sourceReserveWeight, _targetReserveBalance, _targetReserveWeight, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function calculateCrossConnectorReturn(uint256 _sourceReserveBalance,
                                                                   uint32 _sourceReserveWeight,
                                                                   uint256 _targetReserveBalance,
                                                                   uint32 _targetReserveWeight,
                                                                   uint256 _amount)
                                                                   public view returns (uint256)
                            {
                                return crossReserveTargetAmount(_sourceReserveBalance, _sourceReserveWeight, _targetReserveBalance, _targetReserveWeight, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function calculateFundCost(uint256 _supply,
                                                       uint256 _reserveBalance,
                                                       uint32 _reserveRatio,
                                                       uint256 _amount)
                                                       public view returns (uint256)
                            {
                                return fundCost(_supply, _reserveBalance, _reserveRatio, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function calculateLiquidateReturn(uint256 _supply,
                                                              uint256 _reserveBalance,
                                                              uint32 _reserveRatio,
                                                              uint256 _amount)
                                                              public view returns (uint256)
                            {
                                return liquidateReserveAmount(_supply, _reserveBalance, _reserveRatio, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function purchaseRate(uint256 _supply,
                                                  uint256 _reserveBalance,
                                                  uint32 _reserveWeight,
                                                  uint256 _amount)
                                                  public view returns (uint256)
                            {
                                return purchaseTargetAmount(_supply, _reserveBalance, _reserveWeight, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function saleRate(uint256 _supply,
                                              uint256 _reserveBalance,
                                              uint32 _reserveWeight,
                                              uint256 _amount)
                                              public view returns (uint256)
                            {
                                return saleTargetAmount(_supply, _reserveBalance, _reserveWeight, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function crossReserveRate(uint256 _sourceReserveBalance,
                                                      uint32 _sourceReserveWeight,
                                                      uint256 _targetReserveBalance,
                                                      uint32 _targetReserveWeight,
                                                      uint256 _amount)
                                                      public view returns (uint256)
                            {
                                return crossReserveTargetAmount(_sourceReserveBalance, _sourceReserveWeight, _targetReserveBalance, _targetReserveWeight, _amount);
                            }
                        
                            /**
                              * @dev deprecated, backward compatibility
                            */
                            function liquidateRate(uint256 _supply,
                                                   uint256 _reserveBalance,
                                                   uint32 _reserveRatio,
                                                   uint256 _amount)
                                                   public view returns (uint256)
                            {
                                return liquidateReserveAmount(_supply, _reserveBalance, _reserveRatio, _amount);
                            }
                        }