Transaction Hash:
Block:
9509219 at Feb-18-2020 08:14:09 PM +UTC
Transaction Fee:
0.0012238 ETH
$2.45
Gas Used:
244,760 Gas / 5 Gwei
Emitted Events:
| 108 |
CrowdsaleToken.Transfer( from=KyberReserve, to=KyberNetwork, value=16491960466 )
|
| 109 |
KyberReserve.TradeExecute( origin=KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=29045605956262898, destToken=CrowdsaleToken, destAmount=16491960466, destAddress=KyberNetwork )
|
| 110 |
CrowdsaleToken.Transfer( from=KyberNetwork, to=[Sender] 0x391e02f9aaec9486281c2c785ce8fd06033660a1, value=16491960466 )
|
| 111 |
FeeBurner.AssignFeeToWallet( reserve=KyberReserve, wallet=0xF1AA99C6...E35Cc504d, walletFee=22548834610324422 )
|
| 112 |
FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=22548834610324423 )
|
| 113 |
KyberNetwork.KyberTrade( trader=[Sender] 0x391e02f9aaec9486281c2c785ce8fd06033660a1, src=0xEeeeeEee...eeeeeEEeE, dest=CrowdsaleToken, srcAmount=29045605956262898, dstAmount=16491960466, destAddress=[Sender] 0x391e02f9aaec9486281c2c785ce8fd06033660a1, ethWeiValue=29045605956262898, reserve1=0x00000000...000000000, reserve2=KyberReserve, hint=0x )
|
| 114 |
KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0x391e02f9aaec9486281c2c785ce8fd06033660a1, src=0xEeeeeEee...eeeeeEEeE, dest=CrowdsaleToken, actualSrcAmount=29045605956262898, actualDestAmount=16491960466 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x391E02f9...6033660a1 |
1.040008964814564191 Eth
Nonce: 1
|
1.009739558858301293 Eth
Nonce: 2
| 0.030269405956262898 | ||
|
0x5A0b54D5...D3E029c4c
Miner
| (Spark Pool) | 33.114340649939513925 Eth | 33.115564449939513925 Eth | 0.0012238 | |
| 0x5Af2Be19...77Db30756 | |||||
| 0x63825c17...bD36A0D8F | (Kyber: Reserve) | 3,023.344979088545047445 Eth | 3,023.374024694501310343 Eth | 0.029045605956262898 | |
| 0x65bF64Ff...77ced86Cd | (Kyber: Contract) | ||||
| 0x798AbDA6...d3d11191B | (Kyber: Conversion Rates) | ||||
| 0x8007aa43...E5fF626d1 | (Kyber: Fee Burner) |
Execution Trace
ETH 0.03
KyberNetworkProxy.trade( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=30000000000000000, dest=0x5Af2Be193a6ABCa9c8817001F45744777Db30756, destAddress=0x391E02f9AaEc9486281C2C785Ce8FD06033660a1, maxDestAmount=16491960466, minConversionRate=181906814910288, walletId=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d ) => ( 16491960466 )
-
CrowdsaleToken.balanceOf( _owner=0x391E02f9AaEc9486281C2C785Ce8FD06033660a1 ) => ( balance=3350263200 )
ETH 0.03
KyberNetwork.tradeWithHint( trader=0x391E02f9AaEc9486281C2C785Ce8FD06033660a1, src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=30000000000000000, dest=0x5Af2Be193a6ABCa9c8817001F45744777Db30756, destAddress=0x391E02f9AaEc9486281C2C785Ce8FD06033660a1, maxDestAmount=16491960466, minConversionRate=181906814910288, walletId=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d, hint=0x ) => ( 16491960466 )KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x5Af2Be193a6ABCa9c8817001F45744777Db30756, srcQty=30000000000000000, blockNumber=9509219 ) => ( 5677953660472335930490 )-
ConversionRates.getRate( token=0x5Af2Be193a6ABCa9c8817001F45744777Db30756, currentBlockNumber=9509219, buy=True, qty=30000000000000000 ) => ( 5677953660472335930490 ) -
CrowdsaleToken.balanceOf( _owner=0x63825c174ab367968EC60f061753D3bbD36A0D8F ) => ( balance=628336203504 )
-
SanityRates.getSanityRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x5Af2Be193a6ABCa9c8817001F45744777Db30756 ) => ( 6307668247927856074476 )
-
- ETH 0.000954394043737102
0x391e02f9aaec9486281c2c785ce8fd06033660a1.CALL( ) ETH 0.029045605956262898
KyberReserve.trade( srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=29045605956262898, destToken=0x5Af2Be193a6ABCa9c8817001F45744777Db30756, destAddress=0x65bF64Ff5f51272f729BDcD7AcFB00677ced86Cd, conversionRate=5677953660472335930490, validate=True ) => ( True )-
ConversionRates.recordImbalance( token=0x5Af2Be193a6ABCa9c8817001F45744777Db30756, buyAmount=16491960466, rateUpdateBlock=0, currentBlock=9509219 ) -
CrowdsaleToken.transfer( _to=0x65bF64Ff5f51272f729BDcD7AcFB00677ced86Cd, _value=16491960466 ) => ( success=True )
-
-
CrowdsaleToken.transfer( _to=0x391E02f9AaEc9486281C2C785Ce8FD06033660a1, _value=16491960466 ) => ( success=True )
-
FeeBurner.handleFees( tradeWeiAmount=29045605956262898, reserve=0x63825c174ab367968EC60f061753D3bbD36A0D8F, wallet=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d ) => ( True )
-
CrowdsaleToken.balanceOf( _owner=0x391E02f9AaEc9486281C2C785Ce8FD06033660a1 ) => ( balance=19842223666 )
trade[KyberNetworkProxy (ln:349)]
tradeWithHint[KyberNetworkProxy (ln:364)]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 7: KyberNetworkProxy
File 2 of 7: KyberReserve
File 3 of 7: KyberNetwork
File 4 of 7: CrowdsaleToken
File 5 of 7: FeeBurner
File 6 of 7: ConversionRates
File 7 of 7: SanityRates
pragma solidity 0.4.18;
// File: contracts/ERC20Interface.sol
// https://github.com/ethereum/EIPs/issues/20
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
// File: contracts/KyberNetworkInterface.sol
/// @title Kyber Network interface
interface KyberNetworkInterface {
function maxGasPrice() public view returns(uint);
function getUserCapInWei(address user) public view returns(uint);
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
function enabled() public view returns(bool);
function info(bytes32 id) public view returns(uint);
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
returns (uint expectedRate, uint slippageRate);
function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,
uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
}
// File: contracts/KyberNetworkProxyInterface.sol
/// @title Kyber Network interface
interface KyberNetworkProxyInterface {
function maxGasPrice() public view returns(uint);
function getUserCapInWei(address user) public view returns(uint);
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
function enabled() public view returns(bool);
function info(bytes32 id) public view returns(uint);
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
returns (uint expectedRate, uint slippageRate);
function tradeWithHint(ERC20 src, uint srcAmount, ERC20 dest, address destAddress, uint maxDestAmount,
uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
}
// File: contracts/SimpleNetworkInterface.sol
/// @title simple interface for Kyber Network
interface SimpleNetworkInterface {
function swapTokenToToken(ERC20 src, uint srcAmount, ERC20 dest, uint minConversionRate) public returns(uint);
function swapEtherToToken(ERC20 token, uint minConversionRate) public payable returns(uint);
function swapTokenToEther(ERC20 token, uint srcAmount, uint minConversionRate) public returns(uint);
}
// File: contracts/Utils.sol
/// @title Kyber constants contract
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
// File: contracts/Utils2.sol
contract Utils2 is Utils {
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(ERC20 token, address user) public view returns(uint) {
if (token == ETH_TOKEN_ADDRESS)
return user.balance;
else
return token.balanceOf(user);
}
function getDecimalsSafe(ERC20 token) internal returns(uint) {
if (decimals[token] == 0) {
setDecimals(token);
}
return decimals[token];
}
function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)
internal pure returns(uint)
{
require(srcAmount <= MAX_QTY);
require(destAmount <= MAX_QTY);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);
}
}
}
// File: contracts/PermissionGroups.sol
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
// File: contracts/Withdrawable.sol
/**
* @title Contracts that should be able to recover tokens or ethers
* @author Ilan Doron
* @dev This allows to recover any tokens or Ethers received in a contract.
* This will prevent any accidental loss of tokens.
*/
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
// File: contracts/KyberNetworkProxy.sol
////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @title Kyber Network proxy for main contract
contract KyberNetworkProxy is KyberNetworkProxyInterface, SimpleNetworkInterface, Withdrawable, Utils2 {
KyberNetworkInterface public kyberNetworkContract;
function KyberNetworkProxy(address _admin) public {
require(_admin != address(0));
admin = _admin;
}
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev makes a trade between src and dest token and send dest token to destAddress
/// @param src Src token
/// @param srcAmount amount of src tokens
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @param walletId is the wallet ID to send part of the fees
/// @return amount of actual dest tokens
function trade(
ERC20 src,
uint srcAmount,
ERC20 dest,
address destAddress,
uint maxDestAmount,
uint minConversionRate,
address walletId
)
public
payable
returns(uint)
{
bytes memory hint;
return tradeWithHint(
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
walletId,
hint
);
}
/// @dev makes a trade between src and dest token and send dest tokens to msg sender
/// @param src Src token
/// @param srcAmount amount of src tokens
/// @param dest Destination token
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @return amount of actual dest tokens
function swapTokenToToken(
ERC20 src,
uint srcAmount,
ERC20 dest,
uint minConversionRate
)
public
returns(uint)
{
bytes memory hint;
return tradeWithHint(
src,
srcAmount,
dest,
msg.sender,
MAX_QTY,
minConversionRate,
0,
hint
);
}
/// @dev makes a trade from Ether to token. Sends token to msg sender
/// @param token Destination token
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @return amount of actual dest tokens
function swapEtherToToken(ERC20 token, uint minConversionRate) public payable returns(uint) {
bytes memory hint;
return tradeWithHint(
ETH_TOKEN_ADDRESS,
msg.value,
token,
msg.sender,
MAX_QTY,
minConversionRate,
0,
hint
);
}
/// @dev makes a trade from token to Ether, sends Ether to msg sender
/// @param token Src token
/// @param srcAmount amount of src tokens
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @return amount of actual dest tokens
function swapTokenToEther(ERC20 token, uint srcAmount, uint minConversionRate) public returns(uint) {
bytes memory hint;
return tradeWithHint(
token,
srcAmount,
ETH_TOKEN_ADDRESS,
msg.sender,
MAX_QTY,
minConversionRate,
0,
hint
);
}
struct UserBalance {
uint srcBalance;
uint destBalance;
}
event ExecuteTrade(address indexed trader, ERC20 src, ERC20 dest, uint actualSrcAmount, uint actualDestAmount);
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev makes a trade between src and dest token and send dest token to destAddress
/// @param src Src token
/// @param srcAmount amount of src tokens
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @param walletId is the wallet ID to send part of the fees
/// @param hint will give hints for the trade.
/// @return amount of actual dest tokens
function tradeWithHint(
ERC20 src,
uint srcAmount,
ERC20 dest,
address destAddress,
uint maxDestAmount,
uint minConversionRate,
address walletId,
bytes hint
)
public
payable
returns(uint)
{
require(src == ETH_TOKEN_ADDRESS || msg.value == 0);
UserBalance memory userBalanceBefore;
userBalanceBefore.srcBalance = getBalance(src, msg.sender);
userBalanceBefore.destBalance = getBalance(dest, destAddress);
if (src == ETH_TOKEN_ADDRESS) {
userBalanceBefore.srcBalance += msg.value;
} else {
require(src.transferFrom(msg.sender, kyberNetworkContract, srcAmount));
}
uint reportedDestAmount = kyberNetworkContract.tradeWithHint.value(msg.value)(
msg.sender,
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
walletId,
hint
);
TradeOutcome memory tradeOutcome = calculateTradeOutcome(
userBalanceBefore.srcBalance,
userBalanceBefore.destBalance,
src,
dest,
destAddress
);
require(reportedDestAmount == tradeOutcome.userDeltaDestAmount);
require(tradeOutcome.userDeltaDestAmount <= maxDestAmount);
require(tradeOutcome.actualRate >= minConversionRate);
ExecuteTrade(msg.sender, src, dest, tradeOutcome.userDeltaSrcAmount, tradeOutcome.userDeltaDestAmount);
return tradeOutcome.userDeltaDestAmount;
}
event KyberNetworkSet(address newNetworkContract, address oldNetworkContract);
function setKyberNetworkContract(KyberNetworkInterface _kyberNetworkContract) public onlyAdmin {
require(_kyberNetworkContract != address(0));
KyberNetworkSet(_kyberNetworkContract, kyberNetworkContract);
kyberNetworkContract = _kyberNetworkContract;
}
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty)
public view
returns(uint expectedRate, uint slippageRate)
{
return kyberNetworkContract.getExpectedRate(src, dest, srcQty);
}
function getUserCapInWei(address user) public view returns(uint) {
return kyberNetworkContract.getUserCapInWei(user);
}
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint) {
return kyberNetworkContract.getUserCapInTokenWei(user, token);
}
function maxGasPrice() public view returns(uint) {
return kyberNetworkContract.maxGasPrice();
}
function enabled() public view returns(bool) {
return kyberNetworkContract.enabled();
}
function info(bytes32 field) public view returns(uint) {
return kyberNetworkContract.info(field);
}
struct TradeOutcome {
uint userDeltaSrcAmount;
uint userDeltaDestAmount;
uint actualRate;
}
function calculateTradeOutcome (uint srcBalanceBefore, uint destBalanceBefore, ERC20 src, ERC20 dest,
address destAddress)
internal returns(TradeOutcome outcome)
{
uint userSrcBalanceAfter;
uint userDestBalanceAfter;
userSrcBalanceAfter = getBalance(src, msg.sender);
userDestBalanceAfter = getBalance(dest, destAddress);
//protect from underflow
require(userDestBalanceAfter > destBalanceBefore);
require(srcBalanceBefore > userSrcBalanceAfter);
outcome.userDeltaDestAmount = userDestBalanceAfter - destBalanceBefore;
outcome.userDeltaSrcAmount = srcBalanceBefore - userSrcBalanceAfter;
outcome.actualRate = calcRateFromQty(
outcome.userDeltaSrcAmount,
outcome.userDeltaDestAmount,
getDecimalsSafe(src),
getDecimalsSafe(dest)
);
}
}File 2 of 7: KyberReserve
pragma solidity 0.4.18;
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
interface ConversionRatesInterface {
function recordImbalance(
ERC20 token,
int buyAmount,
uint rateUpdateBlock,
uint currentBlock
)
public;
function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);
}
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
interface KyberReserveInterface {
function trade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
public
payable
returns(bool);
function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint);
}
interface SanityRatesInterface {
function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);
}
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
contract KyberReserve is KyberReserveInterface, Withdrawable, Utils {
address public kyberNetwork;
bool public tradeEnabled;
ConversionRatesInterface public conversionRatesContract;
SanityRatesInterface public sanityRatesContract;
mapping(bytes32=>bool) public approvedWithdrawAddresses; // sha3(token,address)=>bool
function KyberReserve(address _kyberNetwork, ConversionRatesInterface _ratesContract, address _admin) public {
require(_admin != address(0));
require(_ratesContract != address(0));
require(_kyberNetwork != address(0));
kyberNetwork = _kyberNetwork;
conversionRatesContract = _ratesContract;
admin = _admin;
tradeEnabled = true;
}
event DepositToken(ERC20 token, uint amount);
function() public payable {
DepositToken(ETH_TOKEN_ADDRESS, msg.value);
}
event TradeExecute(
address indexed origin,
address src,
uint srcAmount,
address destToken,
uint destAmount,
address destAddress
);
function trade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
public
payable
returns(bool)
{
require(tradeEnabled);
require(msg.sender == kyberNetwork);
require(doTrade(srcToken, srcAmount, destToken, destAddress, conversionRate, validate));
return true;
}
event TradeEnabled(bool enable);
function enableTrade() public onlyAdmin returns(bool) {
tradeEnabled = true;
TradeEnabled(true);
return true;
}
function disableTrade() public onlyAlerter returns(bool) {
tradeEnabled = false;
TradeEnabled(false);
return true;
}
event WithdrawAddressApproved(ERC20 token, address addr, bool approve);
function approveWithdrawAddress(ERC20 token, address addr, bool approve) public onlyAdmin {
approvedWithdrawAddresses[keccak256(token, addr)] = approve;
WithdrawAddressApproved(token, addr, approve);
setDecimals(token);
}
event WithdrawFunds(ERC20 token, uint amount, address destination);
function withdraw(ERC20 token, uint amount, address destination) public onlyOperator returns(bool) {
require(approvedWithdrawAddresses[keccak256(token, destination)]);
if (token == ETH_TOKEN_ADDRESS) {
destination.transfer(amount);
} else {
require(token.transfer(destination, amount));
}
WithdrawFunds(token, amount, destination);
return true;
}
event SetContractAddresses(address network, address rate, address sanity);
function setContracts(address _kyberNetwork, ConversionRatesInterface _conversionRates, SanityRatesInterface _sanityRates)
public
onlyAdmin
{
require(_kyberNetwork != address(0));
require(_conversionRates != address(0));
kyberNetwork = _kyberNetwork;
conversionRatesContract = _conversionRates;
sanityRatesContract = _sanityRates;
SetContractAddresses(kyberNetwork, conversionRatesContract, sanityRatesContract);
}
////////////////////////////////////////////////////////////////////////////
/// status functions ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
function getBalance(ERC20 token) public view returns(uint) {
if (token == ETH_TOKEN_ADDRESS)
return this.balance;
else
return token.balanceOf(this);
}
function getDestQty(ERC20 src, ERC20 dest, uint srcQty, uint rate) public view returns(uint) {
uint dstDecimals = getDecimals(dest);
uint srcDecimals = getDecimals(src);
return calcDstQty(srcQty, srcDecimals, dstDecimals, rate);
}
function getSrcQty(ERC20 src, ERC20 dest, uint dstQty, uint rate) public view returns(uint) {
uint dstDecimals = getDecimals(dest);
uint srcDecimals = getDecimals(src);
return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate);
}
function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint) {
ERC20 token;
bool buy;
if (!tradeEnabled) return 0;
if (ETH_TOKEN_ADDRESS == src) {
buy = true;
token = dest;
} else if (ETH_TOKEN_ADDRESS == dest) {
buy = false;
token = src;
} else {
return 0; // pair is not listed
}
uint rate = conversionRatesContract.getRate(token, blockNumber, buy, srcQty);
uint destQty = getDestQty(src, dest, srcQty, rate);
if (getBalance(dest) < destQty) return 0;
if (sanityRatesContract != address(0)) {
uint sanityRate = sanityRatesContract.getSanityRate(src, dest);
if (rate > sanityRate) return 0;
}
return rate;
}
/// @dev do a trade
/// @param srcToken Src token
/// @param srcAmount Amount of src token
/// @param destToken Destination token
/// @param destAddress Destination address to send tokens to
/// @param validate If true, additional validations are applicable
/// @return true iff trade is successful
function doTrade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
internal
returns(bool)
{
// can skip validation if done at kyber network level
if (validate) {
require(conversionRate > 0);
if (srcToken == ETH_TOKEN_ADDRESS)
require(msg.value == srcAmount);
else
require(msg.value == 0);
}
uint destAmount = getDestQty(srcToken, destToken, srcAmount, conversionRate);
// sanity check
require(destAmount > 0);
// add to imbalance
ERC20 token;
int buy;
if (srcToken == ETH_TOKEN_ADDRESS) {
buy = int(destAmount);
token = destToken;
} else {
buy = -1 * int(srcAmount);
token = srcToken;
}
conversionRatesContract.recordImbalance(
token,
buy,
0,
block.number
);
// collect src tokens
if (srcToken != ETH_TOKEN_ADDRESS) {
require(srcToken.transferFrom(msg.sender, this, srcAmount));
}
// send dest tokens
if (destToken == ETH_TOKEN_ADDRESS) {
destAddress.transfer(destAmount);
} else {
require(destToken.transfer(destAddress, destAmount));
}
TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress);
return true;
}
}File 3 of 7: KyberNetwork
{"ERC20Interface.sol":{"content":"pragma solidity 0.4.18;\n\n\n// https://github.com/ethereum/EIPs/issues/20\ninterface ERC20 {\n function totalSupply() public view returns (uint supply);\n function balanceOf(address _owner) public view returns (uint balance);\n function transfer(address _to, uint _value) public returns (bool success);\n function transferFrom(address _from, address _to, uint _value) public returns (bool success);\n function approve(address _spender, uint _value) public returns (bool success);\n function allowance(address _owner, address _spender) public view returns (uint remaining);\n function decimals() public view returns(uint digits);\n event Approval(address indexed _owner, address indexed _spender, uint _value);\n}\n"},"ExpectedRateInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\ninterface ExpectedRateInterface {\n function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty, bool usePermissionless) public view\n returns (uint expectedRate, uint slippageRate);\n}\n"},"FeeBurnerInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\ninterface FeeBurnerInterface {\n function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);\n function setReserveData(address reserve, uint feesInBps, address kncWallet) public;\n}\n"},"KyberNetwork.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\nimport \"./KyberReserveInterface.sol\";\nimport \"./KyberNetworkInterface.sol\";\nimport \"./Withdrawable.sol\";\nimport \"./Utils3.sol\";\nimport \"./WhiteListInterface.sol\";\nimport \"./ExpectedRateInterface.sol\";\nimport \"./FeeBurnerInterface.sol\";\n\n\n/**\n * @title Helps contracts guard against reentrancy attacks.\n */\ncontract ReentrancyGuard {\n\n /// @dev counter to allow mutex lock with only one SSTORE operation\n uint256 private guardCounter = 1;\n\n /**\n * @dev Prevents a function from calling itself, directly or indirectly.\n * Calling one `nonReentrant` function from\n * another is not supported. Instead, you can implement a\n * `private` function doing the actual work, and an `external`\n * wrapper marked as `nonReentrant`.\n */\n modifier nonReentrant() {\n guardCounter += 1;\n uint256 localCounter = guardCounter;\n _;\n require(localCounter == guardCounter);\n }\n}\n\n\n////////////////////////////////////////////////////////////////////////////////////////////////////////\n/// @title Kyber Network main contract\ncontract KyberNetwork is Withdrawable, Utils3, KyberNetworkInterface, ReentrancyGuard {\n\n bytes public constant PERM_HINT = \"PERM\";\n uint public constant PERM_HINT_GET_RATE = 1 \u003c\u003c 255; // for get rate. bit mask hint.\n\n uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01%\n KyberReserveInterface[] public reserves;\n mapping(address=\u003eReserveType) public reserveType;\n WhiteListInterface public whiteListContract;\n ExpectedRateInterface public expectedRateContract;\n FeeBurnerInterface public feeBurnerContract;\n address public kyberNetworkProxyContract;\n uint public maxGasPriceValue = 50 * 1000 * 1000 * 1000; // 50 gwei\n bool public isEnabled = false; // network is enabled\n mapping(bytes32=\u003euint) public infoFields; // this is only a UI field for external app.\n\n mapping(address=\u003eaddress[]) public reservesPerTokenSrc; //reserves supporting token to eth\n mapping(address=\u003eaddress[]) public reservesPerTokenDest;//reserves support eth to token\n\n enum ReserveType {NONE, PERMISSIONED, PERMISSIONLESS}\n bytes internal constant EMPTY_HINT = \"\";\n\n function KyberNetwork(address _admin) public {\n require(_admin != address(0));\n admin = _admin;\n }\n\n event EtherReceival(address indexed sender, uint amount);\n\n /* solhint-disable no-complex-fallback */\n function() public payable {\n EtherReceival(msg.sender, msg.value);\n }\n /* solhint-enable no-complex-fallback */\n\n struct TradeInput {\n address trader;\n ERC20 src;\n uint srcAmount;\n ERC20 dest;\n address destAddress;\n uint maxDestAmount;\n uint minConversionRate;\n address walletId;\n bytes hint;\n }\n\n function tradeWithHint(\n address trader,\n ERC20 src,\n uint srcAmount,\n ERC20 dest,\n address destAddress,\n uint maxDestAmount,\n uint minConversionRate,\n address walletId,\n bytes hint\n )\n public\n nonReentrant\n payable\n returns(uint)\n {\n require(msg.sender == kyberNetworkProxyContract);\n require((hint.length == 0) || (hint.length == 4));\n\n TradeInput memory tradeInput;\n\n tradeInput.trader = trader;\n tradeInput.src = src;\n tradeInput.srcAmount = srcAmount;\n tradeInput.dest = dest;\n tradeInput.destAddress = destAddress;\n tradeInput.maxDestAmount = maxDestAmount;\n tradeInput.minConversionRate = minConversionRate;\n tradeInput.walletId = walletId;\n tradeInput.hint = hint;\n\n return trade(tradeInput);\n }\n\n event AddReserveToNetwork(KyberReserveInterface indexed reserve, bool add, bool isPermissionless);\n\n /// @notice can be called only by operator\n /// @dev add or deletes a reserve to/from the network.\n /// @param reserve The reserve address.\n /// @param isPermissionless is the new reserve from permissionless type.\n function addReserve(KyberReserveInterface reserve, bool isPermissionless) public onlyOperator\n returns(bool)\n {\n require(reserveType[reserve] == ReserveType.NONE);\n reserves.push(reserve);\n\n reserveType[reserve] = isPermissionless ? ReserveType.PERMISSIONLESS : ReserveType.PERMISSIONED;\n\n AddReserveToNetwork(reserve, true, isPermissionless);\n\n return true;\n }\n\n event RemoveReserveFromNetwork(KyberReserveInterface reserve);\n\n /// @notice can be called only by operator\n /// @dev removes a reserve from Kyber network.\n /// @param reserve The reserve address.\n /// @param index in reserve array.\n function removeReserve(KyberReserveInterface reserve, uint index) public onlyOperator\n returns(bool)\n {\n\n require(reserveType[reserve] != ReserveType.NONE);\n require(reserves[index] == reserve);\n\n reserveType[reserve] = ReserveType.NONE;\n reserves[index] = reserves[reserves.length - 1];\n reserves.length--;\n\n RemoveReserveFromNetwork(reserve);\n\n return true;\n }\n\n event ListReservePairs(address indexed reserve, ERC20 src, ERC20 dest, bool add);\n\n /// @notice can be called only by operator\n /// @dev allow or prevent a specific reserve to trade a pair of tokens\n /// @param reserve The reserve address.\n /// @param token token address\n /// @param ethToToken will it support ether to token trade\n /// @param tokenToEth will it support token to ether trade\n /// @param add If true then list this pair, otherwise unlist it.\n function listPairForReserve(address reserve, ERC20 token, bool ethToToken, bool tokenToEth, bool add)\n public\n onlyOperator\n returns(bool)\n {\n require(reserveType[reserve] != ReserveType.NONE);\n\n if (ethToToken) {\n listPairs(reserve, token, false, add);\n\n ListReservePairs(reserve, ETH_TOKEN_ADDRESS, token, add);\n }\n\n if (tokenToEth) {\n listPairs(reserve, token, true, add);\n\n if (add) {\n require(token.approve(reserve, 2**255)); // approve infinity\n } else {\n require(token.approve(reserve, 0));\n }\n\n ListReservePairs(reserve, token, ETH_TOKEN_ADDRESS, add);\n }\n\n setDecimals(token);\n\n return true;\n }\n\n event WhiteListContractSet(WhiteListInterface newContract, WhiteListInterface currentContract);\n\n ///@param whiteList can be empty\n function setWhiteList(WhiteListInterface whiteList) public onlyAdmin {\n WhiteListContractSet(whiteList, whiteListContract);\n whiteListContract = whiteList;\n }\n\n event ExpectedRateContractSet(ExpectedRateInterface newContract, ExpectedRateInterface currentContract);\n\n function setExpectedRate(ExpectedRateInterface expectedRate) public onlyAdmin {\n require(expectedRate != address(0));\n\n ExpectedRateContractSet(expectedRate, expectedRateContract);\n expectedRateContract = expectedRate;\n }\n\n event FeeBurnerContractSet(FeeBurnerInterface newContract, FeeBurnerInterface currentContract);\n\n function setFeeBurner(FeeBurnerInterface feeBurner) public onlyAdmin {\n require(feeBurner != address(0));\n\n FeeBurnerContractSet(feeBurner, feeBurnerContract);\n feeBurnerContract = feeBurner;\n }\n\n event KyberNetwrokParamsSet(uint maxGasPrice, uint negligibleRateDiff);\n\n function setParams(\n uint _maxGasPrice,\n uint _negligibleRateDiff\n )\n public\n onlyAdmin\n {\n require(_negligibleRateDiff \u003c= 100 * 100); // at most 100%\n\n maxGasPriceValue = _maxGasPrice;\n negligibleRateDiff = _negligibleRateDiff;\n KyberNetwrokParamsSet(maxGasPriceValue, negligibleRateDiff);\n }\n\n event KyberNetworkSetEnable(bool isEnabled);\n\n function setEnable(bool _enable) public onlyAdmin {\n if (_enable) {\n require(feeBurnerContract != address(0));\n require(expectedRateContract != address(0));\n require(kyberNetworkProxyContract != address(0));\n }\n isEnabled = _enable;\n\n KyberNetworkSetEnable(isEnabled);\n }\n\n function setInfo(bytes32 field, uint value) public onlyOperator {\n infoFields[field] = value;\n }\n\n event KyberProxySet(address proxy, address sender);\n\n function setKyberProxy(address networkProxy) public onlyAdmin {\n require(networkProxy != address(0));\n kyberNetworkProxyContract = networkProxy;\n KyberProxySet(kyberNetworkProxyContract, msg.sender);\n }\n\n /// @dev returns number of reserves\n /// @return number of reserves\n function getNumReserves() public view returns(uint) {\n return reserves.length;\n }\n\n /// @notice should be called off chain\n /// @dev get an array of all reserves\n /// @return An array of all reserves\n function getReserves() public view returns(KyberReserveInterface[]) {\n return reserves;\n }\n\n function maxGasPrice() public view returns(uint) {\n return maxGasPriceValue;\n }\n\n function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty)\n public view\n returns(uint expectedRate, uint slippageRate)\n {\n require(expectedRateContract != address(0));\n if (src == dest) return (0,0);\n bool includePermissionless = true;\n\n if (srcQty \u0026 PERM_HINT_GET_RATE \u003e 0) {\n includePermissionless = false;\n srcQty = srcQty \u0026 ~PERM_HINT_GET_RATE;\n }\n\n return expectedRateContract.getExpectedRate(src, dest, srcQty, includePermissionless);\n }\n\n function getExpectedRateOnlyPermission(ERC20 src, ERC20 dest, uint srcQty)\n public view\n returns(uint expectedRate, uint slippageRate)\n {\n require(expectedRateContract != address(0));\n if (src == dest) return (0,0);\n return expectedRateContract.getExpectedRate(src, dest, srcQty, false);\n }\n\n function getUserCapInWei(address user) public view returns(uint) {\n if (whiteListContract == address(0)) return (2 ** 255);\n return whiteListContract.getUserCapInWei(user);\n }\n\n function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint) {\n //future feature\n user;\n token;\n require(false);\n }\n\n struct BestRateResult {\n uint rate;\n address reserve1;\n address reserve2;\n uint weiAmount;\n uint rateSrcToEth;\n uint rateEthToDest;\n uint destAmount;\n }\n\n /// @notice use token address ETH_TOKEN_ADDRESS for ether\n /// @dev best conversion rate for a pair of tokens, if number of reserves have small differences. randomize\n /// @param src Src token\n /// @param dest Destination token\n /// @return obsolete - used to return best reserve index. not relevant anymore for this API.\n function findBestRate(ERC20 src, ERC20 dest, uint srcAmount) public view returns(uint obsolete, uint rate) {\n BestRateResult memory result = findBestRateTokenToToken(src, dest, srcAmount, EMPTY_HINT);\n return(0, result.rate);\n }\n\n function findBestRateOnlyPermission(ERC20 src, ERC20 dest, uint srcAmount)\n public\n view\n returns(uint obsolete, uint rate)\n {\n BestRateResult memory result = findBestRateTokenToToken(src, dest, srcAmount, PERM_HINT);\n return(0, result.rate);\n }\n\n function enabled() public view returns(bool) {\n return isEnabled;\n }\n\n function info(bytes32 field) public view returns(uint) {\n return infoFields[field];\n }\n\n /* solhint-disable code-complexity */\n // Regarding complexity. Below code follows the required algorithm for choosing a reserve.\n // It has been tested, reviewed and found to be clear enough.\n //@dev this function always src or dest are ether. can\u0027t do token to token\n function searchBestRate(ERC20 src, ERC20 dest, uint srcAmount, bool usePermissionless)\n public\n view\n returns(address, uint)\n {\n uint bestRate = 0;\n uint bestReserve = 0;\n uint numRelevantReserves = 0;\n\n //return 1 for ether to ether\n if (src == dest) return (reserves[bestReserve], PRECISION);\n\n address[] memory reserveArr;\n\n reserveArr = src == ETH_TOKEN_ADDRESS ? reservesPerTokenDest[dest] : reservesPerTokenSrc[src];\n\n if (reserveArr.length == 0) return (reserves[bestReserve], bestRate);\n\n uint[] memory rates = new uint[](reserveArr.length);\n uint[] memory reserveCandidates = new uint[](reserveArr.length);\n\n for (uint i = 0; i \u003c reserveArr.length; i++) {\n //list all reserves that have this token.\n if (!usePermissionless \u0026\u0026 reserveType[reserveArr[i]] == ReserveType.PERMISSIONLESS) {\n continue;\n }\n\n rates[i] = (KyberReserveInterface(reserveArr[i])).getConversionRate(src, dest, srcAmount, block.number);\n\n if (rates[i] \u003e bestRate) {\n //best rate is highest rate\n bestRate = rates[i];\n }\n }\n\n if (bestRate \u003e 0) {\n uint smallestRelevantRate = (bestRate * 10000) / (10000 + negligibleRateDiff);\n\n for (i = 0; i \u003c reserveArr.length; i++) {\n if (rates[i] \u003e= smallestRelevantRate) {\n reserveCandidates[numRelevantReserves++] = i;\n }\n }\n\n if (numRelevantReserves \u003e 1) {\n //when encountering small rate diff from bestRate. draw from relevant reserves\n bestReserve = reserveCandidates[uint(block.blockhash(block.number-1)) % numRelevantReserves];\n } else {\n bestReserve = reserveCandidates[0];\n }\n\n bestRate = rates[bestReserve];\n }\n\n return (reserveArr[bestReserve], bestRate);\n }\n /* solhint-enable code-complexity */\n\n function getReservesRates(ERC20 token, uint optionalAmount) public view\n returns(address[] buyReserves, uint[] buyRates, address[] sellReserves, uint[] sellRates)\n {\n uint amount = optionalAmount \u003e 0 ? optionalAmount : 1000;\n ERC20 ETH = ETH_TOKEN_ADDRESS;\n\n buyReserves = reservesPerTokenDest[token];\n buyRates = new uint[](buyReserves.length);\n\n for (uint i = 0; i \u003c buyReserves.length; i++) {\n buyRates[i] = (KyberReserveInterface(buyReserves[i])).getConversionRate(ETH, token, amount, block.number);\n }\n\n sellReserves = reservesPerTokenSrc[token];\n sellRates = new uint[](sellReserves.length);\n\n for (i = 0; i \u003c sellReserves.length; i++) {\n sellRates[i] = (KyberReserveInterface(sellReserves[i])).getConversionRate(token, ETH, amount, block.number);\n }\n }\n\n function findBestRateTokenToToken(ERC20 src, ERC20 dest, uint srcAmount, bytes hint) internal view\n returns(BestRateResult result)\n {\n //by default we use permission less reserves\n bool usePermissionless = true;\n\n // if hint in first 4 bytes == \u0027PERM\u0027 only permissioned reserves will be used.\n if ((hint.length \u003e= 4) \u0026\u0026 (keccak256(hint[0], hint[1], hint[2], hint[3]) == keccak256(PERM_HINT))) {\n usePermissionless = false;\n }\n\n uint srcDecimals = getDecimals(src);\n uint destDecimals = getDecimals(dest);\n\n (result.reserve1, result.rateSrcToEth) =\n searchBestRate(src, ETH_TOKEN_ADDRESS, srcAmount, usePermissionless);\n\n result.weiAmount = calcDestAmountWithDecimals(srcDecimals, ETH_DECIMALS, srcAmount, result.rateSrcToEth);\n //if weiAmount is zero, return zero rate to avoid revert in ETH -\u003e token call\n if (result.weiAmount == 0) {\n result.rate = 0;\n return;\n }\n \n (result.reserve2, result.rateEthToDest) =\n searchBestRate(ETH_TOKEN_ADDRESS, dest, result.weiAmount, usePermissionless);\n\n result.destAmount = calcDestAmountWithDecimals(ETH_DECIMALS, destDecimals, result.weiAmount, result.rateEthToDest);\n\n result.rate = calcRateFromQty(srcAmount, result.destAmount, srcDecimals, destDecimals);\n }\n\n function listPairs(address reserve, ERC20 token, bool isTokenToEth, bool add) internal {\n uint i;\n address[] storage reserveArr = reservesPerTokenDest[token];\n\n if (isTokenToEth) {\n reserveArr = reservesPerTokenSrc[token];\n }\n\n for (i = 0; i \u003c reserveArr.length; i++) {\n if (reserve == reserveArr[i]) {\n if (add) {\n break; //already added\n } else {\n //remove\n reserveArr[i] = reserveArr[reserveArr.length - 1];\n reserveArr.length--;\n break;\n }\n }\n }\n\n if (add \u0026\u0026 i == reserveArr.length) {\n //if reserve wasn\u0027t found add it\n reserveArr.push(reserve);\n }\n }\n\n event KyberTrade(address indexed trader, ERC20 src, ERC20 dest, uint srcAmount, uint dstAmount,\n address destAddress, uint ethWeiValue, address reserve1, address reserve2, bytes hint);\n\n /* solhint-disable function-max-lines */\n // Most of the lines here are functions calls spread over multiple lines. We find this function readable enough\n /// @notice use token address ETH_TOKEN_ADDRESS for ether\n /// @dev trade api for kyber network.\n /// @param tradeInput structure of trade inputs\n function trade(TradeInput tradeInput) internal returns(uint) {\n require(isEnabled);\n require(tx.gasprice \u003c= maxGasPriceValue);\n require(validateTradeInput(tradeInput.src, tradeInput.srcAmount, tradeInput.dest, tradeInput.destAddress));\n\n BestRateResult memory rateResult =\n findBestRateTokenToToken(tradeInput.src, tradeInput.dest, tradeInput.srcAmount, tradeInput.hint);\n\n require(rateResult.rate \u003e 0);\n require(rateResult.rate \u003c MAX_RATE);\n require(rateResult.rate \u003e= tradeInput.minConversionRate);\n\n uint actualDestAmount;\n uint weiAmount;\n uint actualSrcAmount;\n\n (actualSrcAmount, weiAmount, actualDestAmount) = calcActualAmounts(tradeInput.src,\n tradeInput.dest,\n tradeInput.srcAmount,\n tradeInput.maxDestAmount,\n rateResult);\n\n require(getUserCapInWei(tradeInput.trader) \u003e= weiAmount);\n require(handleChange(tradeInput.src, tradeInput.srcAmount, actualSrcAmount, tradeInput.trader));\n\n require(doReserveTrade( //src to ETH\n tradeInput.src,\n actualSrcAmount,\n ETH_TOKEN_ADDRESS,\n this,\n weiAmount,\n KyberReserveInterface(rateResult.reserve1),\n rateResult.rateSrcToEth,\n true));\n\n require(doReserveTrade( //Eth to dest\n ETH_TOKEN_ADDRESS,\n weiAmount,\n tradeInput.dest,\n tradeInput.destAddress,\n actualDestAmount,\n KyberReserveInterface(rateResult.reserve2),\n rateResult.rateEthToDest,\n true));\n\n if (tradeInput.src != ETH_TOKEN_ADDRESS) //\"fake\" trade. (ether to ether) - don\u0027t burn.\n require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve1, tradeInput.walletId));\n if (tradeInput.dest != ETH_TOKEN_ADDRESS) //\"fake\" trade. (ether to ether) - don\u0027t burn.\n require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve2, tradeInput.walletId));\n\n KyberTrade({\n trader: tradeInput.trader,\n src: tradeInput.src,\n dest: tradeInput.dest,\n srcAmount: actualSrcAmount,\n dstAmount: actualDestAmount,\n destAddress: tradeInput.destAddress,\n ethWeiValue: weiAmount,\n reserve1: (tradeInput.src == ETH_TOKEN_ADDRESS) ? address(0) : rateResult.reserve1,\n reserve2: (tradeInput.dest == ETH_TOKEN_ADDRESS) ? address(0) : rateResult.reserve2,\n hint: tradeInput.hint\n });\n\n return actualDestAmount;\n }\n /* solhint-enable function-max-lines */\n\n function calcActualAmounts (ERC20 src, ERC20 dest, uint srcAmount, uint maxDestAmount, BestRateResult rateResult)\n internal view returns(uint actualSrcAmount, uint weiAmount, uint actualDestAmount)\n {\n if (rateResult.destAmount \u003e maxDestAmount) {\n actualDestAmount = maxDestAmount;\n weiAmount = calcSrcAmount(ETH_TOKEN_ADDRESS, dest, actualDestAmount, rateResult.rateEthToDest);\n actualSrcAmount = calcSrcAmount(src, ETH_TOKEN_ADDRESS, weiAmount, rateResult.rateSrcToEth);\n require(actualSrcAmount \u003c= srcAmount);\n } else {\n actualDestAmount = rateResult.destAmount;\n actualSrcAmount = srcAmount;\n weiAmount = rateResult.weiAmount;\n }\n }\n\n /// @notice use token address ETH_TOKEN_ADDRESS for ether\n /// @dev do one trade with a reserve\n /// @param src Src token\n /// @param amount amount of src tokens\n /// @param dest Destination token\n /// @param destAddress Address to send tokens to\n /// @param reserve Reserve to use\n /// @param validate If true, additional validations are applicable\n /// @return true if trade is successful\n function doReserveTrade(\n ERC20 src,\n uint amount,\n ERC20 dest,\n address destAddress,\n uint expectedDestAmount,\n KyberReserveInterface reserve,\n uint conversionRate,\n bool validate\n )\n internal\n returns(bool)\n {\n uint callValue = 0;\n\n if (src == dest) {\n //this is for a \"fake\" trade when both src and dest are ethers.\n if (destAddress != (address(this)))\n destAddress.transfer(amount);\n return true;\n }\n\n if (src == ETH_TOKEN_ADDRESS) {\n callValue = amount;\n }\n\n // reserve sends tokens/eth to network. network sends it to destination\n require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, validate));\n\n if (destAddress != address(this)) {\n //for token to token dest address is network. and Ether / token already here...\n if (dest == ETH_TOKEN_ADDRESS) {\n destAddress.transfer(expectedDestAmount);\n } else {\n require(dest.transfer(destAddress, expectedDestAmount));\n }\n }\n\n return true;\n }\n\n /// when user sets max dest amount we could have too many source tokens == change. so we send it back to user.\n function handleChange (ERC20 src, uint srcAmount, uint requiredSrcAmount, address trader) internal returns (bool) {\n\n if (requiredSrcAmount \u003c srcAmount) {\n //if there is \"change\" send back to trader\n if (src == ETH_TOKEN_ADDRESS) {\n trader.transfer(srcAmount - requiredSrcAmount);\n } else {\n require(src.transfer(trader, (srcAmount - requiredSrcAmount)));\n }\n }\n\n return true;\n }\n\n /// @notice use token address ETH_TOKEN_ADDRESS for ether\n /// @dev checks that user sent ether/tokens to contract before trade\n /// @param src Src token\n /// @param srcAmount amount of src tokens\n /// @return true if tradeInput is valid\n function validateTradeInput(ERC20 src, uint srcAmount, ERC20 dest, address destAddress)\n internal\n view\n returns(bool)\n {\n require(srcAmount \u003c= MAX_QTY);\n require(srcAmount != 0);\n require(destAddress != address(0));\n require(src != dest);\n\n if (src == ETH_TOKEN_ADDRESS) {\n require(msg.value == srcAmount);\n } else {\n require(msg.value == 0);\n //funds should have been moved to this contract already.\n require(src.balanceOf(this) \u003e= srcAmount);\n }\n\n return true;\n }\n}\n"},"KyberNetworkInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\n/// @title Kyber Network interface\ninterface KyberNetworkInterface {\n function maxGasPrice() public view returns(uint);\n function getUserCapInWei(address user) public view returns(uint);\n function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);\n function enabled() public view returns(bool);\n function info(bytes32 id) public view returns(uint);\n\n function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view\n returns (uint expectedRate, uint slippageRate);\n\n function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,\n uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);\n}\n"},"KyberReserveInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n/// @title Kyber Reserve contract\ninterface KyberReserveInterface {\n\n function trade(\n ERC20 srcToken,\n uint srcAmount,\n ERC20 destToken,\n address destAddress,\n uint conversionRate,\n bool validate\n )\n public\n payable\n returns(bool);\n\n function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint);\n}\n"},"PermissionGroups.sol":{"content":"pragma solidity 0.4.18;\n\n\ncontract PermissionGroups {\n\n address public admin;\n address public pendingAdmin;\n mapping(address=\u003ebool) internal operators;\n mapping(address=\u003ebool) internal alerters;\n address[] internal operatorsGroup;\n address[] internal alertersGroup;\n uint constant internal MAX_GROUP_SIZE = 50;\n\n function PermissionGroups() public {\n admin = msg.sender;\n }\n\n modifier onlyAdmin() {\n require(msg.sender == admin);\n _;\n }\n\n modifier onlyOperator() {\n require(operators[msg.sender]);\n _;\n }\n\n modifier onlyAlerter() {\n require(alerters[msg.sender]);\n _;\n }\n\n function getOperators () external view returns(address[]) {\n return operatorsGroup;\n }\n\n function getAlerters () external view returns(address[]) {\n return alertersGroup;\n }\n\n event TransferAdminPending(address pendingAdmin);\n\n /**\n * @dev Allows the current admin to set the pendingAdmin address.\n * @param newAdmin The address to transfer ownership to.\n */\n function transferAdmin(address newAdmin) public onlyAdmin {\n require(newAdmin != address(0));\n TransferAdminPending(pendingAdmin);\n pendingAdmin = newAdmin;\n }\n\n /**\n * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.\n * @param newAdmin The address to transfer ownership to.\n */\n function transferAdminQuickly(address newAdmin) public onlyAdmin {\n require(newAdmin != address(0));\n TransferAdminPending(newAdmin);\n AdminClaimed(newAdmin, admin);\n admin = newAdmin;\n }\n\n event AdminClaimed( address newAdmin, address previousAdmin);\n\n /**\n * @dev Allows the pendingAdmin address to finalize the change admin process.\n */\n function claimAdmin() public {\n require(pendingAdmin == msg.sender);\n AdminClaimed(pendingAdmin, admin);\n admin = pendingAdmin;\n pendingAdmin = address(0);\n }\n\n event AlerterAdded (address newAlerter, bool isAdd);\n\n function addAlerter(address newAlerter) public onlyAdmin {\n require(!alerters[newAlerter]); // prevent duplicates.\n require(alertersGroup.length \u003c MAX_GROUP_SIZE);\n\n AlerterAdded(newAlerter, true);\n alerters[newAlerter] = true;\n alertersGroup.push(newAlerter);\n }\n\n function removeAlerter (address alerter) public onlyAdmin {\n require(alerters[alerter]);\n alerters[alerter] = false;\n\n for (uint i = 0; i \u003c alertersGroup.length; ++i) {\n if (alertersGroup[i] == alerter) {\n alertersGroup[i] = alertersGroup[alertersGroup.length - 1];\n alertersGroup.length--;\n AlerterAdded(alerter, false);\n break;\n }\n }\n }\n\n event OperatorAdded(address newOperator, bool isAdd);\n\n function addOperator(address newOperator) public onlyAdmin {\n require(!operators[newOperator]); // prevent duplicates.\n require(operatorsGroup.length \u003c MAX_GROUP_SIZE);\n\n OperatorAdded(newOperator, true);\n operators[newOperator] = true;\n operatorsGroup.push(newOperator);\n }\n\n function removeOperator (address operator) public onlyAdmin {\n require(operators[operator]);\n operators[operator] = false;\n\n for (uint i = 0; i \u003c operatorsGroup.length; ++i) {\n if (operatorsGroup[i] == operator) {\n operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];\n operatorsGroup.length -= 1;\n OperatorAdded(operator, false);\n break;\n }\n }\n }\n}\n"},"Utils.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\n/// @title Kyber constants contract\ncontract Utils {\n\n ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);\n uint constant internal PRECISION = (10**18);\n uint constant internal MAX_QTY = (10**28); // 10B tokens\n uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH\n uint constant internal MAX_DECIMALS = 18;\n uint constant internal ETH_DECIMALS = 18;\n mapping(address=\u003euint) internal decimals;\n\n function setDecimals(ERC20 token) internal {\n if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;\n else decimals[token] = token.decimals();\n }\n\n function getDecimals(ERC20 token) internal view returns(uint) {\n if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access\n uint tokenDecimals = decimals[token];\n // technically, there might be token with decimals 0\n // moreover, very possible that old tokens have decimals 0\n // these tokens will just have higher gas fees.\n if(tokenDecimals == 0) return token.decimals();\n\n return tokenDecimals;\n }\n\n function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {\n require(srcQty \u003c= MAX_QTY);\n require(rate \u003c= MAX_RATE);\n\n if (dstDecimals \u003e= srcDecimals) {\n require((dstDecimals - srcDecimals) \u003c= MAX_DECIMALS);\n return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;\n } else {\n require((srcDecimals - dstDecimals) \u003c= MAX_DECIMALS);\n return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));\n }\n }\n\n function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {\n require(dstQty \u003c= MAX_QTY);\n require(rate \u003c= MAX_RATE);\n \n //source quantity is rounded up. to avoid dest quantity being too low.\n uint numerator;\n uint denominator;\n if (srcDecimals \u003e= dstDecimals) {\n require((srcDecimals - dstDecimals) \u003c= MAX_DECIMALS);\n numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));\n denominator = rate;\n } else {\n require((dstDecimals - srcDecimals) \u003c= MAX_DECIMALS);\n numerator = (PRECISION * dstQty);\n denominator = (rate * (10**(dstDecimals - srcDecimals)));\n }\n return (numerator + denominator - 1) / denominator; //avoid rounding down errors\n }\n}\n"},"Utils2.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./Utils.sol\";\n\n\ncontract Utils2 is Utils {\n\n /// @dev get the balance of a user.\n /// @param token The token type\n /// @return The balance\n function getBalance(ERC20 token, address user) public view returns(uint) {\n if (token == ETH_TOKEN_ADDRESS)\n return user.balance;\n else\n return token.balanceOf(user);\n }\n\n function getDecimalsSafe(ERC20 token) internal returns(uint) {\n\n if (decimals[token] == 0) {\n setDecimals(token);\n }\n\n return decimals[token];\n }\n\n function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {\n return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);\n }\n\n function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {\n return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);\n }\n\n function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)\n internal pure returns(uint)\n {\n require(srcAmount \u003c= MAX_QTY);\n require(destAmount \u003c= MAX_QTY);\n\n if (dstDecimals \u003e= srcDecimals) {\n require((dstDecimals - srcDecimals) \u003c= MAX_DECIMALS);\n return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));\n } else {\n require((srcDecimals - dstDecimals) \u003c= MAX_DECIMALS);\n return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);\n }\n }\n}\n"},"Utils3.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./Utils2.sol\";\n\n\ncontract Utils3 is Utils2 {\n\n function calcDestAmountWithDecimals(uint srcDecimals, uint destDecimals, uint srcAmount, uint rate) internal pure returns(uint) {\n return calcDstQty(srcAmount, srcDecimals, destDecimals, rate);\n }\n\n}\n"},"WhiteListInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\ncontract WhiteListInterface {\n function getUserCapInWei(address user) external view returns (uint userCapWei);\n}\n"},"Withdrawable.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\nimport \"./PermissionGroups.sol\";\n\n\n/**\n * @title Contracts that should be able to recover tokens or ethers\n * @author Ilan Doron\n * @dev This allows to recover any tokens or Ethers received in a contract.\n * This will prevent any accidental loss of tokens.\n */\ncontract Withdrawable is PermissionGroups {\n\n event TokenWithdraw(ERC20 token, uint amount, address sendTo);\n\n /**\n * @dev Withdraw all ERC20 compatible tokens\n * @param token ERC20 The address of the token contract\n */\n function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {\n require(token.transfer(sendTo, amount));\n TokenWithdraw(token, amount, sendTo);\n }\n\n event EtherWithdraw(uint amount, address sendTo);\n\n /**\n * @dev Withdraw Ethers\n */\n function withdrawEther(uint amount, address sendTo) external onlyAdmin {\n sendTo.transfer(amount);\n EtherWithdraw(amount, sendTo);\n }\n}\n"}}File 4 of 7: CrowdsaleToken
/*
* ERC20 interface
* see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 {
uint public totalSupply;
function balanceOf(address who) constant returns (uint);
function allowance(address owner, address spender) constant returns (uint);
function transfer(address to, uint value) returns (bool ok);
function transferFrom(address from, address to, uint value) returns (bool ok);
function approve(address spender, uint value) returns (bool ok);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
/**
* Math operations with safety checks
*/
contract SafeMath {
function safeMul(uint a, uint b) internal returns (uint) {
uint c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function safeDiv(uint a, uint b) internal returns (uint) {
assert(b > 0);
uint c = a / b;
assert(a == b * c + a % b);
return c;
}
function safeSub(uint a, uint b) internal returns (uint) {
assert(b <= a);
return a - b;
}
function safeAdd(uint a, uint b) internal returns (uint) {
uint c = a + b;
assert(c>=a && c>=b);
return c;
}
function max64(uint64 a, uint64 b) internal constant returns (uint64) {
return a >= b ? a : b;
}
function min64(uint64 a, uint64 b) internal constant returns (uint64) {
return a < b ? a : b;
}
function max256(uint256 a, uint256 b) internal constant returns (uint256) {
return a >= b ? a : b;
}
function min256(uint256 a, uint256 b) internal constant returns (uint256) {
return a < b ? a : b;
}
function assert(bool assertion) internal {
if (!assertion) {
throw;
}
}
}
/**
* Standard ERC20 token with Short Hand Attack and approve() race condition mitigation.
*
* Based on code by FirstBlood:
* https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract StandardToken is ERC20, SafeMath {
/* Token supply got increased and a new owner received these tokens */
event Minted(address receiver, uint amount);
/* Actual balances of token holders */
mapping(address => uint) balances;
/* approve() allowances */
mapping (address => mapping (address => uint)) allowed;
/* Interface declaration */
function isToken() public constant returns (bool weAre) {
return true;
}
/**
*
* Fix for the ERC20 short address attack
*
* http://vessenes.com/the-erc20-short-address-attack-explained/
*/
modifier onlyPayloadSize(uint size) {
if(msg.data.length < size + 4) {
throw;
}
_;
}
function transfer(address _to, uint _value) onlyPayloadSize(2 * 32) returns (bool success) {
balances[msg.sender] = safeSub(balances[msg.sender], _value);
balances[_to] = safeAdd(balances[_to], _value);
Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint _value) returns (bool success) {
uint _allowance = allowed[_from][msg.sender];
balances[_to] = safeAdd(balances[_to], _value);
balances[_from] = safeSub(balances[_from], _value);
allowed[_from][msg.sender] = safeSub(_allowance, _value);
Transfer(_from, _to, _value);
return true;
}
function balanceOf(address _owner) constant returns (uint balance) {
return balances[_owner];
}
function approve(address _spender, uint _value) returns (bool success) {
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender, 0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
if ((_value != 0) && (allowed[msg.sender][_spender] != 0)) throw;
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) constant returns (uint remaining) {
return allowed[_owner][_spender];
}
}
/**
* Upgrade agent interface inspired by Lunyr.
*
* Upgrade agent transfers tokens to a new contract.
* Upgrade agent itself can be the token contract, or just a middle man contract doing the heavy lifting.
*/
contract UpgradeAgent {
uint public originalSupply;
/** Interface marker */
function isUpgradeAgent() public constant returns (bool) {
return true;
}
function upgradeFrom(address _from, uint256 _value) public;
}
/**
* A token upgrade mechanism where users can opt-in amount of tokens to the next smart contract revision.
*
* First envisioned by Golem and Lunyr projects.
*/
contract UpgradeableToken is StandardToken {
/** Contract / person who can set the upgrade path. This can be the same as team multisig wallet, as what it is with its default value. */
address public upgradeMaster;
/** The next contract where the tokens will be migrated. */
UpgradeAgent public upgradeAgent;
/** How many tokens we have upgraded by now. */
uint256 public totalUpgraded;
/**
* Upgrade states.
*
* - NotAllowed: The child contract has not reached a condition where the upgrade can bgun
* - WaitingForAgent: Token allows upgrade, but we don't have a new agent yet
* - ReadyToUpgrade: The agent is set, but not a single token has been upgraded yet
* - Upgrading: Upgrade agent is set and the balance holders can upgrade their tokens
*
*/
enum UpgradeState {Unknown, NotAllowed, WaitingForAgent, ReadyToUpgrade, Upgrading}
/**
* Somebody has upgraded some of his tokens.
*/
event Upgrade(address indexed _from, address indexed _to, uint256 _value);
/**
* New upgrade agent available.
*/
event UpgradeAgentSet(address agent);
/**
* Do not allow construction without upgrade master set.
*/
function UpgradeableToken(address _upgradeMaster) {
upgradeMaster = _upgradeMaster;
}
/**
* Allow the token holder to upgrade some of their tokens to a new contract.
*/
function upgrade(uint256 value) public {
UpgradeState state = getUpgradeState();
if(!(state == UpgradeState.ReadyToUpgrade || state == UpgradeState.Upgrading)) {
// Called in a bad state
throw;
}
// Validate input value.
if (value == 0) throw;
balances[msg.sender] = safeSub(balances[msg.sender], value);
// Take tokens out from circulation
totalSupply = safeSub(totalSupply, value);
totalUpgraded = safeAdd(totalUpgraded, value);
// Upgrade agent reissues the tokens
upgradeAgent.upgradeFrom(msg.sender, value);
Upgrade(msg.sender, upgradeAgent, value);
}
/**
* Set an upgrade agent that handles
*/
function setUpgradeAgent(address agent) external {
if(!canUpgrade()) {
// The token is not yet in a state that we could think upgrading
throw;
}
if (agent == 0x0) throw;
// Only a master can designate the next agent
if (msg.sender != upgradeMaster) throw;
// Upgrade has already begun for an agent
if (getUpgradeState() == UpgradeState.Upgrading) throw;
upgradeAgent = UpgradeAgent(agent);
// Bad interface
if(!upgradeAgent.isUpgradeAgent()) throw;
// Make sure that token supplies match in source and target
if (upgradeAgent.originalSupply() != totalSupply) throw;
UpgradeAgentSet(upgradeAgent);
}
/**
* Get the state of the token upgrade.
*/
function getUpgradeState() public constant returns(UpgradeState) {
if(!canUpgrade()) return UpgradeState.NotAllowed;
else if(address(upgradeAgent) == 0x00) return UpgradeState.WaitingForAgent;
else if(totalUpgraded == 0) return UpgradeState.ReadyToUpgrade;
else return UpgradeState.Upgrading;
}
/**
* Change the upgrade master.
*
* This allows us to set a new owner for the upgrade mechanism.
*/
function setUpgradeMaster(address master) public {
if (master == 0x0) throw;
if (msg.sender != upgradeMaster) throw;
upgradeMaster = master;
}
/**
* Child contract can enable to provide the condition when the upgrade can begun.
*/
function canUpgrade() public constant returns(bool) {
return true;
}
}
/*
* Ownable
*
* Base contract with an owner.
* Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
*/
contract Ownable {
address public owner;
function Ownable() {
owner = msg.sender;
}
modifier onlyOwner() {
if (msg.sender != owner) {
throw;
}
_;
}
function transferOwnership(address newOwner) onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
/**
* Define interface for releasing the token transfer after a successful crowdsale.
*/
contract ReleasableToken is ERC20, Ownable {
/* The finalizer contract that allows unlift the transfer limits on this token */
address public releaseAgent;
/** A crowdsale contract can release us to the wild if ICO success. If false we are are in transfer lock up period.*/
bool public released = false;
/** Map of agents that are allowed to transfer tokens regardless of the lock down period. These are crowdsale contracts and possible the team multisig itself. */
mapping (address => bool) public transferAgents;
/**
* Limit token transfer until the crowdsale is over.
*
*/
modifier canTransfer(address _sender) {
if(!released) {
if(!transferAgents[_sender]) {
throw;
}
}
_;
}
/**
* Set the contract that can call release and make the token transferable.
*
* Design choice. Allow reset the release agent to fix fat finger mistakes.
*/
function setReleaseAgent(address addr) onlyOwner inReleaseState(false) public {
// We don't do interface check here as we might want to a normal wallet address to act as a release agent
releaseAgent = addr;
}
/**
* Owner can allow a particular address (a crowdsale contract) to transfer tokens despite the lock up period.
*/
function setTransferAgent(address addr, bool state) onlyOwner inReleaseState(false) public {
transferAgents[addr] = state;
}
/**
* One way function to release the tokens to the wild.
*
* Can be called only from the release agent that is the final ICO contract. It is only called if the crowdsale has been success (first milestone reached).
*/
function releaseTokenTransfer() public onlyReleaseAgent {
released = true;
}
/** The function can be called only before or after the tokens have been releasesd */
modifier inReleaseState(bool releaseState) {
if(releaseState != released) {
throw;
}
_;
}
/** The function can be called only by a whitelisted release agent. */
modifier onlyReleaseAgent() {
if(msg.sender != releaseAgent) {
throw;
}
_;
}
function transfer(address _to, uint _value) canTransfer(msg.sender) returns (bool success) {
// Call StandardToken.transfer()
return super.transfer(_to, _value);
}
function transferFrom(address _from, address _to, uint _value) canTransfer(_from) returns (bool success) {
// Call StandardToken.transferForm()
return super.transferFrom(_from, _to, _value);
}
}
/**
* Safe unsigned safe math.
*
* https://blog.aragon.one/library-driven-development-in-solidity-2bebcaf88736#.750gwtwli
*
* Originally from https://raw.githubusercontent.com/AragonOne/zeppelin-solidity/master/contracts/SafeMathLib.sol
*
* Maintained here until merged to mainline zeppelin-solidity.
*
*/
library SafeMathLib {
function times(uint a, uint b) returns (uint) {
uint c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function minus(uint a, uint b) returns (uint) {
assert(b <= a);
return a - b;
}
function plus(uint a, uint b) returns (uint) {
uint c = a + b;
assert(c>=a);
return c;
}
function assert(bool assertion) private {
if (!assertion) throw;
}
}
/**
* A token that can increase its supply by another contract.
*
* This allows uncapped crowdsale by dynamically increasing the supply when money pours in.
* Only mint agents, contracts whitelisted by owner, can mint new tokens.
*
*/
contract MintableToken is StandardToken, Ownable {
using SafeMathLib for uint;
bool public mintingFinished = false;
/** List of agents that are allowed to create new tokens */
mapping (address => bool) public mintAgents;
event MintingAgentChanged(address addr, bool state );
/**
* Create new tokens and allocate them to an address..
*
* Only callably by a crowdsale contract (mint agent).
*/
function mint(address receiver, uint amount) onlyMintAgent canMint public {
totalSupply = totalSupply.plus(amount);
balances[receiver] = balances[receiver].plus(amount);
// This will make the mint transaction apper in EtherScan.io
// We can remove this after there is a standardized minting event
Transfer(0, receiver, amount);
}
/**
* Owner can allow a crowdsale contract to mint new tokens.
*/
function setMintAgent(address addr, bool state) onlyOwner canMint public {
mintAgents[addr] = state;
MintingAgentChanged(addr, state);
}
modifier onlyMintAgent() {
// Only crowdsale contracts are allowed to mint new tokens
if(!mintAgents[msg.sender]) {
throw;
}
_;
}
/** Make sure we are not done yet. */
modifier canMint() {
if(mintingFinished) throw;
_;
}
}
/**
* A crowdsaled token.
*
* An ERC-20 token designed specifically for crowdsales with investor protection and further development path.
*
* - The token transfer() is disabled until the crowdsale is over
* - The token contract gives an opt-in upgrade path to a new contract
* - The same token can be part of several crowdsales through approve() mechanism
* - The token can be capped (supply set in the constructor) or uncapped (crowdsale contract can mint new tokens)
*
*/
contract CrowdsaleToken is ReleasableToken, MintableToken, UpgradeableToken {
/** Name and symbol were updated. */
event UpdatedTokenInformation(string newName, string newSymbol);
string public name;
string public symbol;
uint public decimals;
/**
* Construct the token.
*
* This token must be created through a team multisig wallet, so that it is owned by that wallet.
*
* @param _name Token name
* @param _symbol Token symbol - should be all caps
* @param _initialSupply How many tokens we start with
* @param _decimals Number of decimal places
* @param _mintable Are new tokens created over the crowdsale or do we distribute only the initial supply? Note that when the token becomes transferable the minting always ends.
*/
function CrowdsaleToken(string _name, string _symbol, uint _initialSupply, uint _decimals, bool _mintable)
UpgradeableToken(msg.sender) {
// Create any address, can be transferred
// to team multisig via changeOwner(),
// also remember to call setUpgradeMaster()
owner = msg.sender;
name = _name;
symbol = _symbol;
totalSupply = _initialSupply;
decimals = _decimals;
// Create initially all balance on the team multisig
balances[owner] = totalSupply;
if(totalSupply > 0) {
Minted(owner, totalSupply);
}
// No more new supply allowed after the token creation
if(!_mintable) {
mintingFinished = true;
if(totalSupply == 0) {
throw; // Cannot create a token without supply and no minting
}
}
}
/**
* When token is released to be transferable, enforce no new tokens can be created.
*/
function releaseTokenTransfer() public onlyReleaseAgent {
mintingFinished = true;
super.releaseTokenTransfer();
}
/**
* Allow upgrade agent functionality kick in only if the crowdsale was success.
*/
function canUpgrade() public constant returns(bool) {
return released && super.canUpgrade();
}
/**
* Owner can update token information here.
*
* It is often useful to conceal the actual token association, until
* the token operations, like central issuance or reissuance have been completed.
*
* This function allows the token owner to rename the token after the operations
* have been completed and then point the audience to use the token contract.
*/
function setTokenInformation(string _name, string _symbol) onlyOwner {
name = _name;
symbol = _symbol;
UpdatedTokenInformation(name, symbol);
}
}File 5 of 7: FeeBurner
{"ERC20Interface.sol":{"content":"pragma solidity 0.4.18;\n\n\n// https://github.com/ethereum/EIPs/issues/20\ninterface ERC20 {\n function totalSupply() public view returns (uint supply);\n function balanceOf(address _owner) public view returns (uint balance);\n function transfer(address _to, uint _value) public returns (bool success);\n function transferFrom(address _from, address _to, uint _value) public returns (bool success);\n function approve(address _spender, uint _value) public returns (bool success);\n function allowance(address _owner, address _spender) public view returns (uint remaining);\n function decimals() public view returns(uint digits);\n event Approval(address indexed _owner, address indexed _spender, uint _value);\n}\n"},"FeeBurner.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./FeeBurnerInterface.sol\";\nimport \"./Withdrawable.sol\";\nimport \"./Utils3.sol\";\nimport \"./KyberNetworkInterface.sol\";\n\n\ninterface BurnableToken {\n function transferFrom(address _from, address _to, uint _value) public returns (bool);\n function burnFrom(address _from, uint256 _value) public returns (bool);\n}\n\n\ncontract FeeBurner is Withdrawable, FeeBurnerInterface, Utils3 {\n\n mapping(address=\u003euint) public reserveFeesInBps;\n mapping(address=\u003eaddress) public reserveKNCWallet; //wallet holding knc per reserve. from here burn and send fees.\n mapping(address=\u003euint) public walletFeesInBps; // wallet that is the source of tx is entitled so some fees.\n mapping(address=\u003euint) public reserveFeeToBurn;\n mapping(address=\u003euint) public feePayedPerReserve; // track burned fees and sent wallet fees per reserve.\n mapping(address=\u003emapping(address=\u003euint)) public reserveFeeToWallet;\n address public taxWallet;\n uint public taxFeeBps = 0; // burned fees are taxed. % out of burned fees.\n\n BurnableToken public knc;\n KyberNetworkInterface public kyberNetwork;\n uint public kncPerEthRatePrecision = 600 * PRECISION; //--\u003e 1 ether = 600 knc tokens\n\n function FeeBurner(\n address _admin,\n BurnableToken _kncToken,\n KyberNetworkInterface _kyberNetwork,\n uint _initialKncToEthRatePrecision\n )\n public\n {\n require(_admin != address(0));\n require(_kncToken != address(0));\n require(_kyberNetwork != address(0));\n require(_initialKncToEthRatePrecision != 0);\n\n kyberNetwork = _kyberNetwork;\n admin = _admin;\n knc = _kncToken;\n kncPerEthRatePrecision = _initialKncToEthRatePrecision;\n }\n\n event ReserveDataSet(address reserve, uint feeInBps, address kncWallet);\n\n function setReserveData(address reserve, uint feesInBps, address kncWallet) public onlyOperator {\n require(feesInBps \u003c 100); // make sure it is always \u003c 1%\n require(kncWallet != address(0));\n reserveFeesInBps[reserve] = feesInBps;\n reserveKNCWallet[reserve] = kncWallet;\n ReserveDataSet(reserve, feesInBps, kncWallet);\n }\n\n event WalletFeesSet(address wallet, uint feesInBps);\n\n function setWalletFees(address wallet, uint feesInBps) public onlyAdmin {\n require(feesInBps \u003c 10000); // under 100%\n walletFeesInBps[wallet] = feesInBps;\n WalletFeesSet(wallet, feesInBps);\n }\n\n event TaxFeesSet(uint feesInBps);\n\n function setTaxInBps(uint _taxFeeBps) public onlyAdmin {\n require(_taxFeeBps \u003c 10000); // under 100%\n taxFeeBps = _taxFeeBps;\n TaxFeesSet(_taxFeeBps);\n }\n\n event TaxWalletSet(address taxWallet);\n\n function setTaxWallet(address _taxWallet) public onlyAdmin {\n require(_taxWallet != address(0));\n taxWallet = _taxWallet;\n TaxWalletSet(_taxWallet);\n }\n\n event KNCRateSet(uint ethToKncRatePrecision, uint kyberEthKnc, uint kyberKncEth, address updater);\n\n function setKNCRate() public {\n //query kyber for knc rate sell and buy\n uint kyberEthKncRate;\n uint kyberKncEthRate;\n (kyberEthKncRate, ) = kyberNetwork.getExpectedRate(ETH_TOKEN_ADDRESS, ERC20(knc), (10 ** 18));\n (kyberKncEthRate, ) = kyberNetwork.getExpectedRate(ERC20(knc), ETH_TOKEN_ADDRESS, (10 ** 18));\n\n //check \"reasonable\" spread == diff not too big. rate wasn\u0027t tampered.\n require(kyberEthKncRate * kyberKncEthRate \u003c PRECISION ** 2 * 2);\n require(kyberEthKncRate * kyberKncEthRate \u003e PRECISION ** 2 / 2);\n\n require(kyberEthKncRate \u003c= MAX_RATE);\n kncPerEthRatePrecision = kyberEthKncRate;\n KNCRateSet(kncPerEthRatePrecision, kyberEthKncRate, kyberKncEthRate, msg.sender);\n }\n\n event AssignFeeToWallet(address reserve, address wallet, uint walletFee);\n event AssignBurnFees(address reserve, uint burnFee);\n\n function handleFees(uint tradeWeiAmount, address reserve, address wallet) public returns(bool) {\n require(msg.sender == address(kyberNetwork));\n require(tradeWeiAmount \u003c= MAX_QTY);\n\n // MAX_DECIMALS = 18 = KNC_DECIMALS, use this value instead of calling getDecimals() to save gas\n uint kncAmount = calcDestAmountWithDecimals(ETH_DECIMALS, MAX_DECIMALS, tradeWeiAmount, kncPerEthRatePrecision);\n uint fee = kncAmount * reserveFeesInBps[reserve] / 10000;\n\n uint walletFee = fee * walletFeesInBps[wallet] / 10000;\n require(fee \u003e= walletFee);\n uint feeToBurn = fee - walletFee;\n\n if (walletFee \u003e 0) {\n reserveFeeToWallet[reserve][wallet] += walletFee;\n AssignFeeToWallet(reserve, wallet, walletFee);\n }\n\n if (feeToBurn \u003e 0) {\n AssignBurnFees(reserve, feeToBurn);\n reserveFeeToBurn[reserve] += feeToBurn;\n }\n\n return true;\n }\n\n event BurnAssignedFees(address indexed reserve, address sender, uint quantity);\n\n event SendTaxFee(address indexed reserve, address sender, address taxWallet, uint quantity);\n\n // this function is callable by anyone\n function burnReserveFees(address reserve) public {\n uint burnAmount = reserveFeeToBurn[reserve];\n uint taxToSend = 0;\n require(burnAmount \u003e 2);\n reserveFeeToBurn[reserve] = 1; // leave 1 twei to avoid spikes in gas fee\n if (taxWallet != address(0) \u0026\u0026 taxFeeBps != 0) {\n taxToSend = (burnAmount - 1) * taxFeeBps / 10000;\n require(burnAmount - 1 \u003e taxToSend);\n burnAmount -= taxToSend;\n if (taxToSend \u003e 0) {\n require(knc.transferFrom(reserveKNCWallet[reserve], taxWallet, taxToSend));\n SendTaxFee(reserve, msg.sender, taxWallet, taxToSend);\n }\n }\n require(knc.burnFrom(reserveKNCWallet[reserve], burnAmount - 1));\n\n //update reserve \"payments\" so far\n feePayedPerReserve[reserve] += (taxToSend + burnAmount - 1);\n\n BurnAssignedFees(reserve, msg.sender, (burnAmount - 1));\n }\n\n event SendWalletFees(address indexed wallet, address reserve, address sender);\n\n // this function is callable by anyone\n function sendFeeToWallet(address wallet, address reserve) public {\n uint feeAmount = reserveFeeToWallet[reserve][wallet];\n require(feeAmount \u003e 1);\n reserveFeeToWallet[reserve][wallet] = 1; // leave 1 twei to avoid spikes in gas fee\n require(knc.transferFrom(reserveKNCWallet[reserve], wallet, feeAmount - 1));\n\n feePayedPerReserve[reserve] += (feeAmount - 1);\n SendWalletFees(wallet, reserve, msg.sender);\n }\n}\n"},"FeeBurnerInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\ninterface FeeBurnerInterface {\n function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);\n function setReserveData(address reserve, uint feesInBps, address kncWallet) public;\n}\n"},"KyberNetworkInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\n/// @title Kyber Network interface\ninterface KyberNetworkInterface {\n function maxGasPrice() public view returns(uint);\n function getUserCapInWei(address user) public view returns(uint);\n function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);\n function enabled() public view returns(bool);\n function info(bytes32 id) public view returns(uint);\n\n function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view\n returns (uint expectedRate, uint slippageRate);\n\n function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,\n uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);\n}\n"},"PermissionGroups.sol":{"content":"pragma solidity 0.4.18;\n\n\ncontract PermissionGroups {\n\n address public admin;\n address public pendingAdmin;\n mapping(address=\u003ebool) internal operators;\n mapping(address=\u003ebool) internal alerters;\n address[] internal operatorsGroup;\n address[] internal alertersGroup;\n uint constant internal MAX_GROUP_SIZE = 50;\n\n function PermissionGroups() public {\n admin = msg.sender;\n }\n\n modifier onlyAdmin() {\n require(msg.sender == admin);\n _;\n }\n\n modifier onlyOperator() {\n require(operators[msg.sender]);\n _;\n }\n\n modifier onlyAlerter() {\n require(alerters[msg.sender]);\n _;\n }\n\n function getOperators () external view returns(address[]) {\n return operatorsGroup;\n }\n\n function getAlerters () external view returns(address[]) {\n return alertersGroup;\n }\n\n event TransferAdminPending(address pendingAdmin);\n\n /**\n * @dev Allows the current admin to set the pendingAdmin address.\n * @param newAdmin The address to transfer ownership to.\n */\n function transferAdmin(address newAdmin) public onlyAdmin {\n require(newAdmin != address(0));\n TransferAdminPending(pendingAdmin);\n pendingAdmin = newAdmin;\n }\n\n /**\n * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.\n * @param newAdmin The address to transfer ownership to.\n */\n function transferAdminQuickly(address newAdmin) public onlyAdmin {\n require(newAdmin != address(0));\n TransferAdminPending(newAdmin);\n AdminClaimed(newAdmin, admin);\n admin = newAdmin;\n }\n\n event AdminClaimed( address newAdmin, address previousAdmin);\n\n /**\n * @dev Allows the pendingAdmin address to finalize the change admin process.\n */\n function claimAdmin() public {\n require(pendingAdmin == msg.sender);\n AdminClaimed(pendingAdmin, admin);\n admin = pendingAdmin;\n pendingAdmin = address(0);\n }\n\n event AlerterAdded (address newAlerter, bool isAdd);\n\n function addAlerter(address newAlerter) public onlyAdmin {\n require(!alerters[newAlerter]); // prevent duplicates.\n require(alertersGroup.length \u003c MAX_GROUP_SIZE);\n\n AlerterAdded(newAlerter, true);\n alerters[newAlerter] = true;\n alertersGroup.push(newAlerter);\n }\n\n function removeAlerter (address alerter) public onlyAdmin {\n require(alerters[alerter]);\n alerters[alerter] = false;\n\n for (uint i = 0; i \u003c alertersGroup.length; ++i) {\n if (alertersGroup[i] == alerter) {\n alertersGroup[i] = alertersGroup[alertersGroup.length - 1];\n alertersGroup.length--;\n AlerterAdded(alerter, false);\n break;\n }\n }\n }\n\n event OperatorAdded(address newOperator, bool isAdd);\n\n function addOperator(address newOperator) public onlyAdmin {\n require(!operators[newOperator]); // prevent duplicates.\n require(operatorsGroup.length \u003c MAX_GROUP_SIZE);\n\n OperatorAdded(newOperator, true);\n operators[newOperator] = true;\n operatorsGroup.push(newOperator);\n }\n\n function removeOperator (address operator) public onlyAdmin {\n require(operators[operator]);\n operators[operator] = false;\n\n for (uint i = 0; i \u003c operatorsGroup.length; ++i) {\n if (operatorsGroup[i] == operator) {\n operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];\n operatorsGroup.length -= 1;\n OperatorAdded(operator, false);\n break;\n }\n }\n }\n}\n"},"Utils.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\n/// @title Kyber constants contract\ncontract Utils {\n\n ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);\n uint constant internal PRECISION = (10**18);\n uint constant internal MAX_QTY = (10**28); // 10B tokens\n uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH\n uint constant internal MAX_DECIMALS = 18;\n uint constant internal ETH_DECIMALS = 18;\n mapping(address=\u003euint) internal decimals;\n\n function setDecimals(ERC20 token) internal {\n if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;\n else decimals[token] = token.decimals();\n }\n\n function getDecimals(ERC20 token) internal view returns(uint) {\n if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access\n uint tokenDecimals = decimals[token];\n // technically, there might be token with decimals 0\n // moreover, very possible that old tokens have decimals 0\n // these tokens will just have higher gas fees.\n if(tokenDecimals == 0) return token.decimals();\n\n return tokenDecimals;\n }\n\n function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {\n require(srcQty \u003c= MAX_QTY);\n require(rate \u003c= MAX_RATE);\n\n if (dstDecimals \u003e= srcDecimals) {\n require((dstDecimals - srcDecimals) \u003c= MAX_DECIMALS);\n return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;\n } else {\n require((srcDecimals - dstDecimals) \u003c= MAX_DECIMALS);\n return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));\n }\n }\n\n function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {\n require(dstQty \u003c= MAX_QTY);\n require(rate \u003c= MAX_RATE);\n \n //source quantity is rounded up. to avoid dest quantity being too low.\n uint numerator;\n uint denominator;\n if (srcDecimals \u003e= dstDecimals) {\n require((srcDecimals - dstDecimals) \u003c= MAX_DECIMALS);\n numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));\n denominator = rate;\n } else {\n require((dstDecimals - srcDecimals) \u003c= MAX_DECIMALS);\n numerator = (PRECISION * dstQty);\n denominator = (rate * (10**(dstDecimals - srcDecimals)));\n }\n return (numerator + denominator - 1) / denominator; //avoid rounding down errors\n }\n}\n"},"Utils2.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./Utils.sol\";\n\n\ncontract Utils2 is Utils {\n\n /// @dev get the balance of a user.\n /// @param token The token type\n /// @return The balance\n function getBalance(ERC20 token, address user) public view returns(uint) {\n if (token == ETH_TOKEN_ADDRESS)\n return user.balance;\n else\n return token.balanceOf(user);\n }\n\n function getDecimalsSafe(ERC20 token) internal returns(uint) {\n\n if (decimals[token] == 0) {\n setDecimals(token);\n }\n\n return decimals[token];\n }\n\n function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {\n return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);\n }\n\n function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {\n return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);\n }\n\n function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)\n internal pure returns(uint)\n {\n require(srcAmount \u003c= MAX_QTY);\n require(destAmount \u003c= MAX_QTY);\n\n if (dstDecimals \u003e= srcDecimals) {\n require((dstDecimals - srcDecimals) \u003c= MAX_DECIMALS);\n return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));\n } else {\n require((srcDecimals - dstDecimals) \u003c= MAX_DECIMALS);\n return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);\n }\n }\n}\n"},"Utils3.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./Utils2.sol\";\n\n\ncontract Utils3 is Utils2 {\n\n function calcDestAmountWithDecimals(uint srcDecimals, uint destDecimals, uint srcAmount, uint rate) internal pure returns(uint) {\n return calcDstQty(srcAmount, srcDecimals, destDecimals, rate);\n }\n\n}\n"},"Withdrawable.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\nimport \"./PermissionGroups.sol\";\n\n\n/**\n * @title Contracts that should be able to recover tokens or ethers\n * @author Ilan Doron\n * @dev This allows to recover any tokens or Ethers received in a contract.\n * This will prevent any accidental loss of tokens.\n */\ncontract Withdrawable is PermissionGroups {\n\n event TokenWithdraw(ERC20 token, uint amount, address sendTo);\n\n /**\n * @dev Withdraw all ERC20 compatible tokens\n * @param token ERC20 The address of the token contract\n */\n function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {\n require(token.transfer(sendTo, amount));\n TokenWithdraw(token, amount, sendTo);\n }\n\n event EtherWithdraw(uint amount, address sendTo);\n\n /**\n * @dev Withdraw Ethers\n */\n function withdrawEther(uint amount, address sendTo) external onlyAdmin {\n sendTo.transfer(amount);\n EtherWithdraw(amount, sendTo);\n }\n}\n"}}File 6 of 7: ConversionRates
pragma solidity 0.4.18;
interface ConversionRatesInterface {
function recordImbalance(
ERC20 token,
int buyAmount,
uint rateUpdateBlock,
uint currentBlock
)
public;
function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);
}
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
contract VolumeImbalanceRecorder is Withdrawable {
uint constant internal SLIDING_WINDOW_SIZE = 5;
uint constant internal POW_2_64 = 2 ** 64;
struct TokenControlInfo {
uint minimalRecordResolution; // can be roughly 1 cent
uint maxPerBlockImbalance; // in twei resolution
uint maxTotalImbalance; // max total imbalance (between rate updates)
// before halting trade
}
mapping(address => TokenControlInfo) internal tokenControlInfo;
struct TokenImbalanceData {
int lastBlockBuyUnitsImbalance;
uint lastBlock;
int totalBuyUnitsImbalance;
uint lastRateUpdateBlock;
}
mapping(address => mapping(uint=>uint)) public tokenImbalanceData;
function VolumeImbalanceRecorder(address _admin) public {
require(_admin != address(0));
admin = _admin;
}
function setTokenControlInfo(
ERC20 token,
uint minimalRecordResolution,
uint maxPerBlockImbalance,
uint maxTotalImbalance
)
public
onlyAdmin
{
tokenControlInfo[token] =
TokenControlInfo(
minimalRecordResolution,
maxPerBlockImbalance,
maxTotalImbalance
);
}
function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) {
return (tokenControlInfo[token].minimalRecordResolution,
tokenControlInfo[token].maxPerBlockImbalance,
tokenControlInfo[token].maxTotalImbalance);
}
function addImbalance(
ERC20 token,
int buyAmount,
uint rateUpdateBlock,
uint currentBlock
)
internal
{
uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE;
int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution));
int prevImbalance = 0;
TokenImbalanceData memory currentBlockData =
decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]);
// first scenario - this is not the first tx in the current block
if (currentBlockData.lastBlock == currentBlock) {
if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) {
// just increase imbalance
currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;
currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount;
} else {
// imbalance was changed in the middle of the block
prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock);
currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;
currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;
currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);
}
} else {
// first tx in the current block
int currentBlockImbalance;
(prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock);
currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount;
currentBlockData.lastBlock = uint(currentBlock);
currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);
currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;
}
tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData);
}
function setGarbageToVolumeRecorder(ERC20 token) internal {
for (uint i = 0; i < SLIDING_WINDOW_SIZE; i++) {
tokenImbalanceData[token][i] = 0x1;
}
}
function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) {
// check the imbalance in the sliding window
require(startBlock <= endBlock);
buyImbalance = 0;
for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) {
TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);
if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) {
buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance);
}
}
}
function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock)
internal view
returns(int buyImbalance, int currentBlockImbalance)
{
buyImbalance = 0;
currentBlockImbalance = 0;
uint latestBlock = 0;
int imbalanceInRange = 0;
uint startBlock = rateUpdateBlock;
uint endBlock = currentBlock;
for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) {
TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);
if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) {
imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance;
}
if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue;
if (perBlockData.lastBlock < latestBlock) continue;
latestBlock = perBlockData.lastBlock;
buyImbalance = perBlockData.totalBuyUnitsImbalance;
if (uint(perBlockData.lastBlock) == currentBlock) {
currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance;
}
}
if (buyImbalance == 0) {
buyImbalance = imbalanceInRange;
}
}
function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock)
internal view
returns(int totalImbalance, int currentBlockImbalance)
{
int resolution = int(tokenControlInfo[token].minimalRecordResolution);
(totalImbalance, currentBlockImbalance) =
getImbalanceSinceRateUpdate(
token,
rateUpdateBlock,
currentBlock);
totalImbalance *= resolution;
currentBlockImbalance *= resolution;
}
function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) {
return tokenControlInfo[token].maxPerBlockImbalance;
}
function getMaxTotalImbalance(ERC20 token) internal view returns(uint) {
return tokenControlInfo[token].maxTotalImbalance;
}
function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) {
// check for overflows
require(data.lastBlockBuyUnitsImbalance < int(POW_2_64 / 2));
require(data.lastBlockBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2));
require(data.lastBlock < POW_2_64);
require(data.totalBuyUnitsImbalance < int(POW_2_64 / 2));
require(data.totalBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2));
require(data.lastRateUpdateBlock < POW_2_64);
// do encoding
uint result = uint(data.lastBlockBuyUnitsImbalance) & (POW_2_64 - 1);
result |= data.lastBlock * POW_2_64;
result |= (uint(data.totalBuyUnitsImbalance) & (POW_2_64 - 1)) * POW_2_64 * POW_2_64;
result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64;
return result;
}
function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) {
TokenImbalanceData memory data;
data.lastBlockBuyUnitsImbalance = int(int64(input & (POW_2_64 - 1)));
data.lastBlock = uint(uint64((input / POW_2_64) & (POW_2_64 - 1)));
data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) & (POW_2_64 - 1)));
data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64))));
return data;
}
}
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
contract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils {
// bps - basic rate steps. one step is 1 / 10000 of the rate.
struct StepFunction {
int[] x; // quantity for each step. Quantity of each step includes previous steps.
int[] y; // rate change per quantity step in bps.
}
struct TokenData {
bool listed; // was added to reserve
bool enabled; // whether trade is enabled
// position in the compact data
uint compactDataArrayIndex;
uint compactDataFieldIndex;
// rate data. base and changes according to quantity and reserve balance.
// generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction.
uint baseBuyRate; // in PRECISION units. see KyberConstants
uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate
StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate.
StepFunction sellRateQtyStepFunction;// in bps. higher the qua
StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate.
StepFunction sellRateImbalanceStepFunction;
}
/*
this is the data for tokenRatesCompactData
but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write
so we represent it as bytes32 and do the byte tricks ourselves.
struct TokenRatesCompactData {
bytes14 buy; // change buy rate of token from baseBuyRate in 10 bps
bytes14 sell; // change sell rate of token from baseSellRate in 10 bps
uint32 blockNumber;
} */
uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks
ERC20[] internal listedTokens;
mapping(address=>TokenData) internal tokenData;
bytes32[] internal tokenRatesCompactData;
uint public numTokensInCurrentCompactData = 0;
address public reserveContract;
uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14;
uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA));
uint constant internal MAX_STEPS_IN_FUNCTION = 10;
int constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B %
int constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100%
function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin)
{ } // solhint-disable-line no-empty-blocks
function addToken(ERC20 token) public onlyAdmin {
require(!tokenData[token].listed);
tokenData[token].listed = true;
listedTokens.push(token);
if (numTokensInCurrentCompactData == 0) {
tokenRatesCompactData.length++; // add new structure
}
tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1;
tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData;
numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA;
setGarbageToVolumeRecorder(token);
setDecimals(token);
}
function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator {
require(buy.length == sell.length);
require(indices.length == buy.length);
require(blockNumber <= 0xFFFFFFFF);
uint bytes14Offset = BYTES_14_OFFSET;
for (uint i = 0; i < indices.length; i++) {
require(indices[i] < tokenRatesCompactData.length);
uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset));
tokenRatesCompactData[indices[i]] = bytes32(data);
}
}
function setBaseRate(
ERC20[] tokens,
uint[] baseBuy,
uint[] baseSell,
bytes14[] buy,
bytes14[] sell,
uint blockNumber,
uint[] indices
)
public
onlyOperator
{
require(tokens.length == baseBuy.length);
require(tokens.length == baseSell.length);
require(sell.length == buy.length);
require(sell.length == indices.length);
for (uint ind = 0; ind < tokens.length; ind++) {
require(tokenData[tokens[ind]].listed);
tokenData[tokens[ind]].baseBuyRate = baseBuy[ind];
tokenData[tokens[ind]].baseSellRate = baseSell[ind];
}
setCompactData(buy, sell, blockNumber, indices);
}
function setQtyStepFunction(
ERC20 token,
int[] xBuy,
int[] yBuy,
int[] xSell,
int[] ySell
)
public
onlyOperator
{
require(xBuy.length == yBuy.length);
require(xSell.length == ySell.length);
require(xBuy.length <= MAX_STEPS_IN_FUNCTION);
require(xSell.length <= MAX_STEPS_IN_FUNCTION);
require(tokenData[token].listed);
tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy);
tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell);
}
function setImbalanceStepFunction(
ERC20 token,
int[] xBuy,
int[] yBuy,
int[] xSell,
int[] ySell
)
public
onlyOperator
{
require(xBuy.length == yBuy.length);
require(xSell.length == ySell.length);
require(xBuy.length <= MAX_STEPS_IN_FUNCTION);
require(xSell.length <= MAX_STEPS_IN_FUNCTION);
require(tokenData[token].listed);
tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy);
tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell);
}
function setValidRateDurationInBlocks(uint duration) public onlyAdmin {
validRateDurationInBlocks = duration;
}
function enableTokenTrade(ERC20 token) public onlyAdmin {
require(tokenData[token].listed);
require(tokenControlInfo[token].minimalRecordResolution != 0);
tokenData[token].enabled = true;
}
function disableTokenTrade(ERC20 token) public onlyAlerter {
require(tokenData[token].listed);
tokenData[token].enabled = false;
}
function setReserveAddress(address reserve) public onlyAdmin {
reserveContract = reserve;
}
function recordImbalance(
ERC20 token,
int buyAmount,
uint rateUpdateBlock,
uint currentBlock
)
public
{
require(msg.sender == reserveContract);
if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token);
return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock);
}
/* solhint-disable function-max-lines */
function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) {
// check if trade is enabled
if (!tokenData[token].enabled) return 0;
if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set
// get rate update block
bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
uint updateRateBlock = getLast4Bytes(compactData);
if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired
// check imbalance
int totalImbalance;
int blockImbalance;
(totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber);
// calculate actual rate
int imbalanceQty;
int extraBps;
int8 rateUpdate;
uint rate;
if (buy) {
// start with base rate
rate = tokenData[token].baseBuyRate;
// add rate update
rateUpdate = getRateByteFromCompactData(compactData, token, true);
extraBps = int(rateUpdate) * 10;
rate = addBps(rate, extraBps);
// compute token qty
qty = getTokenQty(token, rate, qty);
imbalanceQty = int(qty);
totalImbalance += imbalanceQty;
// add qty overhead
extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty));
rate = addBps(rate, extraBps);
// add imbalance overhead
extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance);
rate = addBps(rate, extraBps);
} else {
// start with base rate
rate = tokenData[token].baseSellRate;
// add rate update
rateUpdate = getRateByteFromCompactData(compactData, token, false);
extraBps = int(rateUpdate) * 10;
rate = addBps(rate, extraBps);
// compute token qty
imbalanceQty = -1 * int(qty);
totalImbalance += imbalanceQty;
// add qty overhead
extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty));
rate = addBps(rate, extraBps);
// add imbalance overhead
extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance);
rate = addBps(rate, extraBps);
}
if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0;
if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0;
return rate;
}
/* solhint-enable function-max-lines */
function getBasicRate(ERC20 token, bool buy) public view returns(uint) {
if (buy)
return tokenData[token].baseBuyRate;
else
return tokenData[token].baseSellRate;
}
function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) {
require(tokenData[token].listed);
uint arrayIndex = tokenData[token].compactDataArrayIndex;
uint fieldOffset = tokenData[token].compactDataFieldIndex;
return (
arrayIndex,
fieldOffset,
byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)),
byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false))
);
}
function getTokenBasicData(ERC20 token) public view returns(bool, bool) {
return (tokenData[token].listed, tokenData[token].enabled);
}
/* solhint-disable code-complexity */
function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) {
if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length);
if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param];
if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length);
if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param];
if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length);
if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param];
if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length);
if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param];
if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length);
if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param];
if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length);
if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param];
if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length);
if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param];
if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length);
if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param];
revert();
}
/* solhint-enable code-complexity */
function getRateUpdateBlock(ERC20 token) public view returns(uint) {
bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
return getLast4Bytes(compactData);
}
function getListedTokens() public view returns(ERC20[]) {
return listedTokens;
}
function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) {
uint dstDecimals = getDecimals(token);
uint srcDecimals = ETH_DECIMALS;
return calcDstQty(ethQty, srcDecimals, dstDecimals, rate);
}
function getLast4Bytes(bytes32 b) internal pure returns(uint) {
// cannot trust compiler with not turning bit operations into EXP opcode
return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET);
}
function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) {
uint fieldOffset = tokenData[token].compactDataFieldIndex;
uint byteOffset;
if (buy)
byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset;
else
byteOffset = 4 + fieldOffset;
return int8(data[byteOffset]);
}
function executeStepFunction(StepFunction f, int x) internal pure returns(int) {
uint len = f.y.length;
for (uint ind = 0; ind < len; ind++) {
if (x <= f.x[ind]) return f.y[ind];
}
return f.y[len-1];
}
function addBps(uint rate, int bps) internal pure returns(uint) {
require(rate <= MAX_RATE);
require(bps >= MIN_BPS_ADJUSTMENT);
require(bps <= MAX_BPS_ADJUSTMENT);
uint maxBps = 100 * 100;
return (rate * uint(int(maxBps) + bps)) / maxBps;
}
function abs(int x) internal pure returns(uint) {
if (x < 0)
return uint(-1 * x);
else
return uint(x);
}
}File 7 of 7: SanityRates
pragma solidity 0.4.18;
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
interface SanityRatesInterface {
function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);
}
contract SanityRates is SanityRatesInterface, Withdrawable, Utils {
mapping(address=>uint) public tokenRate;
mapping(address=>uint) public reasonableDiffInBps;
function SanityRates(address _admin) public {
require(_admin != address(0));
admin = _admin;
}
function setReasonableDiff(ERC20[] srcs, uint[] diff) public onlyAdmin {
require(srcs.length == diff.length);
for (uint i = 0; i < srcs.length; i++) {
require(diff[i] <= 100 * 100);
reasonableDiffInBps[srcs[i]] = diff[i];
}
}
function setSanityRates(ERC20[] srcs, uint[] rates) public onlyOperator {
require(srcs.length == rates.length);
for (uint i = 0; i < srcs.length; i++) {
require(rates[i] <= MAX_RATE);
tokenRate[srcs[i]] = rates[i];
}
}
function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint) {
if (src != ETH_TOKEN_ADDRESS && dest != ETH_TOKEN_ADDRESS) return 0;
uint rate;
address token;
if (src == ETH_TOKEN_ADDRESS) {
rate = (PRECISION*PRECISION)/tokenRate[dest];
token = dest;
} else {
rate = tokenRate[src];
token = src;
}
return rate * (10000 + reasonableDiffInBps[token])/10000;
}
}