ETH Price: $2,492.45 (+2.12%)

Transaction Decoder

Block:
10066872 at May-14-2020 10:15:33 PM +UTC
Transaction Fee:
0.002163464 ETH $5.39
Gas Used:
58,472 Gas / 37 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x00000000...7E06019A9
0x2323A79E...090a1952F
0.32462019652253315 Eth
Nonce: 21844
0.32245673252253315 Eth
Nonce: 21845
0.002163464
(Spark Pool)
69.908562347394035716 Eth69.910725811394035716 Eth0.002163464
0x9FB1FaEd...3c8089b02
0 Eth
Nonce: 0
0 Eth
Nonce: 0
From: 0 To: 0
0xc7796a0d...7DE9E3151
0 Eth
Nonce: 0
0 Eth
Nonce: 0
From: 0 To: 0

Execution Trace

0x0000000000c90bc353314b6911180ed7e06019a9.010074b6( )
  • RLC.balanceOf( _owner=0xA825CAE02B310E9901b4776806CE25db520c8642 ) => ( balance=151346644267528 )
  • KyberReserve.getConversionRate( src=0x607F4C5BB672230e8672085532f7e901544a7375, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=3195640632705, blockNumber=10066872 ) => ( 1835550782872772 )
    • ConversionRates.getRate( token=0x607F4C5BB672230e8672085532f7e901544a7375, currentBlockNumber=10066872, buy=False, qty=3195640632705 ) => ( 1835550782872772 )
    • SanityRates.getSanityRate( src=0x607F4C5BB672230e8672085532f7e901544a7375, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ) => ( 2055900000000000 )
    • 0xc7796a0d9f42acd77019d939f690bac7de9e3151.CALL( )
      • 0x0000000000c90bc353314b6911180ed7e06019a9.SELFDESTRUCT( )
      • 0x9fb1faed58d295f69ac6d55c76c2a553c8089b02.CALL( )
        • 0x0000000000c90bc353314b6911180ed7e06019a9.SELFDESTRUCT( )
          File 1 of 4: RLC
          pragma solidity ^0.4.8;
          
          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);
          }
          
          
          contract Ownable {
            address public owner;
          
            function Ownable() {
              owner = msg.sender;
            }
          
            modifier onlyOwner() {
              if (msg.sender == owner)
                _;
            }
          
            function transferOwnership(address newOwner) onlyOwner {
              if (newOwner != address(0)) owner = newOwner;
            }
          
          }
          
          
          contract TokenSpender {
              function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData);
          }
          
          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;
              }
            }
          }
          
          
          contract RLC is ERC20, SafeMath, Ownable {
          
              /* Public variables of the token */
            string public name;       //fancy name
            string public symbol;
            uint8 public decimals;    //How many decimals to show.
            string public version = 'v0.1'; 
            uint public initialSupply;
            uint public totalSupply;
            bool public locked;
            //uint public unlockBlock;
          
            mapping(address => uint) balances;
            mapping (address => mapping (address => uint)) allowed;
          
            // lock transfer during the ICO
            modifier onlyUnlocked() {
              if (msg.sender != owner && locked) throw;
              _;
            }
          
            /*
             *  The RLC Token created with the time at which the crowdsale end
             */
          
            function RLC() {
              // lock the transfer function during the crowdsale
              locked = true;
              //unlockBlock=  now + 45 days; // (testnet) - for mainnet put the block number
          
              initialSupply = 87000000000000000;
              totalSupply = initialSupply;
              balances[msg.sender] = initialSupply;// Give the creator all initial tokens                    
              name = 'iEx.ec Network Token';        // Set the name for display purposes     
              symbol = 'RLC';                       // Set the symbol for display purposes  
              decimals = 9;                        // Amount of decimals for display purposes
            }
          
            function unlock() onlyOwner {
              locked = false;
            }
          
            function burn(uint256 _value) returns (bool){
              balances[msg.sender] = safeSub(balances[msg.sender], _value) ;
              totalSupply = safeSub(totalSupply, _value);
              Transfer(msg.sender, 0x0, _value);
              return true;
            }
          
            function transfer(address _to, uint _value) onlyUnlocked returns (bool) {
              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) onlyUnlocked returns (bool) {
              var _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) {
              allowed[msg.sender][_spender] = _value;
              Approval(msg.sender, _spender, _value);
              return true;
            }
          
              /* Approve and then comunicate the approved contract in a single tx */
            function approveAndCall(address _spender, uint256 _value, bytes _extraData){    
                TokenSpender spender = TokenSpender(_spender);
                if (approve(_spender, _value)) {
                    spender.receiveApproval(msg.sender, _value, this, _extraData);
                }
            }
          
            function allowance(address _owner, address _spender) constant returns (uint remaining) {
              return allowed[_owner][_spender];
            }
            
          }

          File 2 of 4: 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 4: 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 4 of 4: 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;
              }
          }