Transaction Hash:
Block:
7512938 at Apr-06-2019 06:23:08 AM +UTC
Transaction Fee:
0.000192993 ETH
$0.48
Gas Used:
192,993 Gas / 1 Gwei
Emitted Events:
64 |
PayToken.Transfer( from=[Sender] 0xa85e728d5d08b2af397fb30d19b83d2bcc950fae, to=KyberNetwork, value=35000000000000000000 )
|
65 |
PayToken.Transfer( from=KyberNetwork, to=KyberReserve, value=35000000000000000000 )
|
66 |
KyberNetwork.EtherReceival( sender=KyberReserve, amount=70722143507513645 )
|
67 |
KyberReserve.TradeExecute( origin=KyberNetwork, src=PayToken, srcAmount=35000000000000000000, destToken=0xEeeeeEee...eeeeeEEeE, destAmount=70722143507513645, destAddress=KyberNetwork )
|
68 |
FeeBurner.AssignFeeToWallet( reserve=KyberReserve, wallet=0x440bBd6a...16874faa9, walletFee=31255914812663352 )
|
69 |
FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=72930467896214490 )
|
70 |
KyberNetwork.KyberTrade( trader=[Sender] 0xa85e728d5d08b2af397fb30d19b83d2bcc950fae, src=PayToken, dest=0xEeeeeEee...eeeeeEEeE, srcAmount=35000000000000000000, dstAmount=70722143507513645, destAddress=[Sender] 0xa85e728d5d08b2af397fb30d19b83d2bcc950fae, ethWeiValue=70722143507513645, reserve1=KyberReserve, reserve2=0x00000000...000000000, hint=0x5045524D )
|
71 |
KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0xa85e728d5d08b2af397fb30d19b83d2bcc950fae, src=PayToken, dest=0xEeeeeEee...eeeeeEEeE, actualSrcAmount=35000000000000000000, actualDestAmount=70722143507513645 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x52166528...1a4e128A3 | |||||
0x63825c17...bD36A0D8F | (Kyber: Reserve) | 1,347.516756420030462579 Eth | 1,347.446034276522948934 Eth | 0.070722143507513645 | |
0x798AbDA6...d3d11191B | (Kyber: Conversion Rates) | ||||
0x9ae49C0d...294E20950 | (Kyber: Old Contract) | ||||
0xA85e728d...bcC950Fae |
26.50573151481691918 Eth
Nonce: 14
|
26.576260665324432825 Eth
Nonce: 15
| 0.070529150507513645 | ||
0xB9704862...1A905B280 | |||||
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 614.143463693566629993 Eth | 614.143656686566629993 Eth | 0.000192993 |
Execution Trace
KyberNetworkProxy.tradeWithHint( src=0xB97048628DB6B661D4C2aA833e95Dbe1A905B280, srcAmount=35000000000000000000, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0xA85e728d5d08B2aF397fB30D19b83D2bcC950Fae, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=1936341744788361, walletId=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, hint=0x5045524D ) => ( 70722143507513645 )
-
PayToken.balanceOf( _owner=0xA85e728d5d08B2aF397fB30D19b83D2bcC950Fae ) => ( balance=35000000000000000000 )
-
PayToken.transferFrom( _from=0xA85e728d5d08B2aF397fB30D19b83D2bcC950Fae, _to=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, _value=35000000000000000000 )
KyberNetwork.tradeWithHint( trader=0xA85e728d5d08B2aF397fB30D19b83D2bcC950Fae, src=0xB97048628DB6B661D4C2aA833e95Dbe1A905B280, srcAmount=35000000000000000000, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0xA85e728d5d08B2aF397fB30D19b83D2bcC950Fae, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=1936341744788361, walletId=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, hint=0x5045524D ) => ( 70722143507513645 )
-
PayToken.balanceOf( _owner=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950 ) => ( balance=35000000000000000000 )
KyberReserve.getConversionRate( src=0xB97048628DB6B661D4C2aA833e95Dbe1A905B280, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=35000000000000000000, blockNumber=7512938 ) => ( 2020632671643247 )
-
ConversionRates.getRate( token=0xB97048628DB6B661D4C2aA833e95Dbe1A905B280, currentBlockNumber=7512938, buy=False, qty=35000000000000000000 ) => ( 2020632671643247 )
-
SanityRates.getSanityRate( src=0xB97048628DB6B661D4C2aA833e95Dbe1A905B280, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ) => ( 2187108000000000 )
-
KyberReserve.trade( srcToken=0xB97048628DB6B661D4C2aA833e95Dbe1A905B280, srcAmount=35000000000000000000, destToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, conversionRate=2020632671643247, validate=True ) => ( True )
-
ConversionRates.recordImbalance( token=0xB97048628DB6B661D4C2aA833e95Dbe1A905B280, buyAmount=-35000000000000000000, rateUpdateBlock=0, currentBlock=7512938 )
-
PayToken.transferFrom( _from=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, _to=0x63825c174ab367968EC60f061753D3bbD36A0D8F, _value=35000000000000000000 )
- ETH 0.070722143507513645
KyberNetwork.CALL( )
-
- ETH 0.070722143507513645
0xa85e728d5d08b2af397fb30d19b83d2bcc950fae.CALL( )
FeeBurner.handleFees( tradeWeiAmount=70722143507513645, reserve=0x63825c174ab367968EC60f061753D3bbD36A0D8F, wallet=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9 ) => ( True )
-
KyberNetworkCrystal.CALL( )
-
-
-
PayToken.balanceOf( _owner=0xA85e728d5d08B2aF397fB30D19b83D2bcC950Fae ) => ( balance=0 )
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 8: KyberNetworkProxy
File 2 of 8: KyberNetwork
File 3 of 8: PayToken
File 4 of 8: KyberReserve
File 5 of 8: FeeBurner
File 6 of 8: ConversionRates
File 7 of 8: SanityRates
File 8 of 8: KyberNetworkCrystal
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 8: KyberNetwork
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/KyberReserveInterface.sol /// @title Kyber Reserve contract 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); } // 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/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/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/WhiteListInterface.sol contract WhiteListInterface { function getUserCapInWei(address user) external view returns (uint userCapWei); } // File: contracts/ExpectedRateInterface.sol interface ExpectedRateInterface { function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty, bool usePermissionless) public view returns (uint expectedRate, uint slippageRate); } // File: contracts/FeeBurnerInterface.sol interface FeeBurnerInterface { function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool); function setReserveData(address reserve, uint feesInBps, address kncWallet) public; } // File: contracts/KyberNetwork.sol /** * @title Helps contracts guard against reentrancy attacks. */ contract ReentrancyGuard { /// @dev counter to allow mutex lock with only one SSTORE operation uint256 private guardCounter = 1; /** * @dev Prevents a function from calling itself, directly or indirectly. * Calling one `nonReentrant` function from * another is not supported. Instead, you can implement a * `private` function doing the actual work, and an `external` * wrapper marked as `nonReentrant`. */ modifier nonReentrant() { guardCounter += 1; uint256 localCounter = guardCounter; _; require(localCounter == guardCounter); } } //////////////////////////////////////////////////////////////////////////////////////////////////////// /// @title Kyber Network main contract contract KyberNetwork is Withdrawable, Utils2, KyberNetworkInterface, ReentrancyGuard { bytes public constant PERM_HINT = "PERM"; uint public constant PERM_HINT_GET_RATE = 1 << 255; // for get rate. bit mask hint. uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01% KyberReserveInterface[] public reserves; mapping(address=>ReserveType) public reserveType; WhiteListInterface public whiteListContract; ExpectedRateInterface public expectedRateContract; FeeBurnerInterface public feeBurnerContract; address public kyberNetworkProxyContract; uint public maxGasPriceValue = 50 * 1000 * 1000 * 1000; // 50 gwei bool public isEnabled = false; // network is enabled mapping(bytes32=>uint) public infoFields; // this is only a UI field for external app. mapping(address=>address[]) public reservesPerTokenSrc; //reserves supporting token to eth mapping(address=>address[]) public reservesPerTokenDest;//reserves support eth to token enum ReserveType {NONE, PERMISSIONED, PERMISSIONLESS} bytes internal constant EMPTY_HINT = ""; function KyberNetwork(address _admin) public { require(_admin != address(0)); admin = _admin; } event EtherReceival(address indexed sender, uint amount); /* solhint-disable no-complex-fallback */ // To avoid users trying to swap tokens using default payable function. We added this short code // to verify Ethers will be received only from reserves if transferred without a specific function call. function() public payable { require(reserveType[msg.sender] != ReserveType.NONE); EtherReceival(msg.sender, msg.value); } /* solhint-enable no-complex-fallback */ struct TradeInput { address trader; ERC20 src; uint srcAmount; ERC20 dest; address destAddress; uint maxDestAmount; uint minConversionRate; address walletId; bytes hint; } function tradeWithHint( address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress, uint maxDestAmount, uint minConversionRate, address walletId, bytes hint ) public nonReentrant payable returns(uint) { require(msg.sender == kyberNetworkProxyContract); require((hint.length == 0) || (hint.length == 4)); TradeInput memory tradeInput; tradeInput.trader = trader; tradeInput.src = src; tradeInput.srcAmount = srcAmount; tradeInput.dest = dest; tradeInput.destAddress = destAddress; tradeInput.maxDestAmount = maxDestAmount; tradeInput.minConversionRate = minConversionRate; tradeInput.walletId = walletId; tradeInput.hint = hint; return trade(tradeInput); } event AddReserveToNetwork(KyberReserveInterface indexed reserve, bool add, bool isPermissionless); /// @notice can be called only by operator /// @dev add or deletes a reserve to/from the network. /// @param reserve The reserve address. /// @param isPermissionless is the new reserve from permissionless type. function addReserve(KyberReserveInterface reserve, bool isPermissionless) public onlyOperator returns(bool) { require(reserveType[reserve] == ReserveType.NONE); reserves.push(reserve); reserveType[reserve] = isPermissionless ? ReserveType.PERMISSIONLESS : ReserveType.PERMISSIONED; AddReserveToNetwork(reserve, true, isPermissionless); return true; } event RemoveReserveFromNetwork(KyberReserveInterface reserve); /// @notice can be called only by operator /// @dev removes a reserve from Kyber network. /// @param reserve The reserve address. /// @param index in reserve array. function removeReserve(KyberReserveInterface reserve, uint index) public onlyOperator returns(bool) { require(reserveType[reserve] != ReserveType.NONE); require(reserves[index] == reserve); reserveType[reserve] = ReserveType.NONE; reserves[index] = reserves[reserves.length - 1]; reserves.length--; RemoveReserveFromNetwork(reserve); return true; } event ListReservePairs(address indexed reserve, ERC20 src, ERC20 dest, bool add); /// @notice can be called only by operator /// @dev allow or prevent a specific reserve to trade a pair of tokens /// @param reserve The reserve address. /// @param token token address /// @param ethToToken will it support ether to token trade /// @param tokenToEth will it support token to ether trade /// @param add If true then list this pair, otherwise unlist it. function listPairForReserve(address reserve, ERC20 token, bool ethToToken, bool tokenToEth, bool add) public onlyOperator returns(bool) { require(reserveType[reserve] != ReserveType.NONE); if (ethToToken) { listPairs(reserve, token, false, add); ListReservePairs(reserve, ETH_TOKEN_ADDRESS, token, add); } if (tokenToEth) { listPairs(reserve, token, true, add); if (add) { require(token.approve(reserve, 2**255)); // approve infinity } else { require(token.approve(reserve, 0)); } ListReservePairs(reserve, token, ETH_TOKEN_ADDRESS, add); } setDecimals(token); return true; } event WhiteListContractSet(WhiteListInterface newContract, WhiteListInterface currentContract); ///@param whiteList can be empty function setWhiteList(WhiteListInterface whiteList) public onlyAdmin { WhiteListContractSet(whiteList, whiteListContract); whiteListContract = whiteList; } event ExpectedRateContractSet(ExpectedRateInterface newContract, ExpectedRateInterface currentContract); function setExpectedRate(ExpectedRateInterface expectedRate) public onlyAdmin { require(expectedRate != address(0)); ExpectedRateContractSet(expectedRate, expectedRateContract); expectedRateContract = expectedRate; } event FeeBurnerContractSet(FeeBurnerInterface newContract, FeeBurnerInterface currentContract); function setFeeBurner(FeeBurnerInterface feeBurner) public onlyAdmin { require(feeBurner != address(0)); FeeBurnerContractSet(feeBurner, feeBurnerContract); feeBurnerContract = feeBurner; } event KyberNetwrokParamsSet(uint maxGasPrice, uint negligibleRateDiff); function setParams( uint _maxGasPrice, uint _negligibleRateDiff ) public onlyAdmin { require(_negligibleRateDiff <= 100 * 100); // at most 100% maxGasPriceValue = _maxGasPrice; negligibleRateDiff = _negligibleRateDiff; KyberNetwrokParamsSet(maxGasPriceValue, negligibleRateDiff); } event KyberNetworkSetEnable(bool isEnabled); function setEnable(bool _enable) public onlyAdmin { if (_enable) { require(feeBurnerContract != address(0)); require(expectedRateContract != address(0)); require(kyberNetworkProxyContract != address(0)); } isEnabled = _enable; KyberNetworkSetEnable(isEnabled); } function setInfo(bytes32 field, uint value) public onlyOperator { infoFields[field] = value; } event KyberProxySet(address proxy, address sender); function setKyberProxy(address networkProxy) public onlyAdmin { require(networkProxy != address(0)); kyberNetworkProxyContract = networkProxy; KyberProxySet(kyberNetworkProxyContract, msg.sender); } /// @dev returns number of reserves /// @return number of reserves function getNumReserves() public view returns(uint) { return reserves.length; } /// @notice should be called off chain /// @dev get an array of all reserves /// @return An array of all reserves function getReserves() public view returns(KyberReserveInterface[]) { return reserves; } function maxGasPrice() public view returns(uint) { return maxGasPriceValue; } function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view returns(uint expectedRate, uint slippageRate) { require(expectedRateContract != address(0)); bool includePermissionless = true; if (srcQty & PERM_HINT_GET_RATE > 0) { includePermissionless = false; srcQty = srcQty & ~PERM_HINT_GET_RATE; } return expectedRateContract.getExpectedRate(src, dest, srcQty, includePermissionless); } function getExpectedRateOnlyPermission(ERC20 src, ERC20 dest, uint srcQty) public view returns(uint expectedRate, uint slippageRate) { require(expectedRateContract != address(0)); return expectedRateContract.getExpectedRate(src, dest, srcQty, false); } function getUserCapInWei(address user) public view returns(uint) { if (whiteListContract == address(0)) return (2 ** 255); return whiteListContract.getUserCapInWei(user); } function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint) { //future feature user; token; require(false); } struct BestRateResult { uint rate; address reserve1; address reserve2; uint weiAmount; uint rateSrcToEth; uint rateEthToDest; uint destAmount; } /// @notice use token address ETH_TOKEN_ADDRESS for ether /// @dev best conversion rate for a pair of tokens, if number of reserves have small differences. randomize /// @param src Src token /// @param dest Destination token /// @return obsolete - used to return best reserve index. not relevant anymore for this API. function findBestRate(ERC20 src, ERC20 dest, uint srcAmount) public view returns(uint obsolete, uint rate) { BestRateResult memory result = findBestRateTokenToToken(src, dest, srcAmount, EMPTY_HINT); return(0, result.rate); } function findBestRateOnlyPermission(ERC20 src, ERC20 dest, uint srcAmount) public view returns(uint obsolete, uint rate) { BestRateResult memory result = findBestRateTokenToToken(src, dest, srcAmount, PERM_HINT); return(0, result.rate); } function enabled() public view returns(bool) { return isEnabled; } function info(bytes32 field) public view returns(uint) { return infoFields[field]; } /* solhint-disable code-complexity */ // Regarding complexity. Below code follows the required algorithm for choosing a reserve. // It has been tested, reviewed and found to be clear enough. //@dev this function always src or dest are ether. can't do token to token function searchBestRate(ERC20 src, ERC20 dest, uint srcAmount, bool usePermissionless) public view returns(address, uint) { uint bestRate = 0; uint bestReserve = 0; uint numRelevantReserves = 0; //return 1 for ether to ether if (src == dest) return (reserves[bestReserve], PRECISION); address[] memory reserveArr; reserveArr = src == ETH_TOKEN_ADDRESS ? reservesPerTokenDest[dest] : reservesPerTokenSrc[src]; if (reserveArr.length == 0) return (reserves[bestReserve], bestRate); uint[] memory rates = new uint[](reserveArr.length); uint[] memory reserveCandidates = new uint[](reserveArr.length); for (uint i = 0; i < reserveArr.length; i++) { //list all reserves that have this token. if (!usePermissionless && reserveType[reserveArr[i]] == ReserveType.PERMISSIONLESS) { continue; } rates[i] = (KyberReserveInterface(reserveArr[i])).getConversionRate(src, dest, srcAmount, block.number); if (rates[i] > bestRate) { //best rate is highest rate bestRate = rates[i]; } } if (bestRate > 0) { uint smallestRelevantRate = (bestRate * 10000) / (10000 + negligibleRateDiff); for (i = 0; i < reserveArr.length; i++) { if (rates[i] >= smallestRelevantRate) { reserveCandidates[numRelevantReserves++] = i; } } if (numRelevantReserves > 1) { //when encountering small rate diff from bestRate. draw from relevant reserves bestReserve = reserveCandidates[uint(block.blockhash(block.number-1)) % numRelevantReserves]; } else { bestReserve = reserveCandidates[0]; } bestRate = rates[bestReserve]; } return (reserveArr[bestReserve], bestRate); } /* solhint-enable code-complexity */ function findBestRateTokenToToken(ERC20 src, ERC20 dest, uint srcAmount, bytes hint) internal view returns(BestRateResult result) { //by default we use permission less reserves bool usePermissionless = true; // if hint in first 4 bytes == 'PERM' only permissioned reserves will be used. if ((hint.length >= 4) && (keccak256(hint[0], hint[1], hint[2], hint[3]) == keccak256(PERM_HINT))) { usePermissionless = false; } (result.reserve1, result.rateSrcToEth) = searchBestRate(src, ETH_TOKEN_ADDRESS, srcAmount, usePermissionless); result.weiAmount = calcDestAmount(src, ETH_TOKEN_ADDRESS, srcAmount, result.rateSrcToEth); (result.reserve2, result.rateEthToDest) = searchBestRate(ETH_TOKEN_ADDRESS, dest, result.weiAmount, usePermissionless); result.destAmount = calcDestAmount(ETH_TOKEN_ADDRESS, dest, result.weiAmount, result.rateEthToDest); result.rate = calcRateFromQty(srcAmount, result.destAmount, getDecimals(src), getDecimals(dest)); } function listPairs(address reserve, ERC20 token, bool isTokenToEth, bool add) internal { uint i; address[] storage reserveArr = reservesPerTokenDest[token]; if (isTokenToEth) { reserveArr = reservesPerTokenSrc[token]; } for (i = 0; i < reserveArr.length; i++) { if (reserve == reserveArr[i]) { if (add) { break; //already added } else { //remove reserveArr[i] = reserveArr[reserveArr.length - 1]; reserveArr.length--; break; } } } if (add && i == reserveArr.length) { //if reserve wasn't found add it reserveArr.push(reserve); } } event KyberTrade(address indexed trader, ERC20 src, ERC20 dest, uint srcAmount, uint dstAmount, address destAddress, uint ethWeiValue, address reserve1, address reserve2, bytes hint); /* solhint-disable function-max-lines */ // Most of the lines here are functions calls spread over multiple lines. We find this function readable enough /// @notice use token address ETH_TOKEN_ADDRESS for ether /// @dev trade api for kyber network. /// @param tradeInput structure of trade inputs function trade(TradeInput tradeInput) internal returns(uint) { require(isEnabled); require(tx.gasprice <= maxGasPriceValue); require(validateTradeInput(tradeInput.src, tradeInput.srcAmount, tradeInput.dest, tradeInput.destAddress)); BestRateResult memory rateResult = findBestRateTokenToToken(tradeInput.src, tradeInput.dest, tradeInput.srcAmount, tradeInput.hint); require(rateResult.rate > 0); require(rateResult.rate < MAX_RATE); require(rateResult.rate >= tradeInput.minConversionRate); uint actualDestAmount; uint weiAmount; uint actualSrcAmount; (actualSrcAmount, weiAmount, actualDestAmount) = calcActualAmounts(tradeInput.src, tradeInput.dest, tradeInput.srcAmount, tradeInput.maxDestAmount, rateResult); require(getUserCapInWei(tradeInput.trader) >= weiAmount); require(handleChange(tradeInput.src, tradeInput.srcAmount, actualSrcAmount, tradeInput.trader)); require(doReserveTrade( //src to ETH tradeInput.src, actualSrcAmount, ETH_TOKEN_ADDRESS, this, weiAmount, KyberReserveInterface(rateResult.reserve1), rateResult.rateSrcToEth, true)); require(doReserveTrade( //Eth to dest ETH_TOKEN_ADDRESS, weiAmount, tradeInput.dest, tradeInput.destAddress, actualDestAmount, KyberReserveInterface(rateResult.reserve2), rateResult.rateEthToDest, true)); if (tradeInput.src != ETH_TOKEN_ADDRESS) //"fake" trade. (ether to ether) - don't burn. require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve1, tradeInput.walletId)); if (tradeInput.dest != ETH_TOKEN_ADDRESS) //"fake" trade. (ether to ether) - don't burn. require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve2, tradeInput.walletId)); KyberTrade({ trader: tradeInput.trader, src: tradeInput.src, dest: tradeInput.dest, srcAmount: actualSrcAmount, dstAmount: actualDestAmount, destAddress: tradeInput.destAddress, ethWeiValue: weiAmount, reserve1: (tradeInput.src == ETH_TOKEN_ADDRESS) ? address(0) : rateResult.reserve1, reserve2: (tradeInput.dest == ETH_TOKEN_ADDRESS) ? address(0) : rateResult.reserve2, hint: tradeInput.hint }); return actualDestAmount; } /* solhint-enable function-max-lines */ function calcActualAmounts (ERC20 src, ERC20 dest, uint srcAmount, uint maxDestAmount, BestRateResult rateResult) internal view returns(uint actualSrcAmount, uint weiAmount, uint actualDestAmount) { if (rateResult.destAmount > maxDestAmount) { actualDestAmount = maxDestAmount; weiAmount = calcSrcAmount(ETH_TOKEN_ADDRESS, dest, actualDestAmount, rateResult.rateEthToDest); actualSrcAmount = calcSrcAmount(src, ETH_TOKEN_ADDRESS, weiAmount, rateResult.rateSrcToEth); require(actualSrcAmount <= srcAmount); } else { actualDestAmount = rateResult.destAmount; actualSrcAmount = srcAmount; weiAmount = rateResult.weiAmount; } } /// @notice use token address ETH_TOKEN_ADDRESS for ether /// @dev do one trade with a reserve /// @param src Src token /// @param amount amount of src tokens /// @param dest Destination token /// @param destAddress Address to send tokens to /// @param reserve Reserve to use /// @param validate If true, additional validations are applicable /// @return true if trade is successful function doReserveTrade( ERC20 src, uint amount, ERC20 dest, address destAddress, uint expectedDestAmount, KyberReserveInterface reserve, uint conversionRate, bool validate ) internal returns(bool) { uint callValue = 0; if (src == dest) { //this is for a "fake" trade when both src and dest are ethers. if (destAddress != (address(this))) destAddress.transfer(amount); return true; } if (src == ETH_TOKEN_ADDRESS) { callValue = amount; } // reserve sends tokens/eth to network. network sends it to destination require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, validate)); if (destAddress != address(this)) { //for token to token dest address is network. and Ether / token already here... if (dest == ETH_TOKEN_ADDRESS) { destAddress.transfer(expectedDestAmount); } else { require(dest.transfer(destAddress, expectedDestAmount)); } } return true; } /// when user sets max dest amount we could have too many source tokens == change. so we send it back to user. function handleChange (ERC20 src, uint srcAmount, uint requiredSrcAmount, address trader) internal returns (bool) { if (requiredSrcAmount < srcAmount) { //if there is "change" send back to trader if (src == ETH_TOKEN_ADDRESS) { trader.transfer(srcAmount - requiredSrcAmount); } else { require(src.transfer(trader, (srcAmount - requiredSrcAmount))); } } return true; } /// @notice use token address ETH_TOKEN_ADDRESS for ether /// @dev checks that user sent ether/tokens to contract before trade /// @param src Src token /// @param srcAmount amount of src tokens /// @return true if tradeInput is valid function validateTradeInput(ERC20 src, uint srcAmount, ERC20 dest, address destAddress) internal view returns(bool) { require(srcAmount <= MAX_QTY); require(srcAmount != 0); require(destAddress != address(0)); require(src != dest); if (src == ETH_TOKEN_ADDRESS) { require(msg.value == srcAmount); } else { require(msg.value == 0); //funds should have been moved to this contract already. require(src.balanceOf(this) >= srcAmount); } return true; } }
File 3 of 8: PayToken
pragma solidity 0.4.11; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { if (msg.sender != owner) { throw; } _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) onlyOwner { if (newOwner != address(0)) { owner = newOwner; } } } /** * @title Authorizable * @dev Allows to authorize access to certain function calls * * ABI * [{"constant":true,"inputs":[{"name":"authorizerIndex","type":"uint256"}],"name":"getAuthorizer","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"addAuthorized","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"isAuthorized","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"}] */ contract Authorizable { address[] authorizers; mapping(address => uint) authorizerIndex; /** * @dev Throws if called by any account tat is not authorized. */ modifier onlyAuthorized { require(isAuthorized(msg.sender)); _; } /** * @dev Contructor that authorizes the msg.sender. */ function Authorizable() { authorizers.length = 2; authorizers[1] = msg.sender; authorizerIndex[msg.sender] = 1; } /** * @dev Function to get a specific authorizer * @param authorizerIndex index of the authorizer to be retrieved. * @return The address of the authorizer. */ function getAuthorizer(uint authorizerIndex) external constant returns(address) { return address(authorizers[authorizerIndex + 1]); } /** * @dev Function to check if an address is authorized * @param _addr the address to check if it is authorized. * @return boolean flag if address is authorized. */ function isAuthorized(address _addr) constant returns(bool) { return authorizerIndex[_addr] > 0; } /** * @dev Function to add a new authorizer * @param _addr the address to add as a new authorizer. */ function addAuthorized(address _addr) external onlyAuthorized { authorizerIndex[_addr] = authorizers.length; authorizers.length++; authorizers[authorizers.length - 1] = _addr; } } /** * @title ExchangeRate * @dev Allows updating and retrieveing of Conversion Rates for PAY tokens * * ABI * [{"constant":false,"inputs":[{"name":"_symbol","type":"string"},{"name":"_rate","type":"uint256"}],"name":"updateRate","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"data","type":"uint256[]"}],"name":"updateRates","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_symbol","type":"string"}],"name":"getRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"rates","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"timestamp","type":"uint256"},{"indexed":false,"name":"symbol","type":"bytes32"},{"indexed":false,"name":"rate","type":"uint256"}],"name":"RateUpdated","type":"event"}] */ contract ExchangeRate is Ownable { event RateUpdated(uint timestamp, bytes32 symbol, uint rate); mapping(bytes32 => uint) public rates; /** * @dev Allows the current owner to update a single rate. * @param _symbol The symbol to be updated. * @param _rate the rate for the symbol. */ function updateRate(string _symbol, uint _rate) public onlyOwner { rates[sha3(_symbol)] = _rate; RateUpdated(now, sha3(_symbol), _rate); } /** * @dev Allows the current owner to update multiple rates. * @param data an array that alternates sha3 hashes of the symbol and the corresponding rate . */ function updateRates(uint[] data) public onlyOwner { if (data.length % 2 > 0) throw; uint i = 0; while (i < data.length / 2) { bytes32 symbol = bytes32(data[i * 2]); uint rate = data[i * 2 + 1]; rates[symbol] = rate; RateUpdated(now, symbol, rate); i++; } } /** * @dev Allows the anyone to read the current rate. * @param _symbol the symbol to be retrieved. */ function getRate(string _symbol) public constant returns(uint) { return rates[sha3(_symbol)]; } } /** * Math operations with safety checks */ library SafeMath { function mul(uint a, uint b) internal returns (uint) { uint c = a * b; assert(a == 0 || c / a == b); return c; } function div(uint a, uint b) internal returns (uint) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function sub(uint a, uint b) internal returns (uint) { assert(b <= a); return a - b; } function add(uint a, uint b) internal returns (uint) { uint c = a + b; assert(c >= a); return c; } function max64(uint64 a, uint64 b) internal constant returns (uint64) { return a >= b ? a : b; } function min64(uint64 a, uint64 b) internal constant returns (uint64) { return a < b ? a : b; } function max256(uint256 a, uint256 b) internal constant returns (uint256) { return a >= b ? a : b; } function min256(uint256 a, uint256 b) internal constant returns (uint256) { return a < b ? a : b; } function assert(bool assertion) internal { if (!assertion) { throw; } } } /** * @title ERC20Basic * @dev Simpler version of ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20Basic { uint public totalSupply; function balanceOf(address who) constant returns (uint); function transfer(address to, uint value); event Transfer(address indexed from, address indexed to, uint value); } /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20 is ERC20Basic { function allowance(address owner, address spender) constant returns (uint); function transferFrom(address from, address to, uint value); function approve(address spender, uint value); event Approval(address indexed owner, address indexed spender, uint value); } /** * @title Basic token * @dev Basic version of StandardToken, with no allowances. */ contract BasicToken is ERC20Basic { using SafeMath for uint; mapping(address => uint) balances; /** * @dev Fix for the ERC20 short address attack. */ modifier onlyPayloadSize(uint size) { if(msg.data.length < size + 4) { throw; } _; } /** * @dev transfer token for a specified address * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint _value) onlyPayloadSize(2 * 32) { balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); Transfer(msg.sender, _to, _value); } /** * @dev Gets the balance of the specified address. * @param _owner The address to query the the balance of. * @return An uint representing the amount owned by the passed address. */ function balanceOf(address _owner) constant returns (uint balance) { return balances[_owner]; } } /** * @title Standard ERC20 token * * @dev Implemantation of the basic standart token. * @dev https://github.com/ethereum/EIPs/issues/20 * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol */ contract StandardToken is BasicToken, ERC20 { mapping (address => mapping (address => uint)) allowed; /** * @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 uint the amout of tokens to be transfered */ function transferFrom(address _from, address _to, uint _value) onlyPayloadSize(3 * 32) { var _allowance = allowed[_from][msg.sender]; // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met // if (_value > _allowance) throw; balances[_to] = balances[_to].add(_value); balances[_from] = balances[_from].sub(_value); allowed[_from][msg.sender] = _allowance.sub(_value); Transfer(_from, _to, _value); } /** * @dev Aprove the passed address to spend the specified amount of tokens on beahlf of msg.sender. * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint _value) { // To change the approve amount you first have to reduce the addresses` // allowance to zero by calling `approve(_spender, 0)` if it is not // already 0 to mitigate the race condition described here: // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) throw; allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); } /** * @dev Function to check the amount of tokens than 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 uint specifing the amount of tokens still avaible for the spender. */ function allowance(address _owner, address _spender) constant returns (uint remaining) { return allowed[_owner][_spender]; } } /** * @title Mintable token * @dev Simple ERC20 Token example, with mintable token creation * @dev Issue: * https://github.com/OpenZeppelin/zeppelin-solidity/issues/120 * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol */ contract MintableToken is StandardToken, Ownable { event Mint(address indexed to, uint value); event MintFinished(); bool public mintingFinished = false; uint public totalSupply = 0; modifier canMint() { if(mintingFinished) throw; _; } /** * @dev Function to mint tokens * @param _to The address that will recieve the minted tokens. * @param _amount The amount of tokens to mint. * @return A boolean that indicates if the operation was successful. */ function mint(address _to, uint _amount) onlyOwner canMint returns (bool) { totalSupply = totalSupply.add(_amount); balances[_to] = balances[_to].add(_amount); Mint(_to, _amount); return true; } /** * @dev Function to stop minting new tokens. * @return True if the operation was successful. */ function finishMinting() onlyOwner returns (bool) { mintingFinished = true; MintFinished(); return true; } } /** * @title PayToken * @dev The main PAY token contract * * ABI * [{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"startTrading","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"tradingStarted","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] */ contract PayToken is MintableToken { string public name = "TenX Pay Token"; string public symbol = "PAY"; uint public decimals = 18; bool public tradingStarted = false; /** * @dev modifier that throws if trading has not started yet */ modifier hasStartedTrading() { require(tradingStarted); _; } /** * @dev Allows the owner to enable the trading. This can not be undone */ function startTrading() onlyOwner { tradingStarted = true; } /** * @dev Allows anyone to transfer the PAY tokens once trading has started * @param _to the recipient address of the tokens. * @param _value number of tokens to be transfered. */ function transfer(address _to, uint _value) hasStartedTrading { super.transfer(_to, _value); } /** * @dev Allows anyone to transfer the PAY tokens once trading has started * @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 uint the amout of tokens to be transfered */ function transferFrom(address _from, address _to, uint _value) hasStartedTrading { super.transferFrom(_from, _to, _value); } } /** * @title MainSale * @dev The main PAY token sale contract * * ABI * [{"constant":false,"inputs":[{"name":"_multisigVault","type":"address"}],"name":"setMultisigVault","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"authorizerIndex","type":"uint256"}],"name":"getAuthorizer","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"exchangeRate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"altDeposits","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"recipient","type":"address"},{"name":"tokens","type":"uint256"}],"name":"authorizedCreateTokens","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_exchangeRate","type":"address"}],"name":"setExchangeRate","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"retrieveTokens","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"totalAltDeposits","type":"uint256"}],"name":"setAltDeposit","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"start","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"recipient","type":"address"}],"name":"createTokens","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"addAuthorized","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"multisigVault","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hardcap","type":"uint256"}],"name":"setHardCap","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_start","type":"uint256"}],"name":"setStart","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"isAuthorized","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"payable":true,"type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"recipient","type":"address"},{"indexed":false,"name":"ether_amount","type":"uint256"},{"indexed":false,"name":"pay_amount","type":"uint256"},{"indexed":false,"name":"exchangerate","type":"uint256"}],"name":"TokenSold","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"recipient","type":"address"},{"indexed":false,"name":"pay_amount","type":"uint256"}],"name":"AuthorizedCreate","type":"event"},{"anonymous":false,"inputs":[],"name":"MainSaleClosed","type":"event"}] */ contract MainSale is Ownable, Authorizable { using SafeMath for uint; event TokenSold(address recipient, uint ether_amount, uint pay_amount, uint exchangerate); event AuthorizedCreate(address recipient, uint pay_amount); event MainSaleClosed(); PayToken public token = new PayToken(); address public multisigVault; uint hardcap = 200000 ether; ExchangeRate public exchangeRate; uint public altDeposits = 0; uint public start = 1498302000; //new Date("Jun 24 2017 11:00:00 GMT").getTime() / 1000 /** * @dev modifier to allow token creation only when the sale IS ON */ modifier saleIsOn() { require(now > start && now < start + 28 days); _; } /** * @dev modifier to allow token creation only when the hardcap has not been reached */ modifier isUnderHardCap() { require(multisigVault.balance + altDeposits <= hardcap); _; } /** * @dev Allows anyone to create tokens by depositing ether. * @param recipient the recipient to receive tokens. */ function createTokens(address recipient) public isUnderHardCap saleIsOn payable { uint rate = exchangeRate.getRate("ETH"); uint tokens = rate.mul(msg.value).div(1 ether); token.mint(recipient, tokens); require(multisigVault.send(msg.value)); TokenSold(recipient, msg.value, tokens, rate); } /** * @dev Allows to set the toal alt deposit measured in ETH to make sure the hardcap includes other deposits * @param totalAltDeposits total amount ETH equivalent */ function setAltDeposit(uint totalAltDeposits) public onlyOwner { altDeposits = totalAltDeposits; } /** * @dev Allows authorized acces to create tokens. This is used for Bitcoin and ERC20 deposits * @param recipient the recipient to receive tokens. * @param tokens number of tokens to be created. */ function authorizedCreateTokens(address recipient, uint tokens) public onlyAuthorized { token.mint(recipient, tokens); AuthorizedCreate(recipient, tokens); } /** * @dev Allows the owner to set the hardcap. * @param _hardcap the new hardcap */ function setHardCap(uint _hardcap) public onlyOwner { hardcap = _hardcap; } /** * @dev Allows the owner to set the starting time. * @param _start the new _start */ function setStart(uint _start) public onlyOwner { start = _start; } /** * @dev Allows the owner to set the multisig contract. * @param _multisigVault the multisig contract address */ function setMultisigVault(address _multisigVault) public onlyOwner { if (_multisigVault != address(0)) { multisigVault = _multisigVault; } } /** * @dev Allows the owner to set the exchangerate contract. * @param _exchangeRate the exchangerate address */ function setExchangeRate(address _exchangeRate) public onlyOwner { exchangeRate = ExchangeRate(_exchangeRate); } /** * @dev Allows the owner to finish the minting. This will create the * restricted tokens and then close the minting. * Then the ownership of the PAY token contract is transfered * to this owner. */ function finishMinting() public onlyOwner { uint issuedTokenSupply = token.totalSupply(); uint restrictedTokens = issuedTokenSupply.mul(49).div(51); token.mint(multisigVault, restrictedTokens); token.finishMinting(); token.transferOwnership(owner); MainSaleClosed(); } /** * @dev Allows the owner to transfer ERC20 tokens to the multi sig vault * @param _token the contract address of the ERC20 contract */ function retrieveTokens(address _token) public onlyOwner { ERC20 token = ERC20(_token); token.transfer(multisigVault, token.balanceOf(this)); } /** * @dev Fallback function which receives ether and created the appropriate number of tokens for the * msg.sender. */ function() external payable { createTokens(msg.sender); } }
File 4 of 8: 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 5 of 8: FeeBurner
pragma solidity 0.4.18; // File: contracts/FeeBurnerInterface.sol interface FeeBurnerInterface { function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool); function setReserveData(address reserve, uint feesInBps, address kncWallet) public; } // 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/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/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/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/FeeBurner.sol interface BurnableToken { function transferFrom(address _from, address _to, uint _value) public returns (bool); function burnFrom(address _from, uint256 _value) public returns (bool); } contract FeeBurner is Withdrawable, FeeBurnerInterface, Utils2 { mapping(address=>uint) public reserveFeesInBps; mapping(address=>address) public reserveKNCWallet; //wallet holding knc per reserve. from here burn and send fees. mapping(address=>uint) public walletFeesInBps; // wallet that is the source of tx is entitled so some fees. mapping(address=>uint) public reserveFeeToBurn; mapping(address=>uint) public feePayedPerReserve; // track burned fees and sent wallet fees per reserve. mapping(address=>mapping(address=>uint)) public reserveFeeToWallet; address public taxWallet; uint public taxFeeBps = 0; // burned fees are taxed. % out of burned fees. BurnableToken public knc; KyberNetworkInterface public kyberNetwork; uint public kncPerEthRatePrecision = 600 * PRECISION; //--> 1 ether = 600 knc tokens function FeeBurner( address _admin, BurnableToken _kncToken, KyberNetworkInterface _kyberNetwork, uint _initialKncToEthRatePrecision ) public { require(_admin != address(0)); require(_kncToken != address(0)); require(_kyberNetwork != address(0)); require(_initialKncToEthRatePrecision != 0); kyberNetwork = _kyberNetwork; admin = _admin; knc = _kncToken; kncPerEthRatePrecision = _initialKncToEthRatePrecision; } event ReserveDataSet(address reserve, uint feeInBps, address kncWallet); function setReserveData(address reserve, uint feesInBps, address kncWallet) public onlyOperator { require(feesInBps < 100); // make sure it is always < 1% require(kncWallet != address(0)); reserveFeesInBps[reserve] = feesInBps; reserveKNCWallet[reserve] = kncWallet; ReserveDataSet(reserve, feesInBps, kncWallet); } event WalletFeesSet(address wallet, uint feesInBps); function setWalletFees(address wallet, uint feesInBps) public onlyAdmin { require(feesInBps < 10000); // under 100% walletFeesInBps[wallet] = feesInBps; WalletFeesSet(wallet, feesInBps); } event TaxFeesSet(uint feesInBps); function setTaxInBps(uint _taxFeeBps) public onlyAdmin { require(_taxFeeBps < 10000); // under 100% taxFeeBps = _taxFeeBps; TaxFeesSet(_taxFeeBps); } event TaxWalletSet(address taxWallet); function setTaxWallet(address _taxWallet) public onlyAdmin { require(_taxWallet != address(0)); taxWallet = _taxWallet; TaxWalletSet(_taxWallet); } event KNCRateSet(uint ethToKncRatePrecision, uint kyberEthKnc, uint kyberKncEth, address updater); function setKNCRate() public { //query kyber for knc rate sell and buy uint kyberEthKncRate; uint kyberKncEthRate; (kyberEthKncRate, ) = kyberNetwork.getExpectedRate(ETH_TOKEN_ADDRESS, ERC20(knc), (10 ** 18)); (kyberKncEthRate, ) = kyberNetwork.getExpectedRate(ERC20(knc), ETH_TOKEN_ADDRESS, (10 ** 18)); //check "reasonable" spread == diff not too big. rate wasn't tampered. require(kyberEthKncRate * kyberKncEthRate < PRECISION ** 2 * 2); require(kyberEthKncRate * kyberKncEthRate > PRECISION ** 2 / 2); require(kyberEthKncRate <= MAX_RATE); kncPerEthRatePrecision = kyberEthKncRate; KNCRateSet(kncPerEthRatePrecision, kyberEthKncRate, kyberKncEthRate, msg.sender); } event AssignFeeToWallet(address reserve, address wallet, uint walletFee); event AssignBurnFees(address reserve, uint burnFee); function handleFees(uint tradeWeiAmount, address reserve, address wallet) public returns(bool) { require(msg.sender == address(kyberNetwork)); require(tradeWeiAmount <= MAX_QTY); uint kncAmount = calcDestAmount(ETH_TOKEN_ADDRESS, ERC20(knc), tradeWeiAmount, kncPerEthRatePrecision); uint fee = kncAmount * reserveFeesInBps[reserve] / 10000; uint walletFee = fee * walletFeesInBps[wallet] / 10000; require(fee >= walletFee); uint feeToBurn = fee - walletFee; if (walletFee > 0) { reserveFeeToWallet[reserve][wallet] += walletFee; AssignFeeToWallet(reserve, wallet, walletFee); } if (feeToBurn > 0) { AssignBurnFees(reserve, feeToBurn); reserveFeeToBurn[reserve] += feeToBurn; } return true; } event BurnAssignedFees(address indexed reserve, address sender, uint quantity); event SendTaxFee(address indexed reserve, address sender, address taxWallet, uint quantity); // this function is callable by anyone function burnReserveFees(address reserve) public { uint burnAmount = reserveFeeToBurn[reserve]; uint taxToSend = 0; require(burnAmount > 2); reserveFeeToBurn[reserve] = 1; // leave 1 twei to avoid spikes in gas fee if (taxWallet != address(0) && taxFeeBps != 0) { taxToSend = (burnAmount - 1) * taxFeeBps / 10000; require(burnAmount - 1 > taxToSend); burnAmount -= taxToSend; if (taxToSend > 0) { require(knc.transferFrom(reserveKNCWallet[reserve], taxWallet, taxToSend)); SendTaxFee(reserve, msg.sender, taxWallet, taxToSend); } } require(knc.burnFrom(reserveKNCWallet[reserve], burnAmount - 1)); //update reserve "payments" so far feePayedPerReserve[reserve] += (taxToSend + burnAmount - 1); BurnAssignedFees(reserve, msg.sender, (burnAmount - 1)); } event SendWalletFees(address indexed wallet, address reserve, address sender); // this function is callable by anyone function sendFeeToWallet(address wallet, address reserve) public { uint feeAmount = reserveFeeToWallet[reserve][wallet]; require(feeAmount > 1); reserveFeeToWallet[reserve][wallet] = 1; // leave 1 twei to avoid spikes in gas fee require(knc.transferFrom(reserveKNCWallet[reserve], wallet, feeAmount - 1)); feePayedPerReserve[reserve] += (feeAmount - 1); SendWalletFees(wallet, reserve, msg.sender); } }
File 6 of 8: ConversionRates
pragma solidity 0.4.18; interface ConversionRatesInterface { function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public; function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint); } interface ERC20 { function totalSupply() public view returns (uint supply); function balanceOf(address _owner) public view returns (uint balance); function transfer(address _to, uint _value) public returns (bool success); function transferFrom(address _from, address _to, uint _value) public returns (bool success); function approve(address _spender, uint _value) public returns (bool success); function allowance(address _owner, address _spender) public view returns (uint remaining); function decimals() public view returns(uint digits); event Approval(address indexed _owner, address indexed _spender, uint _value); } contract PermissionGroups { address public admin; address public pendingAdmin; mapping(address=>bool) internal operators; mapping(address=>bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint constant internal MAX_GROUP_SIZE = 50; function PermissionGroups() public { admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin); _; } modifier onlyOperator() { require(operators[msg.sender]); _; } modifier onlyAlerter() { require(alerters[msg.sender]); _; } function getOperators () external view returns(address[]) { return operatorsGroup; } function getAlerters () external view returns(address[]) { return alertersGroup; } event TransferAdminPending(address pendingAdmin); /** * @dev Allows the current admin to set the pendingAdmin address. * @param newAdmin The address to transfer ownership to. */ function transferAdmin(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(pendingAdmin); pendingAdmin = newAdmin; } /** * @dev Allows the current admin to set the admin in one tx. Useful initial deployment. * @param newAdmin The address to transfer ownership to. */ function transferAdminQuickly(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(newAdmin); AdminClaimed(newAdmin, admin); admin = newAdmin; } event AdminClaimed( address newAdmin, address previousAdmin); /** * @dev Allows the pendingAdmin address to finalize the change admin process. */ function claimAdmin() public { require(pendingAdmin == msg.sender); AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } event AlerterAdded (address newAlerter, bool isAdd); function addAlerter(address newAlerter) public onlyAdmin { require(!alerters[newAlerter]); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE); AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function removeAlerter (address alerter) public onlyAdmin { require(alerters[alerter]); alerters[alerter] = false; for (uint i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.length--; AlerterAdded(alerter, false); break; } } } event OperatorAdded(address newOperator, bool isAdd); function addOperator(address newOperator) public onlyAdmin { require(!operators[newOperator]); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE); OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } function removeOperator (address operator) public onlyAdmin { require(operators[operator]); operators[operator] = false; for (uint i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.length -= 1; OperatorAdded(operator, false); break; } } } } contract Withdrawable is PermissionGroups { event TokenWithdraw(ERC20 token, uint amount, address sendTo); /** * @dev Withdraw all ERC20 compatible tokens * @param token ERC20 The address of the token contract */ function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin { require(token.transfer(sendTo, amount)); TokenWithdraw(token, amount, sendTo); } event EtherWithdraw(uint amount, address sendTo); /** * @dev Withdraw Ethers */ function withdrawEther(uint amount, address sendTo) external onlyAdmin { sendTo.transfer(amount); EtherWithdraw(amount, sendTo); } } contract VolumeImbalanceRecorder is Withdrawable { uint constant internal SLIDING_WINDOW_SIZE = 5; uint constant internal POW_2_64 = 2 ** 64; struct TokenControlInfo { uint minimalRecordResolution; // can be roughly 1 cent uint maxPerBlockImbalance; // in twei resolution uint maxTotalImbalance; // max total imbalance (between rate updates) // before halting trade } mapping(address => TokenControlInfo) internal tokenControlInfo; struct TokenImbalanceData { int lastBlockBuyUnitsImbalance; uint lastBlock; int totalBuyUnitsImbalance; uint lastRateUpdateBlock; } mapping(address => mapping(uint=>uint)) public tokenImbalanceData; function VolumeImbalanceRecorder(address _admin) public { require(_admin != address(0)); admin = _admin; } function setTokenControlInfo( ERC20 token, uint minimalRecordResolution, uint maxPerBlockImbalance, uint maxTotalImbalance ) public onlyAdmin { tokenControlInfo[token] = TokenControlInfo( minimalRecordResolution, maxPerBlockImbalance, maxTotalImbalance ); } function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) { return (tokenControlInfo[token].minimalRecordResolution, tokenControlInfo[token].maxPerBlockImbalance, tokenControlInfo[token].maxTotalImbalance); } function addImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) internal { uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE; int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution)); int prevImbalance = 0; TokenImbalanceData memory currentBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]); // first scenario - this is not the first tx in the current block if (currentBlockData.lastBlock == currentBlock) { if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) { // just increase imbalance currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount; currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount; } else { // imbalance was changed in the middle of the block prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock); currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount; currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount; currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock); } } else { // first tx in the current block int currentBlockImbalance; (prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock); currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount; currentBlockData.lastBlock = uint(currentBlock); currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock); currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount; } tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData); } function setGarbageToVolumeRecorder(ERC20 token) internal { for (uint i = 0; i < SLIDING_WINDOW_SIZE; i++) { tokenImbalanceData[token][i] = 0x1; } } function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) { // check the imbalance in the sliding window require(startBlock <= endBlock); buyImbalance = 0; for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) { TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]); if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) { buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance); } } } function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int buyImbalance, int currentBlockImbalance) { buyImbalance = 0; currentBlockImbalance = 0; uint latestBlock = 0; int imbalanceInRange = 0; uint startBlock = rateUpdateBlock; uint endBlock = currentBlock; for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) { TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]); if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) { imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance; } if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue; if (perBlockData.lastBlock < latestBlock) continue; latestBlock = perBlockData.lastBlock; buyImbalance = perBlockData.totalBuyUnitsImbalance; if (uint(perBlockData.lastBlock) == currentBlock) { currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance; } } if (buyImbalance == 0) { buyImbalance = imbalanceInRange; } } function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int totalImbalance, int currentBlockImbalance) { int resolution = int(tokenControlInfo[token].minimalRecordResolution); (totalImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate( token, rateUpdateBlock, currentBlock); totalImbalance *= resolution; currentBlockImbalance *= resolution; } function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) { return tokenControlInfo[token].maxPerBlockImbalance; } function getMaxTotalImbalance(ERC20 token) internal view returns(uint) { return tokenControlInfo[token].maxTotalImbalance; } function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) { // check for overflows require(data.lastBlockBuyUnitsImbalance < int(POW_2_64 / 2)); require(data.lastBlockBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2)); require(data.lastBlock < POW_2_64); require(data.totalBuyUnitsImbalance < int(POW_2_64 / 2)); require(data.totalBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2)); require(data.lastRateUpdateBlock < POW_2_64); // do encoding uint result = uint(data.lastBlockBuyUnitsImbalance) & (POW_2_64 - 1); result |= data.lastBlock * POW_2_64; result |= (uint(data.totalBuyUnitsImbalance) & (POW_2_64 - 1)) * POW_2_64 * POW_2_64; result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64; return result; } function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) { TokenImbalanceData memory data; data.lastBlockBuyUnitsImbalance = int(int64(input & (POW_2_64 - 1))); data.lastBlock = uint(uint64((input / POW_2_64) & (POW_2_64 - 1))); data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) & (POW_2_64 - 1))); data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64)))); return data; } } contract Utils { ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee); uint constant internal PRECISION = (10**18); uint constant internal MAX_QTY = (10**28); // 10B tokens uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH uint constant internal MAX_DECIMALS = 18; uint constant internal ETH_DECIMALS = 18; mapping(address=>uint) internal decimals; function setDecimals(ERC20 token) internal { if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS; else decimals[token] = token.decimals(); } function getDecimals(ERC20 token) internal view returns(uint) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint tokenDecimals = decimals[token]; // technically, there might be token with decimals 0 // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if(tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(srcQty <= MAX_QTY); require(rate <= MAX_RATE); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(dstQty <= MAX_QTY); require(rate <= MAX_RATE); //source quantity is rounded up. to avoid dest quantity being too low. uint numerator; uint denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } } contract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils { // bps - basic rate steps. one step is 1 / 10000 of the rate. struct StepFunction { int[] x; // quantity for each step. Quantity of each step includes previous steps. int[] y; // rate change per quantity step in bps. } struct TokenData { bool listed; // was added to reserve bool enabled; // whether trade is enabled // position in the compact data uint compactDataArrayIndex; uint compactDataFieldIndex; // rate data. base and changes according to quantity and reserve balance. // generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction. uint baseBuyRate; // in PRECISION units. see KyberConstants uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate. StepFunction sellRateQtyStepFunction;// in bps. higher the qua StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate. StepFunction sellRateImbalanceStepFunction; } /* this is the data for tokenRatesCompactData but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write so we represent it as bytes32 and do the byte tricks ourselves. struct TokenRatesCompactData { bytes14 buy; // change buy rate of token from baseBuyRate in 10 bps bytes14 sell; // change sell rate of token from baseSellRate in 10 bps uint32 blockNumber; } */ uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks ERC20[] internal listedTokens; mapping(address=>TokenData) internal tokenData; bytes32[] internal tokenRatesCompactData; uint public numTokensInCurrentCompactData = 0; address public reserveContract; uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14; uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA)); uint constant internal MAX_STEPS_IN_FUNCTION = 10; int constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B % int constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100% function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin) { } // solhint-disable-line no-empty-blocks function addToken(ERC20 token) public onlyAdmin { require(!tokenData[token].listed); tokenData[token].listed = true; listedTokens.push(token); if (numTokensInCurrentCompactData == 0) { tokenRatesCompactData.length++; // add new structure } tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1; tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData; numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA; setGarbageToVolumeRecorder(token); setDecimals(token); } function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator { require(buy.length == sell.length); require(indices.length == buy.length); require(blockNumber <= 0xFFFFFFFF); uint bytes14Offset = BYTES_14_OFFSET; for (uint i = 0; i < indices.length; i++) { require(indices[i] < tokenRatesCompactData.length); uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset)); tokenRatesCompactData[indices[i]] = bytes32(data); } } function setBaseRate( ERC20[] tokens, uint[] baseBuy, uint[] baseSell, bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices ) public onlyOperator { require(tokens.length == baseBuy.length); require(tokens.length == baseSell.length); require(sell.length == buy.length); require(sell.length == indices.length); for (uint ind = 0; ind < tokens.length; ind++) { require(tokenData[tokens[ind]].listed); tokenData[tokens[ind]].baseBuyRate = baseBuy[ind]; tokenData[tokens[ind]].baseSellRate = baseSell[ind]; } setCompactData(buy, sell, blockNumber, indices); } function setQtyStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length == yBuy.length); require(xSell.length == ySell.length); require(xBuy.length <= MAX_STEPS_IN_FUNCTION); require(xSell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy); tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell); } function setImbalanceStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length == yBuy.length); require(xSell.length == ySell.length); require(xBuy.length <= MAX_STEPS_IN_FUNCTION); require(xSell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy); tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell); } function setValidRateDurationInBlocks(uint duration) public onlyAdmin { validRateDurationInBlocks = duration; } function enableTokenTrade(ERC20 token) public onlyAdmin { require(tokenData[token].listed); require(tokenControlInfo[token].minimalRecordResolution != 0); tokenData[token].enabled = true; } function disableTokenTrade(ERC20 token) public onlyAlerter { require(tokenData[token].listed); tokenData[token].enabled = false; } function setReserveAddress(address reserve) public onlyAdmin { reserveContract = reserve; } function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public { require(msg.sender == reserveContract); if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token); return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock); } /* solhint-disable function-max-lines */ function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) { // check if trade is enabled if (!tokenData[token].enabled) return 0; if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set // get rate update block bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; uint updateRateBlock = getLast4Bytes(compactData); if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired // check imbalance int totalImbalance; int blockImbalance; (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber); // calculate actual rate int imbalanceQty; int extraBps; int8 rateUpdate; uint rate; if (buy) { // start with base rate rate = tokenData[token].baseBuyRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, true); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty qty = getTokenQty(token, rate, qty); imbalanceQty = int(qty); totalImbalance += imbalanceQty; // add qty overhead extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty)); rate = addBps(rate, extraBps); // add imbalance overhead extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance); rate = addBps(rate, extraBps); } else { // start with base rate rate = tokenData[token].baseSellRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, false); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty imbalanceQty = -1 * int(qty); totalImbalance += imbalanceQty; // add qty overhead extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty)); rate = addBps(rate, extraBps); // add imbalance overhead extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance); rate = addBps(rate, extraBps); } if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0; if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0; return rate; } /* solhint-enable function-max-lines */ function getBasicRate(ERC20 token, bool buy) public view returns(uint) { if (buy) return tokenData[token].baseBuyRate; else return tokenData[token].baseSellRate; } function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) { require(tokenData[token].listed); uint arrayIndex = tokenData[token].compactDataArrayIndex; uint fieldOffset = tokenData[token].compactDataFieldIndex; return ( arrayIndex, fieldOffset, byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)), byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false)) ); } function getTokenBasicData(ERC20 token) public view returns(bool, bool) { return (tokenData[token].listed, tokenData[token].enabled); } /* solhint-disable code-complexity */ function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) { if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length); if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param]; if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length); if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param]; if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length); if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param]; if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length); if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param]; if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length); if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param]; if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length); if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param]; if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length); if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param]; if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length); if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param]; revert(); } /* solhint-enable code-complexity */ function getRateUpdateBlock(ERC20 token) public view returns(uint) { bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; return getLast4Bytes(compactData); } function getListedTokens() public view returns(ERC20[]) { return listedTokens; } function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) { uint dstDecimals = getDecimals(token); uint srcDecimals = ETH_DECIMALS; return calcDstQty(ethQty, srcDecimals, dstDecimals, rate); } function getLast4Bytes(bytes32 b) internal pure returns(uint) { // cannot trust compiler with not turning bit operations into EXP opcode return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET); } function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) { uint fieldOffset = tokenData[token].compactDataFieldIndex; uint byteOffset; if (buy) byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset; else byteOffset = 4 + fieldOffset; return int8(data[byteOffset]); } function executeStepFunction(StepFunction f, int x) internal pure returns(int) { uint len = f.y.length; for (uint ind = 0; ind < len; ind++) { if (x <= f.x[ind]) return f.y[ind]; } return f.y[len-1]; } function addBps(uint rate, int bps) internal pure returns(uint) { require(rate <= MAX_RATE); require(bps >= MIN_BPS_ADJUSTMENT); require(bps <= MAX_BPS_ADJUSTMENT); uint maxBps = 100 * 100; return (rate * uint(int(maxBps) + bps)) / maxBps; } function abs(int x) internal pure returns(uint) { if (x < 0) return uint(-1 * x); else return uint(x); } }
File 7 of 8: 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 8 of 8: KyberNetworkCrystal
pragma solidity ^0.4.13; library SafeMath { function mul(uint256 a, uint256 b) internal constant returns (uint256) { uint256 c = a * b; assert(a == 0 || c / a == b); return c; } function div(uint256 a, uint256 b) internal constant returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function sub(uint256 a, uint256 b) internal constant returns (uint256) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal constant returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } contract Ownable { address public owner; /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) onlyOwner { if (newOwner != address(0)) { owner = newOwner; } } } contract ERC20Basic { uint256 public totalSupply; function balanceOf(address who) constant returns (uint256); function transfer(address to, uint256 value) returns (bool); // KYBER-NOTE! code changed to comply with ERC20 standard event Transfer(address indexed _from, address indexed _to, uint _value); //event Transfer(address indexed from, address indexed to, uint256 value); } contract BasicToken is ERC20Basic { using SafeMath for uint256; mapping(address => uint256) balances; /** * @dev transfer token for a specified address * @param _to The address to transfer to. * @param _value The amount to be transferred. */ function transfer(address _to, uint256 _value) returns (bool) { balances[msg.sender] = balances[msg.sender].sub(_value); balances[_to] = balances[_to].add(_value); Transfer(msg.sender, _to, _value); return true; } /** * @dev Gets the balance of the specified address. * @param _owner The address to query the the balance of. * @return An uint256 representing the amount owned by the passed address. */ function balanceOf(address _owner) constant returns (uint256 balance) { return balances[_owner]; } } contract ERC20 is ERC20Basic { function allowance(address owner, address spender) constant returns (uint256); function transferFrom(address from, address to, uint256 value) returns (bool); function approve(address spender, uint256 value) returns (bool); // KYBER-NOTE! code changed to comply with ERC20 standard event Approval(address indexed _owner, address indexed _spender, uint _value); //event Approval(address indexed owner, address indexed spender, uint256 value); } contract StandardToken is ERC20, BasicToken { mapping (address => mapping (address => uint256)) allowed; /** * @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 amout of tokens to be transfered */ function transferFrom(address _from, address _to, uint256 _value) returns (bool) { var _allowance = allowed[_from][msg.sender]; // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met // require (_value <= _allowance); // KYBER-NOTE! code changed to comply with ERC20 standard balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); //balances[_from] = balances[_from].sub(_value); // this was removed allowed[_from][msg.sender] = _allowance.sub(_value); Transfer(_from, _to, _value); return true; } /** * @dev Aprove the passed address to spend the specified amount of tokens on behalf of msg.sender. * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint256 _value) returns (bool) { // To change the approve amount you first have to reduce the addresses` // allowance to zero by calling `approve(_spender, 0)` if it is not // already 0 to mitigate the race condition described here: // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 require((_value == 0) || (allowed[msg.sender][_spender] == 0)); allowed[msg.sender][_spender] = _value; 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 specifing the amount of tokens still avaible for the spender. */ function allowance(address _owner, address _spender) constant returns (uint256 remaining) { return allowed[_owner][_spender]; } } contract KyberNetworkCrystal is StandardToken, Ownable { string public constant name = "Kyber Network Crystal"; string public constant symbol = "KNC"; uint public constant decimals = 18; uint public saleStartTime; uint public saleEndTime; address public tokenSaleContract; modifier onlyWhenTransferEnabled() { if( now <= saleEndTime && now >= saleStartTime ) { require( msg.sender == tokenSaleContract ); } _; } modifier validDestination( address to ) { require(to != address(0x0)); require(to != address(this) ); _; } function KyberNetworkCrystal( uint tokenTotalAmount, uint startTime, uint endTime, address admin ) { // Mint all tokens. Then disable minting forever. balances[msg.sender] = tokenTotalAmount; totalSupply = tokenTotalAmount; Transfer(address(0x0), msg.sender, tokenTotalAmount); saleStartTime = startTime; saleEndTime = endTime; tokenSaleContract = msg.sender; transferOwnership(admin); // admin could drain tokens that were sent here by mistake } function transfer(address _to, uint _value) onlyWhenTransferEnabled validDestination(_to) returns (bool) { return super.transfer(_to, _value); } function transferFrom(address _from, address _to, uint _value) onlyWhenTransferEnabled validDestination(_to) returns (bool) { return super.transferFrom(_from, _to, _value); } event Burn(address indexed _burner, uint _value); function burn(uint _value) onlyWhenTransferEnabled returns (bool){ balances[msg.sender] = balances[msg.sender].sub(_value); totalSupply = totalSupply.sub(_value); Burn(msg.sender, _value); Transfer(msg.sender, address(0x0), _value); return true; } // save some gas by making only one contract call function burnFrom(address _from, uint256 _value) onlyWhenTransferEnabled returns (bool) { assert( transferFrom( _from, msg.sender, _value ) ); return burn(_value); } function emergencyERC20Drain( ERC20 token, uint amount ) onlyOwner { token.transfer( owner, amount ); } }