Transaction Hash:
Block:
10100930 at May-20-2020 05:25:04 AM +UTC
Transaction Fee:
0.008954484 ETH
$16.99
Gas Used:
407,022 Gas / 22 Gwei
Emitted Events:
154 |
AdminUpgradeabilityProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000018f80de8783f6b158ae18423c3d5b9e19617072c, 0x00000000000000000000000065bf64ff5f51272f729bdcd7acfb00677ced86cd, 0000000000000000000000000000000000000000000000008ac7230489e80000 )
|
155 |
AdminUpgradeabilityProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000065bf64ff5f51272f729bdcd7acfb00677ced86cd, 0x00000000000000000000000056178a0d5f301baf6cf3e1cd53d9863437345bf9, 0000000000000000000000000000000000000000000000008ac7230489e80000 )
|
156 |
KyberNetwork.EtherReceival( sender=KyberReserve, amount=46695479930351170 )
|
157 |
KyberReserve.TradeExecute( origin=KyberNetwork, src=AdminUpgradeabilityProxy, srcAmount=10000000000000000000, destToken=0xEeeeeEee...eeeeeEEeE, destAmount=46695479930351170, destAddress=KyberNetwork )
|
158 |
FeeBurner.AssignFeeToWallet( reserve=KyberReserve, wallet=0x3fFFF2F4...AC2Ea501b, walletFee=6002501268923718 )
|
159 |
FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=14005836294155342 )
|
160 |
KyberNetwork.KyberTrade( trader=[Sender] 0x18f80de8783f6b158ae18423c3d5b9e19617072c, src=AdminUpgradeabilityProxy, dest=0xEeeeeEee...eeeeeEEeE, srcAmount=10000000000000000000, dstAmount=46695479930351170, destAddress=[Sender] 0x18f80de8783f6b158ae18423c3d5b9e19617072c, ethWeiValue=46695479930351170, reserve1=KyberReserve, reserve2=0x00000000...000000000, hint=0x5045524D )
|
161 |
KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0x18f80de8783f6b158ae18423c3d5b9e19617072c, src=AdminUpgradeabilityProxy, dest=0xEeeeeEee...eeeeeEEeE, actualSrcAmount=10000000000000000000, actualDestAmount=46695479930351170 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x04668Ec2...D451c8F7F
Miner
| (zhizhu.top) | 1,403.799701683005220633 Eth | 1,403.808656167005220633 Eth | 0.008954484 | |
0x18f80De8...19617072C |
0.03856191691628298 Eth
Nonce: 63
|
0.07630291284663415 Eth
Nonce: 64
| 0.03774099593035117 | ||
0x65bF64Ff...77ced86Cd | (Kyber: Contract) | ||||
0x7a337007...5552d4F7D | (Kyber: Reserve 2) | 817.051415924626908835 Eth | 817.004720444696557665 Eth | 0.04669547993035117 | |
0x8007aa43...E5fF626d1 | (Kyber: Fee Burner) | ||||
0x8E870D67...d388289E1 | |||||
0xFB80Bfa1...deeb10483 |
Execution Trace
KyberNetworkProxy.tradeWithHint( src=0x8E870D67F660D95d5be530380D0eC0bd388289E1, srcAmount=10000000000000000000, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x18f80De8783f6B158Ae18423C3D5B9e19617072C, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=4529461553244063, walletId=0x3fFFF2F4f6C0831FAC59534694ACd14AC2Ea501b, hint=0x5045524D ) => ( 46695479930351170 )
AdminUpgradeabilityProxy.70a08231( )
-
PAXImplementationV2.balanceOf( _addr=0x18f80De8783f6B158Ae18423C3D5B9e19617072C ) => ( 490000000000000000000 )
-
AdminUpgradeabilityProxy.23b872dd( )
-
PAXImplementationV2.transferFrom( _from=0x18f80De8783f6B158Ae18423C3D5B9e19617072C, _to=0x65bF64Ff5f51272f729BDcD7AcFB00677ced86Cd, _value=10000000000000000000 ) => ( True )
-
KyberNetwork.tradeWithHint( trader=0x18f80De8783f6B158Ae18423C3D5B9e19617072C, src=0x8E870D67F660D95d5be530380D0eC0bd388289E1, srcAmount=10000000000000000000, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x18f80De8783f6B158Ae18423C3D5B9e19617072C, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=4529461553244063, walletId=0x3fFFF2F4f6C0831FAC59534694ACd14AC2Ea501b, hint=0x5045524D ) => ( 46695479930351170 )
AdminUpgradeabilityProxy.70a08231( )
-
PAXImplementationV2.balanceOf( _addr=0x65bF64Ff5f51272f729BDcD7AcFB00677ced86Cd ) => ( 10000000000000160895 )
-
KyberReserve.getConversionRate( src=0x8E870D67F660D95d5be530380D0eC0bd388289E1, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=10000000000000000000, blockNumber=10100930 ) => ( 4661316764926632 )
-
ConversionRates.getRate( token=0x8E870D67F660D95d5be530380D0eC0bd388289E1, currentBlockNumber=10100930, buy=False, qty=10000000000000000000 ) => ( 4661316764926632 )
AdminUpgradeabilityProxy.CALL( )
-
PAXImplementationV2.DELEGATECALL( )
-
-
SanityRates.getSanityRate( src=0x8E870D67F660D95d5be530380D0eC0bd388289E1, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ) => ( 5202913631633714 )
-
KyberReserve.getConversionRate( src=0x8E870D67F660D95d5be530380D0eC0bd388289E1, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=10000000000000000000, blockNumber=10100930 ) => ( 4669547993035117 )
KyberReserve.getConversionRate( src=0x8E870D67F660D95d5be530380D0eC0bd388289E1, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=10000000000000000000, blockNumber=10100930 ) => ( 4667029786566904 )
-
ConversionRates.getRate( token=0x8E870D67F660D95d5be530380D0eC0bd388289E1, currentBlockNumber=10100930, buy=False, qty=10000000000000000000 ) => ( 4667029786566904 )
-
SanityRates.getSanityRate( src=0x8E870D67F660D95d5be530380D0eC0bd388289E1, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ) => ( 5326356118301173 )
-
KyberReserve.trade( srcToken=0x8E870D67F660D95d5be530380D0eC0bd388289E1, srcAmount=10000000000000000000, destToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x65bF64Ff5f51272f729BDcD7AcFB00677ced86Cd, conversionRate=4669547993035117, validate=True ) => ( True )
AdminUpgradeabilityProxy.CALL( )
-
PAXImplementationV2.DELEGATECALL( )
-
-
ConversionRates.recordImbalance( token=0x8E870D67F660D95d5be530380D0eC0bd388289E1, buyAmount=-10000000000000000000, rateUpdateBlock=0, currentBlock=10100930 )
AdminUpgradeabilityProxy.23b872dd( )
-
PAXImplementationV2.transferFrom( _from=0x65bF64Ff5f51272f729BDcD7AcFB00677ced86Cd, _to=0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, _value=10000000000000000000 ) => ( True )
-
- ETH 0.04669547993035117
KyberNetwork.CALL( )
- ETH 0.04669547993035117
0x18f80de8783f6b158ae18423c3d5b9e19617072c.CALL( )
-
FeeBurner.handleFees( tradeWeiAmount=46695479930351170, reserve=0x7a3370075a54B187d7bD5DceBf0ff2B5552d4F7D, wallet=0x3fFFF2F4f6C0831FAC59534694ACd14AC2Ea501b ) => ( True )
AdminUpgradeabilityProxy.70a08231( )
-
PAXImplementationV2.balanceOf( _addr=0x18f80De8783f6B158Ae18423C3D5B9e19617072C ) => ( 480000000000000000000 )
-
tradeWithHint[KyberNetworkProxy (ln:462)]
getBalance[KyberNetworkProxy (ln:480)]
getBalance[KyberNetworkProxy (ln:481)]
transferFrom[KyberNetworkProxy (ln:486)]
value[KyberNetworkProxy (ln:489)]
calculateTradeOutcome[KyberNetworkProxy (ln:501)]
getBalance[KyberNetworkProxy (ln:568)]
getBalance[KyberNetworkProxy (ln:569)]
calcRateFromQty[KyberNetworkProxy (ln:578)]
getDecimalsSafe[KyberNetworkProxy (ln:581)]
getDecimalsSafe[KyberNetworkProxy (ln:582)]
ExecuteTrade[KyberNetworkProxy (ln:513)]
File 1 of 13: KyberNetworkProxy
File 2 of 13: AdminUpgradeabilityProxy
File 3 of 13: KyberNetwork
File 4 of 13: KyberReserve
File 5 of 13: FeeBurner
File 6 of 13: PAXImplementationV2
File 7 of 13: KyberReserve
File 8 of 13: ConversionRates
File 9 of 13: SanityRates
File 10 of 13: ConversionRates
File 11 of 13: KyberReserve
File 12 of 13: ConversionRates
File 13 of 13: SanityRates
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 13: AdminUpgradeabilityProxy
pragma solidity ^0.4.24; // File: contracts/zeppelin/Proxy.sol /** * @title Proxy * @dev Implements delegation of calls to other contracts, with proper * forwarding of return values and bubbling of failures. * It defines a fallback function that delegates all calls to the address * returned by the abstract _implementation() internal function. */ contract Proxy { /** * @dev Fallback function. * Implemented entirely in `_fallback`. */ function () payable external { _fallback(); } /** * @return The Address of the implementation. */ function _implementation() internal view returns (address); /** * @dev Delegates execution to an implementation contract. * This is a low level function that doesn't return to its internal call site. * It will return to the external caller whatever the implementation returns. * @param implementation Address to delegate. */ function _delegate(address implementation) internal { assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize) // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize) } default { return(0, returndatasize) } } } /** * @dev Function that is run as the first thing in the fallback function. * Can be redefined in derived contracts to add functionality. * Redefinitions must call super._willFallback(). */ function _willFallback() internal { } /** * @dev fallback implementation. * Extracted to enable manual triggering. */ function _fallback() internal { _willFallback(); _delegate(_implementation()); } } // File: contracts/zeppelin/AddressUtils.sol /** * Utility library of inline functions on addresses */ library AddressUtils { /** * Returns whether the target address is a contract * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param addr address to check * @return whether the target address is a contract */ function isContract(address addr) internal view returns (bool) { uint256 size; // XXX Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://ethereum.stackexchange.com/a/14016/36603 // for more details about how this works. // TODO Check this again before the Serenity release, because all addresses will be // contracts then. // solium-disable-next-line security/no-inline-assembly assembly { size := extcodesize(addr) } return size > 0; } } // File: contracts/zeppelin/UpgradeabilityProxy.sol /** * @title UpgradeabilityProxy * @dev This contract implements a proxy that allows to change the * implementation address to which it will delegate. * Such a change is called an implementation upgrade. */ contract UpgradeabilityProxy is Proxy { /** * @dev Emitted when the implementation is upgraded. * @param implementation Address of the new implementation. */ event Upgraded(address implementation); /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "org.zeppelinos.proxy.implementation", and is * validated in the constructor. */ bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3; /** * @dev Contract constructor. * @param _implementation Address of the initial implementation. */ constructor(address _implementation) public { assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation")); _setImplementation(_implementation); } /** * @dev Returns the current implementation. * @return Address of the current implementation */ function _implementation() internal view returns (address impl) { bytes32 slot = IMPLEMENTATION_SLOT; assembly { impl := sload(slot) } } /** * @dev Upgrades the proxy to a new implementation. * @param newImplementation Address of the new implementation. */ function _upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } /** * @dev Sets the implementation address of the proxy. * @param newImplementation Address of the new implementation. */ function _setImplementation(address newImplementation) private { require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address"); bytes32 slot = IMPLEMENTATION_SLOT; assembly { sstore(slot, newImplementation) } } } // File: contracts/zeppelin/AdminUpgradeabilityProxy.sol /** * @title AdminUpgradeabilityProxy * @dev This contract combines an upgradeability proxy with an authorization * mechanism for administrative tasks. * All external functions in this contract must be guarded by the * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity * feature proposal that would enable this to be done automatically. */ contract AdminUpgradeabilityProxy is UpgradeabilityProxy { /** * @dev Emitted when the administration has been transferred. * @param previousAdmin Address of the previous admin. * @param newAdmin Address of the new admin. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is * validated in the constructor. */ bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b; /** * @dev Modifier to check whether the `msg.sender` is the admin. * If it is, it will run the function. Otherwise, it will delegate the call * to the implementation. */ modifier ifAdmin() { if (msg.sender == _admin()) { _; } else { _fallback(); } } /** * Contract constructor. * It sets the `msg.sender` as the proxy administrator. * @param _implementation address of the initial implementation. */ constructor(address _implementation) UpgradeabilityProxy(_implementation) public { assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin")); _setAdmin(msg.sender); } /** * @return The address of the proxy admin. */ function admin() external view ifAdmin returns (address) { return _admin(); } /** * @return The address of the implementation. */ function implementation() external view ifAdmin returns (address) { return _implementation(); } /** * @dev Changes the admin of the proxy. * Only the current admin can call this function. * @param newAdmin Address to transfer proxy administration to. */ function changeAdmin(address newAdmin) external ifAdmin { require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address"); emit AdminChanged(_admin(), newAdmin); _setAdmin(newAdmin); } /** * @dev Upgrade the backing implementation of the proxy. * Only the admin can call this function. * @param newImplementation Address of the new implementation. */ function upgradeTo(address newImplementation) external ifAdmin { _upgradeTo(newImplementation); } /** * @dev Upgrade the backing implementation of the proxy and call a function * on the new implementation. * This is useful to initialize the proxied contract. * @param newImplementation Address of the new implementation. * @param data Data to send as msg.data in the low level call. * It should include the signature and the parameters of the function to be * called, as described in * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding. */ function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin { _upgradeTo(newImplementation); require(address(this).call.value(msg.value)(data)); } /** * @return The admin slot. */ function _admin() internal view returns (address adm) { bytes32 slot = ADMIN_SLOT; assembly { adm := sload(slot) } } /** * @dev Sets the address of the proxy admin. * @param newAdmin Address of the new proxy admin. */ function _setAdmin(address newAdmin) internal { bytes32 slot = ADMIN_SLOT; assembly { sstore(slot, newAdmin) } } /** * @dev Only fall back when the sender is not the admin. */ function _willFallback() internal { require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin"); super._willFallback(); } }
File 3 of 13: 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 13: KyberReserve
pragma solidity ^0.4.13; interface ConversionRatesInterface { function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public; function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint); } interface ERC20 { function totalSupply() public view returns (uint supply); function balanceOf(address _owner) public view returns (uint balance); function transfer(address _to, uint _value) public returns (bool success); function transferFrom(address _from, address _to, uint _value) public returns (bool success); function approve(address _spender, uint _value) public returns (bool success); function allowance(address _owner, address _spender) public view returns (uint remaining); function decimals() public view returns(uint digits); event Approval(address indexed _owner, address indexed _spender, uint _value); } interface KyberReserveInterface { function trade( ERC20 srcToken, uint srcAmount, ERC20 destToken, address destAddress, uint conversionRate, bool validate ) public payable returns(bool); function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint); } contract PermissionGroups { address public admin; address public pendingAdmin; mapping(address=>bool) internal operators; mapping(address=>bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint constant internal MAX_GROUP_SIZE = 50; function PermissionGroups() public { admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin); _; } modifier onlyOperator() { require(operators[msg.sender]); _; } modifier onlyAlerter() { require(alerters[msg.sender]); _; } function getOperators () external view returns(address[]) { return operatorsGroup; } function getAlerters () external view returns(address[]) { return alertersGroup; } event TransferAdminPending(address pendingAdmin); /** * @dev Allows the current admin to set the pendingAdmin address. * @param newAdmin The address to transfer ownership to. */ function transferAdmin(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(pendingAdmin); pendingAdmin = newAdmin; } /** * @dev Allows the current admin to set the admin in one tx. Useful initial deployment. * @param newAdmin The address to transfer ownership to. */ function transferAdminQuickly(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(newAdmin); AdminClaimed(newAdmin, admin); admin = newAdmin; } event AdminClaimed( address newAdmin, address previousAdmin); /** * @dev Allows the pendingAdmin address to finalize the change admin process. */ function claimAdmin() public { require(pendingAdmin == msg.sender); AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } event AlerterAdded (address newAlerter, bool isAdd); function addAlerter(address newAlerter) public onlyAdmin { require(!alerters[newAlerter]); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE); AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function removeAlerter (address alerter) public onlyAdmin { require(alerters[alerter]); alerters[alerter] = false; for (uint i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.length--; AlerterAdded(alerter, false); break; } } } event OperatorAdded(address newOperator, bool isAdd); function addOperator(address newOperator) public onlyAdmin { require(!operators[newOperator]); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE); OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } function removeOperator (address operator) public onlyAdmin { require(operators[operator]); operators[operator] = false; for (uint i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.length -= 1; OperatorAdded(operator, false); break; } } } } interface SanityRatesInterface { function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint); } contract Utils { ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee); uint constant internal PRECISION = (10**18); uint constant internal MAX_QTY = (10**28); // 10B tokens uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH uint constant internal MAX_DECIMALS = 18; uint constant internal ETH_DECIMALS = 18; mapping(address=>uint) internal decimals; function setDecimals(ERC20 token) internal { if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS; else decimals[token] = token.decimals(); } function getDecimals(ERC20 token) internal view returns(uint) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint tokenDecimals = decimals[token]; // technically, there might be token with decimals 0 // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if(tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(srcQty <= MAX_QTY); require(rate <= MAX_RATE); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(dstQty <= MAX_QTY); require(rate <= MAX_RATE); //source quantity is rounded up. to avoid dest quantity being too low. uint numerator; uint denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } } contract Withdrawable is PermissionGroups { event TokenWithdraw(ERC20 token, uint amount, address sendTo); /** * @dev Withdraw all ERC20 compatible tokens * @param token ERC20 The address of the token contract */ function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin { require(token.transfer(sendTo, amount)); TokenWithdraw(token, amount, sendTo); } event EtherWithdraw(uint amount, address sendTo); /** * @dev Withdraw Ethers */ function withdrawEther(uint amount, address sendTo) external onlyAdmin { sendTo.transfer(amount); EtherWithdraw(amount, sendTo); } } contract KyberReserve is KyberReserveInterface, Withdrawable, Utils { address public kyberNetwork; bool public tradeEnabled; ConversionRatesInterface public conversionRatesContract; SanityRatesInterface public sanityRatesContract; mapping(bytes32=>bool) public approvedWithdrawAddresses; // sha3(token,address)=>bool mapping(address=>address) public tokenWallet; function KyberReserve(address _kyberNetwork, ConversionRatesInterface _ratesContract, address _admin) public { require(_admin != address(0)); require(_ratesContract != address(0)); require(_kyberNetwork != address(0)); kyberNetwork = _kyberNetwork; conversionRatesContract = _ratesContract; admin = _admin; tradeEnabled = true; } event DepositToken(ERC20 token, uint amount); function() public payable { DepositToken(ETH_TOKEN_ADDRESS, msg.value); } event TradeExecute( address indexed origin, address src, uint srcAmount, address destToken, uint destAmount, address destAddress ); function trade( ERC20 srcToken, uint srcAmount, ERC20 destToken, address destAddress, uint conversionRate, bool validate ) public payable returns(bool) { require(tradeEnabled); require(msg.sender == kyberNetwork); require(doTrade(srcToken, srcAmount, destToken, destAddress, conversionRate, validate)); return true; } event TradeEnabled(bool enable); function enableTrade() public onlyAdmin returns(bool) { tradeEnabled = true; TradeEnabled(true); return true; } function disableTrade() public onlyAlerter returns(bool) { tradeEnabled = false; TradeEnabled(false); return true; } event WithdrawAddressApproved(ERC20 token, address addr, bool approve); function approveWithdrawAddress(ERC20 token, address addr, bool approve) public onlyAdmin { approvedWithdrawAddresses[keccak256(token, addr)] = approve; WithdrawAddressApproved(token, addr, approve); setDecimals(token); if ((tokenWallet[token] == address(0x0)) && (token != ETH_TOKEN_ADDRESS)) { tokenWallet[token] = this; // by default require(token.approve(this, 2 ** 255)); } } event NewTokenWallet(ERC20 token, address wallet); function setTokenWallet(ERC20 token, address wallet) public onlyAdmin { require(wallet != address(0x0)); tokenWallet[token] = wallet; NewTokenWallet(token, wallet); } event WithdrawFunds(ERC20 token, uint amount, address destination); function withdraw(ERC20 token, uint amount, address destination) public onlyOperator returns(bool) { require(approvedWithdrawAddresses[keccak256(token, destination)]); if (token == ETH_TOKEN_ADDRESS) { destination.transfer(amount); } else { require(token.transferFrom(tokenWallet[token], destination, amount)); } WithdrawFunds(token, amount, destination); return true; } event SetContractAddresses(address network, address rate, address sanity); function setContracts( address _kyberNetwork, ConversionRatesInterface _conversionRates, SanityRatesInterface _sanityRates ) public onlyAdmin { require(_kyberNetwork != address(0)); require(_conversionRates != address(0)); kyberNetwork = _kyberNetwork; conversionRatesContract = _conversionRates; sanityRatesContract = _sanityRates; SetContractAddresses(kyberNetwork, conversionRatesContract, sanityRatesContract); } //////////////////////////////////////////////////////////////////////////// /// status functions /////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// function getBalance(ERC20 token) public view returns(uint) { if (token == ETH_TOKEN_ADDRESS) return this.balance; else { address wallet = tokenWallet[token]; uint balanceOfWallet = token.balanceOf(wallet); uint allowanceOfWallet = token.allowance(wallet, this); return (balanceOfWallet < allowanceOfWallet) ? balanceOfWallet : allowanceOfWallet; } } function getDestQty(ERC20 src, ERC20 dest, uint srcQty, uint rate) public view returns(uint) { uint dstDecimals = getDecimals(dest); uint srcDecimals = getDecimals(src); return calcDstQty(srcQty, srcDecimals, dstDecimals, rate); } function getSrcQty(ERC20 src, ERC20 dest, uint dstQty, uint rate) public view returns(uint) { uint dstDecimals = getDecimals(dest); uint srcDecimals = getDecimals(src); return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate); } function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint) { ERC20 token; bool isBuy; if (!tradeEnabled) return 0; if (ETH_TOKEN_ADDRESS == src) { isBuy = true; token = dest; } else if (ETH_TOKEN_ADDRESS == dest) { isBuy = false; token = src; } else { return 0; // pair is not listed } uint rate = conversionRatesContract.getRate(token, blockNumber, isBuy, srcQty); uint destQty = getDestQty(src, dest, srcQty, rate); if (getBalance(dest) < destQty) return 0; if (sanityRatesContract != address(0)) { uint sanityRate = sanityRatesContract.getSanityRate(src, dest); if (rate > sanityRate) return 0; } return rate; } /// @dev do a trade /// @param srcToken Src token /// @param srcAmount Amount of src token /// @param destToken Destination token /// @param destAddress Destination address to send tokens to /// @param validate If true, additional validations are applicable /// @return true iff trade is successful function doTrade( ERC20 srcToken, uint srcAmount, ERC20 destToken, address destAddress, uint conversionRate, bool validate ) internal returns(bool) { // can skip validation if done at kyber network level if (validate) { require(conversionRate > 0); if (srcToken == ETH_TOKEN_ADDRESS) require(msg.value == srcAmount); else require(msg.value == 0); } uint destAmount = getDestQty(srcToken, destToken, srcAmount, conversionRate); // sanity check require(destAmount > 0); // add to imbalance ERC20 token; int tradeAmount; if (srcToken == ETH_TOKEN_ADDRESS) { tradeAmount = int(destAmount); token = destToken; } else { tradeAmount = -1 * int(srcAmount); token = srcToken; } conversionRatesContract.recordImbalance( token, tradeAmount, 0, block.number ); // collect src tokens if (srcToken != ETH_TOKEN_ADDRESS) { require(srcToken.transferFrom(msg.sender, tokenWallet[srcToken], srcAmount)); } // send dest tokens if (destToken == ETH_TOKEN_ADDRESS) { destAddress.transfer(destAmount); } else { require(destToken.transferFrom(tokenWallet[destToken], destAddress, destAmount)); } TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress); return true; } }
File 5 of 13: 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 13: PAXImplementationV2
/** *Submitted for verification at Etherscan.io on 2020-02-03 */ // File: contracts/zeppelin/SafeMath.sol pragma solidity ^0.4.24; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two numbers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } } // File: contracts/PAXImplementationV2.sol pragma solidity ^0.4.24; pragma experimental "v0.5.0"; /** * @title PAXImplementationV2 * @dev this contract is a Pausable ERC20 token with Burn and Mint * controlled by a central SupplyController. By implementing PaxosImplementation * this contract also includes external methods for setting * a new implementation contract for the Proxy. * NOTE: The storage defined here will actually be held in the Proxy * contract and all calls to this contract should be made through * the proxy, including admin actions done as owner or supplyController. * Any call to transfer against this contract should fail * with insufficient funds since no tokens will be issued there. */ contract PAXImplementationV2 { /** * MATH */ using SafeMath for uint256; /** * DATA */ // INITIALIZATION DATA bool private initialized = false; // ERC20 BASIC DATA mapping(address => uint256) internal balances; uint256 internal totalSupply_; string public constant name = "Paxos Standard"; // solium-disable-line string public constant symbol = "PAX"; // solium-disable-line uppercase uint8 public constant decimals = 18; // solium-disable-line uppercase // ERC20 DATA mapping(address => mapping(address => uint256)) internal allowed; // OWNER DATA PART 1 address public owner; // PAUSABILITY DATA bool public paused = false; // ASSET PROTECTION DATA address public assetProtectionRole; mapping(address => bool) internal frozen; // SUPPLY CONTROL DATA address public supplyController; // OWNER DATA PART 2 address public proposedOwner; // DELEGATED TRANSFER DATA address public betaDelegateWhitelister; mapping(address => bool) internal betaDelegateWhitelist; mapping(address => uint256) internal nextSeqs; // EIP191 header for EIP712 prefix string constant internal EIP191_HEADER = "\x19\x01"; // Hash of the EIP712 Domain Separator Schema bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256( "EIP712Domain(string name,address verifyingContract)" ); bytes32 constant internal EIP712_DELEGATED_TRANSFER_SCHEMA_HASH = keccak256( "BetaDelegatedTransfer(address to,uint256 value,uint256 fee,uint256 seq,uint256 deadline)" ); // Hash of the EIP712 Domain Separator data // solhint-disable-next-line var-name-mixedcase bytes32 public EIP712_DOMAIN_HASH; /** * EVENTS */ // ERC20 BASIC EVENTS event Transfer(address indexed from, address indexed to, uint256 value); // ERC20 EVENTS event Approval( address indexed owner, address indexed spender, uint256 value ); // OWNABLE EVENTS event OwnershipTransferProposed( address indexed currentOwner, address indexed proposedOwner ); event OwnershipTransferDisregarded( address indexed oldProposedOwner ); event OwnershipTransferred( address indexed oldOwner, address indexed newOwner ); // PAUSABLE EVENTS event Pause(); event Unpause(); // ASSET PROTECTION EVENTS event AddressFrozen(address indexed addr); event AddressUnfrozen(address indexed addr); event FrozenAddressWiped(address indexed addr); event AssetProtectionRoleSet ( address indexed oldAssetProtectionRole, address indexed newAssetProtectionRole ); // SUPPLY CONTROL EVENTS event SupplyIncreased(address indexed to, uint256 value); event SupplyDecreased(address indexed from, uint256 value); event SupplyControllerSet( address indexed oldSupplyController, address indexed newSupplyController ); // DELEGATED TRANSFER EVENTS event BetaDelegatedTransfer( address indexed from, address indexed to, uint256 value, uint256 seq, uint256 fee ); event BetaDelegateWhitelisterSet( address indexed oldWhitelister, address indexed newWhitelister ); event BetaDelegateWhitelisted(address indexed newDelegate); event BetaDelegateUnwhitelisted(address indexed oldDelegate); /** * FUNCTIONALITY */ // INITIALIZATION FUNCTIONALITY /** * @dev sets 0 initials tokens, the owner, and the supplyController. * this serves as the constructor for the proxy but compiles to the * memory model of the Implementation contract. */ function initialize() public { require(!initialized, "already initialized"); owner = msg.sender; assetProtectionRole = address(0); totalSupply_ = 0; supplyController = msg.sender; initialized = true; } /** * The constructor is used here to ensure that the implementation * contract is initialized. An uncontrolled implementation * contract might lead to misleading state * for users who accidentally interact with it. */ constructor() public { initialize(); pause(); // Added in V2 initializeDomainSeparator(); } /** * @dev To be called when upgrading the contract using upgradeAndCall to add delegated transfers */ function initializeDomainSeparator() public { // hash the name context with the contract address EIP712_DOMAIN_HASH = keccak256(abi.encodePacked(// solium-disable-line EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, keccak256(bytes(name)), bytes32(address(this)) )); proposedOwner = address(0); } // ERC20 BASIC FUNCTIONALITY /** * @dev Total number of tokens in existence */ function totalSupply() public view returns (uint256) { return totalSupply_; } /** * @dev Transfer token to a specified address from msg.sender * Note: the use of Safemath ensures that _value is nonnegative. * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) { require(_to != address(0), "cannot transfer to address zero"); require(!frozen[_to] && !frozen[msg.sender], "address frozen"); require(_value <= balances[msg.sender], "insufficient funds"); balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); emit Transfer(msg.sender, _to, _value); return true; } /** * @dev Gets the balance of the specified address. * @param _addr The address to query the the balance of. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address _addr) public view returns (uint256) { return balances[_addr]; } // ERC20 FUNCTIONALITY /** * @dev Transfer tokens from one address to another * @param _from address The address which you want to send tokens from * @param _to address The address which you want to transfer to * @param _value uint256 the amount of tokens to be transferred */ function transferFrom( address _from, address _to, uint256 _value ) public whenNotPaused returns (bool) { require(_to != address(0), "cannot transfer to address zero"); require(!frozen[_to] && !frozen[_from] && !frozen[msg.sender], "address frozen"); require(_value <= balances[_from], "insufficient funds"); require(_value <= allowed[_from][msg.sender], "insufficient allowance"); balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); emit Transfer(_from, _to, _value); return true; } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * Beware that changing an allowance with this method brings the risk that someone may use both the old * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) { require(!frozen[_spender] && !frozen[msg.sender], "address frozen"); allowed[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; } /** * @dev Function to check the amount of tokens that an owner allowed to a spender. * @param _owner address The address which owns the funds. * @param _spender address The address which will spend the funds. * @return A uint256 specifying the amount of tokens still available for the spender. */ function allowance( address _owner, address _spender ) public view returns (uint256) { return allowed[_owner][_spender]; } // OWNER FUNCTIONALITY /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner, "onlyOwner"); _; } /** * @dev Allows the current owner to begin transferring control of the contract to a proposedOwner * @param _proposedOwner The address to transfer ownership to. */ function proposeOwner(address _proposedOwner) public onlyOwner { require(_proposedOwner != address(0), "cannot transfer ownership to address zero"); require(msg.sender != _proposedOwner, "caller already is owner"); proposedOwner = _proposedOwner; emit OwnershipTransferProposed(owner, proposedOwner); } /** * @dev Allows the current owner or proposed owner to cancel transferring control of the contract to a proposedOwner */ function disregardProposeOwner() public { require(msg.sender == proposedOwner || msg.sender == owner, "only proposedOwner or owner"); require(proposedOwner != address(0), "can only disregard a proposed owner that was previously set"); address _oldProposedOwner = proposedOwner; proposedOwner = address(0); emit OwnershipTransferDisregarded(_oldProposedOwner); } /** * @dev Allows the proposed owner to complete transferring control of the contract to the proposedOwner. */ function claimOwnership() public { require(msg.sender == proposedOwner, "onlyProposedOwner"); address _oldOwner = owner; owner = proposedOwner; proposedOwner = address(0); emit OwnershipTransferred(_oldOwner, owner); } /** * @dev Reclaim all PAX at the contract address. * This sends the PAX tokens that this contract add holding to the owner. * Note: this is not affected by freeze constraints. */ function reclaimPAX() external onlyOwner { uint256 _balance = balances[this]; balances[this] = 0; balances[owner] = balances[owner].add(_balance); emit Transfer(this, owner, _balance); } // PAUSABILITY FUNCTIONALITY /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused, "whenNotPaused"); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() public onlyOwner { require(!paused, "already paused"); paused = true; emit Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() public onlyOwner { require(paused, "already unpaused"); paused = false; emit Unpause(); } // ASSET PROTECTION FUNCTIONALITY /** * @dev Sets a new asset protection role address. * @param _newAssetProtectionRole The new address allowed to freeze/unfreeze addresses and seize their tokens. */ function setAssetProtectionRole(address _newAssetProtectionRole) public { require(msg.sender == assetProtectionRole || msg.sender == owner, "only assetProtectionRole or Owner"); emit AssetProtectionRoleSet(assetProtectionRole, _newAssetProtectionRole); assetProtectionRole = _newAssetProtectionRole; } modifier onlyAssetProtectionRole() { require(msg.sender == assetProtectionRole, "onlyAssetProtectionRole"); _; } /** * @dev Freezes an address balance from being transferred. * @param _addr The new address to freeze. */ function freeze(address _addr) public onlyAssetProtectionRole { require(!frozen[_addr], "address already frozen"); frozen[_addr] = true; emit AddressFrozen(_addr); } /** * @dev Unfreezes an address balance allowing transfer. * @param _addr The new address to unfreeze. */ function unfreeze(address _addr) public onlyAssetProtectionRole { require(frozen[_addr], "address already unfrozen"); frozen[_addr] = false; emit AddressUnfrozen(_addr); } /** * @dev Wipes the balance of a frozen address, burning the tokens * and setting the approval to zero. * @param _addr The new frozen address to wipe. */ function wipeFrozenAddress(address _addr) public onlyAssetProtectionRole { require(frozen[_addr], "address is not frozen"); uint256 _balance = balances[_addr]; balances[_addr] = 0; totalSupply_ = totalSupply_.sub(_balance); emit FrozenAddressWiped(_addr); emit SupplyDecreased(_addr, _balance); emit Transfer(_addr, address(0), _balance); } /** * @dev Gets whether the address is currently frozen. * @param _addr The address to check if frozen. * @return A bool representing whether the given address is frozen. */ function isFrozen(address _addr) public view returns (bool) { return frozen[_addr]; } // SUPPLY CONTROL FUNCTIONALITY /** * @dev Sets a new supply controller address. * @param _newSupplyController The address allowed to burn/mint tokens to control supply. */ function setSupplyController(address _newSupplyController) public { require(msg.sender == supplyController || msg.sender == owner, "only SupplyController or Owner"); require(_newSupplyController != address(0), "cannot set supply controller to address zero"); emit SupplyControllerSet(supplyController, _newSupplyController); supplyController = _newSupplyController; } modifier onlySupplyController() { require(msg.sender == supplyController, "onlySupplyController"); _; } /** * @dev Increases the total supply by minting the specified number of tokens to the supply controller account. * @param _value The number of tokens to add. * @return A boolean that indicates if the operation was successful. */ function increaseSupply(uint256 _value) public onlySupplyController returns (bool success) { totalSupply_ = totalSupply_.add(_value); balances[supplyController] = balances[supplyController].add(_value); emit SupplyIncreased(supplyController, _value); emit Transfer(address(0), supplyController, _value); return true; } /** * @dev Decreases the total supply by burning the specified number of tokens from the supply controller account. * @param _value The number of tokens to remove. * @return A boolean that indicates if the operation was successful. */ function decreaseSupply(uint256 _value) public onlySupplyController returns (bool success) { require(_value <= balances[supplyController], "not enough supply"); balances[supplyController] = balances[supplyController].sub(_value); totalSupply_ = totalSupply_.sub(_value); emit SupplyDecreased(supplyController, _value); emit Transfer(supplyController, address(0), _value); return true; } // DELEGATED TRANSFER FUNCTIONALITY /** * @dev returns the next seq for a target address. * The transactor must submit nextSeqOf(transactor) in the next transaction for it to be valid. * Note: that the seq context is specific to this smart contract. * @param target The target address. * @return the seq. */ // function nextSeqOf(address target) public view returns (uint256) { return nextSeqs[target]; } /** * @dev Performs a transfer on behalf of the from address, identified by its signature on the delegatedTransfer msg. * Splits a signature byte array into r,s,v for convenience. * @param sig the signature of the delgatedTransfer msg. * @param to The address to transfer to. * @param value The amount to be transferred. * @param fee an optional ERC20 fee paid to the executor of betaDelegatedTransfer by the from address. * @param seq a sequencing number included by the from address specific to this contract to protect from replays. * @param deadline a block number after which the pre-signed transaction has expired. * @return A boolean that indicates if the operation was successful. */ function betaDelegatedTransfer( bytes sig, address to, uint256 value, uint256 fee, uint256 seq, uint256 deadline ) public returns (bool) { require(sig.length == 65, "signature should have length 65"); bytes32 r; bytes32 s; uint8 v; assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } require(_betaDelegatedTransfer(r, s, v, to, value, fee, seq, deadline), "failed transfer"); return true; } /** * @dev Performs a transfer on behalf of the from address, identified by its signature on the betaDelegatedTransfer msg. * Note: both the delegate and transactor sign in the fees. The transactor, however, * has no control over the gas price, and therefore no control over the transaction time. * Beta prefix chosen to avoid a name clash with an emerging standard in ERC865 or elsewhere. * Internal to the contract - see betaDelegatedTransfer and betaDelegatedTransferBatch. * @param r the r signature of the delgatedTransfer msg. * @param s the s signature of the delgatedTransfer msg. * @param v the v signature of the delgatedTransfer msg. * @param to The address to transfer to. * @param value The amount to be transferred. * @param fee an optional ERC20 fee paid to the delegate of betaDelegatedTransfer by the from address. * @param seq a sequencing number included by the from address specific to this contract to protect from replays. * @param deadline a block number after which the pre-signed transaction has expired. * @return A boolean that indicates if the operation was successful. */ function _betaDelegatedTransfer( bytes32 r, bytes32 s, uint8 v, address to, uint256 value, uint256 fee, uint256 seq, uint256 deadline ) internal whenNotPaused returns (bool) { require(betaDelegateWhitelist[msg.sender], "Beta feature only accepts whitelisted delegates"); require(value > 0 || fee > 0, "cannot transfer zero tokens with zero fee"); require(block.number <= deadline, "transaction expired"); // prevent sig malleability from ecrecover() require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "signature incorrect"); require(v == 27 || v == 28, "signature incorrect"); // EIP712 scheme: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md bytes32 delegatedTransferHash = keccak256(abi.encodePacked(// solium-disable-line EIP712_DELEGATED_TRANSFER_SCHEMA_HASH, bytes32(to), value, fee, seq, deadline )); bytes32 hash = keccak256(abi.encodePacked(EIP191_HEADER, EIP712_DOMAIN_HASH, delegatedTransferHash)); address _from = ecrecover(hash, v, r, s); require(_from != address(0), "error determining from address from signature"); require(to != address(0), "canno use address zero"); require(!frozen[to] && !frozen[_from] && !frozen[msg.sender], "address frozen"); require(value.add(fee) <= balances[_from], "insufficient fund"); require(nextSeqs[_from] == seq, "incorrect seq"); nextSeqs[_from] = nextSeqs[_from].add(1); balances[_from] = balances[_from].sub(value.add(fee)); if (fee != 0) { balances[msg.sender] = balances[msg.sender].add(fee); emit Transfer(_from, msg.sender, fee); } balances[to] = balances[to].add(value); emit Transfer(_from, to, value); emit BetaDelegatedTransfer(_from, to, value, seq, fee); return true; } /** * @dev Performs an atomic batch of transfers on behalf of the from addresses, identified by their signatures. * Lack of nested array support in arguments requires all arguments to be passed as equal size arrays where * delegated transfer number i is the combination of all arguments at index i * @param r the r signatures of the delgatedTransfer msg. * @param s the s signatures of the delgatedTransfer msg. * @param v the v signatures of the delgatedTransfer msg. * @param to The addresses to transfer to. * @param value The amounts to be transferred. * @param fee optional ERC20 fees paid to the delegate of betaDelegatedTransfer by the from address. * @param seq sequencing numbers included by the from address specific to this contract to protect from replays. * @param deadline block numbers after which the pre-signed transactions have expired. * @return A boolean that indicates if the operation was successful. */ function betaDelegatedTransferBatch( bytes32[] r, bytes32[] s, uint8[] v, address[] to, uint256[] value, uint256[] fee, uint256[] seq, uint256[] deadline ) public returns (bool) { require(r.length == s.length && r.length == v.length && r.length == to.length && r.length == value.length, "length mismatch"); require(r.length == fee.length && r.length == seq.length && r.length == deadline.length, "length mismatch"); for (uint i = 0; i < r.length; i++) { require( _betaDelegatedTransfer(r[i], s[i], v[i], to[i], value[i], fee[i], seq[i], deadline[i]), "failed transfer" ); } return true; } /** * @dev Gets whether the address is currently whitelisted for betaDelegateTransfer. * @param _addr The address to check if whitelisted. * @return A bool representing whether the given address is whitelisted. */ function isWhitelistedBetaDelegate(address _addr) public view returns (bool) { return betaDelegateWhitelist[_addr]; } /** * @dev Sets a new betaDelegate whitelister. * @param _newWhitelister The address allowed to whitelist betaDelegates. */ function setBetaDelegateWhitelister(address _newWhitelister) public { require(msg.sender == betaDelegateWhitelister || msg.sender == owner, "only Whitelister or Owner"); betaDelegateWhitelister = _newWhitelister; emit BetaDelegateWhitelisterSet(betaDelegateWhitelister, _newWhitelister); } modifier onlyBetaDelegateWhitelister() { require(msg.sender == betaDelegateWhitelister, "onlyBetaDelegateWhitelister"); _; } /** * @dev Whitelists an address to allow calling BetaDelegatedTransfer. * @param _addr The new address to whitelist. */ function whitelistBetaDelegate(address _addr) public onlyBetaDelegateWhitelister { require(!betaDelegateWhitelist[_addr], "delegate already whitelisted"); betaDelegateWhitelist[_addr] = true; emit BetaDelegateWhitelisted(_addr); } /** * @dev Unwhitelists an address to disallow calling BetaDelegatedTransfer. * @param _addr The new address to whitelist. */ function unwhitelistBetaDelegate(address _addr) public onlyBetaDelegateWhitelister { require(betaDelegateWhitelist[_addr], "delegate not whitelisted"); betaDelegateWhitelist[_addr] = false; emit BetaDelegateUnwhitelisted(_addr); } }
File 7 of 13: 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 8 of 13: ConversionRates
pragma solidity 0.4.18; interface ConversionRatesInterface { function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public; function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint); } interface ERC20 { function totalSupply() public view returns (uint supply); function balanceOf(address _owner) public view returns (uint balance); function transfer(address _to, uint _value) public returns (bool success); function transferFrom(address _from, address _to, uint _value) public returns (bool success); function approve(address _spender, uint _value) public returns (bool success); function allowance(address _owner, address _spender) public view returns (uint remaining); function decimals() public view returns(uint digits); event Approval(address indexed _owner, address indexed _spender, uint _value); } contract PermissionGroups { address public admin; address public pendingAdmin; mapping(address=>bool) internal operators; mapping(address=>bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint constant internal MAX_GROUP_SIZE = 50; function PermissionGroups() public { admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin); _; } modifier onlyOperator() { require(operators[msg.sender]); _; } modifier onlyAlerter() { require(alerters[msg.sender]); _; } function getOperators () external view returns(address[]) { return operatorsGroup; } function getAlerters () external view returns(address[]) { return alertersGroup; } event TransferAdminPending(address pendingAdmin); /** * @dev Allows the current admin to set the pendingAdmin address. * @param newAdmin The address to transfer ownership to. */ function transferAdmin(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(pendingAdmin); pendingAdmin = newAdmin; } /** * @dev Allows the current admin to set the admin in one tx. Useful initial deployment. * @param newAdmin The address to transfer ownership to. */ function transferAdminQuickly(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(newAdmin); AdminClaimed(newAdmin, admin); admin = newAdmin; } event AdminClaimed( address newAdmin, address previousAdmin); /** * @dev Allows the pendingAdmin address to finalize the change admin process. */ function claimAdmin() public { require(pendingAdmin == msg.sender); AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } event AlerterAdded (address newAlerter, bool isAdd); function addAlerter(address newAlerter) public onlyAdmin { require(!alerters[newAlerter]); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE); AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function removeAlerter (address alerter) public onlyAdmin { require(alerters[alerter]); alerters[alerter] = false; for (uint i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.length--; AlerterAdded(alerter, false); break; } } } event OperatorAdded(address newOperator, bool isAdd); function addOperator(address newOperator) public onlyAdmin { require(!operators[newOperator]); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE); OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } function removeOperator (address operator) public onlyAdmin { require(operators[operator]); operators[operator] = false; for (uint i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.length -= 1; OperatorAdded(operator, false); break; } } } } contract Withdrawable is PermissionGroups { event TokenWithdraw(ERC20 token, uint amount, address sendTo); /** * @dev Withdraw all ERC20 compatible tokens * @param token ERC20 The address of the token contract */ function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin { require(token.transfer(sendTo, amount)); TokenWithdraw(token, amount, sendTo); } event EtherWithdraw(uint amount, address sendTo); /** * @dev Withdraw Ethers */ function withdrawEther(uint amount, address sendTo) external onlyAdmin { sendTo.transfer(amount); EtherWithdraw(amount, sendTo); } } contract VolumeImbalanceRecorder is Withdrawable { uint constant internal SLIDING_WINDOW_SIZE = 5; uint constant internal POW_2_64 = 2 ** 64; struct TokenControlInfo { uint minimalRecordResolution; // can be roughly 1 cent uint maxPerBlockImbalance; // in twei resolution uint maxTotalImbalance; // max total imbalance (between rate updates) // before halting trade } mapping(address => TokenControlInfo) internal tokenControlInfo; struct TokenImbalanceData { int lastBlockBuyUnitsImbalance; uint lastBlock; int totalBuyUnitsImbalance; uint lastRateUpdateBlock; } mapping(address => mapping(uint=>uint)) public tokenImbalanceData; function VolumeImbalanceRecorder(address _admin) public { require(_admin != address(0)); admin = _admin; } function setTokenControlInfo( ERC20 token, uint minimalRecordResolution, uint maxPerBlockImbalance, uint maxTotalImbalance ) public onlyAdmin { tokenControlInfo[token] = TokenControlInfo( minimalRecordResolution, maxPerBlockImbalance, maxTotalImbalance ); } function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) { return (tokenControlInfo[token].minimalRecordResolution, tokenControlInfo[token].maxPerBlockImbalance, tokenControlInfo[token].maxTotalImbalance); } function addImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) internal { uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE; int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution)); int prevImbalance = 0; TokenImbalanceData memory currentBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]); // first scenario - this is not the first tx in the current block if (currentBlockData.lastBlock == currentBlock) { if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) { // just increase imbalance currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount; currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount; } else { // imbalance was changed in the middle of the block prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock); currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount; currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount; currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock); } } else { // first tx in the current block int currentBlockImbalance; (prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock); currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount; currentBlockData.lastBlock = uint(currentBlock); currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock); currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount; } tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData); } function setGarbageToVolumeRecorder(ERC20 token) internal { for (uint i = 0; i < SLIDING_WINDOW_SIZE; i++) { tokenImbalanceData[token][i] = 0x1; } } function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) { // check the imbalance in the sliding window require(startBlock <= endBlock); buyImbalance = 0; for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) { TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]); if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) { buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance); } } } function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int buyImbalance, int currentBlockImbalance) { buyImbalance = 0; currentBlockImbalance = 0; uint latestBlock = 0; int imbalanceInRange = 0; uint startBlock = rateUpdateBlock; uint endBlock = currentBlock; for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) { TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]); if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) { imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance; } if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue; if (perBlockData.lastBlock < latestBlock) continue; latestBlock = perBlockData.lastBlock; buyImbalance = perBlockData.totalBuyUnitsImbalance; if (uint(perBlockData.lastBlock) == currentBlock) { currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance; } } if (buyImbalance == 0) { buyImbalance = imbalanceInRange; } } function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int totalImbalance, int currentBlockImbalance) { int resolution = int(tokenControlInfo[token].minimalRecordResolution); (totalImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate( token, rateUpdateBlock, currentBlock); totalImbalance *= resolution; currentBlockImbalance *= resolution; } function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) { return tokenControlInfo[token].maxPerBlockImbalance; } function getMaxTotalImbalance(ERC20 token) internal view returns(uint) { return tokenControlInfo[token].maxTotalImbalance; } function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) { // check for overflows require(data.lastBlockBuyUnitsImbalance < int(POW_2_64 / 2)); require(data.lastBlockBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2)); require(data.lastBlock < POW_2_64); require(data.totalBuyUnitsImbalance < int(POW_2_64 / 2)); require(data.totalBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2)); require(data.lastRateUpdateBlock < POW_2_64); // do encoding uint result = uint(data.lastBlockBuyUnitsImbalance) & (POW_2_64 - 1); result |= data.lastBlock * POW_2_64; result |= (uint(data.totalBuyUnitsImbalance) & (POW_2_64 - 1)) * POW_2_64 * POW_2_64; result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64; return result; } function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) { TokenImbalanceData memory data; data.lastBlockBuyUnitsImbalance = int(int64(input & (POW_2_64 - 1))); data.lastBlock = uint(uint64((input / POW_2_64) & (POW_2_64 - 1))); data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) & (POW_2_64 - 1))); data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64)))); return data; } } contract Utils { ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee); uint constant internal PRECISION = (10**18); uint constant internal MAX_QTY = (10**28); // 10B tokens uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH uint constant internal MAX_DECIMALS = 18; uint constant internal ETH_DECIMALS = 18; mapping(address=>uint) internal decimals; function setDecimals(ERC20 token) internal { if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS; else decimals[token] = token.decimals(); } function getDecimals(ERC20 token) internal view returns(uint) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint tokenDecimals = decimals[token]; // technically, there might be token with decimals 0 // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if(tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(srcQty <= MAX_QTY); require(rate <= MAX_RATE); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(dstQty <= MAX_QTY); require(rate <= MAX_RATE); //source quantity is rounded up. to avoid dest quantity being too low. uint numerator; uint denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } } contract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils { // bps - basic rate steps. one step is 1 / 10000 of the rate. struct StepFunction { int[] x; // quantity for each step. Quantity of each step includes previous steps. int[] y; // rate change per quantity step in bps. } struct TokenData { bool listed; // was added to reserve bool enabled; // whether trade is enabled // position in the compact data uint compactDataArrayIndex; uint compactDataFieldIndex; // rate data. base and changes according to quantity and reserve balance. // generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction. uint baseBuyRate; // in PRECISION units. see KyberConstants uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate. StepFunction sellRateQtyStepFunction;// in bps. higher the qua StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate. StepFunction sellRateImbalanceStepFunction; } /* this is the data for tokenRatesCompactData but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write so we represent it as bytes32 and do the byte tricks ourselves. struct TokenRatesCompactData { bytes14 buy; // change buy rate of token from baseBuyRate in 10 bps bytes14 sell; // change sell rate of token from baseSellRate in 10 bps uint32 blockNumber; } */ uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks ERC20[] internal listedTokens; mapping(address=>TokenData) internal tokenData; bytes32[] internal tokenRatesCompactData; uint public numTokensInCurrentCompactData = 0; address public reserveContract; uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14; uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA)); uint constant internal MAX_STEPS_IN_FUNCTION = 10; int constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B % int constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100% function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin) { } // solhint-disable-line no-empty-blocks function addToken(ERC20 token) public onlyAdmin { require(!tokenData[token].listed); tokenData[token].listed = true; listedTokens.push(token); if (numTokensInCurrentCompactData == 0) { tokenRatesCompactData.length++; // add new structure } tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1; tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData; numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA; setGarbageToVolumeRecorder(token); setDecimals(token); } function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator { require(buy.length == sell.length); require(indices.length == buy.length); require(blockNumber <= 0xFFFFFFFF); uint bytes14Offset = BYTES_14_OFFSET; for (uint i = 0; i < indices.length; i++) { require(indices[i] < tokenRatesCompactData.length); uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset)); tokenRatesCompactData[indices[i]] = bytes32(data); } } function setBaseRate( ERC20[] tokens, uint[] baseBuy, uint[] baseSell, bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices ) public onlyOperator { require(tokens.length == baseBuy.length); require(tokens.length == baseSell.length); require(sell.length == buy.length); require(sell.length == indices.length); for (uint ind = 0; ind < tokens.length; ind++) { require(tokenData[tokens[ind]].listed); tokenData[tokens[ind]].baseBuyRate = baseBuy[ind]; tokenData[tokens[ind]].baseSellRate = baseSell[ind]; } setCompactData(buy, sell, blockNumber, indices); } function setQtyStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length == yBuy.length); require(xSell.length == ySell.length); require(xBuy.length <= MAX_STEPS_IN_FUNCTION); require(xSell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy); tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell); } function setImbalanceStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length == yBuy.length); require(xSell.length == ySell.length); require(xBuy.length <= MAX_STEPS_IN_FUNCTION); require(xSell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy); tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell); } function setValidRateDurationInBlocks(uint duration) public onlyAdmin { validRateDurationInBlocks = duration; } function enableTokenTrade(ERC20 token) public onlyAdmin { require(tokenData[token].listed); require(tokenControlInfo[token].minimalRecordResolution != 0); tokenData[token].enabled = true; } function disableTokenTrade(ERC20 token) public onlyAlerter { require(tokenData[token].listed); tokenData[token].enabled = false; } function setReserveAddress(address reserve) public onlyAdmin { reserveContract = reserve; } function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public { require(msg.sender == reserveContract); if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token); return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock); } /* solhint-disable function-max-lines */ function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) { // check if trade is enabled if (!tokenData[token].enabled) return 0; if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set // get rate update block bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; uint updateRateBlock = getLast4Bytes(compactData); if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired // check imbalance int totalImbalance; int blockImbalance; (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber); // calculate actual rate int imbalanceQty; int extraBps; int8 rateUpdate; uint rate; if (buy) { // start with base rate rate = tokenData[token].baseBuyRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, true); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty qty = getTokenQty(token, rate, qty); imbalanceQty = int(qty); totalImbalance += imbalanceQty; // add qty overhead extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty)); rate = addBps(rate, extraBps); // add imbalance overhead extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance); rate = addBps(rate, extraBps); } else { // start with base rate rate = tokenData[token].baseSellRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, false); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty imbalanceQty = -1 * int(qty); totalImbalance += imbalanceQty; // add qty overhead extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty)); rate = addBps(rate, extraBps); // add imbalance overhead extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance); rate = addBps(rate, extraBps); } if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0; if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0; return rate; } /* solhint-enable function-max-lines */ function getBasicRate(ERC20 token, bool buy) public view returns(uint) { if (buy) return tokenData[token].baseBuyRate; else return tokenData[token].baseSellRate; } function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) { require(tokenData[token].listed); uint arrayIndex = tokenData[token].compactDataArrayIndex; uint fieldOffset = tokenData[token].compactDataFieldIndex; return ( arrayIndex, fieldOffset, byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)), byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false)) ); } function getTokenBasicData(ERC20 token) public view returns(bool, bool) { return (tokenData[token].listed, tokenData[token].enabled); } /* solhint-disable code-complexity */ function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) { if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length); if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param]; if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length); if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param]; if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length); if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param]; if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length); if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param]; if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length); if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param]; if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length); if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param]; if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length); if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param]; if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length); if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param]; revert(); } /* solhint-enable code-complexity */ function getRateUpdateBlock(ERC20 token) public view returns(uint) { bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; return getLast4Bytes(compactData); } function getListedTokens() public view returns(ERC20[]) { return listedTokens; } function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) { uint dstDecimals = getDecimals(token); uint srcDecimals = ETH_DECIMALS; return calcDstQty(ethQty, srcDecimals, dstDecimals, rate); } function getLast4Bytes(bytes32 b) internal pure returns(uint) { // cannot trust compiler with not turning bit operations into EXP opcode return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET); } function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) { uint fieldOffset = tokenData[token].compactDataFieldIndex; uint byteOffset; if (buy) byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset; else byteOffset = 4 + fieldOffset; return int8(data[byteOffset]); } function executeStepFunction(StepFunction f, int x) internal pure returns(int) { uint len = f.y.length; for (uint ind = 0; ind < len; ind++) { if (x <= f.x[ind]) return f.y[ind]; } return f.y[len-1]; } function addBps(uint rate, int bps) internal pure returns(uint) { require(rate <= MAX_RATE); require(bps >= MIN_BPS_ADJUSTMENT); require(bps <= MAX_BPS_ADJUSTMENT); uint maxBps = 100 * 100; return (rate * uint(int(maxBps) + bps)) / maxBps; } function abs(int x) internal pure returns(uint) { if (x < 0) return uint(-1 * x); else return uint(x); } }
File 9 of 13: 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; } }
File 10 of 13: ConversionRates
pragma solidity 0.4.18; interface ConversionRatesInterface { function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public; function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint); } interface ERC20 { function totalSupply() public view returns (uint supply); function balanceOf(address _owner) public view returns (uint balance); function transfer(address _to, uint _value) public returns (bool success); function transferFrom(address _from, address _to, uint _value) public returns (bool success); function approve(address _spender, uint _value) public returns (bool success); function allowance(address _owner, address _spender) public view returns (uint remaining); function decimals() public view returns(uint digits); event Approval(address indexed _owner, address indexed _spender, uint _value); } contract PermissionGroups { address public admin; address public pendingAdmin; mapping(address=>bool) internal operators; mapping(address=>bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint constant internal MAX_GROUP_SIZE = 50; function PermissionGroups() public { admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin); _; } modifier onlyOperator() { require(operators[msg.sender]); _; } modifier onlyAlerter() { require(alerters[msg.sender]); _; } function getOperators () external view returns(address[]) { return operatorsGroup; } function getAlerters () external view returns(address[]) { return alertersGroup; } event TransferAdminPending(address pendingAdmin); /** * @dev Allows the current admin to set the pendingAdmin address. * @param newAdmin The address to transfer ownership to. */ function transferAdmin(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(pendingAdmin); pendingAdmin = newAdmin; } /** * @dev Allows the current admin to set the admin in one tx. Useful initial deployment. * @param newAdmin The address to transfer ownership to. */ function transferAdminQuickly(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(newAdmin); AdminClaimed(newAdmin, admin); admin = newAdmin; } event AdminClaimed( address newAdmin, address previousAdmin); /** * @dev Allows the pendingAdmin address to finalize the change admin process. */ function claimAdmin() public { require(pendingAdmin == msg.sender); AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } event AlerterAdded (address newAlerter, bool isAdd); function addAlerter(address newAlerter) public onlyAdmin { require(!alerters[newAlerter]); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE); AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function removeAlerter (address alerter) public onlyAdmin { require(alerters[alerter]); alerters[alerter] = false; for (uint i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.length--; AlerterAdded(alerter, false); break; } } } event OperatorAdded(address newOperator, bool isAdd); function addOperator(address newOperator) public onlyAdmin { require(!operators[newOperator]); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE); OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } function removeOperator (address operator) public onlyAdmin { require(operators[operator]); operators[operator] = false; for (uint i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.length -= 1; OperatorAdded(operator, false); break; } } } } contract Withdrawable is PermissionGroups { event TokenWithdraw(ERC20 token, uint amount, address sendTo); /** * @dev Withdraw all ERC20 compatible tokens * @param token ERC20 The address of the token contract */ function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin { require(token.transfer(sendTo, amount)); TokenWithdraw(token, amount, sendTo); } event EtherWithdraw(uint amount, address sendTo); /** * @dev Withdraw Ethers */ function withdrawEther(uint amount, address sendTo) external onlyAdmin { sendTo.transfer(amount); EtherWithdraw(amount, sendTo); } } contract VolumeImbalanceRecorder is Withdrawable { uint constant internal SLIDING_WINDOW_SIZE = 5; uint constant internal POW_2_64 = 2 ** 64; struct TokenControlInfo { uint minimalRecordResolution; // can be roughly 1 cent uint maxPerBlockImbalance; // in twei resolution uint maxTotalImbalance; // max total imbalance (between rate updates) // before halting trade } mapping(address => TokenControlInfo) internal tokenControlInfo; struct TokenImbalanceData { int lastBlockBuyUnitsImbalance; uint lastBlock; int totalBuyUnitsImbalance; uint lastRateUpdateBlock; } mapping(address => mapping(uint=>uint)) public tokenImbalanceData; function VolumeImbalanceRecorder(address _admin) public { require(_admin != address(0)); admin = _admin; } function setTokenControlInfo( ERC20 token, uint minimalRecordResolution, uint maxPerBlockImbalance, uint maxTotalImbalance ) public onlyAdmin { tokenControlInfo[token] = TokenControlInfo( minimalRecordResolution, maxPerBlockImbalance, maxTotalImbalance ); } function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) { return (tokenControlInfo[token].minimalRecordResolution, tokenControlInfo[token].maxPerBlockImbalance, tokenControlInfo[token].maxTotalImbalance); } function addImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) internal { uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE; int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution)); int prevImbalance = 0; TokenImbalanceData memory currentBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]); // first scenario - this is not the first tx in the current block if (currentBlockData.lastBlock == currentBlock) { if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) { // just increase imbalance currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount; currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount; } else { // imbalance was changed in the middle of the block prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock); currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount; currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount; currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock); } } else { // first tx in the current block int currentBlockImbalance; (prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock); currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount; currentBlockData.lastBlock = uint(currentBlock); currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock); currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount; } tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData); } function setGarbageToVolumeRecorder(ERC20 token) internal { for (uint i = 0; i < SLIDING_WINDOW_SIZE; i++) { tokenImbalanceData[token][i] = 0x1; } } function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) { // check the imbalance in the sliding window require(startBlock <= endBlock); buyImbalance = 0; for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) { TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]); if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) { buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance); } } } function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int buyImbalance, int currentBlockImbalance) { buyImbalance = 0; currentBlockImbalance = 0; uint latestBlock = 0; int imbalanceInRange = 0; uint startBlock = rateUpdateBlock; uint endBlock = currentBlock; for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) { TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]); if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) { imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance; } if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue; if (perBlockData.lastBlock < latestBlock) continue; latestBlock = perBlockData.lastBlock; buyImbalance = perBlockData.totalBuyUnitsImbalance; if (uint(perBlockData.lastBlock) == currentBlock) { currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance; } } if (buyImbalance == 0) { buyImbalance = imbalanceInRange; } } function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int totalImbalance, int currentBlockImbalance) { int resolution = int(tokenControlInfo[token].minimalRecordResolution); (totalImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate( token, rateUpdateBlock, currentBlock); totalImbalance *= resolution; currentBlockImbalance *= resolution; } function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) { return tokenControlInfo[token].maxPerBlockImbalance; } function getMaxTotalImbalance(ERC20 token) internal view returns(uint) { return tokenControlInfo[token].maxTotalImbalance; } function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) { // check for overflows require(data.lastBlockBuyUnitsImbalance < int(POW_2_64 / 2)); require(data.lastBlockBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2)); require(data.lastBlock < POW_2_64); require(data.totalBuyUnitsImbalance < int(POW_2_64 / 2)); require(data.totalBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2)); require(data.lastRateUpdateBlock < POW_2_64); // do encoding uint result = uint(data.lastBlockBuyUnitsImbalance) & (POW_2_64 - 1); result |= data.lastBlock * POW_2_64; result |= (uint(data.totalBuyUnitsImbalance) & (POW_2_64 - 1)) * POW_2_64 * POW_2_64; result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64; return result; } function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) { TokenImbalanceData memory data; data.lastBlockBuyUnitsImbalance = int(int64(input & (POW_2_64 - 1))); data.lastBlock = uint(uint64((input / POW_2_64) & (POW_2_64 - 1))); data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) & (POW_2_64 - 1))); data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64)))); return data; } } contract Utils { ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee); uint constant internal PRECISION = (10**18); uint constant internal MAX_QTY = (10**28); // 10B tokens uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH uint constant internal MAX_DECIMALS = 18; uint constant internal ETH_DECIMALS = 18; mapping(address=>uint) internal decimals; function setDecimals(ERC20 token) internal { if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS; else decimals[token] = token.decimals(); } function getDecimals(ERC20 token) internal view returns(uint) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint tokenDecimals = decimals[token]; // technically, there might be token with decimals 0 // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if(tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(srcQty <= MAX_QTY); require(rate <= MAX_RATE); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(dstQty <= MAX_QTY); require(rate <= MAX_RATE); //source quantity is rounded up. to avoid dest quantity being too low. uint numerator; uint denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } } contract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils { // bps - basic rate steps. one step is 1 / 10000 of the rate. struct StepFunction { int[] x; // quantity for each step. Quantity of each step includes previous steps. int[] y; // rate change per quantity step in bps. } struct TokenData { bool listed; // was added to reserve bool enabled; // whether trade is enabled // position in the compact data uint compactDataArrayIndex; uint compactDataFieldIndex; // rate data. base and changes according to quantity and reserve balance. // generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction. uint baseBuyRate; // in PRECISION units. see KyberConstants uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate. StepFunction sellRateQtyStepFunction;// in bps. higher the qua StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate. StepFunction sellRateImbalanceStepFunction; } /* this is the data for tokenRatesCompactData but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write so we represent it as bytes32 and do the byte tricks ourselves. struct TokenRatesCompactData { bytes14 buy; // change buy rate of token from baseBuyRate in 10 bps bytes14 sell; // change sell rate of token from baseSellRate in 10 bps uint32 blockNumber; } */ uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks ERC20[] internal listedTokens; mapping(address=>TokenData) internal tokenData; bytes32[] internal tokenRatesCompactData; uint public numTokensInCurrentCompactData = 0; address public reserveContract; uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14; uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA)); uint constant internal MAX_STEPS_IN_FUNCTION = 10; int constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B % int constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100% function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin) { } // solhint-disable-line no-empty-blocks function addToken(ERC20 token) public onlyAdmin { require(!tokenData[token].listed); tokenData[token].listed = true; listedTokens.push(token); if (numTokensInCurrentCompactData == 0) { tokenRatesCompactData.length++; // add new structure } tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1; tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData; numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA; setGarbageToVolumeRecorder(token); setDecimals(token); } function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator { require(buy.length == sell.length); require(indices.length == buy.length); require(blockNumber <= 0xFFFFFFFF); uint bytes14Offset = BYTES_14_OFFSET; for (uint i = 0; i < indices.length; i++) { require(indices[i] < tokenRatesCompactData.length); uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset)); tokenRatesCompactData[indices[i]] = bytes32(data); } } function setBaseRate( ERC20[] tokens, uint[] baseBuy, uint[] baseSell, bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices ) public onlyOperator { require(tokens.length == baseBuy.length); require(tokens.length == baseSell.length); require(sell.length == buy.length); require(sell.length == indices.length); for (uint ind = 0; ind < tokens.length; ind++) { require(tokenData[tokens[ind]].listed); tokenData[tokens[ind]].baseBuyRate = baseBuy[ind]; tokenData[tokens[ind]].baseSellRate = baseSell[ind]; } setCompactData(buy, sell, blockNumber, indices); } function setQtyStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length == yBuy.length); require(xSell.length == ySell.length); require(xBuy.length <= MAX_STEPS_IN_FUNCTION); require(xSell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy); tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell); } function setImbalanceStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length == yBuy.length); require(xSell.length == ySell.length); require(xBuy.length <= MAX_STEPS_IN_FUNCTION); require(xSell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy); tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell); } function setValidRateDurationInBlocks(uint duration) public onlyAdmin { validRateDurationInBlocks = duration; } function enableTokenTrade(ERC20 token) public onlyAdmin { require(tokenData[token].listed); require(tokenControlInfo[token].minimalRecordResolution != 0); tokenData[token].enabled = true; } function disableTokenTrade(ERC20 token) public onlyAlerter { require(tokenData[token].listed); tokenData[token].enabled = false; } function setReserveAddress(address reserve) public onlyAdmin { reserveContract = reserve; } function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public { require(msg.sender == reserveContract); if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token); return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock); } /* solhint-disable function-max-lines */ function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) { // check if trade is enabled if (!tokenData[token].enabled) return 0; if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set // get rate update block bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; uint updateRateBlock = getLast4Bytes(compactData); if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired // check imbalance int totalImbalance; int blockImbalance; (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber); // calculate actual rate int imbalanceQty; int extraBps; int8 rateUpdate; uint rate; if (buy) { // start with base rate rate = tokenData[token].baseBuyRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, true); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty qty = getTokenQty(token, rate, qty); imbalanceQty = int(qty); totalImbalance += imbalanceQty; // add qty overhead extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty)); rate = addBps(rate, extraBps); // add imbalance overhead extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance); rate = addBps(rate, extraBps); } else { // start with base rate rate = tokenData[token].baseSellRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, false); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty imbalanceQty = -1 * int(qty); totalImbalance += imbalanceQty; // add qty overhead extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty)); rate = addBps(rate, extraBps); // add imbalance overhead extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance); rate = addBps(rate, extraBps); } if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0; if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0; return rate; } /* solhint-enable function-max-lines */ function getBasicRate(ERC20 token, bool buy) public view returns(uint) { if (buy) return tokenData[token].baseBuyRate; else return tokenData[token].baseSellRate; } function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) { require(tokenData[token].listed); uint arrayIndex = tokenData[token].compactDataArrayIndex; uint fieldOffset = tokenData[token].compactDataFieldIndex; return ( arrayIndex, fieldOffset, byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)), byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false)) ); } function getTokenBasicData(ERC20 token) public view returns(bool, bool) { return (tokenData[token].listed, tokenData[token].enabled); } /* solhint-disable code-complexity */ function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) { if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length); if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param]; if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length); if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param]; if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length); if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param]; if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length); if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param]; if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length); if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param]; if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length); if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param]; if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length); if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param]; if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length); if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param]; revert(); } /* solhint-enable code-complexity */ function getRateUpdateBlock(ERC20 token) public view returns(uint) { bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; return getLast4Bytes(compactData); } function getListedTokens() public view returns(ERC20[]) { return listedTokens; } function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) { uint dstDecimals = getDecimals(token); uint srcDecimals = ETH_DECIMALS; return calcDstQty(ethQty, srcDecimals, dstDecimals, rate); } function getLast4Bytes(bytes32 b) internal pure returns(uint) { // cannot trust compiler with not turning bit operations into EXP opcode return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET); } function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) { uint fieldOffset = tokenData[token].compactDataFieldIndex; uint byteOffset; if (buy) byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset; else byteOffset = 4 + fieldOffset; return int8(data[byteOffset]); } function executeStepFunction(StepFunction f, int x) internal pure returns(int) { uint len = f.y.length; for (uint ind = 0; ind < len; ind++) { if (x <= f.x[ind]) return f.y[ind]; } return f.y[len-1]; } function addBps(uint rate, int bps) internal pure returns(uint) { require(rate <= MAX_RATE); require(bps >= MIN_BPS_ADJUSTMENT); require(bps <= MAX_BPS_ADJUSTMENT); uint maxBps = 100 * 100; return (rate * uint(int(maxBps) + bps)) / maxBps; } function abs(int x) internal pure returns(uint) { if (x < 0) return uint(-1 * x); else return uint(x); } }
File 11 of 13: KyberReserve
{"ConversionRatesInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\ninterface ConversionRatesInterface {\n\n function recordImbalance(\n ERC20 token,\n int buyAmount,\n uint rateUpdateBlock,\n uint currentBlock\n )\n public;\n\n function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);\n}\n"},"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"},"KyberReserve.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"../ERC20Interface.sol\";\nimport \"../Utils.sol\";\nimport \"../Withdrawable.sol\";\nimport \"../ConversionRatesInterface.sol\";\nimport \"../SanityRatesInterface.sol\";\nimport \"../KyberReserveInterface.sol\";\n\n\n/// @title Kyber Reserve contract\ncontract KyberReserve is KyberReserveInterface, Withdrawable, Utils {\n\n address public kyberNetwork;\n bool public tradeEnabled;\n ConversionRatesInterface public conversionRatesContract;\n SanityRatesInterface public sanityRatesContract;\n mapping(bytes32=\u003ebool) public approvedWithdrawAddresses; // sha3(token,address)=\u003ebool\n mapping(address=\u003eaddress) public tokenWallet;\n\n function KyberReserve(address _kyberNetwork, ConversionRatesInterface _ratesContract, address _admin) public {\n require(_admin != address(0));\n require(_ratesContract != address(0));\n require(_kyberNetwork != address(0));\n kyberNetwork = _kyberNetwork;\n conversionRatesContract = _ratesContract;\n admin = _admin;\n tradeEnabled = true;\n }\n\n event DepositToken(ERC20 token, uint amount);\n\n function() public payable {\n DepositToken(ETH_TOKEN_ADDRESS, msg.value);\n }\n\n event TradeExecute(\n address indexed origin,\n address src,\n uint srcAmount,\n address destToken,\n uint destAmount,\n address destAddress\n );\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 require(tradeEnabled);\n require(msg.sender == kyberNetwork);\n\n require(doTrade(srcToken, srcAmount, destToken, destAddress, conversionRate, validate));\n\n return true;\n }\n\n event TradeEnabled(bool enable);\n\n function enableTrade() public onlyAdmin returns(bool) {\n tradeEnabled = true;\n TradeEnabled(true);\n\n return true;\n }\n\n function disableTrade() public onlyAlerter returns(bool) {\n tradeEnabled = false;\n TradeEnabled(false);\n\n return true;\n }\n\n event WithdrawAddressApproved(ERC20 token, address addr, bool approve);\n\n function approveWithdrawAddress(ERC20 token, address addr, bool approve) public onlyAdmin {\n approvedWithdrawAddresses[keccak256(token, addr)] = approve;\n WithdrawAddressApproved(token, addr, approve);\n\n setDecimals(token);\n if ((tokenWallet[token] == address(0x0)) \u0026\u0026 (token != ETH_TOKEN_ADDRESS)) {\n tokenWallet[token] = this; // by default\n require(token.approve(this, 2 ** 255));\n }\n }\n\n event NewTokenWallet(ERC20 token, address wallet);\n\n function setTokenWallet(ERC20 token, address wallet) public onlyAdmin {\n require(wallet != address(0x0));\n tokenWallet[token] = wallet;\n NewTokenWallet(token, wallet);\n }\n\n event WithdrawFunds(ERC20 token, uint amount, address destination);\n\n function withdraw(ERC20 token, uint amount, address destination) public onlyOperator returns(bool) {\n require(approvedWithdrawAddresses[keccak256(token, destination)]);\n\n if (token == ETH_TOKEN_ADDRESS) {\n destination.transfer(amount);\n } else {\n require(token.transferFrom(tokenWallet[token], destination, amount));\n }\n\n WithdrawFunds(token, amount, destination);\n\n return true;\n }\n\n event SetContractAddresses(address network, address rate, address sanity);\n\n function setContracts(\n address _kyberNetwork,\n ConversionRatesInterface _conversionRates,\n SanityRatesInterface _sanityRates\n )\n public\n onlyAdmin\n {\n require(_kyberNetwork != address(0));\n require(_conversionRates != address(0));\n\n kyberNetwork = _kyberNetwork;\n conversionRatesContract = _conversionRates;\n sanityRatesContract = _sanityRates;\n\n SetContractAddresses(kyberNetwork, conversionRatesContract, sanityRatesContract);\n }\n\n ////////////////////////////////////////////////////////////////////////////\n /// status functions ///////////////////////////////////////////////////////\n ////////////////////////////////////////////////////////////////////////////\n function getBalance(ERC20 token) public view returns(uint) {\n if (token == ETH_TOKEN_ADDRESS)\n return this.balance;\n else {\n address wallet = tokenWallet[token];\n uint balanceOfWallet = token.balanceOf(wallet);\n uint allowanceOfWallet = token.allowance(wallet, this);\n\n return (balanceOfWallet \u003c allowanceOfWallet) ? balanceOfWallet : allowanceOfWallet;\n }\n }\n\n function getDestQty(ERC20 src, ERC20 dest, uint srcQty, uint rate) public view returns(uint) {\n uint dstDecimals = getDecimals(dest);\n uint srcDecimals = getDecimals(src);\n\n return calcDstQty(srcQty, srcDecimals, dstDecimals, rate);\n }\n\n function getSrcQty(ERC20 src, ERC20 dest, uint dstQty, uint rate) public view returns(uint) {\n uint dstDecimals = getDecimals(dest);\n uint srcDecimals = getDecimals(src);\n\n return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate);\n }\n\n function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint) {\n ERC20 token;\n bool isBuy;\n\n if (!tradeEnabled) return 0;\n\n if (ETH_TOKEN_ADDRESS == src) {\n isBuy = true;\n token = dest;\n } else if (ETH_TOKEN_ADDRESS == dest) {\n isBuy = false;\n token = src;\n } else {\n return 0; // pair is not listed\n }\n\n uint rate = conversionRatesContract.getRate(token, blockNumber, isBuy, srcQty);\n uint destQty = getDestQty(src, dest, srcQty, rate);\n\n if (getBalance(dest) \u003c destQty) return 0;\n\n if (sanityRatesContract != address(0)) {\n uint sanityRate = sanityRatesContract.getSanityRate(src, dest);\n if (rate \u003e sanityRate) return 0;\n }\n\n return rate;\n }\n\n /// @dev do a trade\n /// @param srcToken Src token\n /// @param srcAmount Amount of src token\n /// @param destToken Destination token\n /// @param destAddress Destination address to send tokens to\n /// @param validate If true, additional validations are applicable\n /// @return true iff trade is successful\n function doTrade(\n ERC20 srcToken,\n uint srcAmount,\n ERC20 destToken,\n address destAddress,\n uint conversionRate,\n bool validate\n )\n internal\n returns(bool)\n {\n // can skip validation if done at kyber network level\n if (validate) {\n require(conversionRate \u003e 0);\n if (srcToken == ETH_TOKEN_ADDRESS)\n require(msg.value == srcAmount);\n else\n require(msg.value == 0);\n }\n\n uint destAmount = getDestQty(srcToken, destToken, srcAmount, conversionRate);\n // sanity check\n require(destAmount \u003e 0);\n\n // add to imbalance\n ERC20 token;\n int tradeAmount;\n if (srcToken == ETH_TOKEN_ADDRESS) {\n tradeAmount = int(destAmount);\n token = destToken;\n } else {\n tradeAmount = -1 * int(srcAmount);\n token = srcToken;\n }\n\n conversionRatesContract.recordImbalance(\n token,\n tradeAmount,\n 0,\n block.number\n );\n\n // collect src tokens\n if (srcToken != ETH_TOKEN_ADDRESS) {\n require(srcToken.transferFrom(msg.sender, tokenWallet[srcToken], srcAmount));\n }\n\n // send dest tokens\n if (destToken == ETH_TOKEN_ADDRESS) {\n destAddress.transfer(destAmount);\n } else {\n require(destToken.transferFrom(tokenWallet[destToken], destAddress, destAmount));\n }\n\n TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress);\n\n return true;\n }\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"},"SanityRatesInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\ninterface SanityRatesInterface {\n function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);\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"},"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 12 of 13: ConversionRates
{"ConversionRates.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"../../ERC20Interface.sol\";\nimport \"../../Utils.sol\";\nimport \"../../ConversionRatesInterface.sol\";\nimport \"../VolumeImbalanceRecorder.sol\";\n\n\ncontract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils {\n\n // bps - basic rate steps. one step is 1 / 10000 of the rate.\n struct StepFunction {\n int[] x; // quantity for each step. Quantity of each step includes previous steps.\n int[] y; // rate change per quantity step in bps.\n }\n\n struct TokenData {\n bool listed; // was added to reserve\n bool enabled; // whether trade is enabled\n\n // position in the compact data\n uint compactDataArrayIndex;\n uint compactDataFieldIndex;\n\n // rate data. base and changes according to quantity and reserve balance.\n // generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction.\n uint baseBuyRate; // in PRECISION units. see KyberConstants\n uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate\n StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate.\n StepFunction sellRateQtyStepFunction;// in bps. higher the qua\n StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate.\n StepFunction sellRateImbalanceStepFunction;\n }\n\n /*\n this is the data for tokenRatesCompactData\n but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write\n so we represent it as bytes32 and do the byte tricks ourselves.\n struct TokenRatesCompactData {\n bytes14 buy; // change buy rate of token from baseBuyRate in 10 bps\n bytes14 sell; // change sell rate of token from baseSellRate in 10 bps\n\n uint32 blockNumber;\n } */\n uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks\n ERC20[] internal listedTokens;\n mapping(address=\u003eTokenData) internal tokenData;\n bytes32[] internal tokenRatesCompactData;\n uint public numTokensInCurrentCompactData = 0;\n address public reserveContract;\n uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14;\n uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA));\n uint constant internal MAX_STEPS_IN_FUNCTION = 10;\n int constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B %\n int constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100%\n\n function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin)\n { } // solhint-disable-line no-empty-blocks\n\n function addToken(ERC20 token) public onlyAdmin {\n\n require(!tokenData[token].listed);\n tokenData[token].listed = true;\n listedTokens.push(token);\n\n if (numTokensInCurrentCompactData == 0) {\n tokenRatesCompactData.length++; // add new structure\n }\n\n tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1;\n tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData;\n\n numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA;\n\n setGarbageToVolumeRecorder(token);\n\n setDecimals(token);\n }\n\n function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator {\n\n require(buy.length == sell.length);\n require(indices.length == buy.length);\n require(blockNumber \u003c= 0xFFFFFFFF);\n\n uint bytes14Offset = BYTES_14_OFFSET;\n\n for (uint i = 0; i \u003c indices.length; i++) {\n require(indices[i] \u003c tokenRatesCompactData.length);\n uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset));\n tokenRatesCompactData[indices[i]] = bytes32(data);\n }\n }\n\n function setBaseRate(\n ERC20[] tokens,\n uint[] baseBuy,\n uint[] baseSell,\n bytes14[] buy,\n bytes14[] sell,\n uint blockNumber,\n uint[] indices\n )\n public\n onlyOperator\n {\n require(tokens.length == baseBuy.length);\n require(tokens.length == baseSell.length);\n require(sell.length == buy.length);\n require(sell.length == indices.length);\n\n for (uint ind = 0; ind \u003c tokens.length; ind++) {\n require(tokenData[tokens[ind]].listed);\n tokenData[tokens[ind]].baseBuyRate = baseBuy[ind];\n tokenData[tokens[ind]].baseSellRate = baseSell[ind];\n }\n\n setCompactData(buy, sell, blockNumber, indices);\n }\n\n function setQtyStepFunction(\n ERC20 token,\n int[] xBuy,\n int[] yBuy,\n int[] xSell,\n int[] ySell\n )\n public\n onlyOperator\n {\n require(xBuy.length == yBuy.length);\n require(xSell.length == ySell.length);\n require(xBuy.length \u003c= MAX_STEPS_IN_FUNCTION);\n require(xSell.length \u003c= MAX_STEPS_IN_FUNCTION);\n require(tokenData[token].listed);\n\n tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy);\n tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell);\n }\n\n function setImbalanceStepFunction(\n ERC20 token,\n int[] xBuy,\n int[] yBuy,\n int[] xSell,\n int[] ySell\n )\n public\n onlyOperator\n {\n require(xBuy.length == yBuy.length);\n require(xSell.length == ySell.length);\n require(xBuy.length \u003c= MAX_STEPS_IN_FUNCTION);\n require(xSell.length \u003c= MAX_STEPS_IN_FUNCTION);\n require(tokenData[token].listed);\n\n tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy);\n tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell);\n }\n\n function setValidRateDurationInBlocks(uint duration) public onlyAdmin {\n validRateDurationInBlocks = duration;\n }\n\n function enableTokenTrade(ERC20 token) public onlyAdmin {\n require(tokenData[token].listed);\n require(tokenControlInfo[token].minimalRecordResolution != 0);\n tokenData[token].enabled = true;\n }\n\n function disableTokenTrade(ERC20 token) public onlyAlerter {\n require(tokenData[token].listed);\n tokenData[token].enabled = false;\n }\n\n function setReserveAddress(address reserve) public onlyAdmin {\n reserveContract = reserve;\n }\n\n function recordImbalance(\n ERC20 token,\n int buyAmount,\n uint rateUpdateBlock,\n uint currentBlock\n )\n public\n {\n require(msg.sender == reserveContract);\n\n if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token);\n\n return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock);\n }\n\n /* solhint-disable function-max-lines */\n function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) {\n // check if trade is enabled\n if (!tokenData[token].enabled) return 0;\n if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set\n\n // get rate update block\n bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];\n\n uint updateRateBlock = getLast4Bytes(compactData);\n if (currentBlockNumber \u003e= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired\n // check imbalance\n int totalImbalance;\n int blockImbalance;\n (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber);\n\n // calculate actual rate\n int imbalanceQty;\n int extraBps;\n int8 rateUpdate;\n uint rate;\n\n if (buy) {\n // start with base rate\n rate = tokenData[token].baseBuyRate;\n\n // add rate update\n rateUpdate = getRateByteFromCompactData(compactData, token, true);\n extraBps = int(rateUpdate) * 10;\n rate = addBps(rate, extraBps);\n\n // compute token qty\n qty = getTokenQty(token, rate, qty);\n imbalanceQty = int(qty);\n totalImbalance += imbalanceQty;\n\n // add qty overhead\n extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty));\n rate = addBps(rate, extraBps);\n\n // add imbalance overhead\n extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance);\n rate = addBps(rate, extraBps);\n } else {\n // start with base rate\n rate = tokenData[token].baseSellRate;\n\n // add rate update\n rateUpdate = getRateByteFromCompactData(compactData, token, false);\n extraBps = int(rateUpdate) * 10;\n rate = addBps(rate, extraBps);\n\n // compute token qty\n imbalanceQty = -1 * int(qty);\n totalImbalance += imbalanceQty;\n\n // add qty overhead\n extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty));\n rate = addBps(rate, extraBps);\n\n // add imbalance overhead\n extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance);\n rate = addBps(rate, extraBps);\n }\n\n if (abs(totalImbalance) \u003e= getMaxTotalImbalance(token)) return 0;\n if (abs(blockImbalance + imbalanceQty) \u003e= getMaxPerBlockImbalance(token)) return 0;\n\n return rate;\n }\n /* solhint-enable function-max-lines */\n\n function getBasicRate(ERC20 token, bool buy) public view returns(uint) {\n if (buy)\n return tokenData[token].baseBuyRate;\n else\n return tokenData[token].baseSellRate;\n }\n\n function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) {\n require(tokenData[token].listed);\n\n uint arrayIndex = tokenData[token].compactDataArrayIndex;\n uint fieldOffset = tokenData[token].compactDataFieldIndex;\n\n return (\n arrayIndex,\n fieldOffset,\n byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)),\n byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false))\n );\n }\n\n function getTokenBasicData(ERC20 token) public view returns(bool, bool) {\n return (tokenData[token].listed, tokenData[token].enabled);\n }\n\n /* solhint-disable code-complexity */\n function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) {\n if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length);\n if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param];\n if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length);\n if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param];\n\n if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length);\n if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param];\n if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length);\n if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param];\n\n if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length);\n if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param];\n if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length);\n if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param];\n\n if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length);\n if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param];\n if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length);\n if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param];\n\n revert();\n }\n /* solhint-enable code-complexity */\n\n function getRateUpdateBlock(ERC20 token) public view returns(uint) {\n bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];\n return getLast4Bytes(compactData);\n }\n\n function getListedTokens() public view returns(ERC20[]) {\n return listedTokens;\n }\n\n function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) {\n uint dstDecimals = getDecimals(token);\n uint srcDecimals = ETH_DECIMALS;\n\n return calcDstQty(ethQty, srcDecimals, dstDecimals, rate);\n }\n\n function getLast4Bytes(bytes32 b) internal pure returns(uint) {\n // cannot trust compiler with not turning bit operations into EXP opcode\n return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET);\n }\n\n function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) {\n uint fieldOffset = tokenData[token].compactDataFieldIndex;\n uint byteOffset;\n if (buy)\n byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset;\n else\n byteOffset = 4 + fieldOffset;\n\n return int8(data[byteOffset]);\n }\n\n function executeStepFunction(StepFunction f, int x) internal pure returns(int) {\n uint len = f.y.length;\n for (uint ind = 0; ind \u003c len; ind++) {\n if (x \u003c= f.x[ind]) return f.y[ind];\n }\n\n return f.y[len-1];\n }\n\n function addBps(uint rate, int bps) internal pure returns(uint) {\n require(rate \u003c= MAX_RATE);\n require(bps \u003e= MIN_BPS_ADJUSTMENT);\n require(bps \u003c= MAX_BPS_ADJUSTMENT);\n\n uint maxBps = 100 * 100;\n return (rate * uint(int(maxBps) + bps)) / maxBps;\n }\n\n function abs(int x) internal pure returns(uint) {\n if (x \u003c 0)\n return uint(-1 * x);\n else\n return uint(x);\n }\n}\n"},"ConversionRatesInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\ninterface ConversionRatesInterface {\n\n function recordImbalance(\n ERC20 token,\n int buyAmount,\n uint rateUpdateBlock,\n uint currentBlock\n )\n public;\n\n function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);\n}\n"},"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"},"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"},"VolumeImbalanceRecorder.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"../ERC20Interface.sol\";\nimport \"../Withdrawable.sol\";\n\n\ncontract VolumeImbalanceRecorder is Withdrawable {\n\n uint constant internal SLIDING_WINDOW_SIZE = 5;\n uint constant internal POW_2_64 = 2 ** 64;\n\n struct TokenControlInfo {\n uint minimalRecordResolution; // can be roughly 1 cent\n uint maxPerBlockImbalance; // in twei resolution\n uint maxTotalImbalance; // max total imbalance (between rate updates)\n // before halting trade\n }\n\n mapping(address =\u003e TokenControlInfo) internal tokenControlInfo;\n\n struct TokenImbalanceData {\n int lastBlockBuyUnitsImbalance;\n uint lastBlock;\n\n int totalBuyUnitsImbalance;\n uint lastRateUpdateBlock;\n }\n\n mapping(address =\u003e mapping(uint=\u003euint)) public tokenImbalanceData;\n\n function VolumeImbalanceRecorder(address _admin) public {\n require(_admin != address(0));\n admin = _admin;\n }\n\n function setTokenControlInfo(\n ERC20 token,\n uint minimalRecordResolution,\n uint maxPerBlockImbalance,\n uint maxTotalImbalance\n )\n public\n onlyAdmin\n {\n tokenControlInfo[token] =\n TokenControlInfo(\n minimalRecordResolution,\n maxPerBlockImbalance,\n maxTotalImbalance\n );\n }\n\n function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) {\n return (tokenControlInfo[token].minimalRecordResolution,\n tokenControlInfo[token].maxPerBlockImbalance,\n tokenControlInfo[token].maxTotalImbalance);\n }\n\n function addImbalance(\n ERC20 token,\n int buyAmount,\n uint rateUpdateBlock,\n uint currentBlock\n )\n internal\n {\n uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE;\n int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution));\n\n int prevImbalance = 0;\n\n TokenImbalanceData memory currentBlockData =\n decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]);\n\n // first scenario - this is not the first tx in the current block\n if (currentBlockData.lastBlock == currentBlock) {\n if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) {\n // just increase imbalance\n currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;\n currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount;\n } else {\n // imbalance was changed in the middle of the block\n prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock);\n currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;\n currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;\n currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);\n }\n } else {\n // first tx in the current block\n int currentBlockImbalance;\n (prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock);\n\n currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount;\n currentBlockData.lastBlock = uint(currentBlock);\n currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);\n currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;\n }\n\n tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData);\n }\n\n function setGarbageToVolumeRecorder(ERC20 token) internal {\n for (uint i = 0; i \u003c SLIDING_WINDOW_SIZE; i++) {\n tokenImbalanceData[token][i] = 0x1;\n }\n }\n\n function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) {\n // check the imbalance in the sliding window\n require(startBlock \u003c= endBlock);\n\n buyImbalance = 0;\n\n for (uint windowInd = 0; windowInd \u003c SLIDING_WINDOW_SIZE; windowInd++) {\n TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);\n\n if (perBlockData.lastBlock \u003c= endBlock \u0026\u0026 perBlockData.lastBlock \u003e= startBlock) {\n buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance);\n }\n }\n }\n\n function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock)\n internal view\n returns(int buyImbalance, int currentBlockImbalance)\n {\n buyImbalance = 0;\n currentBlockImbalance = 0;\n uint latestBlock = 0;\n int imbalanceInRange = 0;\n uint startBlock = rateUpdateBlock;\n uint endBlock = currentBlock;\n\n for (uint windowInd = 0; windowInd \u003c SLIDING_WINDOW_SIZE; windowInd++) {\n TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);\n\n if (perBlockData.lastBlock \u003c= endBlock \u0026\u0026 perBlockData.lastBlock \u003e= startBlock) {\n imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance;\n }\n\n if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue;\n if (perBlockData.lastBlock \u003c latestBlock) continue;\n\n latestBlock = perBlockData.lastBlock;\n buyImbalance = perBlockData.totalBuyUnitsImbalance;\n if (uint(perBlockData.lastBlock) == currentBlock) {\n currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance;\n }\n }\n\n if (buyImbalance == 0) {\n buyImbalance = imbalanceInRange;\n }\n }\n\n function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock)\n internal view\n returns(int totalImbalance, int currentBlockImbalance)\n {\n\n int resolution = int(tokenControlInfo[token].minimalRecordResolution);\n\n (totalImbalance, currentBlockImbalance) =\n getImbalanceSinceRateUpdate(\n token,\n rateUpdateBlock,\n currentBlock);\n\n totalImbalance *= resolution;\n currentBlockImbalance *= resolution;\n }\n\n function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) {\n return tokenControlInfo[token].maxPerBlockImbalance;\n }\n\n function getMaxTotalImbalance(ERC20 token) internal view returns(uint) {\n return tokenControlInfo[token].maxTotalImbalance;\n }\n\n function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) {\n // check for overflows\n require(data.lastBlockBuyUnitsImbalance \u003c int(POW_2_64 / 2));\n require(data.lastBlockBuyUnitsImbalance \u003e int(-1 * int(POW_2_64) / 2));\n require(data.lastBlock \u003c POW_2_64);\n require(data.totalBuyUnitsImbalance \u003c int(POW_2_64 / 2));\n require(data.totalBuyUnitsImbalance \u003e int(-1 * int(POW_2_64) / 2));\n require(data.lastRateUpdateBlock \u003c POW_2_64);\n\n // do encoding\n uint result = uint(data.lastBlockBuyUnitsImbalance) \u0026 (POW_2_64 - 1);\n result |= data.lastBlock * POW_2_64;\n result |= (uint(data.totalBuyUnitsImbalance) \u0026 (POW_2_64 - 1)) * POW_2_64 * POW_2_64;\n result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64;\n\n return result;\n }\n\n function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) {\n TokenImbalanceData memory data;\n\n data.lastBlockBuyUnitsImbalance = int(int64(input \u0026 (POW_2_64 - 1)));\n data.lastBlock = uint(uint64((input / POW_2_64) \u0026 (POW_2_64 - 1)));\n data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) \u0026 (POW_2_64 - 1)));\n data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64))));\n\n return data;\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 13 of 13: SanityRates
{"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"},"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"},"SanityRates.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\nimport \"./Withdrawable.sol\";\nimport \"./Utils.sol\";\nimport \"./SanityRatesInterface.sol\";\n\n\ncontract SanityRates is SanityRatesInterface, Withdrawable, Utils {\n mapping(address=\u003euint) public tokenRate;\n mapping(address=\u003euint) public reasonableDiffInBps;\n\n function SanityRates(address _admin) public {\n require(_admin != address(0));\n admin = _admin;\n }\n\n function setReasonableDiff(ERC20[] srcs, uint[] diff) public onlyAdmin {\n require(srcs.length == diff.length);\n for (uint i = 0; i \u003c srcs.length; i++) {\n require(diff[i] \u003c= 100 * 100);\n reasonableDiffInBps[srcs[i]] = diff[i];\n }\n }\n\n function setSanityRates(ERC20[] srcs, uint[] rates) public onlyOperator {\n require(srcs.length == rates.length);\n\n for (uint i = 0; i \u003c srcs.length; i++) {\n require(rates[i] \u003c= MAX_RATE);\n tokenRate[srcs[i]] = rates[i];\n }\n }\n\n function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint) {\n if (src != ETH_TOKEN_ADDRESS \u0026\u0026 dest != ETH_TOKEN_ADDRESS) return 0;\n\n uint rate;\n address token;\n if (src == ETH_TOKEN_ADDRESS) {\n rate = (PRECISION*PRECISION)/tokenRate[dest];\n token = dest;\n } else {\n rate = tokenRate[src];\n token = src;\n }\n\n return rate * (10000 + reasonableDiffInBps[token])/10000;\n }\n}\n"},"SanityRatesInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\ninterface SanityRatesInterface {\n function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);\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"},"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"}}