Transaction Hash:
Block:
7414086 at Mar-21-2019 07:43:00 PM +UTC
Transaction Fee:
0.0007566125 ETH
$1.76
Gas Used:
216,175 Gas / 3.5 Gwei
Emitted Events:
| 106 |
RCNToken.Transfer( _from=0x6924a03BB710EaF199AB6AC9F2BB148215AE9B5D, _to=KyberNetwork, _value=20413713569656918922250 )
|
| 107 |
KyberReserve.TradeExecute( origin=KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=3550000000000000000, destToken=RCNToken, destAmount=20413713569656918922250, destAddress=KyberNetwork )
|
| 108 |
RCNToken.Transfer( _from=KyberNetwork, _to=[Sender] 0x4869a74bb1c3ed6d702af35b115f5c6fbffdfb06, _value=20413713569656918922250 )
|
| 109 |
FeeBurner.AssignFeeToWallet( reserve=KyberReserve, wallet=0x440bBd6a...16874faa9, walletFee=664508535765600743 )
|
| 110 |
FeeBurner.AssignBurnFees( reserve=KyberReserve, burnFee=1550519916786401736 )
|
| 111 |
KyberNetwork.KyberTrade( trader=[Sender] 0x4869a74bb1c3ed6d702af35b115f5c6fbffdfb06, src=0xEeeeeEee...eeeeeEEeE, dest=RCNToken, srcAmount=3550000000000000000, dstAmount=20413713569656918922250, destAddress=[Sender] 0x4869a74bb1c3ed6d702af35b115f5c6fbffdfb06, ethWeiValue=3550000000000000000, reserve1=0x00000000...000000000, reserve2=KyberReserve, hint=0x5045524D )
|
| 112 |
KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0x4869a74bb1c3ed6d702af35b115f5c6fbffdfb06, src=0xEeeeeEee...eeeeeEEeE, dest=RCNToken, actualSrcAmount=3550000000000000000, actualDestAmount=20413713569656918922250 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x097B3B7C...2Cce6EF43 | |||||
| 0x21433Dec...f5aC2EC18 | 40.884090407217394311 Eth | 44.434090407217394311 Eth | 3.55 | ||
| 0x4869a74B...FBFfdFB06 |
7.4392847645 Eth
Nonce: 1
|
3.888528152 Eth
Nonce: 2
| 3.5507566125 | ||
| 0x52166528...1a4e128A3 | |||||
| 0x9ae49C0d...294E20950 | (Kyber: Old Contract) | ||||
|
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 539.343738533792528705 Eth | 539.344495146292528705 Eth | 0.0007566125 | |
| 0xF970b8E3...8D83375A6 |
Execution Trace
ETH 3.55
KyberNetworkProxy.tradeWithHint( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=3550000000000000000, dest=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, destAddress=0x4869a74Bb1c3eD6d702aF35B115F5c6FBFfdFB06, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=5583068995648124658860, walletId=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, hint=0x5045524D ) => ( 20413713569656918922250 )
-
RCNToken.balanceOf( _owner=0x4869a74Bb1c3eD6d702aF35B115F5c6FBFfdFB06 ) => ( balance=0 )
ETH 3.55
KyberNetwork.tradeWithHint( trader=0x4869a74Bb1c3eD6d702aF35B115F5c6FBFfdFB06, src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=3550000000000000000, dest=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, destAddress=0x4869a74Bb1c3eD6d702aF35B115F5c6FBFfdFB06, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=5583068995648124658860, walletId=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, hint=0x5045524D ) => ( 20413713569656918922250 )KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, srcQty=3550000000000000000, blockNumber=7414086 ) => ( 5750341850607582795000 )-
ConversionRates.getRate( token=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, currentBlockNumber=7414086, buy=True, qty=3550000000000000000 ) => ( 5750341850607582795000 ) -
RCNToken.CALL( )
-
RCNToken.balanceOf( _owner=0x6924a03BB710EaF199AB6AC9F2BB148215AE9B5D ) => ( balance=101742717465057567157043 )
-
RCNToken.allowance( _owner=0x6924a03BB710EaF199AB6AC9F2BB148215AE9B5D, _spender=0x21433Dec9Cb634A23c6A4BbcCe08c83f5aC2EC18 ) => ( remaining=115792089237316195423570985008687907853269984665640559164678594278699418689688 )
-
ETH 3.55
KyberReserve.trade( srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=3550000000000000000, destToken=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, destAddress=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, conversionRate=5750341850607582795000, validate=True ) => ( True )-
RCNToken.CALL( )
-
ConversionRates.recordImbalance( token=0xF970b8E36e23F7fC3FD752EeA86f8Be8D83375A6, buyAmount=20413713569656918922250, rateUpdateBlock=0, currentBlock=7414086 ) -
RCNToken.transferFrom( _from=0x6924a03BB710EaF199AB6AC9F2BB148215AE9B5D, _to=0x9ae49C0d7F8F9EF4B864e004FE86Ac8294E20950, _value=20413713569656918922250 ) => ( success=True )
-
-
RCNToken.transfer( _to=0x4869a74Bb1c3eD6d702aF35B115F5c6FBFfdFB06, _value=20413713569656918922250 ) => ( success=True )
FeeBurner.handleFees( tradeWeiAmount=3550000000000000000, reserve=0x21433Dec9Cb634A23c6A4BbcCe08c83f5aC2EC18, wallet=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9 ) => ( True )-
KyberNetworkCrystal.CALL( )
-
-
RCNToken.balanceOf( _owner=0x4869a74Bb1c3eD6d702aF35B115F5c6FBFfdFB06 ) => ( balance=20413713569656918922250 )
tradeWithHint[KyberNetworkProxy (ln:462)]
getBalance[KyberNetworkProxy (ln:480)]getBalance[KyberNetworkProxy (ln:481)]transferFrom[KyberNetworkProxy (ln:486)]value[KyberNetworkProxy (ln:489)]calculateTradeOutcome[KyberNetworkProxy (ln:501)]getBalance[KyberNetworkProxy (ln:568)]getBalance[KyberNetworkProxy (ln:569)]calcRateFromQty[KyberNetworkProxy (ln:578)]getDecimalsSafe[KyberNetworkProxy (ln:581)]getDecimalsSafe[KyberNetworkProxy (ln:582)]
ExecuteTrade[KyberNetworkProxy (ln:513)]
File 1 of 7: KyberNetworkProxy
File 2 of 7: KyberNetwork
File 3 of 7: RCNToken
File 4 of 7: KyberReserve
File 5 of 7: FeeBurner
File 6 of 7: ConversionRates
File 7 of 7: KyberNetworkCrystal
pragma solidity 0.4.18;
// File: contracts/ERC20Interface.sol
// https://github.com/ethereum/EIPs/issues/20
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
// File: contracts/KyberNetworkInterface.sol
/// @title Kyber Network interface
interface KyberNetworkInterface {
function maxGasPrice() public view returns(uint);
function getUserCapInWei(address user) public view returns(uint);
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
function enabled() public view returns(bool);
function info(bytes32 id) public view returns(uint);
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
returns (uint expectedRate, uint slippageRate);
function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,
uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
}
// File: contracts/KyberNetworkProxyInterface.sol
/// @title Kyber Network interface
interface KyberNetworkProxyInterface {
function maxGasPrice() public view returns(uint);
function getUserCapInWei(address user) public view returns(uint);
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
function enabled() public view returns(bool);
function info(bytes32 id) public view returns(uint);
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
returns (uint expectedRate, uint slippageRate);
function tradeWithHint(ERC20 src, uint srcAmount, ERC20 dest, address destAddress, uint maxDestAmount,
uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
}
// File: contracts/SimpleNetworkInterface.sol
/// @title simple interface for Kyber Network
interface SimpleNetworkInterface {
function swapTokenToToken(ERC20 src, uint srcAmount, ERC20 dest, uint minConversionRate) public returns(uint);
function swapEtherToToken(ERC20 token, uint minConversionRate) public payable returns(uint);
function swapTokenToEther(ERC20 token, uint srcAmount, uint minConversionRate) public returns(uint);
}
// File: contracts/Utils.sol
/// @title Kyber constants contract
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
// File: contracts/Utils2.sol
contract Utils2 is Utils {
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(ERC20 token, address user) public view returns(uint) {
if (token == ETH_TOKEN_ADDRESS)
return user.balance;
else
return token.balanceOf(user);
}
function getDecimalsSafe(ERC20 token) internal returns(uint) {
if (decimals[token] == 0) {
setDecimals(token);
}
return decimals[token];
}
function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)
internal pure returns(uint)
{
require(srcAmount <= MAX_QTY);
require(destAmount <= MAX_QTY);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);
}
}
}
// File: contracts/PermissionGroups.sol
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
// File: contracts/Withdrawable.sol
/**
* @title Contracts that should be able to recover tokens or ethers
* @author Ilan Doron
* @dev This allows to recover any tokens or Ethers received in a contract.
* This will prevent any accidental loss of tokens.
*/
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
// File: contracts/KyberNetworkProxy.sol
////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @title Kyber Network proxy for main contract
contract KyberNetworkProxy is KyberNetworkProxyInterface, SimpleNetworkInterface, Withdrawable, Utils2 {
KyberNetworkInterface public kyberNetworkContract;
function KyberNetworkProxy(address _admin) public {
require(_admin != address(0));
admin = _admin;
}
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev makes a trade between src and dest token and send dest token to destAddress
/// @param src Src token
/// @param srcAmount amount of src tokens
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @param walletId is the wallet ID to send part of the fees
/// @return amount of actual dest tokens
function trade(
ERC20 src,
uint srcAmount,
ERC20 dest,
address destAddress,
uint maxDestAmount,
uint minConversionRate,
address walletId
)
public
payable
returns(uint)
{
bytes memory hint;
return tradeWithHint(
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
walletId,
hint
);
}
/// @dev makes a trade between src and dest token and send dest tokens to msg sender
/// @param src Src token
/// @param srcAmount amount of src tokens
/// @param dest Destination token
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @return amount of actual dest tokens
function swapTokenToToken(
ERC20 src,
uint srcAmount,
ERC20 dest,
uint minConversionRate
)
public
returns(uint)
{
bytes memory hint;
return tradeWithHint(
src,
srcAmount,
dest,
msg.sender,
MAX_QTY,
minConversionRate,
0,
hint
);
}
/// @dev makes a trade from Ether to token. Sends token to msg sender
/// @param token Destination token
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @return amount of actual dest tokens
function swapEtherToToken(ERC20 token, uint minConversionRate) public payable returns(uint) {
bytes memory hint;
return tradeWithHint(
ETH_TOKEN_ADDRESS,
msg.value,
token,
msg.sender,
MAX_QTY,
minConversionRate,
0,
hint
);
}
/// @dev makes a trade from token to Ether, sends Ether to msg sender
/// @param token Src token
/// @param srcAmount amount of src tokens
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @return amount of actual dest tokens
function swapTokenToEther(ERC20 token, uint srcAmount, uint minConversionRate) public returns(uint) {
bytes memory hint;
return tradeWithHint(
token,
srcAmount,
ETH_TOKEN_ADDRESS,
msg.sender,
MAX_QTY,
minConversionRate,
0,
hint
);
}
struct UserBalance {
uint srcBalance;
uint destBalance;
}
event ExecuteTrade(address indexed trader, ERC20 src, ERC20 dest, uint actualSrcAmount, uint actualDestAmount);
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev makes a trade between src and dest token and send dest token to destAddress
/// @param src Src token
/// @param srcAmount amount of src tokens
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade is canceled.
/// @param walletId is the wallet ID to send part of the fees
/// @param hint will give hints for the trade.
/// @return amount of actual dest tokens
function tradeWithHint(
ERC20 src,
uint srcAmount,
ERC20 dest,
address destAddress,
uint maxDestAmount,
uint minConversionRate,
address walletId,
bytes hint
)
public
payable
returns(uint)
{
require(src == ETH_TOKEN_ADDRESS || msg.value == 0);
UserBalance memory userBalanceBefore;
userBalanceBefore.srcBalance = getBalance(src, msg.sender);
userBalanceBefore.destBalance = getBalance(dest, destAddress);
if (src == ETH_TOKEN_ADDRESS) {
userBalanceBefore.srcBalance += msg.value;
} else {
require(src.transferFrom(msg.sender, kyberNetworkContract, srcAmount));
}
uint reportedDestAmount = kyberNetworkContract.tradeWithHint.value(msg.value)(
msg.sender,
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
walletId,
hint
);
TradeOutcome memory tradeOutcome = calculateTradeOutcome(
userBalanceBefore.srcBalance,
userBalanceBefore.destBalance,
src,
dest,
destAddress
);
require(reportedDestAmount == tradeOutcome.userDeltaDestAmount);
require(tradeOutcome.userDeltaDestAmount <= maxDestAmount);
require(tradeOutcome.actualRate >= minConversionRate);
ExecuteTrade(msg.sender, src, dest, tradeOutcome.userDeltaSrcAmount, tradeOutcome.userDeltaDestAmount);
return tradeOutcome.userDeltaDestAmount;
}
event KyberNetworkSet(address newNetworkContract, address oldNetworkContract);
function setKyberNetworkContract(KyberNetworkInterface _kyberNetworkContract) public onlyAdmin {
require(_kyberNetworkContract != address(0));
KyberNetworkSet(_kyberNetworkContract, kyberNetworkContract);
kyberNetworkContract = _kyberNetworkContract;
}
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty)
public view
returns(uint expectedRate, uint slippageRate)
{
return kyberNetworkContract.getExpectedRate(src, dest, srcQty);
}
function getUserCapInWei(address user) public view returns(uint) {
return kyberNetworkContract.getUserCapInWei(user);
}
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint) {
return kyberNetworkContract.getUserCapInTokenWei(user, token);
}
function maxGasPrice() public view returns(uint) {
return kyberNetworkContract.maxGasPrice();
}
function enabled() public view returns(bool) {
return kyberNetworkContract.enabled();
}
function info(bytes32 field) public view returns(uint) {
return kyberNetworkContract.info(field);
}
struct TradeOutcome {
uint userDeltaSrcAmount;
uint userDeltaDestAmount;
uint actualRate;
}
function calculateTradeOutcome (uint srcBalanceBefore, uint destBalanceBefore, ERC20 src, ERC20 dest,
address destAddress)
internal returns(TradeOutcome outcome)
{
uint userSrcBalanceAfter;
uint userDestBalanceAfter;
userSrcBalanceAfter = getBalance(src, msg.sender);
userDestBalanceAfter = getBalance(dest, destAddress);
//protect from underflow
require(userDestBalanceAfter > destBalanceBefore);
require(srcBalanceBefore > userSrcBalanceAfter);
outcome.userDeltaDestAmount = userDestBalanceAfter - destBalanceBefore;
outcome.userDeltaSrcAmount = srcBalanceBefore - userSrcBalanceAfter;
outcome.actualRate = calcRateFromQty(
outcome.userDeltaSrcAmount,
outcome.userDeltaDestAmount,
getDecimalsSafe(src),
getDecimalsSafe(dest)
);
}
}File 2 of 7: KyberNetwork
pragma solidity 0.4.18;
// File: contracts/ERC20Interface.sol
// https://github.com/ethereum/EIPs/issues/20
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
// File: contracts/KyberReserveInterface.sol
/// @title Kyber Reserve contract
interface KyberReserveInterface {
function trade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
public
payable
returns(bool);
function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint);
}
// File: contracts/KyberNetworkInterface.sol
/// @title Kyber Network interface
interface KyberNetworkInterface {
function maxGasPrice() public view returns(uint);
function getUserCapInWei(address user) public view returns(uint);
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
function enabled() public view returns(bool);
function info(bytes32 id) public view returns(uint);
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
returns (uint expectedRate, uint slippageRate);
function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,
uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
}
// File: contracts/PermissionGroups.sol
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
// File: contracts/Withdrawable.sol
/**
* @title Contracts that should be able to recover tokens or ethers
* @author Ilan Doron
* @dev This allows to recover any tokens or Ethers received in a contract.
* This will prevent any accidental loss of tokens.
*/
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
// File: contracts/Utils.sol
/// @title Kyber constants contract
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
// File: contracts/Utils2.sol
contract Utils2 is Utils {
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(ERC20 token, address user) public view returns(uint) {
if (token == ETH_TOKEN_ADDRESS)
return user.balance;
else
return token.balanceOf(user);
}
function getDecimalsSafe(ERC20 token) internal returns(uint) {
if (decimals[token] == 0) {
setDecimals(token);
}
return decimals[token];
}
function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)
internal pure returns(uint)
{
require(srcAmount <= MAX_QTY);
require(destAmount <= MAX_QTY);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);
}
}
}
// File: contracts/WhiteListInterface.sol
contract WhiteListInterface {
function getUserCapInWei(address user) external view returns (uint userCapWei);
}
// File: contracts/ExpectedRateInterface.sol
interface ExpectedRateInterface {
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty, bool usePermissionless) public view
returns (uint expectedRate, uint slippageRate);
}
// File: contracts/FeeBurnerInterface.sol
interface FeeBurnerInterface {
function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
function setReserveData(address reserve, uint feesInBps, address kncWallet) public;
}
// File: contracts/KyberNetwork.sol
/**
* @title Helps contracts guard against reentrancy attacks.
*/
contract ReentrancyGuard {
/// @dev counter to allow mutex lock with only one SSTORE operation
uint256 private guardCounter = 1;
/**
* @dev Prevents a function from calling itself, directly or indirectly.
* Calling one `nonReentrant` function from
* another is not supported. Instead, you can implement a
* `private` function doing the actual work, and an `external`
* wrapper marked as `nonReentrant`.
*/
modifier nonReentrant() {
guardCounter += 1;
uint256 localCounter = guardCounter;
_;
require(localCounter == guardCounter);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @title Kyber Network main contract
contract KyberNetwork is Withdrawable, Utils2, KyberNetworkInterface, ReentrancyGuard {
bytes public constant PERM_HINT = "PERM";
uint public constant PERM_HINT_GET_RATE = 1 << 255; // for get rate. bit mask hint.
uint public negligibleRateDiff = 10; // basic rate steps will be in 0.01%
KyberReserveInterface[] public reserves;
mapping(address=>ReserveType) public reserveType;
WhiteListInterface public whiteListContract;
ExpectedRateInterface public expectedRateContract;
FeeBurnerInterface public feeBurnerContract;
address public kyberNetworkProxyContract;
uint public maxGasPriceValue = 50 * 1000 * 1000 * 1000; // 50 gwei
bool public isEnabled = false; // network is enabled
mapping(bytes32=>uint) public infoFields; // this is only a UI field for external app.
mapping(address=>address[]) public reservesPerTokenSrc; //reserves supporting token to eth
mapping(address=>address[]) public reservesPerTokenDest;//reserves support eth to token
enum ReserveType {NONE, PERMISSIONED, PERMISSIONLESS}
bytes internal constant EMPTY_HINT = "";
function KyberNetwork(address _admin) public {
require(_admin != address(0));
admin = _admin;
}
event EtherReceival(address indexed sender, uint amount);
/* solhint-disable no-complex-fallback */
// To avoid users trying to swap tokens using default payable function. We added this short code
// to verify Ethers will be received only from reserves if transferred without a specific function call.
function() public payable {
require(reserveType[msg.sender] != ReserveType.NONE);
EtherReceival(msg.sender, msg.value);
}
/* solhint-enable no-complex-fallback */
struct TradeInput {
address trader;
ERC20 src;
uint srcAmount;
ERC20 dest;
address destAddress;
uint maxDestAmount;
uint minConversionRate;
address walletId;
bytes hint;
}
function tradeWithHint(
address trader,
ERC20 src,
uint srcAmount,
ERC20 dest,
address destAddress,
uint maxDestAmount,
uint minConversionRate,
address walletId,
bytes hint
)
public
nonReentrant
payable
returns(uint)
{
require(msg.sender == kyberNetworkProxyContract);
require((hint.length == 0) || (hint.length == 4));
TradeInput memory tradeInput;
tradeInput.trader = trader;
tradeInput.src = src;
tradeInput.srcAmount = srcAmount;
tradeInput.dest = dest;
tradeInput.destAddress = destAddress;
tradeInput.maxDestAmount = maxDestAmount;
tradeInput.minConversionRate = minConversionRate;
tradeInput.walletId = walletId;
tradeInput.hint = hint;
return trade(tradeInput);
}
event AddReserveToNetwork(KyberReserveInterface indexed reserve, bool add, bool isPermissionless);
/// @notice can be called only by operator
/// @dev add or deletes a reserve to/from the network.
/// @param reserve The reserve address.
/// @param isPermissionless is the new reserve from permissionless type.
function addReserve(KyberReserveInterface reserve, bool isPermissionless) public onlyOperator
returns(bool)
{
require(reserveType[reserve] == ReserveType.NONE);
reserves.push(reserve);
reserveType[reserve] = isPermissionless ? ReserveType.PERMISSIONLESS : ReserveType.PERMISSIONED;
AddReserveToNetwork(reserve, true, isPermissionless);
return true;
}
event RemoveReserveFromNetwork(KyberReserveInterface reserve);
/// @notice can be called only by operator
/// @dev removes a reserve from Kyber network.
/// @param reserve The reserve address.
/// @param index in reserve array.
function removeReserve(KyberReserveInterface reserve, uint index) public onlyOperator
returns(bool)
{
require(reserveType[reserve] != ReserveType.NONE);
require(reserves[index] == reserve);
reserveType[reserve] = ReserveType.NONE;
reserves[index] = reserves[reserves.length - 1];
reserves.length--;
RemoveReserveFromNetwork(reserve);
return true;
}
event ListReservePairs(address indexed reserve, ERC20 src, ERC20 dest, bool add);
/// @notice can be called only by operator
/// @dev allow or prevent a specific reserve to trade a pair of tokens
/// @param reserve The reserve address.
/// @param token token address
/// @param ethToToken will it support ether to token trade
/// @param tokenToEth will it support token to ether trade
/// @param add If true then list this pair, otherwise unlist it.
function listPairForReserve(address reserve, ERC20 token, bool ethToToken, bool tokenToEth, bool add)
public
onlyOperator
returns(bool)
{
require(reserveType[reserve] != ReserveType.NONE);
if (ethToToken) {
listPairs(reserve, token, false, add);
ListReservePairs(reserve, ETH_TOKEN_ADDRESS, token, add);
}
if (tokenToEth) {
listPairs(reserve, token, true, add);
if (add) {
require(token.approve(reserve, 2**255)); // approve infinity
} else {
require(token.approve(reserve, 0));
}
ListReservePairs(reserve, token, ETH_TOKEN_ADDRESS, add);
}
setDecimals(token);
return true;
}
event WhiteListContractSet(WhiteListInterface newContract, WhiteListInterface currentContract);
///@param whiteList can be empty
function setWhiteList(WhiteListInterface whiteList) public onlyAdmin {
WhiteListContractSet(whiteList, whiteListContract);
whiteListContract = whiteList;
}
event ExpectedRateContractSet(ExpectedRateInterface newContract, ExpectedRateInterface currentContract);
function setExpectedRate(ExpectedRateInterface expectedRate) public onlyAdmin {
require(expectedRate != address(0));
ExpectedRateContractSet(expectedRate, expectedRateContract);
expectedRateContract = expectedRate;
}
event FeeBurnerContractSet(FeeBurnerInterface newContract, FeeBurnerInterface currentContract);
function setFeeBurner(FeeBurnerInterface feeBurner) public onlyAdmin {
require(feeBurner != address(0));
FeeBurnerContractSet(feeBurner, feeBurnerContract);
feeBurnerContract = feeBurner;
}
event KyberNetwrokParamsSet(uint maxGasPrice, uint negligibleRateDiff);
function setParams(
uint _maxGasPrice,
uint _negligibleRateDiff
)
public
onlyAdmin
{
require(_negligibleRateDiff <= 100 * 100); // at most 100%
maxGasPriceValue = _maxGasPrice;
negligibleRateDiff = _negligibleRateDiff;
KyberNetwrokParamsSet(maxGasPriceValue, negligibleRateDiff);
}
event KyberNetworkSetEnable(bool isEnabled);
function setEnable(bool _enable) public onlyAdmin {
if (_enable) {
require(feeBurnerContract != address(0));
require(expectedRateContract != address(0));
require(kyberNetworkProxyContract != address(0));
}
isEnabled = _enable;
KyberNetworkSetEnable(isEnabled);
}
function setInfo(bytes32 field, uint value) public onlyOperator {
infoFields[field] = value;
}
event KyberProxySet(address proxy, address sender);
function setKyberProxy(address networkProxy) public onlyAdmin {
require(networkProxy != address(0));
kyberNetworkProxyContract = networkProxy;
KyberProxySet(kyberNetworkProxyContract, msg.sender);
}
/// @dev returns number of reserves
/// @return number of reserves
function getNumReserves() public view returns(uint) {
return reserves.length;
}
/// @notice should be called off chain
/// @dev get an array of all reserves
/// @return An array of all reserves
function getReserves() public view returns(KyberReserveInterface[]) {
return reserves;
}
function maxGasPrice() public view returns(uint) {
return maxGasPriceValue;
}
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty)
public view
returns(uint expectedRate, uint slippageRate)
{
require(expectedRateContract != address(0));
bool includePermissionless = true;
if (srcQty & PERM_HINT_GET_RATE > 0) {
includePermissionless = false;
srcQty = srcQty & ~PERM_HINT_GET_RATE;
}
return expectedRateContract.getExpectedRate(src, dest, srcQty, includePermissionless);
}
function getExpectedRateOnlyPermission(ERC20 src, ERC20 dest, uint srcQty)
public view
returns(uint expectedRate, uint slippageRate)
{
require(expectedRateContract != address(0));
return expectedRateContract.getExpectedRate(src, dest, srcQty, false);
}
function getUserCapInWei(address user) public view returns(uint) {
if (whiteListContract == address(0)) return (2 ** 255);
return whiteListContract.getUserCapInWei(user);
}
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint) {
//future feature
user;
token;
require(false);
}
struct BestRateResult {
uint rate;
address reserve1;
address reserve2;
uint weiAmount;
uint rateSrcToEth;
uint rateEthToDest;
uint destAmount;
}
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev best conversion rate for a pair of tokens, if number of reserves have small differences. randomize
/// @param src Src token
/// @param dest Destination token
/// @return obsolete - used to return best reserve index. not relevant anymore for this API.
function findBestRate(ERC20 src, ERC20 dest, uint srcAmount) public view returns(uint obsolete, uint rate) {
BestRateResult memory result = findBestRateTokenToToken(src, dest, srcAmount, EMPTY_HINT);
return(0, result.rate);
}
function findBestRateOnlyPermission(ERC20 src, ERC20 dest, uint srcAmount)
public
view
returns(uint obsolete, uint rate)
{
BestRateResult memory result = findBestRateTokenToToken(src, dest, srcAmount, PERM_HINT);
return(0, result.rate);
}
function enabled() public view returns(bool) {
return isEnabled;
}
function info(bytes32 field) public view returns(uint) {
return infoFields[field];
}
/* solhint-disable code-complexity */
// Regarding complexity. Below code follows the required algorithm for choosing a reserve.
// It has been tested, reviewed and found to be clear enough.
//@dev this function always src or dest are ether. can't do token to token
function searchBestRate(ERC20 src, ERC20 dest, uint srcAmount, bool usePermissionless)
public
view
returns(address, uint)
{
uint bestRate = 0;
uint bestReserve = 0;
uint numRelevantReserves = 0;
//return 1 for ether to ether
if (src == dest) return (reserves[bestReserve], PRECISION);
address[] memory reserveArr;
reserveArr = src == ETH_TOKEN_ADDRESS ? reservesPerTokenDest[dest] : reservesPerTokenSrc[src];
if (reserveArr.length == 0) return (reserves[bestReserve], bestRate);
uint[] memory rates = new uint[](reserveArr.length);
uint[] memory reserveCandidates = new uint[](reserveArr.length);
for (uint i = 0; i < reserveArr.length; i++) {
//list all reserves that have this token.
if (!usePermissionless && reserveType[reserveArr[i]] == ReserveType.PERMISSIONLESS) {
continue;
}
rates[i] = (KyberReserveInterface(reserveArr[i])).getConversionRate(src, dest, srcAmount, block.number);
if (rates[i] > bestRate) {
//best rate is highest rate
bestRate = rates[i];
}
}
if (bestRate > 0) {
uint smallestRelevantRate = (bestRate * 10000) / (10000 + negligibleRateDiff);
for (i = 0; i < reserveArr.length; i++) {
if (rates[i] >= smallestRelevantRate) {
reserveCandidates[numRelevantReserves++] = i;
}
}
if (numRelevantReserves > 1) {
//when encountering small rate diff from bestRate. draw from relevant reserves
bestReserve = reserveCandidates[uint(block.blockhash(block.number-1)) % numRelevantReserves];
} else {
bestReserve = reserveCandidates[0];
}
bestRate = rates[bestReserve];
}
return (reserveArr[bestReserve], bestRate);
}
/* solhint-enable code-complexity */
function findBestRateTokenToToken(ERC20 src, ERC20 dest, uint srcAmount, bytes hint) internal view
returns(BestRateResult result)
{
//by default we use permission less reserves
bool usePermissionless = true;
// if hint in first 4 bytes == 'PERM' only permissioned reserves will be used.
if ((hint.length >= 4) && (keccak256(hint[0], hint[1], hint[2], hint[3]) == keccak256(PERM_HINT))) {
usePermissionless = false;
}
(result.reserve1, result.rateSrcToEth) =
searchBestRate(src, ETH_TOKEN_ADDRESS, srcAmount, usePermissionless);
result.weiAmount = calcDestAmount(src, ETH_TOKEN_ADDRESS, srcAmount, result.rateSrcToEth);
(result.reserve2, result.rateEthToDest) =
searchBestRate(ETH_TOKEN_ADDRESS, dest, result.weiAmount, usePermissionless);
result.destAmount = calcDestAmount(ETH_TOKEN_ADDRESS, dest, result.weiAmount, result.rateEthToDest);
result.rate = calcRateFromQty(srcAmount, result.destAmount, getDecimals(src), getDecimals(dest));
}
function listPairs(address reserve, ERC20 token, bool isTokenToEth, bool add) internal {
uint i;
address[] storage reserveArr = reservesPerTokenDest[token];
if (isTokenToEth) {
reserveArr = reservesPerTokenSrc[token];
}
for (i = 0; i < reserveArr.length; i++) {
if (reserve == reserveArr[i]) {
if (add) {
break; //already added
} else {
//remove
reserveArr[i] = reserveArr[reserveArr.length - 1];
reserveArr.length--;
break;
}
}
}
if (add && i == reserveArr.length) {
//if reserve wasn't found add it
reserveArr.push(reserve);
}
}
event KyberTrade(address indexed trader, ERC20 src, ERC20 dest, uint srcAmount, uint dstAmount,
address destAddress, uint ethWeiValue, address reserve1, address reserve2, bytes hint);
/* solhint-disable function-max-lines */
// Most of the lines here are functions calls spread over multiple lines. We find this function readable enough
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev trade api for kyber network.
/// @param tradeInput structure of trade inputs
function trade(TradeInput tradeInput) internal returns(uint) {
require(isEnabled);
require(tx.gasprice <= maxGasPriceValue);
require(validateTradeInput(tradeInput.src, tradeInput.srcAmount, tradeInput.dest, tradeInput.destAddress));
BestRateResult memory rateResult =
findBestRateTokenToToken(tradeInput.src, tradeInput.dest, tradeInput.srcAmount, tradeInput.hint);
require(rateResult.rate > 0);
require(rateResult.rate < MAX_RATE);
require(rateResult.rate >= tradeInput.minConversionRate);
uint actualDestAmount;
uint weiAmount;
uint actualSrcAmount;
(actualSrcAmount, weiAmount, actualDestAmount) = calcActualAmounts(tradeInput.src,
tradeInput.dest,
tradeInput.srcAmount,
tradeInput.maxDestAmount,
rateResult);
require(getUserCapInWei(tradeInput.trader) >= weiAmount);
require(handleChange(tradeInput.src, tradeInput.srcAmount, actualSrcAmount, tradeInput.trader));
require(doReserveTrade( //src to ETH
tradeInput.src,
actualSrcAmount,
ETH_TOKEN_ADDRESS,
this,
weiAmount,
KyberReserveInterface(rateResult.reserve1),
rateResult.rateSrcToEth,
true));
require(doReserveTrade( //Eth to dest
ETH_TOKEN_ADDRESS,
weiAmount,
tradeInput.dest,
tradeInput.destAddress,
actualDestAmount,
KyberReserveInterface(rateResult.reserve2),
rateResult.rateEthToDest,
true));
if (tradeInput.src != ETH_TOKEN_ADDRESS) //"fake" trade. (ether to ether) - don't burn.
require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve1, tradeInput.walletId));
if (tradeInput.dest != ETH_TOKEN_ADDRESS) //"fake" trade. (ether to ether) - don't burn.
require(feeBurnerContract.handleFees(weiAmount, rateResult.reserve2, tradeInput.walletId));
KyberTrade({
trader: tradeInput.trader,
src: tradeInput.src,
dest: tradeInput.dest,
srcAmount: actualSrcAmount,
dstAmount: actualDestAmount,
destAddress: tradeInput.destAddress,
ethWeiValue: weiAmount,
reserve1: (tradeInput.src == ETH_TOKEN_ADDRESS) ? address(0) : rateResult.reserve1,
reserve2: (tradeInput.dest == ETH_TOKEN_ADDRESS) ? address(0) : rateResult.reserve2,
hint: tradeInput.hint
});
return actualDestAmount;
}
/* solhint-enable function-max-lines */
function calcActualAmounts (ERC20 src, ERC20 dest, uint srcAmount, uint maxDestAmount, BestRateResult rateResult)
internal view returns(uint actualSrcAmount, uint weiAmount, uint actualDestAmount)
{
if (rateResult.destAmount > maxDestAmount) {
actualDestAmount = maxDestAmount;
weiAmount = calcSrcAmount(ETH_TOKEN_ADDRESS, dest, actualDestAmount, rateResult.rateEthToDest);
actualSrcAmount = calcSrcAmount(src, ETH_TOKEN_ADDRESS, weiAmount, rateResult.rateSrcToEth);
require(actualSrcAmount <= srcAmount);
} else {
actualDestAmount = rateResult.destAmount;
actualSrcAmount = srcAmount;
weiAmount = rateResult.weiAmount;
}
}
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev do one trade with a reserve
/// @param src Src token
/// @param amount amount of src tokens
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param reserve Reserve to use
/// @param validate If true, additional validations are applicable
/// @return true if trade is successful
function doReserveTrade(
ERC20 src,
uint amount,
ERC20 dest,
address destAddress,
uint expectedDestAmount,
KyberReserveInterface reserve,
uint conversionRate,
bool validate
)
internal
returns(bool)
{
uint callValue = 0;
if (src == dest) {
//this is for a "fake" trade when both src and dest are ethers.
if (destAddress != (address(this)))
destAddress.transfer(amount);
return true;
}
if (src == ETH_TOKEN_ADDRESS) {
callValue = amount;
}
// reserve sends tokens/eth to network. network sends it to destination
require(reserve.trade.value(callValue)(src, amount, dest, this, conversionRate, validate));
if (destAddress != address(this)) {
//for token to token dest address is network. and Ether / token already here...
if (dest == ETH_TOKEN_ADDRESS) {
destAddress.transfer(expectedDestAmount);
} else {
require(dest.transfer(destAddress, expectedDestAmount));
}
}
return true;
}
/// when user sets max dest amount we could have too many source tokens == change. so we send it back to user.
function handleChange (ERC20 src, uint srcAmount, uint requiredSrcAmount, address trader) internal returns (bool) {
if (requiredSrcAmount < srcAmount) {
//if there is "change" send back to trader
if (src == ETH_TOKEN_ADDRESS) {
trader.transfer(srcAmount - requiredSrcAmount);
} else {
require(src.transfer(trader, (srcAmount - requiredSrcAmount)));
}
}
return true;
}
/// @notice use token address ETH_TOKEN_ADDRESS for ether
/// @dev checks that user sent ether/tokens to contract before trade
/// @param src Src token
/// @param srcAmount amount of src tokens
/// @return true if tradeInput is valid
function validateTradeInput(ERC20 src, uint srcAmount, ERC20 dest, address destAddress)
internal
view
returns(bool)
{
require(srcAmount <= MAX_QTY);
require(srcAmount != 0);
require(destAddress != address(0));
require(src != dest);
if (src == ETH_TOKEN_ADDRESS) {
require(msg.value == srcAmount);
} else {
require(msg.value == 0);
//funds should have been moved to this contract already.
require(src.balanceOf(this) >= srcAmount);
}
return true;
}
}File 3 of 7: RCNToken
pragma solidity ^0.4.11;
contract Crowdsale {
function buyTokens(address _recipient) payable;
}
contract CapWhitelist {
address public owner;
mapping (address => uint256) public whitelist;
event Set(address _address, uint256 _amount);
function CapWhitelist() {
owner = msg.sender;
// Set in prod
}
function destruct() {
require(msg.sender == owner);
selfdestruct(owner);
}
function setWhitelisted(address _address, uint256 _amount) {
require(msg.sender == owner);
setWhitelistInternal(_address, _amount);
}
function setWhitelistInternal(address _address, uint256 _amount) private {
whitelist[_address] = _amount;
Set(_address, _amount);
}
}
contract Token {
uint256 public totalSupply;
function balanceOf(address _owner) constant returns (uint256 balance);
function transfer(address _to, uint256 _value) returns (bool success);
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
function approve(address _spender, uint256 _value) returns (bool success);
function allowance(address _owner, address _spender) constant returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
/* ERC 20 token */
contract StandardToken is Token {
using SafeMath for uint256;
function transfer(address _to, uint256 _value) returns (bool success) {
if (balances[msg.sender] >= _value) {
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(msg.sender, _to, _value);
return true;
} else {
return false;
}
}
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value) {
balances[_to] = balances[_to].add(_value);
balances[_from] = balances[_from].sub(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
Transfer(_from, _to, _value);
return true;
} else {
return false;
}
}
function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
function increaseApproval (address _spender, uint _addedValue) public returns (bool success) {
allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
function decreaseApproval (address _spender, uint _subtractedValue) public returns (bool success) {
uint oldValue = allowed[msg.sender][_spender];
if (_subtractedValue > oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
}
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) onlyOwner public {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
library SafeMath {
function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal constant returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal constant returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
contract MintableToken is StandardToken, Ownable {
using SafeMath for uint256;
event Mint(address indexed to, uint256 amount);
event MintFinished();
bool public mintingFinished = false;
modifier canMint() {
require(!mintingFinished);
_;
}
/**
* @dev Function to mint tokens
* @param _to The address that will receive the minted tokens.
* @param _amount The amount of tokens to mint.
*/
function mint(address _to, uint256 _amount) onlyOwner canMint public {
totalSupply = totalSupply.add(_amount);
balances[_to] = balances[_to].add(_amount);
Mint(_to, _amount);
Transfer(0x0, _to, _amount);
}
/**
* @dev Function to stop minting new tokens.
*/
function finishMinting() onlyOwner public {
mintingFinished = true;
MintFinished();
}
}
contract RCNToken is MintableToken {
string public constant name = "Ripio Credit Network Token";
string public constant symbol = "RCN";
uint8 public constant decimals = 18;
string public version = "1.0";
}File 4 of 7: KyberReserve
pragma solidity ^0.4.13;
interface ConversionRatesInterface {
function recordImbalance(
ERC20 token,
int buyAmount,
uint rateUpdateBlock,
uint currentBlock
)
public;
function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);
}
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
interface KyberReserveInterface {
function trade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
public
payable
returns(bool);
function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint);
}
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
interface SanityRatesInterface {
function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);
}
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
contract KyberReserve is KyberReserveInterface, Withdrawable, Utils {
address public kyberNetwork;
bool public tradeEnabled;
ConversionRatesInterface public conversionRatesContract;
SanityRatesInterface public sanityRatesContract;
mapping(bytes32=>bool) public approvedWithdrawAddresses; // sha3(token,address)=>bool
mapping(address=>address) public tokenWallet;
function KyberReserve(address _kyberNetwork, ConversionRatesInterface _ratesContract, address _admin) public {
require(_admin != address(0));
require(_ratesContract != address(0));
require(_kyberNetwork != address(0));
kyberNetwork = _kyberNetwork;
conversionRatesContract = _ratesContract;
admin = _admin;
tradeEnabled = true;
}
event DepositToken(ERC20 token, uint amount);
function() public payable {
DepositToken(ETH_TOKEN_ADDRESS, msg.value);
}
event TradeExecute(
address indexed origin,
address src,
uint srcAmount,
address destToken,
uint destAmount,
address destAddress
);
function trade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
public
payable
returns(bool)
{
require(tradeEnabled);
require(msg.sender == kyberNetwork);
require(doTrade(srcToken, srcAmount, destToken, destAddress, conversionRate, validate));
return true;
}
event TradeEnabled(bool enable);
function enableTrade() public onlyAdmin returns(bool) {
tradeEnabled = true;
TradeEnabled(true);
return true;
}
function disableTrade() public onlyAlerter returns(bool) {
tradeEnabled = false;
TradeEnabled(false);
return true;
}
event WithdrawAddressApproved(ERC20 token, address addr, bool approve);
function approveWithdrawAddress(ERC20 token, address addr, bool approve) public onlyAdmin {
approvedWithdrawAddresses[keccak256(token, addr)] = approve;
WithdrawAddressApproved(token, addr, approve);
setDecimals(token);
if ((tokenWallet[token] == address(0x0)) && (token != ETH_TOKEN_ADDRESS)) {
tokenWallet[token] = this; // by default
require(token.approve(this, 2 ** 255));
}
}
event NewTokenWallet(ERC20 token, address wallet);
function setTokenWallet(ERC20 token, address wallet) public onlyAdmin {
require(wallet != address(0x0));
tokenWallet[token] = wallet;
NewTokenWallet(token, wallet);
}
event WithdrawFunds(ERC20 token, uint amount, address destination);
function withdraw(ERC20 token, uint amount, address destination) public onlyOperator returns(bool) {
require(approvedWithdrawAddresses[keccak256(token, destination)]);
if (token == ETH_TOKEN_ADDRESS) {
destination.transfer(amount);
} else {
require(token.transferFrom(tokenWallet[token], destination, amount));
}
WithdrawFunds(token, amount, destination);
return true;
}
event SetContractAddresses(address network, address rate, address sanity);
function setContracts(
address _kyberNetwork,
ConversionRatesInterface _conversionRates,
SanityRatesInterface _sanityRates
)
public
onlyAdmin
{
require(_kyberNetwork != address(0));
require(_conversionRates != address(0));
kyberNetwork = _kyberNetwork;
conversionRatesContract = _conversionRates;
sanityRatesContract = _sanityRates;
SetContractAddresses(kyberNetwork, conversionRatesContract, sanityRatesContract);
}
////////////////////////////////////////////////////////////////////////////
/// status functions ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
function getBalance(ERC20 token) public view returns(uint) {
if (token == ETH_TOKEN_ADDRESS)
return this.balance;
else {
address wallet = tokenWallet[token];
uint balanceOfWallet = token.balanceOf(wallet);
uint allowanceOfWallet = token.allowance(wallet, this);
return (balanceOfWallet < allowanceOfWallet) ? balanceOfWallet : allowanceOfWallet;
}
}
function getDestQty(ERC20 src, ERC20 dest, uint srcQty, uint rate) public view returns(uint) {
uint dstDecimals = getDecimals(dest);
uint srcDecimals = getDecimals(src);
return calcDstQty(srcQty, srcDecimals, dstDecimals, rate);
}
function getSrcQty(ERC20 src, ERC20 dest, uint dstQty, uint rate) public view returns(uint) {
uint dstDecimals = getDecimals(dest);
uint srcDecimals = getDecimals(src);
return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate);
}
function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint) {
ERC20 token;
bool isBuy;
if (!tradeEnabled) return 0;
if (ETH_TOKEN_ADDRESS == src) {
isBuy = true;
token = dest;
} else if (ETH_TOKEN_ADDRESS == dest) {
isBuy = false;
token = src;
} else {
return 0; // pair is not listed
}
uint rate = conversionRatesContract.getRate(token, blockNumber, isBuy, srcQty);
uint destQty = getDestQty(src, dest, srcQty, rate);
if (getBalance(dest) < destQty) return 0;
if (sanityRatesContract != address(0)) {
uint sanityRate = sanityRatesContract.getSanityRate(src, dest);
if (rate > sanityRate) return 0;
}
return rate;
}
/// @dev do a trade
/// @param srcToken Src token
/// @param srcAmount Amount of src token
/// @param destToken Destination token
/// @param destAddress Destination address to send tokens to
/// @param validate If true, additional validations are applicable
/// @return true iff trade is successful
function doTrade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
internal
returns(bool)
{
// can skip validation if done at kyber network level
if (validate) {
require(conversionRate > 0);
if (srcToken == ETH_TOKEN_ADDRESS)
require(msg.value == srcAmount);
else
require(msg.value == 0);
}
uint destAmount = getDestQty(srcToken, destToken, srcAmount, conversionRate);
// sanity check
require(destAmount > 0);
// add to imbalance
ERC20 token;
int tradeAmount;
if (srcToken == ETH_TOKEN_ADDRESS) {
tradeAmount = int(destAmount);
token = destToken;
} else {
tradeAmount = -1 * int(srcAmount);
token = srcToken;
}
conversionRatesContract.recordImbalance(
token,
tradeAmount,
0,
block.number
);
// collect src tokens
if (srcToken != ETH_TOKEN_ADDRESS) {
require(srcToken.transferFrom(msg.sender, tokenWallet[srcToken], srcAmount));
}
// send dest tokens
if (destToken == ETH_TOKEN_ADDRESS) {
destAddress.transfer(destAmount);
} else {
require(destToken.transferFrom(tokenWallet[destToken], destAddress, destAmount));
}
TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress);
return true;
}
}File 5 of 7: FeeBurner
pragma solidity 0.4.18;
// File: contracts/FeeBurnerInterface.sol
interface FeeBurnerInterface {
function handleFees (uint tradeWeiAmount, address reserve, address wallet) public returns(bool);
function setReserveData(address reserve, uint feesInBps, address kncWallet) public;
}
// File: contracts/ERC20Interface.sol
// https://github.com/ethereum/EIPs/issues/20
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
// File: contracts/PermissionGroups.sol
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
// File: contracts/Withdrawable.sol
/**
* @title Contracts that should be able to recover tokens or ethers
* @author Ilan Doron
* @dev This allows to recover any tokens or Ethers received in a contract.
* This will prevent any accidental loss of tokens.
*/
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
// File: contracts/Utils.sol
/// @title Kyber constants contract
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
// File: contracts/Utils2.sol
contract Utils2 is Utils {
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(ERC20 token, address user) public view returns(uint) {
if (token == ETH_TOKEN_ADDRESS)
return user.balance;
else
return token.balanceOf(user);
}
function getDecimalsSafe(ERC20 token) internal returns(uint) {
if (decimals[token] == 0) {
setDecimals(token);
}
return decimals[token];
}
function calcDestAmount(ERC20 src, ERC20 dest, uint srcAmount, uint rate) internal view returns(uint) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(ERC20 src, ERC20 dest, uint destAmount, uint rate) internal view returns(uint) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcRateFromQty(uint srcAmount, uint destAmount, uint srcDecimals, uint dstDecimals)
internal pure returns(uint)
{
require(srcAmount <= MAX_QTY);
require(destAmount <= MAX_QTY);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (destAmount * PRECISION / ((10 ** (dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (destAmount * PRECISION * (10 ** (srcDecimals - dstDecimals)) / srcAmount);
}
}
}
// File: contracts/KyberNetworkInterface.sol
/// @title Kyber Network interface
interface KyberNetworkInterface {
function maxGasPrice() public view returns(uint);
function getUserCapInWei(address user) public view returns(uint);
function getUserCapInTokenWei(address user, ERC20 token) public view returns(uint);
function enabled() public view returns(bool);
function info(bytes32 id) public view returns(uint);
function getExpectedRate(ERC20 src, ERC20 dest, uint srcQty) public view
returns (uint expectedRate, uint slippageRate);
function tradeWithHint(address trader, ERC20 src, uint srcAmount, ERC20 dest, address destAddress,
uint maxDestAmount, uint minConversionRate, address walletId, bytes hint) public payable returns(uint);
}
// File: contracts/FeeBurner.sol
interface BurnableToken {
function transferFrom(address _from, address _to, uint _value) public returns (bool);
function burnFrom(address _from, uint256 _value) public returns (bool);
}
contract FeeBurner is Withdrawable, FeeBurnerInterface, Utils2 {
mapping(address=>uint) public reserveFeesInBps;
mapping(address=>address) public reserveKNCWallet; //wallet holding knc per reserve. from here burn and send fees.
mapping(address=>uint) public walletFeesInBps; // wallet that is the source of tx is entitled so some fees.
mapping(address=>uint) public reserveFeeToBurn;
mapping(address=>uint) public feePayedPerReserve; // track burned fees and sent wallet fees per reserve.
mapping(address=>mapping(address=>uint)) public reserveFeeToWallet;
address public taxWallet;
uint public taxFeeBps = 0; // burned fees are taxed. % out of burned fees.
BurnableToken public knc;
KyberNetworkInterface public kyberNetwork;
uint public kncPerEthRatePrecision = 600 * PRECISION; //--> 1 ether = 600 knc tokens
function FeeBurner(
address _admin,
BurnableToken _kncToken,
KyberNetworkInterface _kyberNetwork,
uint _initialKncToEthRatePrecision
)
public
{
require(_admin != address(0));
require(_kncToken != address(0));
require(_kyberNetwork != address(0));
require(_initialKncToEthRatePrecision != 0);
kyberNetwork = _kyberNetwork;
admin = _admin;
knc = _kncToken;
kncPerEthRatePrecision = _initialKncToEthRatePrecision;
}
event ReserveDataSet(address reserve, uint feeInBps, address kncWallet);
function setReserveData(address reserve, uint feesInBps, address kncWallet) public onlyOperator {
require(feesInBps < 100); // make sure it is always < 1%
require(kncWallet != address(0));
reserveFeesInBps[reserve] = feesInBps;
reserveKNCWallet[reserve] = kncWallet;
ReserveDataSet(reserve, feesInBps, kncWallet);
}
event WalletFeesSet(address wallet, uint feesInBps);
function setWalletFees(address wallet, uint feesInBps) public onlyAdmin {
require(feesInBps < 10000); // under 100%
walletFeesInBps[wallet] = feesInBps;
WalletFeesSet(wallet, feesInBps);
}
event TaxFeesSet(uint feesInBps);
function setTaxInBps(uint _taxFeeBps) public onlyAdmin {
require(_taxFeeBps < 10000); // under 100%
taxFeeBps = _taxFeeBps;
TaxFeesSet(_taxFeeBps);
}
event TaxWalletSet(address taxWallet);
function setTaxWallet(address _taxWallet) public onlyAdmin {
require(_taxWallet != address(0));
taxWallet = _taxWallet;
TaxWalletSet(_taxWallet);
}
event KNCRateSet(uint ethToKncRatePrecision, uint kyberEthKnc, uint kyberKncEth, address updater);
function setKNCRate() public {
//query kyber for knc rate sell and buy
uint kyberEthKncRate;
uint kyberKncEthRate;
(kyberEthKncRate, ) = kyberNetwork.getExpectedRate(ETH_TOKEN_ADDRESS, ERC20(knc), (10 ** 18));
(kyberKncEthRate, ) = kyberNetwork.getExpectedRate(ERC20(knc), ETH_TOKEN_ADDRESS, (10 ** 18));
//check "reasonable" spread == diff not too big. rate wasn't tampered.
require(kyberEthKncRate * kyberKncEthRate < PRECISION ** 2 * 2);
require(kyberEthKncRate * kyberKncEthRate > PRECISION ** 2 / 2);
require(kyberEthKncRate <= MAX_RATE);
kncPerEthRatePrecision = kyberEthKncRate;
KNCRateSet(kncPerEthRatePrecision, kyberEthKncRate, kyberKncEthRate, msg.sender);
}
event AssignFeeToWallet(address reserve, address wallet, uint walletFee);
event AssignBurnFees(address reserve, uint burnFee);
function handleFees(uint tradeWeiAmount, address reserve, address wallet) public returns(bool) {
require(msg.sender == address(kyberNetwork));
require(tradeWeiAmount <= MAX_QTY);
uint kncAmount = calcDestAmount(ETH_TOKEN_ADDRESS, ERC20(knc), tradeWeiAmount, kncPerEthRatePrecision);
uint fee = kncAmount * reserveFeesInBps[reserve] / 10000;
uint walletFee = fee * walletFeesInBps[wallet] / 10000;
require(fee >= walletFee);
uint feeToBurn = fee - walletFee;
if (walletFee > 0) {
reserveFeeToWallet[reserve][wallet] += walletFee;
AssignFeeToWallet(reserve, wallet, walletFee);
}
if (feeToBurn > 0) {
AssignBurnFees(reserve, feeToBurn);
reserveFeeToBurn[reserve] += feeToBurn;
}
return true;
}
event BurnAssignedFees(address indexed reserve, address sender, uint quantity);
event SendTaxFee(address indexed reserve, address sender, address taxWallet, uint quantity);
// this function is callable by anyone
function burnReserveFees(address reserve) public {
uint burnAmount = reserveFeeToBurn[reserve];
uint taxToSend = 0;
require(burnAmount > 2);
reserveFeeToBurn[reserve] = 1; // leave 1 twei to avoid spikes in gas fee
if (taxWallet != address(0) && taxFeeBps != 0) {
taxToSend = (burnAmount - 1) * taxFeeBps / 10000;
require(burnAmount - 1 > taxToSend);
burnAmount -= taxToSend;
if (taxToSend > 0) {
require(knc.transferFrom(reserveKNCWallet[reserve], taxWallet, taxToSend));
SendTaxFee(reserve, msg.sender, taxWallet, taxToSend);
}
}
require(knc.burnFrom(reserveKNCWallet[reserve], burnAmount - 1));
//update reserve "payments" so far
feePayedPerReserve[reserve] += (taxToSend + burnAmount - 1);
BurnAssignedFees(reserve, msg.sender, (burnAmount - 1));
}
event SendWalletFees(address indexed wallet, address reserve, address sender);
// this function is callable by anyone
function sendFeeToWallet(address wallet, address reserve) public {
uint feeAmount = reserveFeeToWallet[reserve][wallet];
require(feeAmount > 1);
reserveFeeToWallet[reserve][wallet] = 1; // leave 1 twei to avoid spikes in gas fee
require(knc.transferFrom(reserveKNCWallet[reserve], wallet, feeAmount - 1));
feePayedPerReserve[reserve] += (feeAmount - 1);
SendWalletFees(wallet, reserve, msg.sender);
}
}File 6 of 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: KyberNetworkCrystal
pragma solidity ^0.4.13;
library SafeMath {
function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal constant returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal constant returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
contract Ownable {
address public owner;
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
contract ERC20Basic {
uint256 public totalSupply;
function balanceOf(address who) constant returns (uint256);
function transfer(address to, uint256 value) returns (bool);
// KYBER-NOTE! code changed to comply with ERC20 standard
event Transfer(address indexed _from, address indexed _to, uint _value);
//event Transfer(address indexed from, address indexed to, uint256 value);
}
contract BasicToken is ERC20Basic {
using SafeMath for uint256;
mapping(address => uint256) balances;
/**
* @dev transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint256 _value) returns (bool) {
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(msg.sender, _to, _value);
return true;
}
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}
}
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender) constant returns (uint256);
function transferFrom(address from, address to, uint256 value) returns (bool);
function approve(address spender, uint256 value) returns (bool);
// KYBER-NOTE! code changed to comply with ERC20 standard
event Approval(address indexed _owner, address indexed _spender, uint _value);
//event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract StandardToken is ERC20, BasicToken {
mapping (address => mapping (address => uint256)) allowed;
/**
* @dev Transfer tokens from one address to another
* @param _from address The address which you want to send tokens from
* @param _to address The address which you want to transfer to
* @param _value uint256 the amout of tokens to be transfered
*/
function transferFrom(address _from, address _to, uint256 _value) returns (bool) {
var _allowance = allowed[_from][msg.sender];
// Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
// require (_value <= _allowance);
// KYBER-NOTE! code changed to comply with ERC20 standard
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
//balances[_from] = balances[_from].sub(_value); // this was removed
allowed[_from][msg.sender] = _allowance.sub(_value);
Transfer(_from, _to, _value);
return true;
}
/**
* @dev Aprove the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint256 _value) returns (bool) {
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender, 0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
require((_value == 0) || (allowed[msg.sender][_spender] == 0));
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param _owner address The address which owns the funds.
* @param _spender address The address which will spend the funds.
* @return A uint256 specifing the amount of tokens still avaible for the spender.
*/
function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
}
contract KyberNetworkCrystal is StandardToken, Ownable {
string public constant name = "Kyber Network Crystal";
string public constant symbol = "KNC";
uint public constant decimals = 18;
uint public saleStartTime;
uint public saleEndTime;
address public tokenSaleContract;
modifier onlyWhenTransferEnabled() {
if( now <= saleEndTime && now >= saleStartTime ) {
require( msg.sender == tokenSaleContract );
}
_;
}
modifier validDestination( address to ) {
require(to != address(0x0));
require(to != address(this) );
_;
}
function KyberNetworkCrystal( uint tokenTotalAmount, uint startTime, uint endTime, address admin ) {
// Mint all tokens. Then disable minting forever.
balances[msg.sender] = tokenTotalAmount;
totalSupply = tokenTotalAmount;
Transfer(address(0x0), msg.sender, tokenTotalAmount);
saleStartTime = startTime;
saleEndTime = endTime;
tokenSaleContract = msg.sender;
transferOwnership(admin); // admin could drain tokens that were sent here by mistake
}
function transfer(address _to, uint _value)
onlyWhenTransferEnabled
validDestination(_to)
returns (bool) {
return super.transfer(_to, _value);
}
function transferFrom(address _from, address _to, uint _value)
onlyWhenTransferEnabled
validDestination(_to)
returns (bool) {
return super.transferFrom(_from, _to, _value);
}
event Burn(address indexed _burner, uint _value);
function burn(uint _value) onlyWhenTransferEnabled
returns (bool){
balances[msg.sender] = balances[msg.sender].sub(_value);
totalSupply = totalSupply.sub(_value);
Burn(msg.sender, _value);
Transfer(msg.sender, address(0x0), _value);
return true;
}
// save some gas by making only one contract call
function burnFrom(address _from, uint256 _value) onlyWhenTransferEnabled
returns (bool) {
assert( transferFrom( _from, msg.sender, _value ) );
return burn(_value);
}
function emergencyERC20Drain( ERC20 token, uint amount ) onlyOwner {
token.transfer( owner, amount );
}
}