ETH Price: $2,641.66 (+0.11%)

Transaction Decoder

Block:
14781314 at May-15-2022 05:41:32 PM +UTC
Transaction Fee:
0.009560374000681432 ETH $25.26
Gas Used:
421,756 Gas / 22.668021322 Gwei

Account State Difference:

  Address   Before After State Difference Code
(Flexpool.io)
6,021.362814416752871065 Eth6,021.364923196752871065 Eth0.00210878
0xe0e3d9e1...58B046999
4.803898012820243361 Eth
Nonce: 33
4.794337638819561929 Eth
Nonce: 34
0.009560374000681432

Execution Trace

AggregationRouterV4.swap( caller=0x220bdA5c8994804Ac96ebe4DF184d25e5c2196D4, desc=[{name:srcToken, type:address, order:1, indexed:false, value:0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, valueString:0xec67005c4E498Ec7f55E092bd1d35cbC47C91892}, {name:dstToken, type:address, order:2, indexed:false, value:0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, valueString:0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE}, {name:srcReceiver, type:address, order:3, indexed:false, value:0x220bdA5c8994804Ac96ebe4DF184d25e5c2196D4, valueString:0x220bdA5c8994804Ac96ebe4DF184d25e5c2196D4}, {name:dstReceiver, type:address, order:4, indexed:false, value:0xe0e3d9e170CA8375b96BddA0b87e56958B046999, valueString:0xe0e3d9e170CA8375b96BddA0b87e56958B046999}, {name:amount, type:uint256, order:5, indexed:false, value:194692018078906485093, valueString:194692018078906485093}, {name:minReturnAmount, type:uint256, order:6, indexed:false, value:3931560594962863555, valueString:3931560594962863555}, {name:flags, type:uint256, order:7, indexed:false, value:4, valueString:4}, {name:permit, type:bytes, order:8, indexed:false, value:0x, valueString:0x}], data=0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001408000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064EB5625D9000000000000000000000000EC67005C4E498EC7F55E092BD1D35CBC47C918920000000000000000000000009AAB3F75489902F3A48495025729A0AF77D4B11E00000000000000000000000000000000000000000000000A8DE4FEEB95268965000000000000000000000000000000000000000000000000000000008000000000000000000000009AAB3F75489902F3A48495025729A0AF77D4B11E0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001E429589F61000000000000000000000000EC67005C4E498EC7F55E092BD1D35CBC47C9189200000000000000000000000000000000000000000000000A8DE4FEEB95268965000000000000000000000000EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE0000000000000000000000001111111254FB6C44BAC0BED2854E76F90643097DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000910BF2D50FA5E014FD06666F456182D4AB7C8BD2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000C00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4D656C6F6E706F727400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) => ( returnAmount=3963877391197344453575983046348115674221700746820753546331534351508065746944, spentAmount=862718293348820473429344482784628181556388621521298319395315527974912, gasLeft=736595904062001181189311382533701486956786394523560224078710411061248 )
  • Melon.transferFrom( from=0xe0e3d9e170CA8375b96BddA0b87e56958B046999, to=0x220bdA5c8994804Ac96ebe4DF184d25e5c2196D4, value=194692018078906485093 ) => ( True )
  • 1inch: Aggregation Executor.2636f7f8( )
    • 1inch: Aggregation Executor.eb5625d9( )
      • Melon.approve( spender=0x9AAb3f75489902f3a48495025729a0AF77d4b11e, value=194692018078906485093 ) => ( True )
      • KyberNetworkProxy.tradeWithHint( src=0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, srcAmount=194692018078906485093, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x1111111254fb6c44bAC0beD2854e76F90643097d, maxDestAmount=115792089237316195423570985008687907853269984665640564039457584007913129639935, minConversionRate=0, walletId=0x910bf2d50fA5e014Fd06666f456182D4Ab7c8bd2, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4D656C6F6E706F7274000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) => ( 3848257841461388602 )
        • Melon.balanceOf( owner=0x220bdA5c8994804Ac96ebe4DF184d25e5c2196D4 ) => ( 194692018078906485093 )
        • Melon.transferFrom( from=0x220bdA5c8994804Ac96ebe4DF184d25e5c2196D4, to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, value=194692018078906485093 ) => ( True )
        • KyberNetwork.tradeWithHintAndFee( trader=0x220bdA5c8994804Ac96ebe4DF184d25e5c2196D4, src=0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, srcAmount=194692018078906485093, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x1111111254fb6c44bAC0beD2854e76F90643097d, maxDestAmount=115792089237316195423570985008687907853269984665640564039457584007913129639935, minConversionRate=0, platformWallet=0x910bf2d50fA5e014Fd06666f456182D4Ab7c8bd2, platformFeeBps=0, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4D656C6F6E706F7274000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) => ( destAmount=3848257841461388602 )
          • Melon.balanceOf( owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 194692018078906485389 )
          • KyberMatchingEngine.getTradingReserves( src=0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, isTokenToToken=False, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4D656C6F6E706F7274000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 )
          • KyberStorage.getReservesData( ) => ( areAllReservesListed=True, feeAccountedArr=[true], entitledRebateArr=[false], reserveAddresses=[0xa33c7c22d0BB673c2aEa2C048BB883b679fa1BE9] )
          • KyberReserve.getConversionRate( src=0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=194692018078906485093, blockNumber=14781314 ) => ( 19785659368180079 )
            • LiquidityConversionRates.getRate( conversionToken=0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, currentBlockNumber=14781314, buy=False, qtyInSrcWei=194692018078906485093 ) => ( 19785659368180079 )
            • KyberMatchingEngine.doMatch( src=0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmounts=[194692018078906485093], feesAccountedDestBps=[10], rates=[19785659368180079] ) => ( reserveIndexes=[0] )
            • Melon.balanceOf( owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 194692018078906485389 )
            • KyberReserve.trade( srcToken=0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, srcAmount=194692018078906485093, destToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, conversionRate=19785659368180079, validate=True ) => ( True )
              • LiquidityConversionRates.recordImbalance( conversionToken=0xec67005c4E498Ec7f55E092bd1d35cbC47C91892, buyAmountInTwei=-194692018078906485093, rateUpdateBlock=0, currentBlock=14781314 )
              • Melon.transferFrom( from=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, to=0xa33c7c22d0BB673c2aEa2C048BB883b679fa1BE9, value=194692018078906485093 ) => ( True )
              • ETH 3.852109951412801403 KyberNetwork.CALL( )
              • Melon.balanceOf( owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 296 )
              • ETH 3.848257841461388602 AggregationRouterV4.CALL( )
              • ETH 0.003852109951412801 KatanaFeeHandler.handleFees( token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, rebateWallets=[], rebateBpsPerWallet=[], platformWallet=0x910bf2d50fA5e014Fd06666f456182D4Ab7c8bd2, platformFee=0, networkFee=3852109951412801 )
                • ETH 0.002802409989652812 TreasuryPool.CALL( )
                • Melon.balanceOf( owner=0x220bdA5c8994804Ac96ebe4DF184d25e5c2196D4 ) => ( 0 )
                  File 1 of 10: AggregationRouterV4
                  /*
                                                                             ,▄▓▓██▌   ,╓▄▄▓▓▓▓▓▓▓▓▄▄▄,,
                                                                          ,▓██▓███▓▄▓███▓╬╬╬╬╬╬╬╬╬╬╬╬╬▓███▓▄,
                                                                    ▄█   ▓██╬╣███████╬▓▀╬╬▓▓▓████████████▓█████▄,
                                                                   ▓██▌ ▓██╬╣██████╬▓▌  ██████████████████████▌╙╙▀ⁿ
                                                                  ▐████████╬▓████▓▓█╨ ▄ ╟█████████▓▓╬╬╬╬╬▓▓█████▓▄
                                                    └▀▓▓▄╓        ╟█▓╣█████▓██████▀ ╓█▌ ███████▓▓▓▓▓╬╬╬╬╬╬╬╬╬╬╬╬▓██▓▄
                                                       └▀████▓▄╥  ▐██╬╬██████████╙ Æ▀─ ▓███▀╚╠╬╩▀▀███████▓▓╬╬╬╬╬╬╬╬╬██▄
                                                          └▀██▓▀▀█████▓╬▓██████▀     ▄█████▒╠"      └╙▓██████▓╬╬╬╬╬╬╬╬██▄
                                                             └▀██▄,└╙▀▀████▌└╙    ^"▀╙╙╙"╙██      @▄    ╙▀███████╬╬╬╬╬╬╬██µ
                                                                └▀██▓▄, ██▌       ╒       ╙█▓     ]▓█▓╔    ▀███████▓╬╬╬╬╬▓█▌
                                                                    ▀█████       ▓         ╟█▌    ]╠██▓░▒╓   ▀████████╬╬╬╬╣█▌
                                                                    ▐████      ╓█▀█▌      ,██▌    ╚Å███▓▒▒╠╓  ╙█████████╬╬╬╣█▌
                                                                    └████     ▓█░░▓█      ▀▀▀    φ▒╫████▒▒▒▒╠╓  █████████▓╬╬▓█µ
                                                                     ╘███µ ▌▄█▓▄▓▀`     ,▀    ,╔╠░▓██████▌╠▒▒▒φ  ██████████╬╬██
                                                                     ▐████µ╙▓▀`     ,▀╙,╔╔φφφ╠░▄▓███████▌░▓╙▒▒▒╠ └██╬███████╬▓█⌐
                                                                     ╫██ ▓▌         ▌φ▒▒░▓██████████████▌▒░▓╚▒▒▒╠ ▓██╬▓██████╣█▌
                                                                     ██▌           ▌╔▒▒▄████████████████▒▒▒░▌╠▒▒▒≥▐██▓╬╬███████▌
                                                                     ██▌      ,╓φ╠▓«▒▒▓████▀  ▀█████████▌▒▒▒╟░▒▒▒▒▐███╬╬╣████▓█▌
                                                                    ▐██      ╠▒▄▓▓███▓████└     ▀████████▌▒▒░▌╚▒▒▒▐███▓╬╬████ ╙▌
                                                                    ███  )  ╠▒░░░▒░╬████▀        └████████░▒▒░╬∩▒▒▓████╬╬╣███
                                                                   ▓██    ╠╠▒▒▐█▀▀▌`░╫██           ███████▒▒▒▒░▒▒½█████╬╬╣███
                                                                  ███ ,█▄ ╠▒▒▒╫▌,▄▀,▒╫██           ╟██████▒▒▒░╣⌠▒▓█████╬╬╣██▌
                                                                 ╘██µ ██` ╠▒▒░██╬φ╠▄▓██`            ██████░░▌φ╠░▓█████▓╬╬▓██
                                                                  ╟██  .φ╠▒░▄█▀░░▄██▀└              █████▌▒╣φ▒░▓██████╬╬╣██
                                                                   ▀██▄▄▄╓▄███████▀                ▐█████░▓φ▒▄███████▓╬╣██
                                                                     ╙▀▀▀██▀└                      ████▓▄▀φ▄▓████████╬▓█▀
                                                                                                  ▓███╬╩╔╣██████████▓██└
                                                                                                ╓████▀▄▓████████▀████▀
                                                                                              ,▓███████████████─]██╙
                                                                                           ,▄▓██████████████▀└  ╙
                                                                                      ,╓▄▓███████████████▀╙
                                                                               `"▀▀▀████████▀▀▀▀`▄███▀▀└
                                                                                                └└
                  
                  
                  
                                      11\   11\                     11\             11\   11\            11\                                       11\
                                    1111 |  \__|                    11 |            111\  11 |           11 |                                      11 |
                                    \_11 |  11\ 1111111\   1111111\ 1111111\        1111\ 11 | 111111\ 111111\   11\  11\  11\  111111\   111111\  11 |  11\
                                      11 |  11 |11  __11\ 11  _____|11  __11\       11 11\11 |11  __11\\_11  _|  11 | 11 | 11 |11  __11\ 11  __11\ 11 | 11  |
                                      11 |  11 |11 |  11 |11 /      11 |  11 |      11 \1111 |11111111 | 11 |    11 | 11 | 11 |11 /  11 |11 |  \__|111111  /
                                      11 |  11 |11 |  11 |11 |      11 |  11 |      11 |\111 |11   ____| 11 |11\ 11 | 11 | 11 |11 |  11 |11 |      11  _11<
                                    111111\ 11 |11 |  11 |\1111111\ 11 |  11 |      11 | \11 |\1111111\  \1111  |\11111\1111  |\111111  |11 |      11 | \11\
                                    \______|\__|\__|  \__| \_______|\__|  \__|      \__|  \__| \_______|  \____/  \_____\____/  \______/ \__|      \__|  \__|
                  
                  
                  
                                                 111111\                                                               11\     11\
                                                11  __11\                                                              11 |    \__|
                                                11 /  11 | 111111\   111111\   111111\   111111\   111111\   111111\ 111111\   11\  111111\  1111111\
                                                11111111 |11  __11\ 11  __11\ 11  __11\ 11  __11\ 11  __11\  \____11\\_11  _|  11 |11  __11\ 11  __11\
                                                11  __11 |11 /  11 |11 /  11 |11 |  \__|11111111 |11 /  11 | 1111111 | 11 |    11 |11 /  11 |11 |  11 |
                                                11 |  11 |11 |  11 |11 |  11 |11 |      11   ____|11 |  11 |11  __11 | 11 |11\ 11 |11 |  11 |11 |  11 |
                                                11 |  11 |\1111111 |\1111111 |11 |      \1111111\ \1111111 |\1111111 | \1111  |11 |\111111  |11 |  11 |
                                                \__|  \__| \____11 | \____11 |\__|       \_______| \____11 | \_______|  \____/ \__| \______/ \__|  \__|
                                                          11\   11 |11\   11 |                    11\   11 |
                                                          \111111  |\111111  |                    \111111  |
                                                           \______/  \______/                      \______/
                                                                  1111111\                        11\
                                                                  11  __11\                       11 |
                                                                  11 |  11 | 111111\  11\   11\ 111111\    111111\   111111\
                                                                  1111111  |11  __11\ 11 |  11 |\_11  _|  11  __11\ 11  __11\
                                                                  11  __11< 11 /  11 |11 |  11 |  11 |    11111111 |11 |  \__|
                                                                  11 |  11 |11 |  11 |11 |  11 |  11 |11\ 11   ____|11 |
                                                                  11 |  11 |\111111  |\111111  |  \1111  |\1111111\ 11 |
                                                                  \__|  \__| \______/  \______/    \____/  \_______|\__|
                  */
                  
                  // File @openzeppelin/contracts/utils/[email protected]
                  
                  // SPDX-License-Identifier: MIT
                  
                  pragma solidity >=0.6.0 <0.8.0;
                  
                  /*
                   * @dev Provides information about the current execution context, including the
                   * sender of the transaction and its data. While these are generally available
                   * via msg.sender and msg.data, they should not be accessed in such a direct
                   * manner, since when dealing with GSN meta-transactions the account sending and
                   * paying for execution may not be the actual sender (as far as an application
                   * is concerned).
                   *
                   * This contract is only required for intermediate, library-like contracts.
                   */
                  abstract contract Context {
                      function _msgSender() internal view virtual returns (address payable) {
                          return msg.sender;
                      }
                  
                      function _msgData() internal view virtual returns (bytes memory) {
                          this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                          return msg.data;
                      }
                  }
                  
                  
                  // File @openzeppelin/contracts/access/[email protected]
                  
                  
                  pragma solidity ^0.7.0;
                  
                  /**
                   * @dev Contract module which provides a basic access control mechanism, where
                   * there is an account (an owner) that can be granted exclusive access to
                   * specific functions.
                   *
                   * By default, the owner account will be the one that deploys the contract. This
                   * can later be changed with {transferOwnership}.
                   *
                   * This module is used through inheritance. It will make available the modifier
                   * `onlyOwner`, which can be applied to your functions to restrict their use to
                   * the owner.
                   */
                  abstract contract Ownable is Context {
                      address private _owner;
                  
                      event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                  
                      /**
                       * @dev Initializes the contract setting the deployer as the initial owner.
                       */
                      constructor () {
                          address msgSender = _msgSender();
                          _owner = msgSender;
                          emit OwnershipTransferred(address(0), msgSender);
                      }
                  
                      /**
                       * @dev Returns the address of the current owner.
                       */
                      function owner() public view virtual returns (address) {
                          return _owner;
                      }
                  
                      /**
                       * @dev Throws if called by any account other than the owner.
                       */
                      modifier onlyOwner() {
                          require(owner() == _msgSender(), "Ownable: caller is not the owner");
                          _;
                      }
                  
                      /**
                       * @dev Leaves the contract without owner. It will not be possible to call
                       * `onlyOwner` functions anymore. Can only be called by the current owner.
                       *
                       * NOTE: Renouncing ownership will leave the contract without an owner,
                       * thereby removing any functionality that is only available to the owner.
                       */
                      function renounceOwnership() public virtual onlyOwner {
                          emit OwnershipTransferred(_owner, address(0));
                          _owner = address(0);
                      }
                  
                      /**
                       * @dev Transfers ownership of the contract to a new account (`newOwner`).
                       * Can only be called by the current owner.
                       */
                      function transferOwnership(address newOwner) public virtual onlyOwner {
                          require(newOwner != address(0), "Ownable: new owner is the zero address");
                          emit OwnershipTransferred(_owner, newOwner);
                          _owner = newOwner;
                      }
                  }
                  
                  
                  // File @openzeppelin/contracts/token/ERC20/[email protected]
                  
                  
                  pragma solidity ^0.7.0;
                  
                  /**
                   * @dev Interface of the ERC20 standard as defined in the EIP.
                   */
                  interface IERC20 {
                      /**
                       * @dev Returns the amount of tokens in existence.
                       */
                      function totalSupply() external view returns (uint256);
                  
                      /**
                       * @dev Returns the amount of tokens owned by `account`.
                       */
                      function balanceOf(address account) external view returns (uint256);
                  
                      /**
                       * @dev Moves `amount` tokens from the caller's account to `recipient`.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transfer(address recipient, uint256 amount) external returns (bool);
                  
                      /**
                       * @dev Returns the remaining number of tokens that `spender` will be
                       * allowed to spend on behalf of `owner` through {transferFrom}. This is
                       * zero by default.
                       *
                       * This value changes when {approve} or {transferFrom} are called.
                       */
                      function allowance(address owner, address spender) external view returns (uint256);
                  
                      /**
                       * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * IMPORTANT: Beware that changing an allowance with this method brings the risk
                       * that someone may use both the old and the new allowance by unfortunate
                       * transaction ordering. One possible solution to mitigate this race
                       * condition is to first reduce the spender's allowance to 0 and set the
                       * desired value afterwards:
                       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                       *
                       * Emits an {Approval} event.
                       */
                      function approve(address spender, uint256 amount) external returns (bool);
                  
                      /**
                       * @dev Moves `amount` tokens from `sender` to `recipient` using the
                       * allowance mechanism. `amount` is then deducted from the caller's
                       * allowance.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                  
                      /**
                       * @dev Emitted when `value` tokens are moved from one account (`from`) to
                       * another (`to`).
                       *
                       * Note that `value` may be zero.
                       */
                      event Transfer(address indexed from, address indexed to, uint256 value);
                  
                      /**
                       * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                       * a call to {approve}. `value` is the new allowance.
                       */
                      event Approval(address indexed owner, address indexed spender, uint256 value);
                  }
                  
                  
                  // File @openzeppelin/contracts/math/[email protected]
                  
                  
                  pragma solidity ^0.7.0;
                  
                  /**
                   * @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, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          uint256 c = a + b;
                          if (c < a) return (false, 0);
                          return (true, c);
                      }
                  
                      /**
                       * @dev Returns the substraction of two unsigned integers, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b > a) return (false, 0);
                          return (true, a - b);
                      }
                  
                      /**
                       * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryMul(uint256 a, uint256 b) internal pure returns (bool, 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 (true, 0);
                          uint256 c = a * b;
                          if (c / a != b) return (false, 0);
                          return (true, c);
                      }
                  
                      /**
                       * @dev Returns the division of two unsigned integers, with a division by zero flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b == 0) return (false, 0);
                          return (true, a / b);
                      }
                  
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b == 0) return (false, 0);
                          return (true, a % b);
                      }
                  
                      /**
                       * @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) {
                          require(b <= a, "SafeMath: subtraction overflow");
                          return a - b;
                      }
                  
                      /**
                       * @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) {
                          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, reverting 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) {
                          require(b > 0, "SafeMath: division by zero");
                          return a / b;
                      }
                  
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                       * reverting 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) {
                          require(b > 0, "SafeMath: modulo by zero");
                          return a % b;
                      }
                  
                      /**
                       * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                       * overflow (when the result is negative).
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {trySub}.
                       *
                       * 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);
                          return a - b;
                      }
                  
                      /**
                       * @dev Returns the integer division of two unsigned integers, reverting with custom message on
                       * division by zero. The result is rounded towards zero.
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {tryDiv}.
                       *
                       * 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) {
                          require(b > 0, errorMessage);
                          return a / b;
                      }
                  
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                       * reverting with custom message when dividing by zero.
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {tryMod}.
                       *
                       * 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;
                      }
                  }
                  
                  
                  // File @openzeppelin/contracts/utils/[email protected]
                  
                  
                  pragma solidity ^0.7.0;
                  
                  /**
                   * @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) {
                          // This method relies on extcodesize, which returns 0 for contracts in
                          // construction, since the code is only stored at the end of the
                          // constructor execution.
                  
                          uint256 size;
                          // solhint-disable-next-line no-inline-assembly
                          assembly { size := extcodesize(account) }
                          return size > 0;
                      }
                  
                      /**
                       * @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");
                      }
                  
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain`call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                        return functionCall(target, data, "Address: low-level call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          require(isContract(target), "Address: call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.call{ value: value }(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                          require(isContract(target), "Address: static call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                      }
                  
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                          require(isContract(target), "Address: delegate call to non-contract");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                  
                      function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              // Look for revert reason and bubble it up if present
                              if (returndata.length > 0) {
                                  // The easiest way to bubble the revert reason is using memory via assembly
                  
                                  // solhint-disable-next-line no-inline-assembly
                                  assembly {
                                      let returndata_size := mload(returndata)
                                      revert(add(32, returndata), returndata_size)
                                  }
                              } else {
                                  revert(errorMessage);
                              }
                          }
                      }
                  }
                  
                  
                  // File @openzeppelin/contracts/token/ERC20/[email protected]
                  
                  
                  pragma solidity ^0.7.0;
                  
                  
                  
                  /**
                   * @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 IERC20;` 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));
                      }
                  
                      /**
                       * @dev Deprecated. This function has issues similar to the ones found in
                       * {IERC20-approve}, and its usage is discouraged.
                       *
                       * Whenever possible, use {safeIncreaseAllowance} and
                       * {safeDecreaseAllowance} instead.
                       */
                      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. We use {Address.functionCall} to perform this call, which verifies that
                          // the target address contains contract code and also asserts for success in the low-level call.
                  
                          bytes memory returndata = address(token).functionCall(data, "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/helpers/EthReceiver.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  /// @title Base contract with common payable logics
                  abstract contract EthReceiver {
                      receive() external payable {
                          // solhint-disable-next-line avoid-tx-origin
                          require(msg.sender != tx.origin, "ETH deposit rejected");
                      }
                  }
                  
                  
                  // File @openzeppelin/contracts/drafts/[email protected]
                  
                  
                  pragma solidity >=0.6.0 <0.8.0;
                  
                  /**
                   * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
                   * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
                   *
                   * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
                   * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
                   * need to send a transaction, and thus is not required to hold Ether at all.
                   */
                  interface IERC20Permit {
                      /**
                       * @dev Sets `value` as the allowance of `spender` over `owner`'s tokens,
                       * given `owner`'s signed approval.
                       *
                       * IMPORTANT: The same issues {IERC20-approve} has related to transaction
                       * ordering also apply here.
                       *
                       * Emits an {Approval} event.
                       *
                       * Requirements:
                       *
                       * - `spender` cannot be the zero address.
                       * - `deadline` must be a timestamp in the future.
                       * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
                       * over the EIP712-formatted function arguments.
                       * - the signature must use ``owner``'s current nonce (see {nonces}).
                       *
                       * For more information on the signature format, see the
                       * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
                       * section].
                       */
                      function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
                  
                      /**
                       * @dev Returns the current nonce for `owner`. This value must be
                       * included whenever a signature is generated for {permit}.
                       *
                       * Every successful call to {permit} increases ``owner``'s nonce by one. This
                       * prevents a signature from being used multiple times.
                       */
                      function nonces(address owner) external view returns (uint256);
                  
                      /**
                       * @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}.
                       */
                      // solhint-disable-next-line func-name-mixedcase
                      function DOMAIN_SEPARATOR() external view returns (bytes32);
                  }
                  
                  
                  // File contracts/helpers/RevertReasonParser.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  /// @title Library that allows to parse unsuccessful arbitrary calls revert reasons.
                  /// See https://solidity.readthedocs.io/en/latest/control-structures.html#revert for details.
                  /// Note that we assume revert reason being abi-encoded as Error(string) so it may fail to parse reason
                  /// if structured reverts appear in the future.
                  ///
                  /// All unsuccessful parsings get encoded as Unknown(data) string
                  library RevertReasonParser {
                      bytes4 constant private _PANIC_SELECTOR = bytes4(keccak256("Panic(uint256)"));
                      bytes4 constant private _ERROR_SELECTOR = bytes4(keccak256("Error(string)"));
                  
                      function parse(bytes memory data, string memory prefix) internal pure returns (string memory) {
                          if (data.length >= 4) {
                              bytes4 selector;
                              assembly {  // solhint-disable-line no-inline-assembly
                                  selector := mload(add(data, 0x20))
                              }
                  
                              // 68 = 4-byte selector + 32 bytes offset + 32 bytes length
                              if (selector == _ERROR_SELECTOR && data.length >= 68) {
                                  uint256 offset;
                                  bytes memory reason;
                                  // solhint-disable no-inline-assembly
                                  assembly {
                                      // 36 = 32 bytes data length + 4-byte selector
                                      offset := mload(add(data, 36))
                                      reason := add(data, add(36, offset))
                                  }
                                  /*
                                      revert reason is padded up to 32 bytes with ABI encoder: Error(string)
                                      also sometimes there is extra 32 bytes of zeros padded in the end:
                                      https://github.com/ethereum/solidity/issues/10170
                                      because of that we can't check for equality and instead check
                                      that offset + string length + extra 36 bytes is less than overall data length
                                  */
                                  require(data.length >= 36 + offset + reason.length, "Invalid revert reason");
                                  return string(abi.encodePacked(prefix, "Error(", reason, ")"));
                              }
                              // 36 = 4-byte selector + 32 bytes integer
                              else if (selector == _PANIC_SELECTOR && data.length == 36) {
                                  uint256 code;
                                  // solhint-disable no-inline-assembly
                                  assembly {
                                      // 36 = 32 bytes data length + 4-byte selector
                                      code := mload(add(data, 36))
                                  }
                                  return string(abi.encodePacked(prefix, "Panic(", _toHex(code), ")"));
                              }
                          }
                  
                          return string(abi.encodePacked(prefix, "Unknown(", _toHex(data), ")"));
                      }
                  
                      function _toHex(uint256 value) private pure returns(string memory) {
                          return _toHex(abi.encodePacked(value));
                      }
                  
                      function _toHex(bytes memory data) private pure returns(string memory) {
                          bytes16 alphabet = 0x30313233343536373839616263646566;
                          bytes memory str = new bytes(2 + data.length * 2);
                          str[0] = "0";
                          str[1] = "x";
                          for (uint256 i = 0; i < data.length; i++) {
                              str[2 * i + 2] = alphabet[uint8(data[i] >> 4)];
                              str[2 * i + 3] = alphabet[uint8(data[i] & 0x0f)];
                          }
                          return string(str);
                      }
                  }
                  
                  
                  // File contracts/interfaces/IDaiLikePermit.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  /// @title Interface for DAI-style permits
                  interface IDaiLikePermit {
                      function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external;
                  }
                  
                  
                  // File contracts/helpers/Permitable.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  
                  
                  /// @title Base contract with common permit handling logics
                  contract Permitable {
                      function _permit(address token, bytes calldata permit) internal {
                          if (permit.length > 0) {
                              bool success;
                              bytes memory result;
                              if (permit.length == 32 * 7) {
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (success, result) = token.call(abi.encodePacked(IERC20Permit.permit.selector, permit));
                              } else if (permit.length == 32 * 8) {
                                  // solhint-disable-next-line avoid-low-level-calls
                                  (success, result) = token.call(abi.encodePacked(IDaiLikePermit.permit.selector, permit));
                              } else {
                                  revert("Wrong permit length");
                              }
                              if (!success) {
                                  revert(RevertReasonParser.parse(result, "Permit failed: "));
                              }
                          }
                      }
                  }
                  
                  
                  // File contracts/helpers/UniERC20.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  
                  
                  
                  library UniERC20 {
                      using SafeMath for uint256;
                      using SafeERC20 for IERC20;
                  
                      IERC20 private constant _ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
                      IERC20 private constant _ZERO_ADDRESS = IERC20(0);
                  
                      function isETH(IERC20 token) internal pure returns (bool) {
                          return (token == _ZERO_ADDRESS || token == _ETH_ADDRESS);
                      }
                  
                      function uniBalanceOf(IERC20 token, address account) internal view returns (uint256) {
                          if (isETH(token)) {
                              return account.balance;
                          } else {
                              return token.balanceOf(account);
                          }
                      }
                  
                      function uniTransfer(IERC20 token, address payable to, uint256 amount) internal {
                          if (amount > 0) {
                              if (isETH(token)) {
                                  to.transfer(amount);
                              } else {
                                  token.safeTransfer(to, amount);
                              }
                          }
                      }
                  
                      function uniApprove(IERC20 token, address to, uint256 amount) internal {
                          require(!isETH(token), "Approve called on ETH");
                  
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = address(token).call(abi.encodeWithSelector(token.approve.selector, to, amount));
                  
                          if (!success || (returndata.length > 0 && !abi.decode(returndata, (bool)))) {
                              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, to, 0));
                              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, to, amount));
                          }
                      }
                  
                      function _callOptionalReturn(IERC20 token, bytes memory data) private {
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory result) = address(token).call(data);
                          if (!success) {
                              revert(RevertReasonParser.parse(result, "Low-level call failed: "));
                          }
                  
                          if (result.length > 0) { // Return data is optional
                              require(abi.decode(result, (bool)), "ERC20 operation did not succeed");
                          }
                      }
                  }
                  
                  
                  // File contracts/interfaces/IAggregationExecutor.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  /// @title Interface for making arbitrary calls during swap
                  interface IAggregationExecutor {
                      /// @notice Make calls on `msgSender` with specified data
                      function callBytes(address msgSender, bytes calldata data) external payable;  // 0x2636f7f8
                  }
                  
                  
                  // File @openzeppelin/contracts/drafts/[email protected]
                  
                  
                  pragma solidity >=0.6.0 <0.8.0;
                  
                  /**
                   * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
                   *
                   * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
                   * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
                   * they need in their contracts using a combination of `abi.encode` and `keccak256`.
                   *
                   * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
                   * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
                   * ({_hashTypedDataV4}).
                   *
                   * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
                   * the chain id to protect against replay attacks on an eventual fork of the chain.
                   *
                   * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
                   * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
                   *
                   * _Available since v3.4._
                   */
                  abstract contract EIP712 {
                      /* solhint-disable var-name-mixedcase */
                      // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
                      // invalidate the cached domain separator if the chain id changes.
                      bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
                      uint256 private immutable _CACHED_CHAIN_ID;
                  
                      bytes32 private immutable _HASHED_NAME;
                      bytes32 private immutable _HASHED_VERSION;
                      bytes32 private immutable _TYPE_HASH;
                      /* solhint-enable var-name-mixedcase */
                  
                      /**
                       * @dev Initializes the domain separator and parameter caches.
                       *
                       * The meaning of `name` and `version` is specified in
                       * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
                       *
                       * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
                       * - `version`: the current major version of the signing domain.
                       *
                       * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
                       * contract upgrade].
                       */
                      constructor(string memory name, string memory version) {
                          bytes32 hashedName = keccak256(bytes(name));
                          bytes32 hashedVersion = keccak256(bytes(version));
                          bytes32 typeHash = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
                          _HASHED_NAME = hashedName;
                          _HASHED_VERSION = hashedVersion;
                          _CACHED_CHAIN_ID = _getChainId();
                          _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
                          _TYPE_HASH = typeHash;
                      }
                  
                      /**
                       * @dev Returns the domain separator for the current chain.
                       */
                      function _domainSeparatorV4() internal view virtual returns (bytes32) {
                          if (_getChainId() == _CACHED_CHAIN_ID) {
                              return _CACHED_DOMAIN_SEPARATOR;
                          } else {
                              return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
                          }
                      }
                  
                      function _buildDomainSeparator(bytes32 typeHash, bytes32 name, bytes32 version) private view returns (bytes32) {
                          return keccak256(
                              abi.encode(
                                  typeHash,
                                  name,
                                  version,
                                  _getChainId(),
                                  address(this)
                              )
                          );
                      }
                  
                      /**
                       * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
                       * function returns the hash of the fully encoded EIP712 message for this domain.
                       *
                       * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
                       *
                       * ```solidity
                       * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
                       *     keccak256("Mail(address to,string contents)"),
                       *     mailTo,
                       *     keccak256(bytes(mailContents))
                       * )));
                       * address signer = ECDSA.recover(digest, signature);
                       * ```
                       */
                      function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
                          return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
                      }
                  
                      function _getChainId() private view returns (uint256 chainId) {
                          this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                          // solhint-disable-next-line no-inline-assembly
                          assembly {
                              chainId := chainid()
                          }
                      }
                  }
                  
                  
                  // File contracts/helpers/ECDSA.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  /**
                   * @dev Simplified copy of OpenZeppelin ECDSA library downgraded to 0.7.6
                   * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/541e82144f691aa171c53ba8c3b32ef7f05b99a5/contracts/utils/cryptography/ECDSA.sol
                   *
                   * Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
                   *
                   * These functions can be used to verify that a message was signed by the holder
                   * of the private keys of a given address.
                   */
                  library ECDSA {
                      /**
                       * @dev Returns the address that signed a hashed message (`hash`) with
                       * `signature` or error string. This address can then be used for verification purposes.
                       *
                       * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
                       * this function rejects them by requiring the `s` value to be in the lower
                       * half order, and the `v` value to be either 27 or 28.
                       *
                       * IMPORTANT: `hash` _must_ be the result of a hash operation for the
                       * verification to be secure: it is possible to craft signatures that
                       * recover to arbitrary addresses for non-hashed data. A safe way to ensure
                       * this is by receiving a hash of the original message (which may otherwise
                       * be too long), and then calling {toEthSignedMessageHash} on it.
                       *
                       * Documentation for signature generation:
                       * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
                       * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
                       *
                       * _Available since v4.3._
                       */
                      function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                          // Check the signature length
                          // - case 65: r,s,v signature (standard)
                          // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
                          if (signature.length == 65) {
                              bytes32 r;
                              bytes32 s;
                              uint8 v;
                              // ecrecover takes the signature parameters, and the only way to get them
                              // currently is to use assembly.
                              assembly {  // solhint-disable-line no-inline-assembly
                                  r := mload(add(signature, 0x20))
                                  s := mload(add(signature, 0x40))
                                  v := byte(0, mload(add(signature, 0x60)))
                              }
                              return tryRecover(hash, v, r, s);
                          } else if (signature.length == 64) {
                              bytes32 r;
                              bytes32 vs;
                              // ecrecover takes the signature parameters, and the only way to get them
                              // currently is to use assembly.
                              assembly {  // solhint-disable-line no-inline-assembly
                                  r := mload(add(signature, 0x20))
                                  vs := mload(add(signature, 0x40))
                              }
                              return tryRecover(hash, r, vs);
                          } else {
                              return address(0);
                          }
                      }
                  
                      /**
                       * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
                       *
                       * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
                       *
                       * _Available since v4.3._
                       */
                      function tryRecover(
                          bytes32 hash,
                          bytes32 r,
                          bytes32 vs
                      ) internal pure returns (address) {
                          bytes32 s;
                          uint8 v;
                          assembly {  // solhint-disable-line no-inline-assembly
                              s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
                              v := add(shr(255, vs), 27)
                          }
                          return tryRecover(hash, v, r, s);
                      }
                  
                      /**
                       * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
                       * `r` and `s` signature fields separately.
                       *
                       * _Available since v4.3._
                       */
                      function tryRecover(
                          bytes32 hash,
                          uint8 v,
                          bytes32 r,
                          bytes32 s
                      ) internal pure returns (address) {
                          // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                          // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                          // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
                          // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                          //
                          // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                          // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                          // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                          // these malleable signatures as well.
                          if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                              return address(0);
                          }
                          if (v != 27 && v != 28) {
                              return address(0);
                          }
                  
                          // If the signature is valid (and not malleable), return the signer address
                          address signer = ecrecover(hash, v, r, s);
                          if (signer == address(0)) {
                              return address(0);
                          }
                  
                          return signer;
                      }
                  }
                  
                  
                  // File contracts/interfaces/IERC1271.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  /**
                   * @dev Interface of the ERC1271 standard signature validation method for
                   * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
                   */
                  interface IERC1271 {
                      /**
                       * @dev Should return whether the signature provided is valid for the provided data
                       * @param hash      Hash of the data to be signed
                       * @param signature Signature byte array associated with _data
                       */
                      function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
                  }
                  
                  
                  // File contracts/interfaces/IWETH.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  /// @title Interface for WETH tokens
                  interface IWETH is IERC20 {
                      function deposit() external payable;
                      function withdraw(uint256 amount) external;
                  }
                  
                  
                  // File contracts/LimitOrderProtocolRFQ.sol
                  
                  
                  pragma solidity ^0.7.6;
                  pragma abicoder v2;
                  
                  
                  
                  
                  
                  
                  
                  contract LimitOrderProtocolRFQ is EthReceiver, EIP712("1inch RFQ", "2"), Permitable {
                      using SafeMath for uint256;
                      using SafeERC20 for IERC20;
                  
                      event OrderFilledRFQ(
                          bytes32 orderHash,
                          uint256 makingAmount
                      );
                  
                      struct OrderRFQ {
                          // lowest 64 bits is the order id, next 64 bits is the expiration timestamp
                          // highest bit is unwrap WETH flag which is set on taker's side
                          // [unwrap eth(1 bit) | unused (127 bits) | expiration timestamp(64 bits) | orderId (64 bits)]
                          uint256 info;
                          IERC20 makerAsset;
                          IERC20 takerAsset;
                          address maker;
                          address allowedSender;  // equals to Zero address on public orders
                          uint256 makingAmount;
                          uint256 takingAmount;
                      }
                  
                      bytes32 constant public LIMIT_ORDER_RFQ_TYPEHASH = keccak256(
                          "OrderRFQ(uint256 info,address makerAsset,address takerAsset,address maker,address allowedSender,uint256 makingAmount,uint256 takingAmount)"
                      );
                      uint256 private constant _UNWRAP_WETH_MASK = 1 << 255;
                  
                      IWETH private immutable _WETH;  // solhint-disable-line var-name-mixedcase
                      mapping(address => mapping(uint256 => uint256)) private _invalidator;
                  
                      constructor(address weth) {
                          _WETH = IWETH(weth);
                      }
                  
                      // solhint-disable-next-line func-name-mixedcase
                      function DOMAIN_SEPARATOR() external view returns(bytes32) {
                          return _domainSeparatorV4();
                      }
                  
                      /// @notice Returns bitmask for double-spend invalidators based on lowest byte of order.info and filled quotes
                      /// @return Result Each bit represents whenever corresponding quote was filled
                      function invalidatorForOrderRFQ(address maker, uint256 slot) external view returns(uint256) {
                          return _invalidator[maker][slot];
                      }
                  
                      /// @notice Cancels order's quote
                      function cancelOrderRFQ(uint256 orderInfo) external {
                          _invalidateOrder(msg.sender, orderInfo);
                      }
                  
                      /// @notice Fills order's quote, fully or partially (whichever is possible)
                      /// @param order Order quote to fill
                      /// @param signature Signature to confirm quote ownership
                      /// @param makingAmount Making amount
                      /// @param takingAmount Taking amount
                      function fillOrderRFQ(
                          OrderRFQ memory order,
                          bytes calldata signature,
                          uint256 makingAmount,
                          uint256 takingAmount
                      ) external payable returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */) {
                          return fillOrderRFQTo(order, signature, makingAmount, takingAmount, payable(msg.sender));
                      }
                  
                      /// @notice Fills Same as `fillOrderRFQ` but calls permit first,
                      /// allowing to approve token spending and make a swap in one transaction.
                      /// Also allows to specify funds destination instead of `msg.sender`
                      /// @param order Order quote to fill
                      /// @param signature Signature to confirm quote ownership
                      /// @param makingAmount Making amount
                      /// @param takingAmount Taking amount
                      /// @param target Address that will receive swap funds
                      /// @param permit Should consist of abiencoded token address and encoded `IERC20Permit.permit` call.
                      /// See tests for examples
                      function fillOrderRFQToWithPermit(
                          OrderRFQ memory order,
                          bytes calldata signature,
                          uint256 makingAmount,
                          uint256 takingAmount,
                          address payable target,
                          bytes calldata permit
                      ) external returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */) {
                          _permit(address(order.takerAsset), permit);
                          return fillOrderRFQTo(order, signature, makingAmount, takingAmount, target);
                      }
                  
                      /// @notice Same as `fillOrderRFQ` but allows to specify funds destination instead of `msg.sender`
                      /// @param order Order quote to fill
                      /// @param signature Signature to confirm quote ownership
                      /// @param makingAmount Making amount
                      /// @param takingAmount Taking amount
                      /// @param target Address that will receive swap funds
                      function fillOrderRFQTo(
                          OrderRFQ memory order,
                          bytes calldata signature,
                          uint256 makingAmount,
                          uint256 takingAmount,
                          address payable target
                      ) public payable returns(uint256 /* actualMakingAmount */, uint256 /* actualTakingAmount */) {
                          address maker = order.maker;
                          bool unwrapWETH = (order.info & _UNWRAP_WETH_MASK) > 0;
                          order.info = order.info & (_UNWRAP_WETH_MASK - 1);  // zero-out unwrap weth flag as it is taker-only
                          {  // Stack too deep
                              uint256 info = order.info;
                              // Check time expiration
                              uint256 expiration = uint128(info) >> 64;
                              require(expiration == 0 || block.timestamp <= expiration, "LOP: order expired");  // solhint-disable-line not-rely-on-time
                              _invalidateOrder(maker, info);
                          }
                  
                          {  // stack too deep
                              uint256 orderMakingAmount = order.makingAmount;
                              uint256 orderTakingAmount = order.takingAmount;
                              // Compute partial fill if needed
                              if (takingAmount == 0 && makingAmount == 0) {
                                  // Two zeros means whole order
                                  makingAmount = orderMakingAmount;
                                  takingAmount = orderTakingAmount;
                              }
                              else if (takingAmount == 0) {
                                  require(makingAmount <= orderMakingAmount, "LOP: making amount exceeded");
                                  takingAmount = orderTakingAmount.mul(makingAmount).add(orderMakingAmount - 1).div(orderMakingAmount);
                              }
                              else if (makingAmount == 0) {
                                  require(takingAmount <= orderTakingAmount, "LOP: taking amount exceeded");
                                  makingAmount = orderMakingAmount.mul(takingAmount).div(orderTakingAmount);
                              }
                              else {
                                  revert("LOP: one of amounts should be 0");
                              }
                          }
                  
                          require(makingAmount > 0 && takingAmount > 0, "LOP: can't swap 0 amount");
                  
                          // Validate order
                          require(order.allowedSender == address(0) || order.allowedSender == msg.sender, "LOP: private order");
                          bytes32 orderHash = _hashTypedDataV4(keccak256(abi.encode(LIMIT_ORDER_RFQ_TYPEHASH, order)));
                          _validate(maker, orderHash, signature);
                  
                          // Maker => Taker
                          if (order.makerAsset == _WETH && unwrapWETH) {
                              order.makerAsset.safeTransferFrom(maker, address(this), makingAmount);
                              _WETH.withdraw(makingAmount);
                              target.transfer(makingAmount);
                          } else {
                              order.makerAsset.safeTransferFrom(maker, target, makingAmount);
                          }
                          // Taker => Maker
                          if (order.takerAsset == _WETH && msg.value > 0) {
                              require(msg.value == takingAmount, "LOP: wrong msg.value");
                              _WETH.deposit{ value: takingAmount }();
                              _WETH.transfer(maker, takingAmount);
                          } else {
                              require(msg.value == 0, "LOP: wrong msg.value");
                              order.takerAsset.safeTransferFrom(msg.sender, maker, takingAmount);
                          }
                  
                          emit OrderFilledRFQ(orderHash, makingAmount);
                          return (makingAmount, takingAmount);
                      }
                  
                      function _validate(address signer, bytes32 orderHash, bytes calldata signature) private view {
                          if (ECDSA.tryRecover(orderHash, signature) != signer) {
                              (bool success, bytes memory result) = signer.staticcall(
                                  abi.encodeWithSelector(IERC1271.isValidSignature.selector, orderHash, signature)
                              );
                              require(success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector, "LOP: bad signature");
                          }
                      }
                  
                      function _invalidateOrder(address maker, uint256 orderInfo) private {
                          uint256 invalidatorSlot = uint64(orderInfo) >> 8;
                          uint256 invalidatorBit = 1 << uint8(orderInfo);
                          mapping(uint256 => uint256) storage invalidatorStorage = _invalidator[maker];
                          uint256 invalidator = invalidatorStorage[invalidatorSlot];
                          require(invalidator & invalidatorBit == 0, "LOP: invalidated order");
                          invalidatorStorage[invalidatorSlot] = invalidator | invalidatorBit;
                      }
                  }
                  
                  
                  // File contracts/UnoswapRouter.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  
                  
                  contract UnoswapRouter is EthReceiver, Permitable {
                      uint256 private constant _TRANSFER_FROM_CALL_SELECTOR_32 = 0x23b872dd00000000000000000000000000000000000000000000000000000000;
                      uint256 private constant _WETH_DEPOSIT_CALL_SELECTOR_32 = 0xd0e30db000000000000000000000000000000000000000000000000000000000;
                      uint256 private constant _WETH_WITHDRAW_CALL_SELECTOR_32 = 0x2e1a7d4d00000000000000000000000000000000000000000000000000000000;
                      uint256 private constant _ERC20_TRANSFER_CALL_SELECTOR_32 = 0xa9059cbb00000000000000000000000000000000000000000000000000000000;
                      uint256 private constant _ADDRESS_MASK =   0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
                      uint256 private constant _REVERSE_MASK =   0x8000000000000000000000000000000000000000000000000000000000000000;
                      uint256 private constant _WETH_MASK =      0x4000000000000000000000000000000000000000000000000000000000000000;
                      uint256 private constant _NUMERATOR_MASK = 0x0000000000000000ffffffff0000000000000000000000000000000000000000;
                      /// @dev WETH address is network-specific and needs to be changed before deployment.
                      /// It can not be moved to immutable as immutables are not supported in assembly
                      uint256 private constant _WETH = 0x000000000000000000000000C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
                      uint256 private constant _UNISWAP_PAIR_RESERVES_CALL_SELECTOR_32 = 0x0902f1ac00000000000000000000000000000000000000000000000000000000;
                      uint256 private constant _UNISWAP_PAIR_SWAP_CALL_SELECTOR_32 = 0x022c0d9f00000000000000000000000000000000000000000000000000000000;
                      uint256 private constant _DENOMINATOR = 1000000000;
                      uint256 private constant _NUMERATOR_OFFSET = 160;
                  
                      /// @notice Same as `unoswap` but calls permit first,
                      /// allowing to approve token spending and make a swap in one transaction.
                      /// @param srcToken Source token
                      /// @param amount Amount of source tokens to swap
                      /// @param minReturn Minimal allowed returnAmount to make transaction commit
                      /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                      /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls.
                      /// See tests for examples
                      function unoswapWithPermit(
                          IERC20 srcToken,
                          uint256 amount,
                          uint256 minReturn,
                          bytes32[] calldata pools,
                          bytes calldata permit
                      ) external returns(uint256 returnAmount) {
                          _permit(address(srcToken), permit);
                          return unoswap(srcToken, amount, minReturn, pools);
                      }
                  
                      /// @notice Performs swap using Uniswap exchange. Wraps and unwraps ETH if required.
                      /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited
                      /// @param srcToken Source token
                      /// @param amount Amount of source tokens to swap
                      /// @param minReturn Minimal allowed returnAmount to make transaction commit
                      /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                      function unoswap(
                          IERC20 srcToken,
                          uint256 amount,
                          uint256 minReturn,
                          // solhint-disable-next-line no-unused-vars
                          bytes32[] calldata pools
                      ) public payable returns(uint256 returnAmount) {
                          assembly {  // solhint-disable-line no-inline-assembly
                              function reRevert() {
                                  returndatacopy(0, 0, returndatasize())
                                  revert(0, returndatasize())
                              }
                  
                              function revertWithReason(m, len) {
                                  mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                                  mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000)
                                  mstore(0x40, m)
                                  revert(0, len)
                              }
                  
                              function swap(emptyPtr, swapAmount, pair, reversed, numerator, dst) -> ret {
                                  mstore(emptyPtr, _UNISWAP_PAIR_RESERVES_CALL_SELECTOR_32)
                                  if iszero(staticcall(gas(), pair, emptyPtr, 0x4, emptyPtr, 0x40)) {
                                      reRevert()
                                  }
                                  if iszero(eq(returndatasize(), 0x60)) {
                                      revertWithReason(0x0000001472657365727665732063616c6c206661696c65640000000000000000, 0x59)  // "reserves call failed"
                                  }
                  
                                  let reserve0 := mload(emptyPtr)
                                  let reserve1 := mload(add(emptyPtr, 0x20))
                                  if reversed {
                                      let tmp := reserve0
                                      reserve0 := reserve1
                                      reserve1 := tmp
                                  }
                                  ret := mul(swapAmount, numerator)
                                  ret := div(mul(ret, reserve1), add(ret, mul(reserve0, _DENOMINATOR)))
                  
                                  mstore(emptyPtr, _UNISWAP_PAIR_SWAP_CALL_SELECTOR_32)
                                  switch reversed
                                  case 0 {
                                      mstore(add(emptyPtr, 0x04), 0)
                                      mstore(add(emptyPtr, 0x24), ret)
                                  }
                                  default {
                                      mstore(add(emptyPtr, 0x04), ret)
                                      mstore(add(emptyPtr, 0x24), 0)
                                  }
                                  mstore(add(emptyPtr, 0x44), dst)
                                  mstore(add(emptyPtr, 0x64), 0x80)
                                  mstore(add(emptyPtr, 0x84), 0)
                                  if iszero(call(gas(), pair, 0, emptyPtr, 0xa4, 0, 0)) {
                                      reRevert()
                                  }
                              }
                  
                              let emptyPtr := mload(0x40)
                              mstore(0x40, add(emptyPtr, 0xc0))
                  
                              let poolsOffset := add(calldataload(0x64), 0x4)
                              let poolsEndOffset := calldataload(poolsOffset)
                              poolsOffset := add(poolsOffset, 0x20)
                              poolsEndOffset := add(poolsOffset, mul(0x20, poolsEndOffset))
                              let rawPair := calldataload(poolsOffset)
                              switch srcToken
                              case 0 {
                                  if iszero(eq(amount, callvalue())) {
                                      revertWithReason(0x00000011696e76616c6964206d73672e76616c75650000000000000000000000, 0x55)  // "invalid msg.value"
                                  }
                  
                                  mstore(emptyPtr, _WETH_DEPOSIT_CALL_SELECTOR_32)
                                  if iszero(call(gas(), _WETH, amount, emptyPtr, 0x4, 0, 0)) {
                                      reRevert()
                                  }
                  
                                  mstore(emptyPtr, _ERC20_TRANSFER_CALL_SELECTOR_32)
                                  mstore(add(emptyPtr, 0x4), and(rawPair, _ADDRESS_MASK))
                                  mstore(add(emptyPtr, 0x24), amount)
                                  if iszero(call(gas(), _WETH, 0, emptyPtr, 0x44, 0, 0)) {
                                      reRevert()
                                  }
                              }
                              default {
                                  if callvalue() {
                                      revertWithReason(0x00000011696e76616c6964206d73672e76616c75650000000000000000000000, 0x55)  // "invalid msg.value"
                                  }
                  
                                  mstore(emptyPtr, _TRANSFER_FROM_CALL_SELECTOR_32)
                                  mstore(add(emptyPtr, 0x4), caller())
                                  mstore(add(emptyPtr, 0x24), and(rawPair, _ADDRESS_MASK))
                                  mstore(add(emptyPtr, 0x44), amount)
                                  if iszero(call(gas(), srcToken, 0, emptyPtr, 0x64, 0, 0)) {
                                      reRevert()
                                  }
                              }
                  
                              returnAmount := amount
                  
                              for {let i := add(poolsOffset, 0x20)} lt(i, poolsEndOffset) {i := add(i, 0x20)} {
                                  let nextRawPair := calldataload(i)
                  
                                  returnAmount := swap(
                                      emptyPtr,
                                      returnAmount,
                                      and(rawPair, _ADDRESS_MASK),
                                      and(rawPair, _REVERSE_MASK),
                                      shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)),
                                      and(nextRawPair, _ADDRESS_MASK)
                                  )
                  
                                  rawPair := nextRawPair
                              }
                  
                              switch and(rawPair, _WETH_MASK)
                              case 0 {
                                  returnAmount := swap(
                                      emptyPtr,
                                      returnAmount,
                                      and(rawPair, _ADDRESS_MASK),
                                      and(rawPair, _REVERSE_MASK),
                                      shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)),
                                      caller()
                                  )
                              }
                              default {
                                  returnAmount := swap(
                                      emptyPtr,
                                      returnAmount,
                                      and(rawPair, _ADDRESS_MASK),
                                      and(rawPair, _REVERSE_MASK),
                                      shr(_NUMERATOR_OFFSET, and(rawPair, _NUMERATOR_MASK)),
                                      address()
                                  )
                  
                                  mstore(emptyPtr, _WETH_WITHDRAW_CALL_SELECTOR_32)
                                  mstore(add(emptyPtr, 0x04), returnAmount)
                                  if iszero(call(gas(), _WETH, 0, emptyPtr, 0x24, 0, 0)) {
                                      reRevert()
                                  }
                  
                                  if iszero(call(gas(), caller(), returnAmount, 0, 0, 0, 0)) {
                                      reRevert()
                                  }
                              }
                  
                              if lt(returnAmount, minReturn) {
                                  revertWithReason(0x000000164d696e2072657475726e206e6f742072656163686564000000000000, 0x5a)  // "Min return not reached"
                              }
                          }
                      }
                  }
                  
                  
                  // File @openzeppelin/contracts/utils/[email protected]
                  
                  
                  pragma solidity ^0.7.0;
                  
                  
                  /**
                   * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
                   * checks.
                   *
                   * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
                   * easily result in undesired exploitation or bugs, since developers usually
                   * assume that overflows raise errors. `SafeCast` restores this intuition by
                   * reverting the transaction when such 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.
                   *
                   * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
                   * all math on `uint256` and `int256` and then downcasting.
                   */
                  library SafeCast {
                  
                      /**
                       * @dev Returns the downcasted uint128 from uint256, reverting on
                       * overflow (when the input is greater than largest uint128).
                       *
                       * Counterpart to Solidity's `uint128` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 128 bits
                       */
                      function toUint128(uint256 value) internal pure returns (uint128) {
                          require(value < 2**128, "SafeCast: value doesn\'t fit in 128 bits");
                          return uint128(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted uint64 from uint256, reverting on
                       * overflow (when the input is greater than largest uint64).
                       *
                       * Counterpart to Solidity's `uint64` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 64 bits
                       */
                      function toUint64(uint256 value) internal pure returns (uint64) {
                          require(value < 2**64, "SafeCast: value doesn\'t fit in 64 bits");
                          return uint64(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted uint32 from uint256, reverting on
                       * overflow (when the input is greater than largest uint32).
                       *
                       * Counterpart to Solidity's `uint32` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 32 bits
                       */
                      function toUint32(uint256 value) internal pure returns (uint32) {
                          require(value < 2**32, "SafeCast: value doesn\'t fit in 32 bits");
                          return uint32(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted uint16 from uint256, reverting on
                       * overflow (when the input is greater than largest uint16).
                       *
                       * Counterpart to Solidity's `uint16` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 16 bits
                       */
                      function toUint16(uint256 value) internal pure returns (uint16) {
                          require(value < 2**16, "SafeCast: value doesn\'t fit in 16 bits");
                          return uint16(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted uint8 from uint256, reverting on
                       * overflow (when the input is greater than largest uint8).
                       *
                       * Counterpart to Solidity's `uint8` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 8 bits.
                       */
                      function toUint8(uint256 value) internal pure returns (uint8) {
                          require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
                          return uint8(value);
                      }
                  
                      /**
                       * @dev Converts a signed int256 into an unsigned uint256.
                       *
                       * Requirements:
                       *
                       * - input must be greater than or equal to 0.
                       */
                      function toUint256(int256 value) internal pure returns (uint256) {
                          require(value >= 0, "SafeCast: value must be positive");
                          return uint256(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted int128 from int256, reverting on
                       * overflow (when the input is less than smallest int128 or
                       * greater than largest int128).
                       *
                       * Counterpart to Solidity's `int128` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 128 bits
                       *
                       * _Available since v3.1._
                       */
                      function toInt128(int256 value) internal pure returns (int128) {
                          require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\'t fit in 128 bits");
                          return int128(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted int64 from int256, reverting on
                       * overflow (when the input is less than smallest int64 or
                       * greater than largest int64).
                       *
                       * Counterpart to Solidity's `int64` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 64 bits
                       *
                       * _Available since v3.1._
                       */
                      function toInt64(int256 value) internal pure returns (int64) {
                          require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\'t fit in 64 bits");
                          return int64(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted int32 from int256, reverting on
                       * overflow (when the input is less than smallest int32 or
                       * greater than largest int32).
                       *
                       * Counterpart to Solidity's `int32` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 32 bits
                       *
                       * _Available since v3.1._
                       */
                      function toInt32(int256 value) internal pure returns (int32) {
                          require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\'t fit in 32 bits");
                          return int32(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted int16 from int256, reverting on
                       * overflow (when the input is less than smallest int16 or
                       * greater than largest int16).
                       *
                       * Counterpart to Solidity's `int16` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 16 bits
                       *
                       * _Available since v3.1._
                       */
                      function toInt16(int256 value) internal pure returns (int16) {
                          require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\'t fit in 16 bits");
                          return int16(value);
                      }
                  
                      /**
                       * @dev Returns the downcasted int8 from int256, reverting on
                       * overflow (when the input is less than smallest int8 or
                       * greater than largest int8).
                       *
                       * Counterpart to Solidity's `int8` operator.
                       *
                       * Requirements:
                       *
                       * - input must fit into 8 bits.
                       *
                       * _Available since v3.1._
                       */
                      function toInt8(int256 value) internal pure returns (int8) {
                          require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\'t fit in 8 bits");
                          return int8(value);
                      }
                  
                      /**
                       * @dev Converts an unsigned uint256 into a signed int256.
                       *
                       * Requirements:
                       *
                       * - input must be less than or equal to maxInt256.
                       */
                      function toInt256(uint256 value) internal pure returns (int256) {
                          require(value < 2**255, "SafeCast: value doesn't fit in an int256");
                          return int256(value);
                      }
                  }
                  
                  
                  // File contracts/interfaces/IUniswapV3Pool.sol
                  
                  pragma solidity ^0.7.6;
                  
                  interface IUniswapV3Pool {
                      /// @notice Swap token0 for token1, or token1 for token0
                      /// @dev The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback
                      /// @param recipient The address to receive the output of the swap
                      /// @param zeroForOne The direction of the swap, true for token0 to token1, false for token1 to token0
                      /// @param amountSpecified The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
                      /// @param sqrtPriceLimitX96 The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this
                      /// value after the swap. If one for zero, the price cannot be greater than this value after the swap
                      /// @param data Any data to be passed through to the callback
                      /// @return amount0 The delta of the balance of token0 of the pool, exact when negative, minimum when positive
                      /// @return amount1 The delta of the balance of token1 of the pool, exact when negative, minimum when positive
                      function swap(
                          address recipient,
                          bool zeroForOne,
                          int256 amountSpecified,
                          uint160 sqrtPriceLimitX96,
                          bytes calldata data
                      ) external returns (int256 amount0, int256 amount1);
                  
                      /// @notice The first of the two tokens of the pool, sorted by address
                      /// @return The token contract address
                      function token0() external view returns (address);
                  
                      /// @notice The second of the two tokens of the pool, sorted by address
                      /// @return The token contract address
                      function token1() external view returns (address);
                  
                      /// @notice The pool's fee in hundredths of a bip, i.e. 1e-6
                      /// @return The fee
                      function fee() external view returns (uint24);
                  }
                  
                  
                  // File contracts/interfaces/IUniswapV3SwapCallback.sol
                  
                  pragma solidity ^0.7.6;
                  
                  /// @title Callback for IUniswapV3PoolActions#swap
                  /// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
                  interface IUniswapV3SwapCallback {
                      /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
                      /// @dev In the implementation you must pay the pool tokens owed for the swap.
                      /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
                      /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
                      /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
                      /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
                      /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
                      /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
                      /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
                      function uniswapV3SwapCallback(
                          int256 amount0Delta,
                          int256 amount1Delta,
                          bytes calldata data
                      ) external;
                  }
                  
                  
                  // File contracts/UnoswapV3Router.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  
                  
                  
                  
                  
                  
                  
                  
                  contract UnoswapV3Router is EthReceiver, Permitable, IUniswapV3SwapCallback {
                      using Address for address payable;
                      using SafeERC20 for IERC20;
                      using SafeMath for uint256;
                  
                      uint256 private constant _ONE_FOR_ZERO_MASK = 1 << 255;
                      uint256 private constant _WETH_WRAP_MASK = 1 << 254;
                      uint256 private constant _WETH_UNWRAP_MASK = 1 << 253;
                      bytes32 private constant _POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
                      bytes32 private constant _FF_FACTORY = 0xff1F98431c8aD98523631AE4a59f267346ea31F9840000000000000000000000;
                      bytes32 private constant _SELECTORS = 0x0dfe1681d21220a7ddca3f430000000000000000000000000000000000000000;
                      uint256 private constant _ADDRESS_MASK =   0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
                      /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
                      uint160 private constant _MIN_SQRT_RATIO = 4295128739 + 1;
                      /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
                      uint160 private constant _MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342 - 1;
                      IWETH private immutable _WETH;  // solhint-disable-line var-name-mixedcase
                  
                      constructor(address weth) {
                          _WETH = IWETH(weth);
                      }
                  
                      /// @notice Same as `uniswapV3SwapTo` but calls permit first,
                      /// allowing to approve token spending and make a swap in one transaction.
                      /// @param recipient Address that will receive swap funds
                      /// @param srcToken Source token
                      /// @param amount Amount of source tokens to swap
                      /// @param minReturn Minimal allowed returnAmount to make transaction commit
                      /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                      /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls.
                      /// See tests for examples
                      function uniswapV3SwapToWithPermit(
                          address payable recipient,
                          IERC20 srcToken,
                          uint256 amount,
                          uint256 minReturn,
                          uint256[] calldata pools,
                          bytes calldata permit
                      ) external returns(uint256 returnAmount) {
                          _permit(address(srcToken), permit);
                          return uniswapV3SwapTo(recipient, amount, minReturn, pools);
                      }
                  
                      /// @notice Same as `uniswapV3SwapTo` but uses `msg.sender` as recipient
                      /// @param amount Amount of source tokens to swap
                      /// @param minReturn Minimal allowed returnAmount to make transaction commit
                      /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                      function uniswapV3Swap(
                          uint256 amount,
                          uint256 minReturn,
                          uint256[] calldata pools
                      ) external payable returns(uint256 returnAmount) {
                          return uniswapV3SwapTo(msg.sender, amount, minReturn, pools);
                      }
                  
                      /// @notice Performs swap using Uniswap V3 exchange. Wraps and unwraps ETH if required.
                      /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited
                      /// @param recipient Address that will receive swap funds
                      /// @param amount Amount of source tokens to swap
                      /// @param minReturn Minimal allowed returnAmount to make transaction commit
                      /// @param pools Pools chain used for swaps. Pools src and dst tokens should match to make swap happen
                      function uniswapV3SwapTo(
                          address payable recipient,
                          uint256 amount,
                          uint256 minReturn,
                          uint256[] calldata pools
                      ) public payable returns(uint256 returnAmount) {
                          uint256 len = pools.length;
                          require(len > 0, "UNIV3R: empty pools");
                          uint256 lastIndex = len - 1;
                          returnAmount = amount;
                          bool wrapWeth = pools[0] & _WETH_WRAP_MASK > 0;
                          bool unwrapWeth = pools[lastIndex] & _WETH_UNWRAP_MASK > 0;
                          if (wrapWeth) {
                              require(msg.value == amount, "UNIV3R: wrong msg.value");
                              _WETH.deposit{value: amount}();
                          } else {
                              require(msg.value == 0, "UNIV3R: msg.value should be 0");
                          }
                          if (len > 1) {
                              returnAmount = _makeSwap(address(this), wrapWeth ? address(this) : msg.sender, pools[0], returnAmount);
                  
                              for (uint256 i = 1; i < lastIndex; i++) {
                                  returnAmount = _makeSwap(address(this), address(this), pools[i], returnAmount);
                              }
                              returnAmount = _makeSwap(unwrapWeth ? address(this) : recipient, address(this), pools[lastIndex], returnAmount);
                          } else {
                              returnAmount = _makeSwap(unwrapWeth ? address(this) : recipient, wrapWeth ? address(this) : msg.sender, pools[0], returnAmount);
                          }
                  
                          require(returnAmount >= minReturn, "UNIV3R: min return");
                  
                          if (unwrapWeth) {
                              _WETH.withdraw(returnAmount);
                              recipient.sendValue(returnAmount);
                          }
                      }
                  
                      /// @inheritdoc IUniswapV3SwapCallback
                      function uniswapV3SwapCallback(
                          int256 amount0Delta,
                          int256 amount1Delta,
                          bytes calldata /* data */
                      ) external override {
                          IERC20 token0;
                          IERC20 token1;
                          bytes32 ffFactoryAddress = _FF_FACTORY;
                          bytes32 poolInitCodeHash = _POOL_INIT_CODE_HASH;
                          address payer;
                  
                          assembly {  // solhint-disable-line no-inline-assembly
                              function reRevert() {
                                  returndatacopy(0, 0, returndatasize())
                                  revert(0, returndatasize())
                              }
                  
                              function revertWithReason(m, len) {
                                  mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                                  mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000)
                                  mstore(0x40, m)
                                  revert(0, len)
                              }
                  
                              let emptyPtr := mload(0x40)
                              let resultPtr := add(emptyPtr, 0x20)
                              mstore(emptyPtr, _SELECTORS)
                  
                              if iszero(staticcall(gas(), caller(), emptyPtr, 0x4, resultPtr, 0x20)) {
                                  reRevert()
                              }
                              token0 := mload(resultPtr)
                              if iszero(staticcall(gas(), caller(), add(emptyPtr, 0x4), 0x4, resultPtr, 0x20)) {
                                  reRevert()
                              }
                              token1 := mload(resultPtr)
                              if iszero(staticcall(gas(), caller(), add(emptyPtr, 0x8), 0x4, resultPtr, 0x20)) {
                                  reRevert()
                              }
                              let fee := mload(resultPtr)
                  
                              let p := emptyPtr
                              mstore(p, ffFactoryAddress)
                              p := add(p, 21)
                              // Compute the inner hash in-place
                              mstore(p, token0)
                              mstore(add(p, 32), token1)
                              mstore(add(p, 64), fee)
                              mstore(p, keccak256(p, 96))
                              p := add(p, 32)
                              mstore(p, poolInitCodeHash)
                              let pool := and(keccak256(emptyPtr, 85), _ADDRESS_MASK)
                  
                              if iszero(eq(pool, caller())) {
                                  revertWithReason(0x00000010554e495633523a2062616420706f6f6c000000000000000000000000, 0x54)  // UNIV3R: bad pool
                              }
                  
                              calldatacopy(emptyPtr, 0x84, 0x20)
                              payer := mload(emptyPtr)
                          }
                  
                          if (amount0Delta > 0) {
                              if (payer == address(this)) {
                                  token0.safeTransfer(msg.sender, uint256(amount0Delta));
                              } else {
                                  token0.safeTransferFrom(payer, msg.sender, uint256(amount0Delta));
                              }
                          }
                          if (amount1Delta > 0) {
                              if (payer == address(this)) {
                                  token1.safeTransfer(msg.sender, uint256(amount1Delta));
                              } else {
                                  token1.safeTransferFrom(payer, msg.sender, uint256(amount1Delta));
                              }
                          }
                      }
                  
                      function _makeSwap(address recipient, address payer, uint256 pool, uint256 amount) private returns (uint256) {
                          bool zeroForOne = pool & _ONE_FOR_ZERO_MASK == 0;
                          if (zeroForOne) {
                              (, int256 amount1) = IUniswapV3Pool(pool).swap(
                                  recipient,
                                  zeroForOne,
                                  SafeCast.toInt256(amount),
                                  _MIN_SQRT_RATIO,
                                  abi.encode(payer)
                              );
                              return SafeCast.toUint256(-amount1);
                          } else {
                              (int256 amount0,) = IUniswapV3Pool(pool).swap(
                                  recipient,
                                  zeroForOne,
                                  SafeCast.toInt256(amount),
                                  _MAX_SQRT_RATIO,
                                  abi.encode(payer)
                              );
                              return SafeCast.toUint256(-amount0);
                          }
                      }
                  }
                  
                  
                  // File contracts/interfaces/IClipperExchangeInterface.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  /// @title Clipper interface subset used in swaps
                  interface IClipperExchangeInterface {
                      function sellTokenForToken(IERC20 inputToken, IERC20 outputToken, address recipient, uint256 minBuyAmount, bytes calldata auxiliaryData) external returns (uint256 boughtAmount);
                      function sellEthForToken(IERC20 outputToken, address recipient, uint256 minBuyAmount, bytes calldata auxiliaryData) external payable returns (uint256 boughtAmount);
                      function sellTokenForEth(IERC20 inputToken, address payable recipient, uint256 minBuyAmount, bytes calldata auxiliaryData) external returns (uint256 boughtAmount);
                      function theExchange() external returns (address payable);
                  }
                  
                  
                  // File contracts/ClipperRouter.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  
                  
                  
                  
                  
                  /// @title Clipper router that allows to use `ClipperExchangeInterface` for swaps
                  contract ClipperRouter is EthReceiver, Permitable {
                      using SafeERC20 for IERC20;
                  
                      IWETH private immutable _WETH;  // solhint-disable-line var-name-mixedcase
                      IERC20 private constant _ETH = IERC20(address(0));
                      bytes private constant _INCH_TAG = "1INCH";
                      IClipperExchangeInterface private immutable _clipperExchange;
                      address payable private immutable _clipperPool;
                  
                      constructor(
                          address weth,
                          IClipperExchangeInterface clipperExchange
                      ) {
                          _clipperExchange = clipperExchange;
                          _clipperPool = clipperExchange.theExchange();
                          _WETH = IWETH(weth);
                      }
                  
                      /// @notice Same as `clipperSwapTo` but calls permit first,
                      /// allowing to approve token spending and make a swap in one transaction.
                      /// @param recipient Address that will receive swap funds
                      /// @param srcToken Source token
                      /// @param dstToken Destination token
                      /// @param amount Amount of source tokens to swap
                      /// @param minReturn Minimal allowed returnAmount to make transaction commit
                      /// @param permit Should contain valid permit that can be used in `IERC20Permit.permit` calls.
                      /// See tests for examples
                      function clipperSwapToWithPermit(
                          address payable recipient,
                          IERC20 srcToken,
                          IERC20 dstToken,
                          uint256 amount,
                          uint256 minReturn,
                          bytes calldata permit
                      ) external returns(uint256 returnAmount) {
                          _permit(address(srcToken), permit);
                          return clipperSwapTo(recipient, srcToken, dstToken, amount, minReturn);
                      }
                  
                      /// @notice Same as `clipperSwapTo` but uses `msg.sender` as recipient
                      /// @param srcToken Source token
                      /// @param dstToken Destination token
                      /// @param amount Amount of source tokens to swap
                      /// @param minReturn Minimal allowed returnAmount to make transaction commit
                      function clipperSwap(
                          IERC20 srcToken,
                          IERC20 dstToken,
                          uint256 amount,
                          uint256 minReturn
                      ) external payable returns(uint256 returnAmount) {
                          return clipperSwapTo(msg.sender, srcToken, dstToken, amount, minReturn);
                      }
                  
                      /// @notice Performs swap using Clipper exchange. Wraps and unwraps ETH if required.
                      /// Sending non-zero `msg.value` for anything but ETH swaps is prohibited
                      /// @param recipient Address that will receive swap funds
                      /// @param srcToken Source token
                      /// @param dstToken Destination token
                      /// @param amount Amount of source tokens to swap
                      /// @param minReturn Minimal allowed returnAmount to make transaction commit
                      function clipperSwapTo(
                          address payable recipient,
                          IERC20 srcToken,
                          IERC20 dstToken,
                          uint256 amount,
                          uint256 minReturn
                      ) public payable returns(uint256 returnAmount) {
                          bool srcETH;
                          if (srcToken == _WETH) {
                              require(msg.value == 0, "CL1IN: msg.value should be 0");
                              _WETH.transferFrom(msg.sender, address(this), amount);
                              _WETH.withdraw(amount);
                              srcETH = true;
                          }
                          else if (srcToken == _ETH) {
                              require(msg.value == amount, "CL1IN: wrong msg.value");
                              srcETH = true;
                          }
                          else {
                              require(msg.value == 0, "CL1IN: msg.value should be 0");
                              srcToken.safeTransferFrom(msg.sender, _clipperPool, amount);
                          }
                  
                          if (srcETH) {
                              _clipperPool.transfer(amount);
                              returnAmount = _clipperExchange.sellEthForToken(dstToken, recipient, minReturn, _INCH_TAG);
                          } else if (dstToken == _WETH) {
                              returnAmount = _clipperExchange.sellTokenForEth(srcToken, address(this), minReturn, _INCH_TAG);
                              _WETH.deposit{ value: returnAmount }();
                              _WETH.transfer(recipient, returnAmount);
                          } else if (dstToken == _ETH) {
                              returnAmount = _clipperExchange.sellTokenForEth(srcToken, recipient, minReturn, _INCH_TAG);
                          } else {
                              returnAmount = _clipperExchange.sellTokenForToken(srcToken, dstToken, recipient, minReturn, _INCH_TAG);
                          }
                      }
                  }
                  
                  
                  // File contracts/AggregationRouterV4.sol
                  
                  
                  pragma solidity ^0.7.6;
                  
                  
                  
                  contract AggregationRouterV4 is Ownable, EthReceiver, Permitable, UnoswapRouter, UnoswapV3Router, LimitOrderProtocolRFQ, ClipperRouter {
                      using SafeMath for uint256;
                      using UniERC20 for IERC20;
                      using SafeERC20 for IERC20;
                  
                      uint256 private constant _PARTIAL_FILL = 1 << 0;
                      uint256 private constant _REQUIRES_EXTRA_ETH = 1 << 1;
                  
                      struct SwapDescription {
                          IERC20 srcToken;
                          IERC20 dstToken;
                          address payable srcReceiver;
                          address payable dstReceiver;
                          uint256 amount;
                          uint256 minReturnAmount;
                          uint256 flags;
                          bytes permit;
                      }
                  
                      constructor(address weth, IClipperExchangeInterface _clipperExchange)
                          UnoswapV3Router(weth)
                          LimitOrderProtocolRFQ(weth)
                          ClipperRouter(weth, _clipperExchange)
                      {}  // solhint-disable-line no-empty-blocks
                  
                      /// @notice Performs a swap, delegating all calls encoded in `data` to `caller`. See tests for usage examples
                      /// @param caller Aggregation executor that executes calls described in `data`
                      /// @param desc Swap description
                      /// @param data Encoded calls that `caller` should execute in between of swaps
                      /// @return returnAmount Resulting token amount
                      /// @return spentAmount Source token amount
                      /// @return gasLeft Gas left
                      function swap(
                          IAggregationExecutor caller,
                          SwapDescription calldata desc,
                          bytes calldata data
                      )
                          external
                          payable
                          returns (
                              uint256 returnAmount,
                              uint256 spentAmount,
                              uint256 gasLeft
                          )
                      {
                          require(desc.minReturnAmount > 0, "Min return should not be 0");
                          require(data.length > 0, "data should not be empty");
                  
                          uint256 flags = desc.flags;
                          IERC20 srcToken = desc.srcToken;
                          IERC20 dstToken = desc.dstToken;
                  
                          bool srcETH = srcToken.isETH();
                          if (flags & _REQUIRES_EXTRA_ETH != 0) {
                              require(msg.value > (srcETH ? desc.amount : 0), "Invalid msg.value");
                          } else {
                              require(msg.value == (srcETH ? desc.amount : 0), "Invalid msg.value");
                          }
                  
                          if (!srcETH) {
                              _permit(address(srcToken), desc.permit);
                              srcToken.safeTransferFrom(msg.sender, desc.srcReceiver, desc.amount);
                          }
                  
                          {
                              bytes memory callData = abi.encodePacked(caller.callBytes.selector, bytes12(0), msg.sender, data);
                              // solhint-disable-next-line avoid-low-level-calls
                              (bool success, bytes memory result) = address(caller).call{value: msg.value}(callData);
                              if (!success) {
                                  revert(RevertReasonParser.parse(result, "callBytes failed: "));
                              }
                          }
                  
                          spentAmount = desc.amount;
                          returnAmount = dstToken.uniBalanceOf(address(this));
                  
                          if (flags & _PARTIAL_FILL != 0) {
                              uint256 unspentAmount = srcToken.uniBalanceOf(address(this));
                              if (unspentAmount > 0) {
                                  spentAmount = spentAmount.sub(unspentAmount);
                                  srcToken.uniTransfer(msg.sender, unspentAmount);
                              }
                              require(returnAmount.mul(desc.amount) >= desc.minReturnAmount.mul(spentAmount), "Return amount is not enough");
                          } else {
                              require(returnAmount >= desc.minReturnAmount, "Return amount is not enough");
                          }
                  
                          address payable dstReceiver = (desc.dstReceiver == address(0)) ? msg.sender : desc.dstReceiver;
                          dstToken.uniTransfer(dstReceiver, returnAmount);
                  
                          gasLeft = gasleft();
                      }
                  
                      function rescueFunds(IERC20 token, uint256 amount) external onlyOwner {
                          token.uniTransfer(msg.sender, amount);
                      }
                  
                      function destroy() external onlyOwner {
                          selfdestruct(msg.sender);
                      }
                  }

                  File 2 of 10: Melon
                  // Verified using https://dapp.tools
                  // hevm: flattened sources of contracts/Melon.sol
                  pragma solidity ^0.4.24;
                  
                  ////// contracts/openzeppelin/IERC20.sol
                  /* pragma solidity ^0.4.24; */
                  
                  /**
                   * @title ERC20 interface
                   * @dev see https://github.com/ethereum/EIPs/issues/20
                   */
                  interface IERC20 {
                      function totalSupply() external view returns (uint256);
                  
                      function balanceOf(address who) external view returns (uint256);
                  
                      function allowance(address owner, address spender) external view returns (uint256);
                  
                      function transfer(address to, uint256 value) external returns (bool);
                  
                      function approve(address spender, uint256 value) external returns (bool);
                  
                      function transferFrom(address from, address to, uint256 value) external returns (bool);
                  
                      event Transfer(address indexed from, address indexed to, uint256 value);
                  
                      event Approval(address indexed owner, address indexed spender, uint256 value);
                  }
                  
                  ////// contracts/openzeppelin/SafeMath.sol
                  /* pragma solidity ^0.4.24; */
                  
                  /**
                   * @title SafeMath
                   * @dev Math operations with safety checks that revert on error
                   */
                  library SafeMath {
                      int256 constant private INT256_MIN = -2**255;
                  
                      /**
                      * @dev Multiplies two unsigned integers, reverts on 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-solidity/pull/522
                          if (a == 0) {
                              return 0;
                          }
                  
                          uint256 c = a * b;
                          require(c / a == b);
                  
                          return c;
                      }
                  
                      /**
                      * @dev Multiplies two signed integers, reverts on overflow.
                      */
                      function mul(int256 a, int256 b) internal pure returns (int256) {
                          // 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-solidity/pull/522
                          if (a == 0) {
                              return 0;
                          }
                  
                          require(!(a == -1 && b == INT256_MIN)); // This is the only case of overflow not detected by the check below
                  
                          int256 c = a * b;
                          require(c / a == b);
                  
                          return c;
                      }
                  
                      /**
                      * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
                      */
                      function div(uint256 a, uint256 b) internal pure returns (uint256) {
                          // Solidity only automatically asserts when dividing by 0
                          require(b > 0);
                          uint256 c = a / b;
                          // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                  
                          return c;
                      }
                  
                      /**
                      * @dev Integer division of two signed integers truncating the quotient, reverts on division by zero.
                      */
                      function div(int256 a, int256 b) internal pure returns (int256) {
                          require(b != 0); // Solidity only automatically asserts when dividing by 0
                          require(!(b == -1 && a == INT256_MIN)); // This is the only case of overflow
                  
                          int256 c = a / b;
                  
                          return c;
                      }
                  
                      /**
                      * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
                      */
                      function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                          require(b <= a);
                          uint256 c = a - b;
                  
                          return c;
                      }
                  
                      /**
                      * @dev Subtracts two signed integers, reverts on overflow.
                      */
                      function sub(int256 a, int256 b) internal pure returns (int256) {
                          int256 c = a - b;
                          require((b >= 0 && c <= a) || (b < 0 && c > a));
                  
                          return c;
                      }
                  
                      /**
                      * @dev Adds two unsigned integers, reverts on overflow.
                      */
                      function add(uint256 a, uint256 b) internal pure returns (uint256) {
                          uint256 c = a + b;
                          require(c >= a);
                  
                          return c;
                      }
                  
                      /**
                      * @dev Adds two signed integers, reverts on overflow.
                      */
                      function add(int256 a, int256 b) internal pure returns (int256) {
                          int256 c = a + b;
                          require((b >= 0 && c >= a) || (b < 0 && c < a));
                  
                          return c;
                      }
                  
                      /**
                      * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
                      * reverts when dividing by zero.
                      */
                      function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                          require(b != 0);
                          return a % b;
                      }
                  }
                  
                  ////// contracts/openzeppelin/ERC20.sol
                  /* pragma solidity ^0.4.24; */
                  
                  /* import "./IERC20.sol"; */
                  /* import "./SafeMath.sol"; */
                  
                  /**
                   * @title Standard ERC20 token
                   *
                   * @dev Implementation of the basic standard token.
                   * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
                   * Originally based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
                   *
                   * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for
                   * all accounts just by listening to said events. Note that this isn't required by the specification, and other
                   * compliant implementations may not do it.
                   */
                  contract ERC20 is IERC20 {
                      using SafeMath for uint256;
                  
                      mapping (address => uint256) private _balances;
                  
                      mapping (address => mapping (address => uint256)) private _allowed;
                  
                      uint256 private _totalSupply;
                  
                      /**
                      * @dev Total number of tokens in existence
                      */
                      function totalSupply() public view returns (uint256) {
                          return _totalSupply;
                      }
                  
                      /**
                      * @dev Gets the balance of the specified address.
                      * @param owner The address to query the balance of.
                      * @return An uint256 representing the amount owned by the passed address.
                      */
                      function balanceOf(address owner) public view returns (uint256) {
                          return _balances[owner];
                      }
                  
                      /**
                       * @dev Function to check the amount of tokens that an owner allowed to a spender.
                       * @param owner address The address which owns the funds.
                       * @param spender address The address which will spend the funds.
                       * @return A uint256 specifying the amount of tokens still available for the spender.
                       */
                      function allowance(address owner, address spender) public view returns (uint256) {
                          return _allowed[owner][spender];
                      }
                  
                      /**
                      * @dev Transfer token for a specified address
                      * @param to The address to transfer to.
                      * @param value The amount to be transferred.
                      */
                      function transfer(address to, uint256 value) public returns (bool) {
                          _transfer(msg.sender, to, value);
                          return true;
                      }
                  
                      /**
                       * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
                       * Beware that changing an allowance with this method brings the risk that someone may use both the old
                       * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
                       * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
                       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                       * @param spender The address which will spend the funds.
                       * @param value The amount of tokens to be spent.
                       */
                      function approve(address spender, uint256 value) public returns (bool) {
                          require(spender != address(0));
                  
                          _allowed[msg.sender][spender] = value;
                          emit Approval(msg.sender, spender, value);
                          return true;
                      }
                  
                      /**
                       * @dev Transfer tokens from one address to another.
                       * Note that while this function emits an Approval event, this is not required as per the specification,
                       * and other compliant implementations may not emit the event.
                       * @param from address The address which you want to send tokens from
                       * @param to address The address which you want to transfer to
                       * @param value uint256 the amount of tokens to be transferred
                       */
                      function transferFrom(address from, address to, uint256 value) public returns (bool) {
                          _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);
                          _transfer(from, to, value);
                          emit Approval(from, msg.sender, _allowed[from][msg.sender]);
                          return true;
                      }
                  
                      /**
                       * @dev Increase the amount of tokens that an owner allowed to a spender.
                       * approve should be called when allowed_[_spender] == 0. To increment
                       * allowed value is better to use this function to avoid 2 calls (and wait until
                       * the first transaction is mined)
                       * From MonolithDAO Token.sol
                       * Emits an Approval event.
                       * @param spender The address which will spend the funds.
                       * @param addedValue The amount of tokens to increase the allowance by.
                       */
                      function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
                          require(spender != address(0));
                  
                          _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add(addedValue);
                          emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
                          return true;
                      }
                  
                      /**
                       * @dev Decrease the amount of tokens that an owner allowed to a spender.
                       * approve should be called when allowed_[_spender] == 0. To decrement
                       * allowed value is better to use this function to avoid 2 calls (and wait until
                       * the first transaction is mined)
                       * From MonolithDAO Token.sol
                       * Emits an Approval event.
                       * @param spender The address which will spend the funds.
                       * @param subtractedValue The amount of tokens to decrease the allowance by.
                       */
                      function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
                          require(spender != address(0));
                  
                          _allowed[msg.sender][spender] = _allowed[msg.sender][spender].sub(subtractedValue);
                          emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
                          return true;
                      }
                  
                      /**
                      * @dev Transfer token for a specified addresses
                      * @param from The address to transfer from.
                      * @param to The address to transfer to.
                      * @param value The amount to be transferred.
                      */
                      function _transfer(address from, address to, uint256 value) internal {
                          require(to != address(0));
                  
                          _balances[from] = _balances[from].sub(value);
                          _balances[to] = _balances[to].add(value);
                          emit Transfer(from, to, value);
                      }
                  
                      /**
                       * @dev Internal function that mints an amount of the token and assigns it to
                       * an account. This encapsulates the modification of balances such that the
                       * proper events are emitted.
                       * @param account The account that will receive the created tokens.
                       * @param value The amount that will be created.
                       */
                      function _mint(address account, uint256 value) internal {
                          require(account != address(0));
                  
                          _totalSupply = _totalSupply.add(value);
                          _balances[account] = _balances[account].add(value);
                          emit Transfer(address(0), account, value);
                      }
                  
                      /**
                       * @dev Internal function that burns an amount of the token of a given
                       * account.
                       * @param account The account whose tokens will be burnt.
                       * @param value The amount that will be burnt.
                       */
                      function _burn(address account, uint256 value) internal {
                          require(account != address(0));
                  
                          _totalSupply = _totalSupply.sub(value);
                          _balances[account] = _balances[account].sub(value);
                          emit Transfer(account, address(0), value);
                      }
                  
                      /**
                       * @dev Internal function that burns an amount of the token of a given
                       * account, deducting from the sender's allowance for said account. Uses the
                       * internal burn function.
                       * Emits an Approval event (reflecting the reduced allowance).
                       * @param account The account whose tokens will be burnt.
                       * @param value The amount that will be burnt.
                       */
                      function _burnFrom(address account, uint256 value) internal {
                          _allowed[account][msg.sender] = _allowed[account][msg.sender].sub(value);
                          _burn(account, value);
                          emit Approval(account, msg.sender, _allowed[account][msg.sender]);
                      }
                  }
                  
                  ////// contracts/openzeppelin/ERC20Burnable.sol
                  /* pragma solidity ^0.4.24; */
                  
                  /* import "./ERC20.sol"; */
                  
                  /**
                   * @title Burnable Token
                   * @dev Token that can be irreversibly burned (destroyed).
                   */
                  contract ERC20Burnable is ERC20 {
                      /**
                       * @dev Burns a specific amount of tokens.
                       * @param value The amount of token to be burned.
                       */
                      function burn(uint256 value) public {
                          _burn(msg.sender, value);
                      }
                  
                      /**
                       * @dev Burns a specific amount of tokens from the target address and decrements allowance
                       * @param from address The address which you want to send tokens from
                       * @param value uint256 The amount of token to be burned
                       */
                      function burnFrom(address from, uint256 value) public {
                          _burnFrom(from, value);
                      }
                  }
                  
                  ////// contracts/openzeppelin/ERC20Detailed.sol
                  /* pragma solidity ^0.4.24; */
                  
                  /* import "./IERC20.sol"; */
                  
                  /**
                   * @title ERC20Detailed token
                   * @dev The decimals are only for visualization purposes.
                   * All the operations are done using the smallest and indivisible token unit,
                   * just as on Ethereum all the operations are done in wei.
                   */
                  contract ERC20Detailed is IERC20 {
                      string private _name;
                      string private _symbol;
                      uint8 private _decimals;
                  
                      constructor (string name, string symbol, uint8 decimals) public {
                          _name = name;
                          _symbol = symbol;
                          _decimals = decimals;
                      }
                  
                      /**
                       * @return the name of the token.
                       */
                      function name() public view returns (string) {
                          return _name;
                      }
                  
                      /**
                       * @return the symbol of the token.
                       */
                      function symbol() public view returns (string) {
                          return _symbol;
                      }
                  
                      /**
                       * @return the number of decimals of the token.
                       */
                      function decimals() public view returns (uint8) {
                          return _decimals;
                      }
                  }
                  
                  ////// contracts/Melon.sol
                  /* pragma solidity ^0.4.24; */
                  
                  /* import "./openzeppelin/ERC20Burnable.sol"; */
                  /* import "./openzeppelin/ERC20Detailed.sol"; */
                  /* import "./openzeppelin/SafeMath.sol"; */
                  
                  contract Melon is ERC20Burnable, ERC20Detailed {
                      using SafeMath for uint;
                  
                      uint public constant BASE_UNITS = 10 ** 18;
                      uint public constant INFLATION_ENABLE_DATE = 1551398400;
                      uint public constant INITIAL_TOTAL_SUPPLY = uint(932613).mul(BASE_UNITS);
                      uint public constant YEARLY_MINTABLE_AMOUNT = uint(300600).mul(BASE_UNITS);
                      uint public constant MINTING_INTERVAL = 365 days;
                  
                      address public council;
                      address public deployer;
                      bool public initialSupplyMinted;
                      uint public nextMinting = INFLATION_ENABLE_DATE;
                  
                      modifier onlyDeployer {
                          require(msg.sender == deployer, "Only deployer can call this");
                          _;
                      }
                  
                      modifier onlyCouncil {
                          require(msg.sender == council, "Only council can call this");
                          _;
                      }
                  
                      modifier anIntervalHasPassed {
                          require(
                              block.timestamp >= uint(nextMinting),
                              "Please wait until an interval has passed"
                          );
                          _;
                      }
                  
                      modifier inflationEnabled {
                          require(
                              block.timestamp >= INFLATION_ENABLE_DATE,
                              "Inflation is not enabled yet"
                          );
                          _;
                      }
                  
                      constructor(
                          string _name,
                          string _symbol,
                          uint8 _decimals,
                          address _council
                      ) public ERC20Detailed(_name, _symbol, _decimals) {
                          deployer = msg.sender;
                          council = _council;
                      }
                  
                      function changeCouncil(address _newCouncil) public onlyCouncil {
                          council = _newCouncil;
                      }
                  
                      function mintInitialSupply(address _initialReceiver) public onlyDeployer {
                          require(!initialSupplyMinted, "Initial minting already complete");
                          initialSupplyMinted = true;
                          _mint(_initialReceiver, INITIAL_TOTAL_SUPPLY);
                      }
                  
                      function mintInflation() public anIntervalHasPassed inflationEnabled {
                          require(initialSupplyMinted, "Initial minting not complete");
                          nextMinting = uint(nextMinting).add(MINTING_INTERVAL);
                          _mint(council, YEARLY_MINTABLE_AMOUNT);
                      }
                  }
                  

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

                  File 8 of 10: LiquidityConversionRates
                  pragma solidity 0.4.18;
                  
                  // File: contracts/ERC20Interface.sol
                  
                  // https://github.com/ethereum/EIPs/issues/20
                  interface ERC20 {
                      function totalSupply() public view returns (uint supply);
                      function balanceOf(address _owner) public view returns (uint balance);
                      function transfer(address _to, uint _value) public returns (bool success);
                      function transferFrom(address _from, address _to, uint _value) public returns (bool success);
                      function approve(address _spender, uint _value) public returns (bool success);
                      function allowance(address _owner, address _spender) public view returns (uint remaining);
                      function decimals() public view returns(uint digits);
                      event Approval(address indexed _owner, address indexed _spender, uint _value);
                  }
                  
                  // File: contracts/ConversionRatesInterface.sol
                  
                  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);
                  }
                  
                  // File: contracts/LiquidityFormula.sol
                  
                  contract UtilMath {
                      uint public constant BIG_NUMBER = (uint(1)<<uint(200));
                  
                      function checkMultOverflow(uint x, uint y) public pure returns(bool) {
                          if (y == 0) return false;
                          return (((x*y) / y) != x);
                      }
                  
                      function compactFraction(uint p, uint q, uint precision) public pure returns (uint, uint) {
                          if (q < precision * precision) return (p, q);
                          return compactFraction(p/precision, q/precision, precision);
                      }
                  
                      /* solhint-disable code-complexity */
                      function exp(uint p, uint q, uint precision) public pure returns (uint) {
                          uint n = 0;
                          uint nFact = 1;
                          uint currentP = 1;
                          uint currentQ = 1;
                  
                          uint sum = 0;
                          uint prevSum = 0;
                  
                          while (true) {
                              if (checkMultOverflow(currentP, precision)) return sum;
                              if (checkMultOverflow(currentQ, nFact)) return sum;
                  
                              sum += (currentP * precision) / (currentQ * nFact);
                  
                              if (sum == prevSum) return sum;
                              prevSum = sum;
                  
                              n++;
                  
                              if (checkMultOverflow(currentP, p)) return sum;
                              if (checkMultOverflow(currentQ, q)) return sum;
                              if (checkMultOverflow(nFact, n)) return sum;
                  
                              currentP *= p;
                              currentQ *= q;
                              nFact *= n;
                  
                              (currentP, currentQ) = compactFraction(currentP, currentQ, precision);
                          }
                      }
                      /* solhint-enable code-complexity */
                  
                      function countLeadingZeros(uint p, uint q) public pure returns (uint) {
                          uint denomator = (uint(1)<<255);
                          for (int i = 255; i >= 0; i--) {
                              if ((q*denomator)/denomator != q) {
                                  // overflow
                                  denomator = denomator/2;
                                  continue;
                              }
                              if (p/(q*denomator) > 0) return uint(i);
                              denomator = denomator/2;
                          }
                  
                          return uint(-1);
                      }
                  
                      // log2 for a number that it in [1,2)
                      function log2ForSmallNumber(uint x, uint numPrecisionBits) public pure returns (uint) {
                          uint res = 0;
                          uint one = (uint(1)<<numPrecisionBits);
                          uint two = 2 * one;
                          uint addition = one;
                  
                          require((x >= one) && (x <= two));
                          require(numPrecisionBits < 125);
                  
                          for (uint i = numPrecisionBits; i > 0; i--) {
                              x = (x*x) / one;
                              addition = addition/2;
                              if (x >= two) {
                                  x = x/2;
                                  res += addition;
                              }
                          }
                  
                          return res;
                      }
                  
                      function logBase2 (uint p, uint q, uint numPrecisionBits) public pure returns (uint) {
                          uint n = 0;
                          uint precision = (uint(1)<<numPrecisionBits);
                  
                          if (p > q) {
                              n = countLeadingZeros(p, q);
                          }
                  
                          require(!checkMultOverflow(p, precision));
                          require(!checkMultOverflow(n, precision));
                          require(!checkMultOverflow(uint(1)<<n, q));
                  
                          uint y = p * precision / (q * (uint(1)<<n));
                          uint log2Small = log2ForSmallNumber(y, numPrecisionBits);
                  
                          require(n*precision <= BIG_NUMBER);
                          require(log2Small <= BIG_NUMBER);
                  
                          return n * precision + log2Small;
                      }
                  
                      function ln(uint p, uint q, uint numPrecisionBits) public pure returns (uint) {
                          uint ln2Numerator   = 6931471805599453094172;
                          uint ln2Denomerator = 10000000000000000000000;
                  
                          uint log2x = logBase2(p, q, numPrecisionBits);
                  
                          require(!checkMultOverflow(ln2Numerator, log2x));
                  
                          return ln2Numerator * log2x / ln2Denomerator;
                      }
                  }
                  
                  
                  contract LiquidityFormula is UtilMath {
                      function pE(uint r, uint pMIn, uint e, uint precision) public pure returns (uint) {
                          uint expRE = exp(r*e, precision*precision, precision);
                          require(!checkMultOverflow(expRE, pMIn));
                          return pMIn*expRE / precision;
                      }
                  
                      function deltaTFunc(uint r, uint pMIn, uint e, uint deltaE, uint precision) public pure returns (uint) {
                          uint pe = pE(r, pMIn, e, precision);
                          uint rpe = r * pe;
                          uint erdeltaE = exp(r*deltaE, precision*precision, precision);
                  
                          require(erdeltaE >= precision);
                          require(!checkMultOverflow(erdeltaE - precision, precision));
                          require(!checkMultOverflow((erdeltaE - precision)*precision, precision));
                          require(!checkMultOverflow((erdeltaE - precision)*precision*precision, precision));
                          require(!checkMultOverflow(rpe, erdeltaE));
                          require(!checkMultOverflow(r, pe));
                  
                          return (erdeltaE - precision) * precision * precision * precision / (rpe*erdeltaE);
                      }
                  
                      function deltaEFunc(uint r, uint pMIn, uint e, uint deltaT, uint precision, uint numPrecisionBits)
                          public pure
                          returns (uint)
                      {
                          uint pe = pE(r, pMIn, e, precision);
                          uint rpe = r * pe;
                          uint lnPart = ln(precision*precision + rpe*deltaT/precision, precision*precision, numPrecisionBits);
                  
                          require(!checkMultOverflow(r, pe));
                          require(!checkMultOverflow(precision, precision));
                          require(!checkMultOverflow(rpe, deltaT));
                          require(!checkMultOverflow(lnPart, precision));
                  
                          return lnPart * precision / r;
                      }
                  }
                  
                  // File: contracts/Utils.sol
                  
                  /// @title Kyber constants contract
                  contract Utils {
                  
                      ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
                      uint  constant internal PRECISION = (10**18);
                      uint  constant internal MAX_QTY   = (10**28); // 10B tokens
                      uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
                      uint  constant internal MAX_DECIMALS = 18;
                      uint  constant internal ETH_DECIMALS = 18;
                      mapping(address=>uint) internal decimals;
                  
                      function setDecimals(ERC20 token) internal {
                          if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                          else decimals[token] = token.decimals();
                      }
                  
                      function getDecimals(ERC20 token) internal view returns(uint) {
                          if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                          uint tokenDecimals = decimals[token];
                          // technically, there might be token with decimals 0
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          if(tokenDecimals == 0) return token.decimals();
                  
                          return tokenDecimals;
                      }
                  
                      function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(srcQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                  
                          if (dstDecimals >= srcDecimals) {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                          } else {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                          }
                      }
                  
                      function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                          require(dstQty <= MAX_QTY);
                          require(rate <= MAX_RATE);
                          
                          //source quantity is rounded up. to avoid dest quantity being too low.
                          uint numerator;
                          uint denominator;
                          if (srcDecimals >= dstDecimals) {
                              require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                              denominator = rate;
                          } else {
                              require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                              numerator = (PRECISION * dstQty);
                              denominator = (rate * (10**(dstDecimals - srcDecimals)));
                          }
                          return (numerator + denominator - 1) / denominator; //avoid rounding down errors
                      }
                  }
                  
                  // File: contracts/PermissionGroups.sol
                  
                  contract PermissionGroups {
                  
                      address public admin;
                      address public pendingAdmin;
                      mapping(address=>bool) internal operators;
                      mapping(address=>bool) internal alerters;
                      address[] internal operatorsGroup;
                      address[] internal alertersGroup;
                      uint constant internal MAX_GROUP_SIZE = 50;
                  
                      function PermissionGroups() public {
                          admin = msg.sender;
                      }
                  
                      modifier onlyAdmin() {
                          require(msg.sender == admin);
                          _;
                      }
                  
                      modifier onlyOperator() {
                          require(operators[msg.sender]);
                          _;
                      }
                  
                      modifier onlyAlerter() {
                          require(alerters[msg.sender]);
                          _;
                      }
                  
                      function getOperators () external view returns(address[]) {
                          return operatorsGroup;
                      }
                  
                      function getAlerters () external view returns(address[]) {
                          return alertersGroup;
                      }
                  
                      event TransferAdminPending(address pendingAdmin);
                  
                      /**
                       * @dev Allows the current admin to set the pendingAdmin address.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdmin(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(pendingAdmin);
                          pendingAdmin = newAdmin;
                      }
                  
                      /**
                       * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
                       * @param newAdmin The address to transfer ownership to.
                       */
                      function transferAdminQuickly(address newAdmin) public onlyAdmin {
                          require(newAdmin != address(0));
                          TransferAdminPending(newAdmin);
                          AdminClaimed(newAdmin, admin);
                          admin = newAdmin;
                      }
                  
                      event AdminClaimed( address newAdmin, address previousAdmin);
                  
                      /**
                       * @dev Allows the pendingAdmin address to finalize the change admin process.
                       */
                      function claimAdmin() public {
                          require(pendingAdmin == msg.sender);
                          AdminClaimed(pendingAdmin, admin);
                          admin = pendingAdmin;
                          pendingAdmin = address(0);
                      }
                  
                      event AlerterAdded (address newAlerter, bool isAdd);
                  
                      function addAlerter(address newAlerter) public onlyAdmin {
                          require(!alerters[newAlerter]); // prevent duplicates.
                          require(alertersGroup.length < MAX_GROUP_SIZE);
                  
                          AlerterAdded(newAlerter, true);
                          alerters[newAlerter] = true;
                          alertersGroup.push(newAlerter);
                      }
                  
                      function removeAlerter (address alerter) public onlyAdmin {
                          require(alerters[alerter]);
                          alerters[alerter] = false;
                  
                          for (uint i = 0; i < alertersGroup.length; ++i) {
                              if (alertersGroup[i] == alerter) {
                                  alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                                  alertersGroup.length--;
                                  AlerterAdded(alerter, false);
                                  break;
                              }
                          }
                      }
                  
                      event OperatorAdded(address newOperator, bool isAdd);
                  
                      function addOperator(address newOperator) public onlyAdmin {
                          require(!operators[newOperator]); // prevent duplicates.
                          require(operatorsGroup.length < MAX_GROUP_SIZE);
                  
                          OperatorAdded(newOperator, true);
                          operators[newOperator] = true;
                          operatorsGroup.push(newOperator);
                      }
                  
                      function removeOperator (address operator) public onlyAdmin {
                          require(operators[operator]);
                          operators[operator] = false;
                  
                          for (uint i = 0; i < operatorsGroup.length; ++i) {
                              if (operatorsGroup[i] == operator) {
                                  operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                                  operatorsGroup.length -= 1;
                                  OperatorAdded(operator, false);
                                  break;
                              }
                          }
                      }
                  }
                  
                  // File: contracts/Withdrawable.sol
                  
                  /**
                   * @title Contracts that should be able to recover tokens or ethers
                   * @author Ilan Doron
                   * @dev This allows to recover any tokens or Ethers received in a contract.
                   * This will prevent any accidental loss of tokens.
                   */
                  contract Withdrawable is PermissionGroups {
                  
                      event TokenWithdraw(ERC20 token, uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw all ERC20 compatible tokens
                       * @param token ERC20 The address of the token contract
                       */
                      function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                          require(token.transfer(sendTo, amount));
                          TokenWithdraw(token, amount, sendTo);
                      }
                  
                      event EtherWithdraw(uint amount, address sendTo);
                  
                      /**
                       * @dev Withdraw Ethers
                       */
                      function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                          sendTo.transfer(amount);
                          EtherWithdraw(amount, sendTo);
                      }
                  }
                  
                  // File: contracts/LiquidityConversionRates.sol
                  
                  contract LiquidityConversionRates is ConversionRatesInterface, LiquidityFormula, Withdrawable, Utils {
                      ERC20 public token;
                      address public reserveContract;
                  
                      uint public numFpBits;
                      uint public formulaPrecision;
                  
                      uint public rInFp;
                      uint public pMinInFp;
                  
                      uint public maxEthCapBuyInFp;
                      uint public maxEthCapSellInFp;
                      uint public maxQtyInFp;
                  
                      uint public feeInBps;
                      uint public collectedFeesInTwei = 0;
                  
                      uint public maxBuyRateInPrecision;
                      uint public minBuyRateInPrecision;
                      uint public maxSellRateInPrecision;
                      uint public minSellRateInPrecision;
                  
                      function LiquidityConversionRates(address _admin, ERC20 _token) public {
                          transferAdminQuickly(_admin);
                          token = _token;
                          setDecimals(token);
                          require(getDecimals(token) <= MAX_DECIMALS);
                      }
                  
                      event ReserveAddressSet(address reserve);
                  
                      function setReserveAddress(address reserve) public onlyAdmin {
                          reserveContract = reserve;
                          ReserveAddressSet(reserve);
                      }
                  
                      event LiquidityParamsSet(
                          uint rInFp,
                          uint pMinInFp,
                          uint numFpBits,
                          uint maxCapBuyInFp,
                          uint maxEthCapSellInFp,
                          uint feeInBps,
                          uint formulaPrecision,
                          uint maxQtyInFp,
                          uint maxBuyRateInPrecision,
                          uint minBuyRateInPrecision,
                          uint maxSellRateInPrecision,
                          uint minSellRateInPrecision
                      );
                  
                      function setLiquidityParams(
                          uint _rInFp,
                          uint _pMinInFp,
                          uint _numFpBits,
                          uint _maxCapBuyInWei,
                          uint _maxCapSellInWei,
                          uint _feeInBps,
                          uint _maxTokenToEthRateInPrecision,
                          uint _minTokenToEthRateInPrecision
                      ) public onlyAdmin {
                  
                          require(_numFpBits < 256);
                          require(formulaPrecision <= MAX_QTY);
                          require(_feeInBps < 10000);
                          require(_minTokenToEthRateInPrecision < _maxTokenToEthRateInPrecision);
                  
                          rInFp = _rInFp;
                          pMinInFp = _pMinInFp;
                          formulaPrecision = uint(1)<<_numFpBits;
                          maxQtyInFp = fromWeiToFp(MAX_QTY);
                          numFpBits = _numFpBits;
                          maxEthCapBuyInFp = fromWeiToFp(_maxCapBuyInWei);
                          maxEthCapSellInFp = fromWeiToFp(_maxCapSellInWei);
                          feeInBps = _feeInBps;
                          maxBuyRateInPrecision = PRECISION * PRECISION / _minTokenToEthRateInPrecision;
                          minBuyRateInPrecision = PRECISION * PRECISION / _maxTokenToEthRateInPrecision;
                          maxSellRateInPrecision = _maxTokenToEthRateInPrecision;
                          minSellRateInPrecision = _minTokenToEthRateInPrecision;
                  
                          LiquidityParamsSet(
                              rInFp,
                              pMinInFp,
                              numFpBits,
                              maxEthCapBuyInFp,
                              maxEthCapSellInFp,
                              feeInBps,
                              formulaPrecision,
                              maxQtyInFp,
                              maxBuyRateInPrecision,
                              minBuyRateInPrecision,
                              maxSellRateInPrecision,
                              minSellRateInPrecision
                          );
                      }
                  
                      function recordImbalance(
                          ERC20 conversionToken,
                          int buyAmountInTwei,
                          uint rateUpdateBlock,
                          uint currentBlock
                      )
                          public
                      {
                          conversionToken;
                          rateUpdateBlock;
                          currentBlock;
                  
                          require(msg.sender == reserveContract);
                          if (buyAmountInTwei > 0) {
                              // Buy case
                              collectedFeesInTwei += calcCollectedFee(abs(buyAmountInTwei));
                          } else {
                              // Sell case
                              collectedFeesInTwei += abs(buyAmountInTwei) * feeInBps / 10000;
                          }
                      }
                  
                      event CollectedFeesReset(uint resetFeesInTwei);
                  
                      function resetCollectedFees() public onlyAdmin {
                          uint resetFeesInTwei = collectedFeesInTwei;
                          collectedFeesInTwei = 0;
                  
                          CollectedFeesReset(resetFeesInTwei);
                      }
                  
                      function getRate(
                              ERC20 conversionToken,
                              uint currentBlockNumber,
                              bool buy,
                              uint qtyInSrcWei
                      ) public view returns(uint) {
                  
                          currentBlockNumber;
                  
                          require(qtyInSrcWei <= MAX_QTY);
                          uint eInFp = fromWeiToFp(reserveContract.balance);
                          uint rateInPrecision = getRateWithE(conversionToken, buy, qtyInSrcWei, eInFp);
                          require(rateInPrecision <= MAX_RATE);
                          return rateInPrecision;
                      }
                  
                      function getRateWithE(ERC20 conversionToken, bool buy, uint qtyInSrcWei, uint eInFp) public view returns(uint) {
                          uint deltaEInFp;
                          uint sellInputTokenQtyInFp;
                          uint deltaTInFp;
                          uint rateInPrecision;
                  
                          require(qtyInSrcWei <= MAX_QTY);
                          require(eInFp <= maxQtyInFp);
                          if (conversionToken != token) return 0;
                  
                          if (buy) {
                              // ETH goes in, token goes out
                              deltaEInFp = fromWeiToFp(qtyInSrcWei);
                              if (deltaEInFp > maxEthCapBuyInFp) return 0;
                  
                              if (deltaEInFp == 0) {
                                  rateInPrecision = buyRateZeroQuantity(eInFp);
                              } else {
                                  rateInPrecision = buyRate(eInFp, deltaEInFp);
                              }
                          } else {
                              sellInputTokenQtyInFp = fromTweiToFp(qtyInSrcWei);
                              deltaTInFp = valueAfterReducingFee(sellInputTokenQtyInFp);
                              if (deltaTInFp == 0) {
                                  rateInPrecision = sellRateZeroQuantity(eInFp);
                                  deltaEInFp = 0;
                              } else {
                                  (rateInPrecision, deltaEInFp) = sellRate(eInFp, sellInputTokenQtyInFp, deltaTInFp);
                              }
                  
                              if (deltaEInFp > maxEthCapSellInFp) return 0;
                          }
                  
                          rateInPrecision = rateAfterValidation(rateInPrecision, buy);
                          return rateInPrecision;
                      }
                  
                      function rateAfterValidation(uint rateInPrecision, bool buy) public view returns(uint) {
                          uint minAllowRateInPrecision;
                          uint maxAllowedRateInPrecision;
                  
                          if (buy) {
                              minAllowRateInPrecision = minBuyRateInPrecision;
                              maxAllowedRateInPrecision = maxBuyRateInPrecision;
                          } else {
                              minAllowRateInPrecision = minSellRateInPrecision;
                              maxAllowedRateInPrecision = maxSellRateInPrecision;
                          }
                  
                          if ((rateInPrecision > maxAllowedRateInPrecision) || (rateInPrecision < minAllowRateInPrecision)) {
                              return 0;
                          } else if (rateInPrecision > MAX_RATE) {
                              return 0;
                          } else {
                              return rateInPrecision;
                          }
                      }
                  
                      function buyRate(uint eInFp, uint deltaEInFp) public view returns(uint) {
                          uint deltaTInFp = deltaTFunc(rInFp, pMinInFp, eInFp, deltaEInFp, formulaPrecision);
                          require(deltaTInFp <= maxQtyInFp);
                          deltaTInFp = valueAfterReducingFee(deltaTInFp);
                          return deltaTInFp * PRECISION / deltaEInFp;
                      }
                  
                      function buyRateZeroQuantity(uint eInFp) public view returns(uint) {
                          uint ratePreReductionInPrecision = formulaPrecision * PRECISION / pE(rInFp, pMinInFp, eInFp, formulaPrecision);
                          return valueAfterReducingFee(ratePreReductionInPrecision);
                      }
                  
                      function sellRate(
                          uint eInFp,
                          uint sellInputTokenQtyInFp,
                          uint deltaTInFp
                      ) public view returns(uint rateInPrecision, uint deltaEInFp) {
                          deltaEInFp = deltaEFunc(rInFp, pMinInFp, eInFp, deltaTInFp, formulaPrecision, numFpBits);
                          require(deltaEInFp <= maxQtyInFp);
                          rateInPrecision = deltaEInFp * PRECISION / sellInputTokenQtyInFp;
                      }
                  
                      function sellRateZeroQuantity(uint eInFp) public view returns(uint) {
                          uint ratePreReductionInPrecision = pE(rInFp, pMinInFp, eInFp, formulaPrecision) * PRECISION / formulaPrecision;
                          return valueAfterReducingFee(ratePreReductionInPrecision);
                      }
                  
                      function fromTweiToFp(uint qtyInTwei) public view returns(uint) {
                          require(qtyInTwei <= MAX_QTY);
                          return qtyInTwei * formulaPrecision / (10 ** getDecimals(token));
                      }
                  
                      function fromWeiToFp(uint qtyInwei) public view returns(uint) {
                          require(qtyInwei <= MAX_QTY);
                          return qtyInwei * formulaPrecision / (10 ** ETH_DECIMALS);
                      }
                  
                      function valueAfterReducingFee(uint val) public view returns(uint) {
                          require(val <= BIG_NUMBER);
                          return ((10000 - feeInBps) * val) / 10000;
                      }
                  
                      function calcCollectedFee(uint val) public view returns(uint) {
                          require(val <= MAX_QTY);
                          return val * feeInBps / (10000 - feeInBps);
                      }
                  
                      function abs(int val) public pure returns(uint) {
                          if (val < 0) {
                              return uint(val * (-1));
                          } else {
                              return uint(val);
                          }
                      }
                  
                  }

                  File 9 of 10: KatanaFeeHandler
                  pragma solidity 0.6.6;
                  import "../utils/Utils5.sol";
                  import "../utils/zeppelin/ReentrancyGuard.sol";
                  import "../utils/zeppelin/SafeMath.sol";
                  import "../IKyberFeeHandler.sol";
                  import "../IKyberNetworkProxy.sol";
                  import "../ISimpleKyberProxy.sol";
                  import "../mock/reserves/ISanityRate.sol";
                  import "../mock/dao/IBurnableToken.sol";
                  import "../mock/dao/DaoOperator.sol";
                  /**
                   * @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:
                   *    Per trade on kyberNetwork, it calls handleFees() function which
                   *    internally accounts for network & platform fees from the trade. 
                   *    Fee distribution:
                   *      rewards: send to fee pool
                   *      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.
                   */
                  contract KatanaFeeHandler is Utils5, DaoOperator, ReentrancyGuard, IKyberFeeHandler {
                      using SafeMath for uint256;
                      uint256 internal constant SANITY_RATE_DIFF_BPS = 1000; // 10%
                      struct BRRData {
                          uint16 rewardBps;
                          uint16 rebateBps;
                      }
                      struct BRRWei {
                          uint256 rewardWei;
                          uint256 fullRebateWei;
                          uint256 paidRebateWei;
                          uint256 burnWei;
                      }
                      IKyberProxy public kyberProxy;
                      address public feePool;
                      IERC20 public immutable knc;
                      uint256 public immutable burnBlockInterval;
                      uint256 public lastBurnBlock;
                      BRRData public brrData;
                      /// @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;
                      // total balance in the contract that is for rebate, reward, platform fee
                      uint256 public totalPayoutBalance;
                      /// @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 sender,
                          address indexed platformWallet,
                          uint256 platformFeeWei,
                          uint256 rewardWei,
                          uint256 rebateWei,
                          address[] rebateWallets,
                          uint256[] rebatePercentBpsPerWallet,
                          uint256 burnAmtWei
                      );
                      event BRRUpdated(uint256 rewardBps, uint256 rebateBps, uint256 burnBps);
                      event FeePoolUpdated(address feePool);
                      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);
                      event EthReceived(uint256 amount);
                      event BurnConfigSet(ISanityRate sanityRate, uint256 weiToBurn);
                      event DaoOperatorUpdated(address daoOperator);
                      event KyberProxyUpdated(IKyberProxy kyberProxy);
                      constructor(
                          IKyberProxy _kyberProxy,
                          IERC20 _knc,
                          uint256 _burnBlockInterval,
                          address _daoOperator,
                          address _feePool,
                          uint256 _rewardBps,
                          uint256 _rebateBps
                      ) public DaoOperator(_daoOperator) {
                          require(_kyberProxy != IKyberProxy(0), "kyberNetworkProxy 0");
                          require(_knc != IERC20(0), "knc 0");
                          require(_burnBlockInterval != 0, "_burnBlockInterval 0");
                          require(_feePool != address(0), "feePool 0");
                          kyberProxy = _kyberProxy;
                          knc = _knc;
                          burnBlockInterval = _burnBlockInterval;
                          feePool = _feePool;
                          // set default brrData
                          require(_rewardBps.add(_rebateBps) <= BPS, "Bad BRR values");
                          brrData.rewardBps = uint16(_rewardBps);
                          brrData.rebateBps = uint16(_rebateBps);
                      }
                      modifier onlyNonContract {
                          require(tx.origin == msg.sender, "only non-contract");
                          _;
                      }
                      receive() external payable {
                          emit EthReceived(msg.value);
                      }
                      function setDaoOperator(address _daoOperator) external onlyDaoOperator {
                          require(_daoOperator != address(0), "daoOperator 0");
                          daoOperator = _daoOperator;
                          emit DaoOperatorUpdated(_daoOperator);
                      }
                      /// @dev only call by daoOperator
                      function setBRRData(
                          uint256 _burnBps,
                          uint256 _rewardBps,
                          uint256 _rebateBps
                      ) external onlyDaoOperator {
                          require(_burnBps.add(_rewardBps).add(_rebateBps) == BPS, "Bad BRR values");
                          brrData.rewardBps = uint16(_rewardBps);
                          brrData.rebateBps = uint16(_rebateBps);
                          emit BRRUpdated(_rewardBps, _rebateBps, _burnBps);
                      }
                      function setFeePool(address _feePool) external onlyDaoOperator {
                          require(_feePool != address(0), "feePool 0");
                          feePool = _feePool;
                          emit FeePoolUpdated(_feePool);
                      }
                      /// @dev handleFees function is called per trade on kyberNetwork
                      /// @dev 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 nonReentrant {
                          require(token == ETH_TOKEN_ADDRESS, "token not eth");
                          require(msg.value == platformFee.add(networkFee), "msg.value != 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,
                                  msg.sender,
                                  platformWallet,
                                  platformFee,
                                  0,
                                  0,
                                  rebateWallets,
                                  rebateBpsPerWallet,
                                  0
                              );
                              return;
                          }
                          BRRWei memory brrAmounts;
                          // Decoding BRR data
                          (brrAmounts.rewardWei, brrAmounts.fullRebateWei) = getRRWeiValues(networkFee);
                          brrAmounts.paidRebateWei = updateRebateValues(
                              brrAmounts.fullRebateWei,
                              rebateWallets,
                              rebateBpsPerWallet
                          );
                          brrAmounts.burnWei = networkFee.sub(brrAmounts.rewardWei).sub(brrAmounts.paidRebateWei);
                          // update total balance of rebates & platform fee
                          totalPayoutBalance = totalPayoutBalance.add(platformFee).add(brrAmounts.paidRebateWei);
                          //TODO: transfer reward to fee pool
                          (bool success, ) = feePool.call{value: brrAmounts.rewardWei}("");
                          require(success, "send fee failed");
                          emit FeeDistributed(
                              ETH_TOKEN_ADDRESS,
                              msg.sender,
                              platformWallet,
                              platformFee,
                              brrAmounts.rewardWei,
                              brrAmounts.paidRebateWei,
                              rebateWallets,
                              rebateBpsPerWallet,
                              brrAmounts.burnWei
                          );
                      }
                      /// @dev claim rebate per reserve wallet. called by any address
                      /// @param rebateWallet the wallet to claim rebates for. 
                      /// @dev 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 implement so this contract is not marked as abstract
                      function claimStakerReward(
                          address, /*staker*/
                          uint256 /*epoch*/
                      ) external override returns (uint256) {
                          revert();
                      }
                      /// @dev claim accumulated fee per platform wallet. Called by any address
                      /// @param platformWallet the wallet to claim fee for.
                      /// @dev 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 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;
                      }
                      /// @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 readBRRData() external view returns (uint256 rewardBps, uint256 rebateBps) {
                          rewardBps = uint256(brrData.rewardBps);
                          rebateBps = uint256(brrData.rebateBps);
                      }
                      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, "totalRebateBps > 100%");
                      }
                      function getRRWeiValues(uint256 rrAmountWei)
                          internal
                          view
                          returns (uint256 rewardWei, uint256 rebateWei)
                      {
                          // Decoding BRR data
                          uint256 rewardInBps = uint256(brrData.rewardBps);
                          uint256 rebateInBps = uint256(brrData.rebateBps);
                          rebateWei = rrAmountWei.mul(rebateInBps).div(BPS);
                          rewardWei = rrAmountWei.mul(rewardInBps).div(BPS);
                      }
                      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"
                          );
                      }
                  }
                  pragma solidity 0.6.6;
                  import "../IERC20.sol";
                  /**
                   * @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;
                      }
                  }
                  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;
                      }
                  }
                  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;
                      }
                  }
                  pragma solidity 0.6.6;
                  import "./IERC20.sol";
                  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);
                  }
                  pragma solidity 0.6.6;
                  import "./IERC20.sol";
                  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);
                  }
                  pragma solidity 0.6.6;
                  import "./IERC20.sol";
                  /*
                   * @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);
                  }
                  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);
                  }
                  pragma solidity 0.6.6;
                  interface IBurnableToken {
                      function burn(uint256 _value) external returns (bool);
                  }
                  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");
                          _;
                      }
                  }
                  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 10 of 10: TreasuryPool
                  // SPDX-License-Identifier: agpl-3.0
                  pragma solidity 0.7.6;
                  import {Pool} from './Pool.sol';
                  contract TreasuryPool is Pool {
                    constructor(address _admin, address[] memory _strategies)
                      Pool(_admin, _strategies) {}
                  }
                  // SPDX-License-Identifier: agpl-3.0
                  pragma solidity 0.7.6;
                  import {Utils} from '@kyber.network/utils-sc/contracts/Utils.sol';
                  import {PermissionAdmin} from '@kyber.network/utils-sc/contracts/PermissionAdmin.sol';
                  import {PermissionOperators} from '@kyber.network/utils-sc/contracts/PermissionOperators.sol';
                  import {IERC20Ext} from '@kyber.network/utils-sc/contracts/IERC20Ext.sol';
                  import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
                  import {EnumerableSet} from '@openzeppelin/contracts/utils/EnumerableSet.sol';
                  import {IPool} from '../interfaces/liquidation/IPool.sol';
                  /**
                  * Pool contract containing all tokens which whitelisted strategies can withdraw funds from
                  */
                  contract Pool is IPool, PermissionAdmin, PermissionOperators, Utils {
                    using SafeERC20 for IERC20Ext;
                    using EnumerableSet for EnumerableSet.AddressSet;
                    EnumerableSet.AddressSet private _authorizedStrategies;
                    bool private _isPaused;
                    constructor(address admin, address[] memory strategies) PermissionAdmin(admin) {
                      for(uint256 i = 0; i < strategies.length; i++) {
                        _authorizeStrategy(strategies[i]);
                      }
                      _isPaused = false;
                    }
                    receive() external payable {}
                    function authorizeStrategies(address[] calldata strategies)
                      external override onlyAdmin
                    {
                      for(uint256 i = 0; i < strategies.length; i++) {
                        _authorizeStrategy(strategies[i]);
                      }
                    }
                    function unauthorizeStrategies(address[] calldata strategies)
                      external override onlyAdmin
                    {
                      for(uint256 i = 0; i < strategies.length; i++) {
                        _unauthorizeStrategy(strategies[i]);
                      }
                    }
                    function pause() external override onlyOperator {
                      _isPaused = true;
                      emit Paused(msg.sender);
                    }
                    function unpause() external override onlyAdmin {
                      _isPaused = false;
                      emit Unpaused(msg.sender);
                    }
                    function withdrawFunds(
                      IERC20Ext[] calldata tokens,
                      uint256[] calldata amounts,
                      address payable recipient
                    ) external override {
                      require(!_isPaused, 'only when not paused');
                      require(isAuthorizedStrategy(msg.sender), 'not authorized');
                      require(tokens.length == amounts.length, 'invalid lengths');
                      for(uint256 i = 0; i < tokens.length; i++) {
                        _transferToken(tokens[i], amounts[i], recipient);
                      }
                    }
                    function isPaused() external view override returns (bool) {
                      return _isPaused;
                    }
                    function getAuthorizedStrategiesLength()
                      external view override returns (uint256)
                    {
                      return _authorizedStrategies.length();
                    }
                    function getAuthorizedStrategyAt(uint256 index)
                      external view override returns (address)
                    {
                      return _authorizedStrategies.at(index);
                    }
                    function getAllAuthorizedStrategies()
                      external view override returns (address[] memory strategies)
                    {
                      uint256 length = _authorizedStrategies.length();
                      strategies = new address[](length);
                      for(uint256 i = 0; i < length; i++) {
                        strategies[i] = _authorizedStrategies.at(i);
                      }
                    }
                    function isAuthorizedStrategy(address strategy)
                      public view override returns (bool)
                    {
                      return _authorizedStrategies.contains(strategy);
                    }
                    function _authorizeStrategy(address strategy) internal {
                      require(strategy != address(0), 'invalid strategy');
                      require(!isAuthorizedStrategy(strategy), 'only unauthorized strategy');
                      require(_authorizedStrategies.add(strategy), 'unable to add new strategy');
                      emit AuthorizedStrategy(strategy);
                    }
                    function _unauthorizeStrategy(address _strategy) internal {
                      require(_strategy != address(0), 'invalid strategy');
                      require(isAuthorizedStrategy(_strategy), 'only authorized strategy');
                      require(_authorizedStrategies.remove(_strategy), 'unable to remove strategy');
                      emit UnauthorizedStrategy(_strategy);
                    }
                    function _transferToken(
                      IERC20Ext _token,
                      uint256 _amount,
                      address payable _recipient
                    ) internal {
                      if (_token == ETH_TOKEN_ADDRESS) {
                        (bool success, ) = _recipient.call{ value: _amount }('');
                          require(success, 'transfer eth failed');
                      } else {
                        _token.safeTransfer(_recipient, _amount);
                      }
                      emit WithdrawToken(_token, msg.sender, _recipient, _amount);
                    }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.7.6;
                  import "./IERC20Ext.sol";
                  /**
                   * @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.
                   */
                  abstract contract Utils {
                      // Declared constants below to be used in tandem with
                      // getDecimalsConstant(), for gas optimization purposes
                      // which return decimals from a constant list of popular
                      // tokens.
                      IERC20Ext internal constant ETH_TOKEN_ADDRESS = IERC20Ext(
                          0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
                      );
                      IERC20Ext internal constant USDT_TOKEN_ADDRESS = IERC20Ext(
                          0xdAC17F958D2ee523a2206206994597C13D831ec7
                      );
                      IERC20Ext internal constant DAI_TOKEN_ADDRESS = IERC20Ext(
                          0x6B175474E89094C44Da98b954EedeAC495271d0F
                      );
                      IERC20Ext internal constant USDC_TOKEN_ADDRESS = IERC20Ext(
                          0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
                      );
                      IERC20Ext internal constant WBTC_TOKEN_ADDRESS = IERC20Ext(
                          0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599
                      );
                      IERC20Ext internal constant KNC_TOKEN_ADDRESS = IERC20Ext(
                          0xdd974D5C2e2928deA5F71b9825b8b646686BD200
                      );
                      uint256 public constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
                      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 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
                      mapping(IERC20Ext => uint256) internal decimals;
                      /// @dev Sets the decimals of a token to storage if not already set, and returns
                      ///      the decimals value of the token. Prefer using this function over
                      ///      getDecimals(), to avoid forgetting to set decimals in local storage.
                      /// @param token The token type
                      /// @return tokenDecimals The decimals of the token
                      function getSetDecimals(IERC20Ext token) internal returns (uint256 tokenDecimals) {
                          tokenDecimals = getDecimalsConstant(token);
                          if (tokenDecimals > 0) return tokenDecimals;
                          tokenDecimals = decimals[token];
                          if (tokenDecimals == 0) {
                              tokenDecimals = token.decimals();
                              decimals[token] = tokenDecimals;
                          }
                      }
                      /// @dev Get the balance of a user
                      /// @param token The token type
                      /// @param user The user's address
                      /// @return The balance
                      function getBalance(IERC20Ext token, address user) internal view returns (uint256) {
                          if (token == ETH_TOKEN_ADDRESS) {
                              return user.balance;
                          } else {
                              return token.balanceOf(user);
                          }
                      }
                      /// @dev Get the decimals of a token, read from the constant list, storage,
                      ///      or from token.decimals(). Prefer using getSetDecimals when possible.
                      /// @param token The token type
                      /// @return tokenDecimals The decimals of the token
                      function getDecimals(IERC20Ext token) internal view returns (uint256 tokenDecimals) {
                          // return token decimals if has constant value
                          tokenDecimals = getDecimalsConstant(token);
                          if (tokenDecimals > 0) return tokenDecimals;
                          // handle case where token decimals is not a declared decimal constant
                          tokenDecimals = decimals[token];
                          // moreover, very possible that old tokens have decimals 0
                          // these tokens will just have higher gas fees.
                          return (tokenDecimals > 0) ? tokenDecimals : token.decimals();
                      }
                      function calcDestAmount(
                          IERC20Ext src,
                          IERC20Ext dest,
                          uint256 srcAmount,
                          uint256 rate
                      ) internal view returns (uint256) {
                          return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
                      }
                      function calcSrcAmount(
                          IERC20Ext src,
                          IERC20Ext 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);
                          }
                      }
                      /// @dev save storage access by declaring token decimal constants
                      /// @param token The token type
                      /// @return token decimals
                      function getDecimalsConstant(IERC20Ext token) internal pure returns (uint256) {
                          if (token == ETH_TOKEN_ADDRESS) {
                              return ETH_DECIMALS;
                          } else if (token == USDT_TOKEN_ADDRESS) {
                              return 6;
                          } else if (token == DAI_TOKEN_ADDRESS) {
                              return 18;
                          } else if (token == USDC_TOKEN_ADDRESS) {
                              return 6;
                          } else if (token == WBTC_TOKEN_ADDRESS) {
                              return 8;
                          } else if (token == KNC_TOKEN_ADDRESS) {
                              return 18;
                          } else {
                              return 0;
                          }
                      }
                      function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
                          return x > y ? y : x;
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.7.6;
                  abstract contract PermissionAdmin {
                      address public admin;
                      address public pendingAdmin;
                      event AdminClaimed(address newAdmin, address previousAdmin);
                      event TransferAdminPending(address pendingAdmin);
                      constructor(address _admin) {
                          require(_admin != address(0), "admin 0");
                          admin = _admin;
                      }
                      modifier onlyAdmin() {
                          require(msg.sender == admin, "only admin");
                          _;
                      }
                      /**
                       * @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;
                      }
                      /**
                       * @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);
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.7.6;
                  import "./PermissionAdmin.sol";
                  abstract contract PermissionOperators is PermissionAdmin {
                      uint256 private constant MAX_GROUP_SIZE = 50;
                      mapping(address => bool) internal operators;
                      address[] internal operatorsGroup;
                      event OperatorAdded(address newOperator, bool isAdd);
                      modifier onlyOperator() {
                          require(operators[msg.sender], "only operator");
                          _;
                      }
                      function getOperators() external view returns (address[] memory) {
                          return operatorsGroup;
                      }
                      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);
                      }
                      function removeOperator(address operator) public onlyAdmin {
                          require(operators[operator], "not operator");
                          operators[operator] = false;
                          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;
                              }
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity 0.7.6;
                  import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                  /**
                   * @dev Interface extending ERC20 standard to include decimals() as
                   *      it is optional in the OpenZeppelin IERC20 interface.
                   */
                  interface IERC20Ext is IERC20 {
                      /**
                       * @dev This function is required as Kyber requires to interact
                       *      with token.decimals() with many of its operations.
                       */
                      function decimals() external view returns (uint8 digits);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.7.0;
                  import "./IERC20.sol";
                  import "../../math/SafeMath.sol";
                  import "../../utils/Address.sol";
                  /**
                   * @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 IERC20;` 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));
                      }
                      /**
                       * @dev Deprecated. This function has issues similar to the ones found in
                       * {IERC20-approve}, and its usage is discouraged.
                       *
                       * Whenever possible, use {safeIncreaseAllowance} and
                       * {safeDecreaseAllowance} instead.
                       */
                      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. We use {Address.functionCall} to perform this call, which verifies that
                          // the target address contains contract code and also asserts for success in the low-level call.
                          bytes memory returndata = address(token).functionCall(data, "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");
                          }
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.7.0;
                  /**
                   * @dev Library for managing
                   * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
                   * types.
                   *
                   * Sets have the following properties:
                   *
                   * - Elements are added, removed, and checked for existence in constant time
                   * (O(1)).
                   * - Elements are enumerated in O(n). No guarantees are made on the ordering.
                   *
                   * ```
                   * contract Example {
                   *     // Add the library methods
                   *     using EnumerableSet for EnumerableSet.AddressSet;
                   *
                   *     // Declare a set state variable
                   *     EnumerableSet.AddressSet private mySet;
                   * }
                   * ```
                   *
                   * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
                   * and `uint256` (`UintSet`) are supported.
                   */
                  library EnumerableSet {
                      // To implement this library for multiple types with as little code
                      // repetition as possible, we write it in terms of a generic Set type with
                      // bytes32 values.
                      // The Set implementation uses private functions, and user-facing
                      // implementations (such as AddressSet) are just wrappers around the
                      // underlying Set.
                      // This means that we can only create new EnumerableSets for types that fit
                      // in bytes32.
                      struct Set {
                          // Storage of set values
                          bytes32[] _values;
                          // Position of the value in the `values` array, plus 1 because index 0
                          // means a value is not in the set.
                          mapping (bytes32 => uint256) _indexes;
                      }
                      /**
                       * @dev Add a value to a set. O(1).
                       *
                       * Returns true if the value was added to the set, that is if it was not
                       * already present.
                       */
                      function _add(Set storage set, bytes32 value) private returns (bool) {
                          if (!_contains(set, value)) {
                              set._values.push(value);
                              // The value is stored at length-1, but we add 1 to all indexes
                              // and use 0 as a sentinel value
                              set._indexes[value] = set._values.length;
                              return true;
                          } else {
                              return false;
                          }
                      }
                      /**
                       * @dev Removes a value from a set. O(1).
                       *
                       * Returns true if the value was removed from the set, that is if it was
                       * present.
                       */
                      function _remove(Set storage set, bytes32 value) private returns (bool) {
                          // We read and store the value's index to prevent multiple reads from the same storage slot
                          uint256 valueIndex = set._indexes[value];
                          if (valueIndex != 0) { // Equivalent to contains(set, value)
                              // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                              // the array, and then remove the last element (sometimes called as 'swap and pop').
                              // This modifies the order of the array, as noted in {at}.
                              uint256 toDeleteIndex = valueIndex - 1;
                              uint256 lastIndex = set._values.length - 1;
                              // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs
                              // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement.
                              bytes32 lastvalue = set._values[lastIndex];
                              // Move the last value to the index where the value to delete is
                              set._values[toDeleteIndex] = lastvalue;
                              // Update the index for the moved value
                              set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based
                              // Delete the slot where the moved value was stored
                              set._values.pop();
                              // Delete the index for the deleted slot
                              delete set._indexes[value];
                              return true;
                          } else {
                              return false;
                          }
                      }
                      /**
                       * @dev Returns true if the value is in the set. O(1).
                       */
                      function _contains(Set storage set, bytes32 value) private view returns (bool) {
                          return set._indexes[value] != 0;
                      }
                      /**
                       * @dev Returns the number of values on the set. O(1).
                       */
                      function _length(Set storage set) private view returns (uint256) {
                          return set._values.length;
                      }
                     /**
                      * @dev Returns the value stored at position `index` in the set. O(1).
                      *
                      * Note that there are no guarantees on the ordering of values inside the
                      * array, and it may change when more values are added or removed.
                      *
                      * Requirements:
                      *
                      * - `index` must be strictly less than {length}.
                      */
                      function _at(Set storage set, uint256 index) private view returns (bytes32) {
                          require(set._values.length > index, "EnumerableSet: index out of bounds");
                          return set._values[index];
                      }
                      // Bytes32Set
                      struct Bytes32Set {
                          Set _inner;
                      }
                      /**
                       * @dev Add a value to a set. O(1).
                       *
                       * Returns true if the value was added to the set, that is if it was not
                       * already present.
                       */
                      function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
                          return _add(set._inner, value);
                      }
                      /**
                       * @dev Removes a value from a set. O(1).
                       *
                       * Returns true if the value was removed from the set, that is if it was
                       * present.
                       */
                      function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
                          return _remove(set._inner, value);
                      }
                      /**
                       * @dev Returns true if the value is in the set. O(1).
                       */
                      function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
                          return _contains(set._inner, value);
                      }
                      /**
                       * @dev Returns the number of values in the set. O(1).
                       */
                      function length(Bytes32Set storage set) internal view returns (uint256) {
                          return _length(set._inner);
                      }
                     /**
                      * @dev Returns the value stored at position `index` in the set. O(1).
                      *
                      * Note that there are no guarantees on the ordering of values inside the
                      * array, and it may change when more values are added or removed.
                      *
                      * Requirements:
                      *
                      * - `index` must be strictly less than {length}.
                      */
                      function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
                          return _at(set._inner, index);
                      }
                      // AddressSet
                      struct AddressSet {
                          Set _inner;
                      }
                      /**
                       * @dev Add a value to a set. O(1).
                       *
                       * Returns true if the value was added to the set, that is if it was not
                       * already present.
                       */
                      function add(AddressSet storage set, address value) internal returns (bool) {
                          return _add(set._inner, bytes32(uint256(uint160(value))));
                      }
                      /**
                       * @dev Removes a value from a set. O(1).
                       *
                       * Returns true if the value was removed from the set, that is if it was
                       * present.
                       */
                      function remove(AddressSet storage set, address value) internal returns (bool) {
                          return _remove(set._inner, bytes32(uint256(uint160(value))));
                      }
                      /**
                       * @dev Returns true if the value is in the set. O(1).
                       */
                      function contains(AddressSet storage set, address value) internal view returns (bool) {
                          return _contains(set._inner, bytes32(uint256(uint160(value))));
                      }
                      /**
                       * @dev Returns the number of values in the set. O(1).
                       */
                      function length(AddressSet storage set) internal view returns (uint256) {
                          return _length(set._inner);
                      }
                     /**
                      * @dev Returns the value stored at position `index` in the set. O(1).
                      *
                      * Note that there are no guarantees on the ordering of values inside the
                      * array, and it may change when more values are added or removed.
                      *
                      * Requirements:
                      *
                      * - `index` must be strictly less than {length}.
                      */
                      function at(AddressSet storage set, uint256 index) internal view returns (address) {
                          return address(uint160(uint256(_at(set._inner, index))));
                      }
                      // UintSet
                      struct UintSet {
                          Set _inner;
                      }
                      /**
                       * @dev Add a value to a set. O(1).
                       *
                       * Returns true if the value was added to the set, that is if it was not
                       * already present.
                       */
                      function add(UintSet storage set, uint256 value) internal returns (bool) {
                          return _add(set._inner, bytes32(value));
                      }
                      /**
                       * @dev Removes a value from a set. O(1).
                       *
                       * Returns true if the value was removed from the set, that is if it was
                       * present.
                       */
                      function remove(UintSet storage set, uint256 value) internal returns (bool) {
                          return _remove(set._inner, bytes32(value));
                      }
                      /**
                       * @dev Returns true if the value is in the set. O(1).
                       */
                      function contains(UintSet storage set, uint256 value) internal view returns (bool) {
                          return _contains(set._inner, bytes32(value));
                      }
                      /**
                       * @dev Returns the number of values on the set. O(1).
                       */
                      function length(UintSet storage set) internal view returns (uint256) {
                          return _length(set._inner);
                      }
                     /**
                      * @dev Returns the value stored at position `index` in the set. O(1).
                      *
                      * Note that there are no guarantees on the ordering of values inside the
                      * array, and it may change when more values are added or removed.
                      *
                      * Requirements:
                      *
                      * - `index` must be strictly less than {length}.
                      */
                      function at(UintSet storage set, uint256 index) internal view returns (uint256) {
                          return uint256(_at(set._inner, index));
                      }
                  }
                  // SPDX-License-Identifier: agpl-3.0
                  pragma solidity 0.7.6;
                  import {IERC20Ext} from '@kyber.network/utils-sc/contracts/IERC20Ext.sol';
                  interface IPool {
                    event AuthorizedStrategy(address indexed strategy);
                    event UnauthorizedStrategy(address indexed strategy);
                    event Paused(address indexed sender);
                    event Unpaused(address indexed sender);
                    event WithdrawToken(
                      IERC20Ext indexed token,
                      address indexed sender,
                      address indexed recipient,
                      uint256 amount
                    );
                    function pause() external;
                    function unpause() external;
                    function authorizeStrategies(address[] calldata strategies) external;
                    function unauthorizeStrategies(address[] calldata strategies) external;
                    function withdrawFunds(
                      IERC20Ext[] calldata tokens,
                      uint256[] calldata amounts,
                      address payable recipient
                    ) external;
                    function isPaused() external view returns (bool);
                    function isAuthorizedStrategy(address strategy) external view returns (bool);
                    function getAuthorizedStrategiesLength() external view returns (uint256);
                    function getAuthorizedStrategyAt(uint256 index) external view returns (address);
                    function getAllAuthorizedStrategies()
                      external view returns (address[] memory strategies);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.7.0;
                  /**
                   * @dev Interface of the ERC20 standard as defined in the EIP.
                   */
                  interface IERC20 {
                      /**
                       * @dev Returns the amount of tokens in existence.
                       */
                      function totalSupply() external view returns (uint256);
                      /**
                       * @dev Returns the amount of tokens owned by `account`.
                       */
                      function balanceOf(address account) external view returns (uint256);
                      /**
                       * @dev Moves `amount` tokens from the caller's account to `recipient`.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transfer(address recipient, uint256 amount) external returns (bool);
                      /**
                       * @dev Returns the remaining number of tokens that `spender` will be
                       * allowed to spend on behalf of `owner` through {transferFrom}. This is
                       * zero by default.
                       *
                       * This value changes when {approve} or {transferFrom} are called.
                       */
                      function allowance(address owner, address spender) external view returns (uint256);
                      /**
                       * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * IMPORTANT: Beware that changing an allowance with this method brings the risk
                       * that someone may use both the old and the new allowance by unfortunate
                       * transaction ordering. One possible solution to mitigate this race
                       * condition is to first reduce the spender's allowance to 0 and set the
                       * desired value afterwards:
                       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                       *
                       * Emits an {Approval} event.
                       */
                      function approve(address spender, uint256 amount) external returns (bool);
                      /**
                       * @dev Moves `amount` tokens from `sender` to `recipient` using the
                       * allowance mechanism. `amount` is then deducted from the caller's
                       * allowance.
                       *
                       * Returns a boolean value indicating whether the operation succeeded.
                       *
                       * Emits a {Transfer} event.
                       */
                      function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                      /**
                       * @dev Emitted when `value` tokens are moved from one account (`from`) to
                       * another (`to`).
                       *
                       * Note that `value` may be zero.
                       */
                      event Transfer(address indexed from, address indexed to, uint256 value);
                      /**
                       * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                       * a call to {approve}. `value` is the new allowance.
                       */
                      event Approval(address indexed owner, address indexed spender, uint256 value);
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.7.0;
                  /**
                   * @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, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          uint256 c = a + b;
                          if (c < a) return (false, 0);
                          return (true, c);
                      }
                      /**
                       * @dev Returns the substraction of two unsigned integers, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b > a) return (false, 0);
                          return (true, a - b);
                      }
                      /**
                       * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryMul(uint256 a, uint256 b) internal pure returns (bool, 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 (true, 0);
                          uint256 c = a * b;
                          if (c / a != b) return (false, 0);
                          return (true, c);
                      }
                      /**
                       * @dev Returns the division of two unsigned integers, with a division by zero flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b == 0) return (false, 0);
                          return (true, a / b);
                      }
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
                       *
                       * _Available since v3.4._
                       */
                      function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                          if (b == 0) return (false, 0);
                          return (true, a % b);
                      }
                      /**
                       * @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) {
                          require(b <= a, "SafeMath: subtraction overflow");
                          return a - b;
                      }
                      /**
                       * @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) {
                          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, reverting 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) {
                          require(b > 0, "SafeMath: division by zero");
                          return a / b;
                      }
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                       * reverting 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) {
                          require(b > 0, "SafeMath: modulo by zero");
                          return a % b;
                      }
                      /**
                       * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                       * overflow (when the result is negative).
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {trySub}.
                       *
                       * 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);
                          return a - b;
                      }
                      /**
                       * @dev Returns the integer division of two unsigned integers, reverting with custom message on
                       * division by zero. The result is rounded towards zero.
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {tryDiv}.
                       *
                       * 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) {
                          require(b > 0, errorMessage);
                          return a / b;
                      }
                      /**
                       * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                       * reverting with custom message when dividing by zero.
                       *
                       * CAUTION: This function is deprecated because it requires allocating memory for the error
                       * message unnecessarily. For custom revert reasons use {tryMod}.
                       *
                       * 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;
                      }
                  }
                  // SPDX-License-Identifier: MIT
                  pragma solidity ^0.7.0;
                  /**
                   * @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) {
                          // This method relies on extcodesize, which returns 0 for contracts in
                          // construction, since the code is only stored at the end of the
                          // constructor execution.
                          uint256 size;
                          // solhint-disable-next-line no-inline-assembly
                          assembly { size := extcodesize(account) }
                          return size > 0;
                      }
                      /**
                       * @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");
                      }
                      /**
                       * @dev Performs a Solidity function call using a low level `call`. A
                       * plain`call` is an unsafe replacement for a function call: use this
                       * function instead.
                       *
                       * If `target` reverts with a revert reason, it is bubbled up by this
                       * function (like regular Solidity function calls).
                       *
                       * Returns the raw returned data. To convert to the expected return value,
                       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                       *
                       * Requirements:
                       *
                       * - `target` must be a contract.
                       * - calling `target` with `data` must not revert.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                        return functionCall(target, data, "Address: low-level call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                       * `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, 0, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but also transferring `value` wei to `target`.
                       *
                       * Requirements:
                       *
                       * - the calling contract must have an ETH balance of at least `value`.
                       * - the called Solidity function must be `payable`.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                       * with `errorMessage` as a fallback revert reason when `target` reverts.
                       *
                       * _Available since v3.1._
                       */
                      function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
                          require(address(this).balance >= value, "Address: insufficient balance for call");
                          require(isContract(target), "Address: call to non-contract");
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.call{ value: value }(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                          return functionStaticCall(target, data, "Address: low-level static call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a static call.
                       *
                       * _Available since v3.3._
                       */
                      function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
                          require(isContract(target), "Address: static call to non-contract");
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.staticcall(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                      }
                      /**
                       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                       * but performing a delegate call.
                       *
                       * _Available since v3.4._
                       */
                      function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
                          require(isContract(target), "Address: delegate call to non-contract");
                          // solhint-disable-next-line avoid-low-level-calls
                          (bool success, bytes memory returndata) = target.delegatecall(data);
                          return _verifyCallResult(success, returndata, errorMessage);
                      }
                      function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) {
                          if (success) {
                              return returndata;
                          } else {
                              // Look for revert reason and bubble it up if present
                              if (returndata.length > 0) {
                                  // The easiest way to bubble the revert reason is using memory via assembly
                                  // solhint-disable-next-line no-inline-assembly
                                  assembly {
                                      let returndata_size := mload(returndata)
                                      revert(add(32, returndata), returndata_size)
                                  }
                              } else {
                                  revert(errorMessage);
                              }
                          }
                      }
                  }