ETH Price: $2,003.79 (+0.21%)

Transaction Decoder

Block:
9509219 at Feb-18-2020 08:14:09 PM +UTC
Transaction Fee:
0.0012238 ETH $2.45
Gas Used:
244,760 Gas / 5 Gwei

Emitted Events:

108 CrowdsaleToken.Transfer( from=KyberReserve, to=KyberNetwork, value=16491960466 )
109 KyberReserve.TradeExecute( origin=KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=29045605956262898, destToken=CrowdsaleToken, destAmount=16491960466, destAddress=KyberNetwork )
110 CrowdsaleToken.Transfer( from=KyberNetwork, to=[Sender] 0x391e02f9aaec9486281c2c785ce8fd06033660a1, value=16491960466 )
111 FeeBurner.AssignFeeToWallet( reserve=KyberReserve, wallet=0xF1AA99C6...E35Cc504d, walletFee=22548834610324422 )
112 FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=22548834610324423 )
113 KyberNetwork.KyberTrade( trader=[Sender] 0x391e02f9aaec9486281c2c785ce8fd06033660a1, src=0xEeeeeEee...eeeeeEEeE, dest=CrowdsaleToken, srcAmount=29045605956262898, dstAmount=16491960466, destAddress=[Sender] 0x391e02f9aaec9486281c2c785ce8fd06033660a1, ethWeiValue=29045605956262898, reserve1=0x00000000...000000000, reserve2=KyberReserve, hint=0x )
114 KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0x391e02f9aaec9486281c2c785ce8fd06033660a1, src=0xEeeeeEee...eeeeeEEeE, dest=CrowdsaleToken, actualSrcAmount=29045605956262898, actualDestAmount=16491960466 )

Account State Difference:

  Address   Before After State Difference Code
0x391E02f9...6033660a1
1.040008964814564191 Eth
Nonce: 1
1.009739558858301293 Eth
Nonce: 2
0.030269405956262898
(Spark Pool)
33.114340649939513925 Eth33.115564449939513925 Eth0.0012238
0x5Af2Be19...77Db30756
0x63825c17...bD36A0D8F
(Kyber: Reserve)
3,023.344979088545047445 Eth3,023.374024694501310343 Eth0.029045605956262898
0x65bF64Ff...77ced86Cd
(Kyber: Contract)
0x798AbDA6...d3d11191B
(Kyber: Conversion Rates)
0x8007aa43...E5fF626d1
(Kyber: Fee Burner)

