pragma solidity 0.4.18;
// 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);
}
pragma solidity 0.4.18;
import "./ERC20Interface.sol";
interface ExpectedRateInterface {
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty, bool usePermissionless) public view
returns (uint expectedRate, uint slippageRate);
}
pragma solidity 0.4.18;
interface FeeBurnerInterface {
function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
function setReserveData(address reserve, uint feesInBps, address kncWallet) public;
}
pragma solidity 0.4.18;
import "./ERC20Interface.sol";
import "./KyberReserveInterface.sol";
import "./KyberNetworkInterface.sol";
import "./Withdrawable.sol";
import "./Utils3.sol";
import "./WhiteListInterface.sol";
import "./ExpectedRateInterface.sol";
import "./FeeBurnerInterface.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, Utils3, 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 */
function() public payable {
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));
if (src == dest) return (0,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));
if (src == dest) return (0,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 getReservesRates(ERC20 token, uint optionalAmount) public view
returns(address[] buyReserves, uint[] buyRates, address[] sellReserves, uint[] sellRates)
{
uint amount = optionalAmount > 0 ? optionalAmount : 1000;
ERC20 ETH = ETH_TOKEN_ADDRESS;
buyReserves = reservesPerTokenDest[token];
buyRates = new uint[](buyReserves.length);
for (uint i = 0; i < buyReserves.length; i++) {
buyRates[i] = (KyberReserveInterface(buyReserves[i])).getConversionRate(ETH, token, amount, block.number);
}
sellReserves = reservesPerTokenSrc[token];
sellRates = new uint[](sellReserves.length);
for (i = 0; i < sellReserves.length; i++) {
sellRates[i] = (KyberReserveInterface(sellReserves[i])).getConversionRate(token, ETH, amount, block.number);
}
}
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;
}
uint srcDecimals = getDecimals(src);
uint destDecimals = getDecimals(dest);
(result.reserve1, result.rateSrcToEth) =
searchBestRate(src, ETH_TOKEN_ADDRESS, srcAmount, usePermissionless);
result.weiAmount = calcDestAmountWithDecimals(srcDecimals, ETH_DECIMALS, srcAmount, result.rateSrcToEth);
//if weiAmount is zero, return zero rate to avoid revert in ETH -> token call
if (result.weiAmount == 0) {
result.rate = 0;
return;
}
(result.reserve2, result.rateEthToDest) =
searchBestRate(ETH_TOKEN_ADDRESS, dest, result.weiAmount, usePermissionless);
result.destAmount = calcDestAmountWithDecimals(ETH_DECIMALS, destDecimals, result.weiAmount, result.rateEthToDest);
result.rate = calcRateFromQty(srcAmount, result.destAmount, srcDecimals, destDecimals);
}
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;
}
}
pragma solidity 0.4.18;
import "./ERC20Interface.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);
}
pragma solidity 0.4.18;
import "./ERC20Interface.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);
}
pragma solidity 0.4.18;
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;
}
}
}
}
pragma solidity 0.4.18;
import "./ERC20Interface.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
}
}
pragma solidity 0.4.18;
import "./Utils.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);
}
}
}
pragma solidity 0.4.18;
import "./Utils2.sol";
contract Utils3 is Utils2 {
function calcDestAmountWithDecimals(uint srcDecimals, uint destDecimals, uint srcAmount, uint rate) internal pure returns(uint) {
return calcDstQty(srcAmount, srcDecimals, destDecimals, rate);
}
}
pragma solidity 0.4.18;
contract WhiteListInterface {
function getUserCapInWei(address user) external view returns (uint userCapWei);
}
pragma solidity 0.4.18;
import "./ERC20Interface.sol";
import "./PermissionGroups.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);
}
}
{
"compilationTarget": {
"KyberNetwork.sol": "KyberNetwork"
},
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"alerter","type":"address"}],"name":"removeAlerter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"trader","type":"address"},{"name":"src","type":"address"},{"name":"srcAmount","type":"uint256"},{"name":"dest","type":"address"},{"name":"destAddress","type":"address"},{"name":"maxDestAmount","type":"uint256"},{"name":"minConversionRate","type":"uint256"},{"name":"walletId","type":"address"},{"name":"hint","type":"bytes"}],"name":"tradeWithHint","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getReserves","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"dest","type":"address"},{"name":"srcAmount","type":"uint256"},{"name":"usePermissionless","type":"bool"}],"name":"searchBestRate","outputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"infoFields","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"feeBurner","type":"address"}],"name":"setFeeBurner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"dest","type":"address"},{"name":"srcAmount","type":"uint256"}],"name":"findBestRateOnlyPermission","outputs":[{"name":"obsolete","type":"uint256"},{"name":"rate","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"enabled","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"reserve","type":"address"},{"name":"index","type":"uint256"}],"name":"removeReserve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"pendingAdmin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"reservesPerTokenSrc","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"whiteList","type":"address"}],"name":"setWhiteList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"token","type":"address"},{"name":"amount","type":"uint256"},{"name":"sendTo","type":"address"}],"name":"withdrawToken","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"maxGasPrice","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newAlerter","type":"address"}],"name":"addAlerter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"negligibleRateDiff","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"feeBurnerContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"expectedRate","type":"address"}],"name":"setExpectedRate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"expectedRateContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"whiteListContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"field","type":"bytes32"},{"name":"value","type":"uint256"}],"name":"setInfo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"user","type":"address"}],"name":"getUserCapInWei","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isEnabled","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"reserveType","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newAdmin","type":"address"}],"name":"transferAdmin","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_enable","type":"bool"}],"name":"setEnable","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"claimAdmin","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"kyberNetworkProxyContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newAdmin","type":"address"}],"name":"transferAdminQuickly","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getAlerters","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"dest","type":"address"},{"name":"srcQty","type":"uint256"}],"name":"getExpectedRate","outputs":[{"name":"expectedRate","type":"uint256"},{"name":"slippageRate","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"reserves","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"dest","type":"address"},{"name":"srcQty","type":"uint256"}],"name":"getExpectedRateOnlyPermission","outputs":[{"name":"expectedRate","type":"uint256"},{"name":"slippageRate","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERM_HINT","outputs":[{"name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"user","type":"address"},{"name":"token","type":"address"}],"name":"getUserCapInTokenWei","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"reservesPerTokenDest","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOperator","type":"address"}],"name":"addOperator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"reserve","type":"address"},{"name":"isPermissionless","type":"bool"}],"name":"addReserve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"operator","type":"address"}],"name":"removeOperator","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"maxGasPriceValue","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"field","type":"bytes32"}],"name":"info","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"src","type":"address"},{"name":"dest","type":"address"},{"name":"srcAmount","type":"uint256"}],"name":"findBestRate","outputs":[{"name":"obsolete","type":"uint256"},{"name":"rate","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_maxGasPrice","type":"uint256"},{"name":"_negligibleRateDiff","type":"uint256"}],"name":"setParams","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PERM_HINT_GET_RATE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"networkProxy","type":"address"}],"name":"setKyberProxy","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"},{"name":"sendTo","type":"address"}],"name":"withdrawEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getNumReserves","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"},{"name":"user","type":"address"}],"name":"getBalance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"reserve","type":"address"},{"name":"token","type":"address"},{"name":"ethToToken","type":"bool"},{"name":"tokenToEth","type":"bool"},{"name":"add","type":"bool"}],"name":"listPairForReserve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"},{"name":"optionalAmount","type":"uint256"}],"name":"getReservesRates","outputs":[{"name":"buyReserves","type":"address[]"},{"name":"buyRates","type":"uint256[]"},{"name":"sellReserves","type":"address[]"},{"name":"sellRates","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"admin","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_admin","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"EtherReceival","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reserve","type":"address"},{"indexed":false,"name":"add","type":"bool"},{"indexed":false,"name":"isPermissionless","type":"bool"}],"name":"AddReserveToNetwork","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"reserve","type":"address"}],"name":"RemoveReserveFromNetwork","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reserve","type":"address"},{"indexed":false,"name":"src","type":"address"},{"indexed":false,"name":"dest","type":"address"},{"indexed":false,"name":"add","type":"bool"}],"name":"ListReservePairs","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newContract","type":"address"},{"indexed":false,"name":"currentContract","type":"address"}],"name":"WhiteListContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newContract","type":"address"},{"indexed":false,"name":"currentContract","type":"address"}],"name":"ExpectedRateContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newContract","type":"address"},{"indexed":false,"name":"currentContract","type":"address"}],"name":"FeeBurnerContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"maxGasPrice","type":"uint256"},{"indexed":false,"name":"negligibleRateDiff","type":"uint256"}],"name":"KyberNetwrokParamsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"isEnabled","type":"bool"}],"name":"KyberNetworkSetEnable","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"proxy","type":"address"},{"indexed":false,"name":"sender","type":"address"}],"name":"KyberProxySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"trader","type":"address"},{"indexed":false,"name":"src","type":"address"},{"indexed":false,"name":"dest","type":"address"},{"indexed":false,"name":"srcAmount","type":"uint256"},{"indexed":false,"name":"dstAmount","type":"uint256"},{"indexed":false,"name":"destAddress","type":"address"},{"indexed":false,"name":"ethWeiValue","type":"uint256"},{"indexed":false,"name":"reserve1","type":"address"},{"indexed":false,"name":"reserve2","type":"address"},{"indexed":false,"name":"hint","type":"bytes"}],"name":"KyberTrade","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sendTo","type":"address"}],"name":"TokenWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sendTo","type":"address"}],"name":"EtherWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"pendingAdmin","type":"address"}],"name":"TransferAdminPending","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newAdmin","type":"address"},{"indexed":false,"name":"previousAdmin","type":"address"}],"name":"AdminClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newAlerter","type":"address"},{"indexed":false,"name":"isAdd","type":"bool"}],"name":"AlerterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOperator","type":"address"},{"indexed":false,"name":"isAdd","type":"bool"}],"name":"OperatorAdded","type":"event"}]