Execution Trace

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

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

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

          File 4 of 7: CrowdsaleToken
          /*
           * ERC20 interface
           * see https://github.com/ethereum/EIPs/issues/20
           */
          contract ERC20 {
            uint public totalSupply;
            function balanceOf(address who) constant returns (uint);
            function allowance(address owner, address spender) constant returns (uint);
          
            function transfer(address to, uint value) returns (bool ok);
            function transferFrom(address from, address to, uint value) returns (bool ok);
            function approve(address spender, uint value) returns (bool ok);
            event Transfer(address indexed from, address indexed to, uint value);
            event Approval(address indexed owner, address indexed spender, uint value);
          }
          
          
          
          /**
           * Math operations with safety checks
           */
          contract SafeMath {
            function safeMul(uint a, uint b) internal returns (uint) {
              uint c = a * b;
              assert(a == 0 || c / a == b);
              return c;
            }
          
            function safeDiv(uint a, uint b) internal returns (uint) {
              assert(b > 0);
              uint c = a / b;
              assert(a == b * c + a % b);
              return c;
            }
          
            function safeSub(uint a, uint b) internal returns (uint) {
              assert(b <= a);
              return a - b;
            }
          
            function safeAdd(uint a, uint b) internal returns (uint) {
              uint c = a + b;
              assert(c>=a && c>=b);
              return c;
            }
          
            function max64(uint64 a, uint64 b) internal constant returns (uint64) {
              return a >= b ? a : b;
            }
          
            function min64(uint64 a, uint64 b) internal constant returns (uint64) {
              return a < b ? a : b;
            }
          
            function max256(uint256 a, uint256 b) internal constant returns (uint256) {
              return a >= b ? a : b;
            }
          
            function min256(uint256 a, uint256 b) internal constant returns (uint256) {
              return a < b ? a : b;
            }
          
            function assert(bool assertion) internal {
              if (!assertion) {
                throw;
              }
            }
          }
          
          
          
          /**
           * Standard ERC20 token with Short Hand Attack and approve() race condition mitigation.
           *
           * Based on code by FirstBlood:
           * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
           */
          contract StandardToken is ERC20, SafeMath {
          
            /* Token supply got increased and a new owner received these tokens */
            event Minted(address receiver, uint amount);
          
            /* Actual balances of token holders */
            mapping(address => uint) balances;
          
            /* approve() allowances */
            mapping (address => mapping (address => uint)) allowed;
          
            /* Interface declaration */
            function isToken() public constant returns (bool weAre) {
              return true;
            }
          
            /**
             *
             * Fix for the ERC20 short address attack
             *
             * http://vessenes.com/the-erc20-short-address-attack-explained/
             */
            modifier onlyPayloadSize(uint size) {
               if(msg.data.length < size + 4) {
                 throw;
               }
               _;
            }
          
            function transfer(address _to, uint _value) onlyPayloadSize(2 * 32) returns (bool success) {
              balances[msg.sender] = safeSub(balances[msg.sender], _value);
              balances[_to] = safeAdd(balances[_to], _value);
              Transfer(msg.sender, _to, _value);
              return true;
            }
          
            function transferFrom(address _from, address _to, uint _value) returns (bool success) {
              uint _allowance = allowed[_from][msg.sender];
          
              balances[_to] = safeAdd(balances[_to], _value);
              balances[_from] = safeSub(balances[_from], _value);
              allowed[_from][msg.sender] = safeSub(_allowance, _value);
              Transfer(_from, _to, _value);
              return true;
            }
          
            function balanceOf(address _owner) constant returns (uint balance) {
              return balances[_owner];
            }
          
            function approve(address _spender, uint _value) returns (bool success) {
          
              // To change the approve amount you first have to reduce the addresses`
              //  allowance to zero by calling `approve(_spender, 0)` if it is not
              //  already 0 to mitigate the race condition described here:
              //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
              if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) throw;
          
              allowed[msg.sender][_spender] = _value;
              Approval(msg.sender, _spender, _value);
              return true;
            }
          
            function allowance(address _owner, address _spender) constant returns (uint remaining) {
              return allowed[_owner][_spender];
            }
          
          }
          
          
          
          
          
          /**
           * Upgrade agent interface inspired by Lunyr.
           *
           * Upgrade agent transfers tokens to a new contract.
           * Upgrade agent itself can be the token contract, or just a middle man contract doing the heavy lifting.
           */
          contract UpgradeAgent {
          
            uint public originalSupply;
          
            /** Interface marker */
            function isUpgradeAgent() public constant returns (bool) {
              return true;
            }
          
            function upgradeFrom(address _from, uint256 _value) public;
          
          }
          
          
          /**
           * A token upgrade mechanism where users can opt-in amount of tokens to the next smart contract revision.
           *
           * First envisioned by Golem and Lunyr projects.
           */
          contract UpgradeableToken is StandardToken {
          
            /** Contract / person who can set the upgrade path. This can be the same as team multisig wallet, as what it is with its default value. */
            address public upgradeMaster;
          
            /** The next contract where the tokens will be migrated. */
            UpgradeAgent public upgradeAgent;
          
            /** How many tokens we have upgraded by now. */
            uint256 public totalUpgraded;
          
            /**
             * Upgrade states.
             *
             * - NotAllowed: The child contract has not reached a condition where the upgrade can bgun
             * - WaitingForAgent: Token allows upgrade, but we don't have a new agent yet
             * - ReadyToUpgrade: The agent is set, but not a single token has been upgraded yet
             * - Upgrading: Upgrade agent is set and the balance holders can upgrade their tokens
             *
             */
            enum UpgradeState {Unknown, NotAllowed, WaitingForAgent, ReadyToUpgrade, Upgrading}
          
            /**
             * Somebody has upgraded some of his tokens.
             */
            event Upgrade(address indexed _from, address indexed _to, uint256 _value);
          
            /**
             * New upgrade agent available.
             */
            event UpgradeAgentSet(address agent);
          
            /**
             * Do not allow construction without upgrade master set.
             */
            function UpgradeableToken(address _upgradeMaster) {
              upgradeMaster = _upgradeMaster;
            }
          
            /**
             * Allow the token holder to upgrade some of their tokens to a new contract.
             */
            function upgrade(uint256 value) public {
          
                UpgradeState state = getUpgradeState();
                if(!(state == UpgradeState.ReadyToUpgrade || state == UpgradeState.Upgrading)) {
                  // Called in a bad state
                  throw;
                }
          
                // Validate input value.
                if (value == 0) throw;
          
                balances[msg.sender] = safeSub(balances[msg.sender], value);
          
                // Take tokens out from circulation
                totalSupply = safeSub(totalSupply, value);
                totalUpgraded = safeAdd(totalUpgraded, value);
          
                // Upgrade agent reissues the tokens
                upgradeAgent.upgradeFrom(msg.sender, value);
                Upgrade(msg.sender, upgradeAgent, value);
            }
          
            /**
             * Set an upgrade agent that handles
             */
            function setUpgradeAgent(address agent) external {
          
                if(!canUpgrade()) {
                  // The token is not yet in a state that we could think upgrading
                  throw;
                }
          
                if (agent == 0x0) throw;
                // Only a master can designate the next agent
                if (msg.sender != upgradeMaster) throw;
                // Upgrade has already begun for an agent
                if (getUpgradeState() == UpgradeState.Upgrading) throw;
          
                upgradeAgent = UpgradeAgent(agent);
          
                // Bad interface
                if(!upgradeAgent.isUpgradeAgent()) throw;
                // Make sure that token supplies match in source and target
                if (upgradeAgent.originalSupply() != totalSupply) throw;
          
                UpgradeAgentSet(upgradeAgent);
            }
          
            /**
             * Get the state of the token upgrade.
             */
            function getUpgradeState() public constant returns(UpgradeState) {
              if(!canUpgrade()) return UpgradeState.NotAllowed;
              else if(address(upgradeAgent) == 0x00) return UpgradeState.WaitingForAgent;
              else if(totalUpgraded == 0) return UpgradeState.ReadyToUpgrade;
              else return UpgradeState.Upgrading;
            }
          
            /**
             * Change the upgrade master.
             *
             * This allows us to set a new owner for the upgrade mechanism.
             */
            function setUpgradeMaster(address master) public {
                if (master == 0x0) throw;
                if (msg.sender != upgradeMaster) throw;
                upgradeMaster = master;
            }
          
            /**
             * Child contract can enable to provide the condition when the upgrade can begun.
             */
            function canUpgrade() public constant returns(bool) {
               return true;
            }
          
          }
          
          
          
          
          /*
           * Ownable
           *
           * Base contract with an owner.
           * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
           */
          contract Ownable {
            address public owner;
          
            function Ownable() {
              owner = msg.sender;
            }
          
            modifier onlyOwner() {
              if (msg.sender != owner) {
                throw;
              }
              _;
            }
          
            function transferOwnership(address newOwner) onlyOwner {
              if (newOwner != address(0)) {
                owner = newOwner;
              }
            }
          
          }
          
          
          
          
          /**
           * Define interface for releasing the token transfer after a successful crowdsale.
           */
          contract ReleasableToken is ERC20, Ownable {
          
            /* The finalizer contract that allows unlift the transfer limits on this token */
            address public releaseAgent;
          
            /** A crowdsale contract can release us to the wild if ICO success. If false we are are in transfer lock up period.*/
            bool public released = false;
          
            /** Map of agents that are allowed to transfer tokens regardless of the lock down period. These are crowdsale contracts and possible the team multisig itself. */
            mapping (address => bool) public transferAgents;
          
            /**
             * Limit token transfer until the crowdsale is over.
             *
             */
            modifier canTransfer(address _sender) {
          
              if(!released) {
                  if(!transferAgents[_sender]) {
                      throw;
                  }
              }
          
              _;
            }
          
            /**
             * Set the contract that can call release and make the token transferable.
             *
             * Design choice. Allow reset the release agent to fix fat finger mistakes.
             */
            function setReleaseAgent(address addr) onlyOwner inReleaseState(false) public {
          
              // We don't do interface check here as we might want to a normal wallet address to act as a release agent
              releaseAgent = addr;
            }
          
            /**
             * Owner can allow a particular address (a crowdsale contract) to transfer tokens despite the lock up period.
             */
            function setTransferAgent(address addr, bool state) onlyOwner inReleaseState(false) public {
              transferAgents[addr] = state;
            }
          
            /**
             * One way function to release the tokens to the wild.
             *
             * Can be called only from the release agent that is the final ICO contract. It is only called if the crowdsale has been success (first milestone reached).
             */
            function releaseTokenTransfer() public onlyReleaseAgent {
              released = true;
            }
          
            /** The function can be called only before or after the tokens have been releasesd */
            modifier inReleaseState(bool releaseState) {
              if(releaseState != released) {
                  throw;
              }
              _;
            }
          
            /** The function can be called only by a whitelisted release agent. */
            modifier onlyReleaseAgent() {
              if(msg.sender != releaseAgent) {
                  throw;
              }
              _;
            }
          
            function transfer(address _to, uint _value) canTransfer(msg.sender) returns (bool success) {
              // Call StandardToken.transfer()
             return super.transfer(_to, _value);
            }
          
            function transferFrom(address _from, address _to, uint _value) canTransfer(_from) returns (bool success) {
              // Call StandardToken.transferForm()
              return super.transferFrom(_from, _to, _value);
            }
          
          }
          
          
          
          
          
          /**
           * Safe unsigned safe math.
           *
           * https://blog.aragon.one/library-driven-development-in-solidity-2bebcaf88736#.750gwtwli
           *
           * Originally from https://raw.githubusercontent.com/AragonOne/zeppelin-solidity/master/contracts/SafeMathLib.sol
           *
           * Maintained here until merged to mainline zeppelin-solidity.
           *
           */
          library SafeMathLib {
          
            function times(uint a, uint b) returns (uint) {
              uint c = a * b;
              assert(a == 0 || c / a == b);
              return c;
            }
          
            function minus(uint a, uint b) returns (uint) {
              assert(b <= a);
              return a - b;
            }
          
            function plus(uint a, uint b) returns (uint) {
              uint c = a + b;
              assert(c>=a);
              return c;
            }
          
            function assert(bool assertion) private {
              if (!assertion) throw;
            }
          }
          
          
          
          /**
           * A token that can increase its supply by another contract.
           *
           * This allows uncapped crowdsale by dynamically increasing the supply when money pours in.
           * Only mint agents, contracts whitelisted by owner, can mint new tokens.
           *
           */
          contract MintableToken is StandardToken, Ownable {
          
            using SafeMathLib for uint;
          
            bool public mintingFinished = false;
          
            /** List of agents that are allowed to create new tokens */
            mapping (address => bool) public mintAgents;
          
            event MintingAgentChanged(address addr, bool state  );
          
            /**
             * Create new tokens and allocate them to an address..
             *
             * Only callably by a crowdsale contract (mint agent).
             */
            function mint(address receiver, uint amount) onlyMintAgent canMint public {
              totalSupply = totalSupply.plus(amount);
              balances[receiver] = balances[receiver].plus(amount);
          
              // This will make the mint transaction apper in EtherScan.io
              // We can remove this after there is a standardized minting event
              Transfer(0, receiver, amount);
            }
          
            /**
             * Owner can allow a crowdsale contract to mint new tokens.
             */
            function setMintAgent(address addr, bool state) onlyOwner canMint public {
              mintAgents[addr] = state;
              MintingAgentChanged(addr, state);
            }
          
            modifier onlyMintAgent() {
              // Only crowdsale contracts are allowed to mint new tokens
              if(!mintAgents[msg.sender]) {
                  throw;
              }
              _;
            }
          
            /** Make sure we are not done yet. */
            modifier canMint() {
              if(mintingFinished) throw;
              _;
            }
          }
          
          
          
          /**
           * A crowdsaled token.
           *
           * An ERC-20 token designed specifically for crowdsales with investor protection and further development path.
           *
           * - The token transfer() is disabled until the crowdsale is over
           * - The token contract gives an opt-in upgrade path to a new contract
           * - The same token can be part of several crowdsales through approve() mechanism
           * - The token can be capped (supply set in the constructor) or uncapped (crowdsale contract can mint new tokens)
           *
           */
          contract CrowdsaleToken is ReleasableToken, MintableToken, UpgradeableToken {
          
            /** Name and symbol were updated. */
            event UpdatedTokenInformation(string newName, string newSymbol);
          
            string public name;
          
            string public symbol;
          
            uint public decimals;
          
            /**
             * Construct the token.
             *
             * This token must be created through a team multisig wallet, so that it is owned by that wallet.
             *
             * @param _name Token name
             * @param _symbol Token symbol - should be all caps
             * @param _initialSupply How many tokens we start with
             * @param _decimals Number of decimal places
             * @param _mintable Are new tokens created over the crowdsale or do we distribute only the initial supply? Note that when the token becomes transferable the minting always ends.
             */
            function CrowdsaleToken(string _name, string _symbol, uint _initialSupply, uint _decimals, bool _mintable)
              UpgradeableToken(msg.sender) {
          
              // Create any address, can be transferred
              // to team multisig via changeOwner(),
              // also remember to call setUpgradeMaster()
              owner = msg.sender;
          
              name = _name;
              symbol = _symbol;
          
              totalSupply = _initialSupply;
          
              decimals = _decimals;
          
              // Create initially all balance on the team multisig
              balances[owner] = totalSupply;
          
              if(totalSupply > 0) {
                Minted(owner, totalSupply);
              }
          
              // No more new supply allowed after the token creation
              if(!_mintable) {
                mintingFinished = true;
                if(totalSupply == 0) {
                  throw; // Cannot create a token without supply and no minting
                }
              }
            }
          
            /**
             * When token is released to be transferable, enforce no new tokens can be created.
             */
            function releaseTokenTransfer() public onlyReleaseAgent {
              mintingFinished = true;
              super.releaseTokenTransfer();
            }
          
            /**
             * Allow upgrade agent functionality kick in only if the crowdsale was success.
             */
            function canUpgrade() public constant returns(bool) {
              return released && super.canUpgrade();
            }
          
            /**
             * Owner can update token information here.
             *
             * It is often useful to conceal the actual token association, until
             * the token operations, like central issuance or reissuance have been completed.
             *
             * This function allows the token owner to rename the token after the operations
             * have been completed and then point the audience to use the token contract.
             */
            function setTokenInformation(string _name, string _symbol) onlyOwner {
              name = _name;
              symbol = _symbol;
          
              UpdatedTokenInformation(name, symbol);
            }
          
          }

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

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

          File 7 of 7: SanityRates
          pragma solidity 0.4.18;
          
          interface ERC20 {
              function totalSupply() public view returns (uint supply);
              function balanceOf(address _owner) public view returns (uint balance);
              function transfer(address _to, uint _value) public returns (bool success);
              function transferFrom(address _from, address _to, uint _value) public returns (bool success);
              function approve(address _spender, uint _value) public returns (bool success);
              function allowance(address _owner, address _spender) public view returns (uint remaining);
              function decimals() public view returns(uint digits);
              event Approval(address indexed _owner, address indexed _spender, uint _value);
          }
          
          contract PermissionGroups {
          
              address public admin;
              address public pendingAdmin;
              mapping(address=>bool) internal operators;
              mapping(address=>bool) internal alerters;
              address[] internal operatorsGroup;
              address[] internal alertersGroup;
              uint constant internal MAX_GROUP_SIZE = 50;
          
              function PermissionGroups() public {
                  admin = msg.sender;
              }
          
              modifier onlyAdmin() {
                  require(msg.sender == admin);
                  _;
              }
          
              modifier onlyOperator() {
                  require(operators[msg.sender]);
                  _;
              }
          
              modifier onlyAlerter() {
                  require(alerters[msg.sender]);
                  _;
              }
          
              function getOperators () external view returns(address[]) {
                  return operatorsGroup;
              }
          
              function getAlerters () external view returns(address[]) {
                  return alertersGroup;
              }
          
              event TransferAdminPending(address pendingAdmin);
          
              /**
               * @dev Allows the current admin to set the pendingAdmin address.
               * @param newAdmin The address to transfer ownership to.
               */
              function transferAdmin(address newAdmin) public onlyAdmin {
                  require(newAdmin != address(0));
                  TransferAdminPending(pendingAdmin);
                  pendingAdmin = newAdmin;
              }
          
              /**
               * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
               * @param newAdmin The address to transfer ownership to.
               */
              function transferAdminQuickly(address newAdmin) public onlyAdmin {
                  require(newAdmin != address(0));
                  TransferAdminPending(newAdmin);
                  AdminClaimed(newAdmin, admin);
                  admin = newAdmin;
              }
          
              event AdminClaimed( address newAdmin, address previousAdmin);
          
              /**
               * @dev Allows the pendingAdmin address to finalize the change admin process.
               */
              function claimAdmin() public {
                  require(pendingAdmin == msg.sender);
                  AdminClaimed(pendingAdmin, admin);
                  admin = pendingAdmin;
                  pendingAdmin = address(0);
              }
          
              event AlerterAdded (address newAlerter, bool isAdd);
          
              function addAlerter(address newAlerter) public onlyAdmin {
                  require(!alerters[newAlerter]); // prevent duplicates.
                  require(alertersGroup.length < MAX_GROUP_SIZE);
          
                  AlerterAdded(newAlerter, true);
                  alerters[newAlerter] = true;
                  alertersGroup.push(newAlerter);
              }
          
              function removeAlerter (address alerter) public onlyAdmin {
                  require(alerters[alerter]);
                  alerters[alerter] = false;
          
                  for (uint i = 0; i < alertersGroup.length; ++i) {
                      if (alertersGroup[i] == alerter) {
                          alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
                          alertersGroup.length--;
                          AlerterAdded(alerter, false);
                          break;
                      }
                  }
              }
          
              event OperatorAdded(address newOperator, bool isAdd);
          
              function addOperator(address newOperator) public onlyAdmin {
                  require(!operators[newOperator]); // prevent duplicates.
                  require(operatorsGroup.length < MAX_GROUP_SIZE);
          
                  OperatorAdded(newOperator, true);
                  operators[newOperator] = true;
                  operatorsGroup.push(newOperator);
              }
          
              function removeOperator (address operator) public onlyAdmin {
                  require(operators[operator]);
                  operators[operator] = false;
          
                  for (uint i = 0; i < operatorsGroup.length; ++i) {
                      if (operatorsGroup[i] == operator) {
                          operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
                          operatorsGroup.length -= 1;
                          OperatorAdded(operator, false);
                          break;
                      }
                  }
              }
          }
          
          contract Utils {
          
              ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
              uint  constant internal PRECISION = (10**18);
              uint  constant internal MAX_QTY   = (10**28); // 10B tokens
              uint  constant internal MAX_RATE  = (PRECISION * 10**6); // up to 1M tokens per ETH
              uint  constant internal MAX_DECIMALS = 18;
              uint  constant internal ETH_DECIMALS = 18;
              mapping(address=>uint) internal decimals;
          
              function setDecimals(ERC20 token) internal {
                  if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
                  else decimals[token] = token.decimals();
              }
          
              function getDecimals(ERC20 token) internal view returns(uint) {
                  if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
                  uint tokenDecimals = decimals[token];
                  // technically, there might be token with decimals 0
                  // moreover, very possible that old tokens have decimals 0
                  // these tokens will just have higher gas fees.
                  if(tokenDecimals == 0) return token.decimals();
          
                  return tokenDecimals;
              }
          
              function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                  require(srcQty <= MAX_QTY);
                  require(rate <= MAX_RATE);
          
                  if (dstDecimals >= srcDecimals) {
                      require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                      return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
                  } else {
                      require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                      return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
                  }
              }
          
              function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
                  require(dstQty <= MAX_QTY);
                  require(rate <= MAX_RATE);
          
                  //source quantity is rounded up. to avoid dest quantity being too low.
                  uint numerator;
                  uint denominator;
                  if (srcDecimals >= dstDecimals) {
                      require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
                      numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
                      denominator = rate;
                  } else {
                      require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
                      numerator = (PRECISION * dstQty);
                      denominator = (rate * (10**(dstDecimals - srcDecimals)));
                  }
                  return (numerator + denominator - 1) / denominator; //avoid rounding down errors
              }
          }
          
          contract Withdrawable is PermissionGroups {
          
              event TokenWithdraw(ERC20 token, uint amount, address sendTo);
          
              /**
               * @dev Withdraw all ERC20 compatible tokens
               * @param token ERC20 The address of the token contract
               */
              function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
                  require(token.transfer(sendTo, amount));
                  TokenWithdraw(token, amount, sendTo);
              }
          
              event EtherWithdraw(uint amount, address sendTo);
          
              /**
               * @dev Withdraw Ethers
               */
              function withdrawEther(uint amount, address sendTo) external onlyAdmin {
                  sendTo.transfer(amount);
                  EtherWithdraw(amount, sendTo);
              }
          }
          
          interface SanityRatesInterface {
              function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);
          }
          
          contract SanityRates is SanityRatesInterface, Withdrawable, Utils {
              mapping(address=>uint) public tokenRate;
              mapping(address=>uint) public reasonableDiffInBps;
          
              function SanityRates(address _admin) public {
                  require(_admin != address(0));
                  admin = _admin;
              }
          
              function setReasonableDiff(ERC20[] srcs, uint[] diff) public onlyAdmin {
                  require(srcs.length == diff.length);
                  for (uint i = 0; i < srcs.length; i++) {
                      require(diff[i] <= 100 * 100);
                      reasonableDiffInBps[srcs[i]] = diff[i];
                  }
              }
          
              function setSanityRates(ERC20[] srcs, uint[] rates) public onlyOperator {
                  require(srcs.length == rates.length);
          
                  for (uint i = 0; i < srcs.length; i++) {
                      require(rates[i] <= MAX_RATE);
                      tokenRate[srcs[i]] = rates[i];
                  }
              }
          
              function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint) {
                  if (src != ETH_TOKEN_ADDRESS && dest != ETH_TOKEN_ADDRESS) return 0;
          
                  uint rate;
                  address token;
                  if (src == ETH_TOKEN_ADDRESS) {
                      rate = (PRECISION*PRECISION)/tokenRate[dest];
                      token = dest;
                  } else {
                      rate = tokenRate[src];
                      token = src;
                  }
          
                  return rate * (10000 + reasonableDiffInBps[token])/10000;
              }
          }