Transaction Hash:
Block:
12179140 at Apr-05-2021 10:31:58 AM +UTC
Transaction Fee:
0.027836024 ETH
$58.92
Gas Used:
262,604 Gas / 106 Gwei
Emitted Events:
| 132 |
GVToken.Transfer( from=KyberReserve, to=KyberNetwork, value=207952688683256989541 )
|
| 133 |
KyberReserve.TradeExecute( origin=KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=998200000000000000, destToken=GVToken, destAmount=207952688683256989541, destAddress=KyberNetwork )
|
| 134 |
GVToken.Transfer( from=KyberNetwork, to=[Sender] 0x25bf14bdf3c71c23422963471f568e17041259fd, value=207952688683256989541 )
|
| 135 |
KyberFeeHandler.FeeDistributed( token=0xEeeeeEee...eeeeeEEeE, platformWallet=0x440bBd6a...16874faa9, platformFeeWei=800000000000000, rewardWei=942300000000000, rebateWei=0, rebateWallets=[], rebatePercentBpsPerWallet=[], burnAmtWei=57700000000000 )
|
| 136 |
KyberNetwork.KyberTrade( 0x30bbea603a7b36858fe5e3ec6ba5ff59dde039d02120d758eacfaed01520577d, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 0x000000000000000000000000103c3a209da59d3e7c4a89307e66521e081cfdf0, 0000000000000000000000000000000000000000000000000de0b6b3a7640000, 00000000000000000000000000000000000000000000000000038d7ea4c68000, 0000000000000000000000000000000000000000000000000002d79883d20000, 0000000000000000000000000000000000000000000000000000000000000120, 0000000000000000000000000000000000000000000000000000000000000140, 0000000000000000000000000000000000000000000000000000000000000180, 00000000000000000000000000000000000000000000000000000000000001a0, 00000000000000000000000000000000000000000000000000000000000001e0, 0000000000000000000000000000000000000000000000000000000000000200, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, aa47565400000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000dda519c7ecb8000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000b4b2097d52263ef82 )
|
| 137 |
KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0x25bf14bdf3c71c23422963471f568e17041259fd, src=0xEeeeeEee...eeeeeEEeE, dest=GVToken, destAddress=[Sender] 0x25bf14bdf3c71c23422963471f568e17041259fd, actualSrcAmount=1000000000000000000, actualDestAmount=207952688683256989541, platformWallet=0x440bBd6a...16874faa9, platformFeeBps=8 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x103c3A20...e081CFDF0 | |||||
| 0x207dd8d1...427373784 | |||||
| 0x25bf14bD...7041259Fd |
4.441569531764137574 Eth
Nonce: 2247
|
3.413733507764137574 Eth
Nonce: 2248
| 1.027836024 | ||
|
0x99C85bb6...993Cb89E3
Miner
| (BeePool) | 820.587898590742513294 Eth | 820.615734614742513294 Eth | 0.027836024 | |
| 0xd3d2b564...a941114fe | (Kyber: Fee Handler) | 915.835597686113380396 Eth | 915.837397686113380396 Eth | 0.0018 | |
| 0xDd69d118...f9B2A2c3f | 106.181478324525089639 Eth | 107.179678324525089639 Eth | 0.9982 |
Execution Trace
ETH 1
KyberNetworkProxy.tradeWithHintAndFee( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=1000000000000000000, dest=0x103c3A209da59d3E7C4A89307e66521e081CFDF0, destAddress=0x25bf14bDf3C71C23422963471F568E17041259Fd, maxDestAmount=115792089237316195423570985008687907853269984665640564039457584007913129639935, minConversionRate=201714108022759279855, platformWallet=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, platformFeeBps=8, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4756540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 ) => ( destAmount=207952688683256989541 )
-
GVToken.balanceOf( _owner=0x25bf14bDf3C71C23422963471F568E17041259Fd ) => ( balance=209410825663348688360 )
ETH 1
KyberNetwork.tradeWithHintAndFee( trader=0x25bf14bDf3C71C23422963471F568E17041259Fd, src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=1000000000000000000, dest=0x103c3A209da59d3E7C4A89307e66521e081CFDF0, destAddress=0x25bf14bDf3C71C23422963471F568E17041259Fd, maxDestAmount=115792089237316195423570985008687907853269984665640564039457584007913129639935, minConversionRate=201714108022759279855, platformWallet=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, platformFeeBps=8, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4756540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 ) => ( destAmount=207952688683256989541 )-
KyberMatchingEngine.getTradingReserves( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x103c3A209da59d3E7C4A89307e66521e081CFDF0, isTokenToToken=False, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4756540000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 ) -
KyberStorage.getReservesData( ) => ( areAllReservesListed=True, feeAccountedArr=[true], entitledRebateArr=[false], reserveAddresses=[0xDd69d118a3c491E83dAC93E751076C9f9B2A2c3f] ) KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x103c3A209da59d3E7C4A89307e66521e081CFDF0, srcQty=998200000000000000, blockNumber=12179140 ) => ( 208327678504565206914 )-
LiquidityConversionRates.getRate( conversionToken=0x103c3A209da59d3E7C4A89307e66521e081CFDF0, currentBlockNumber=12179140, buy=True, qtyInSrcWei=998200000000000000 ) => ( 208327678504565206914 ) -
GVToken.balanceOf( _owner=0xDd69d118a3c491E83dAC93E751076C9f9B2A2c3f ) => ( balance=20995687152239993125799 )
-
GVToken.allowance( _owner=0xDd69d118a3c491E83dAC93E751076C9f9B2A2c3f, _spender=0xDd69d118a3c491E83dAC93E751076C9f9B2A2c3f ) => ( remaining=57896044618658097711785492504343953926634992332820281860541202582768650802684 )
-
-
GVToken.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( balance=158 )
ETH 0.9982
KyberReserve.trade( srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=998200000000000000, destToken=0x103c3A209da59d3E7C4A89307e66521e081CFDF0, destAddress=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, conversionRate=208327678504565206914, validate=True ) => ( True )-
LiquidityConversionRates.recordImbalance( conversionToken=0x103c3A209da59d3E7C4A89307e66521e081CFDF0, buyAmountInTwei=207952688683256989541, rateUpdateBlock=0, currentBlock=12179140 ) -
GVToken.transferFrom( _from=0xDd69d118a3c491E83dAC93E751076C9f9B2A2c3f, _to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, _value=207952688683256989541 ) => ( True )
-
-
GVToken.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( balance=207952688683256989699 )
-
GVToken.transfer( _to=0x25bf14bDf3C71C23422963471F568E17041259Fd, _value=207952688683256989541 ) => ( True )
- ETH 0.0018
KyberFeeHandler.handleFees( token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, rebateWallets=[], rebateBpsPerWallet=[], platformWallet=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, platformFee=800000000000000, networkFee=1000000000000000 )
-
-
GVToken.balanceOf( _owner=0x25bf14bDf3C71C23422963471F568E17041259Fd ) => ( balance=417363514346605677901 )
tradeWithHintAndFee[KyberNetworkProxy (ln:1036)]
doTrade[KyberNetworkProxy (ln:1048)]prepareTrade[KyberNetworkProxy (ln:1233)]getBalance[KyberNetworkProxy (ln:1298)]getBalance[KyberNetworkProxy (ln:1299)]safeTransferFrom[KyberNetworkProxy (ln:1304)]
tradeWithHintAndFee[KyberNetworkProxy (ln:1235)]calculateTradeOutcome[KyberNetworkProxy (ln:1247)]getBalance[KyberNetworkProxy (ln:1318)]getBalance[KyberNetworkProxy (ln:1319)]calcRateFromQty[KyberNetworkProxy (ln:1336)]getUpdateDecimals[KyberNetworkProxy (ln:1339)]getUpdateDecimals[KyberNetworkProxy (ln:1340)]
ExecuteTrade[KyberNetworkProxy (ln:1265)]
File 1 of 8: KyberNetworkProxy
File 2 of 8: KyberReserve
File 3 of 8: KyberNetwork
File 4 of 8: GVToken
File 5 of 8: KyberFeeHandler
File 6 of 8: KyberMatchingEngine
File 7 of 8: KyberStorage
File 8 of 8: LiquidityConversionRates
// File: contracts/sol6/IERC20.sol
pragma solidity 0.6.6;
interface IERC20 {
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function approve(address _spender, uint256 _value) external returns (bool success);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
function balanceOf(address _owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 digits);
function totalSupply() external view returns (uint256 supply);
}
// to support backward compatible contract name -- so function signature remains same
abstract contract ERC20 is IERC20 {
}
// File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
pragma solidity 0.6.6;
contract PermissionGroupsNoModifiers {
address public admin;
address public pendingAdmin;
mapping(address => bool) internal operators;
mapping(address => bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint256 internal constant MAX_GROUP_SIZE = 50;
event AdminClaimed(address newAdmin, address previousAdmin);
event AlerterAdded(address newAlerter, bool isAdd);
event OperatorAdded(address newOperator, bool isAdd);
event TransferAdminPending(address pendingAdmin);
constructor(address _admin) public {
require(_admin != address(0), "admin 0");
admin = _admin;
}
function getOperators() external view returns (address[] memory) {
return operatorsGroup;
}
function getAlerters() external view returns (address[] memory) {
return alertersGroup;
}
function addAlerter(address newAlerter) public {
onlyAdmin();
require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
emit AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function addOperator(address newOperator) public {
onlyAdmin();
require(!operators[newOperator], "operator exists"); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
emit OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
/// @dev Allows the pendingAdmin address to finalize the change admin process.
function claimAdmin() public {
require(pendingAdmin == msg.sender, "not pending");
emit AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
function removeAlerter(address alerter) public {
onlyAdmin();
require(alerters[alerter], "not alerter");
delete alerters[alerter];
for (uint256 i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.pop();
emit AlerterAdded(alerter, false);
break;
}
}
}
function removeOperator(address operator) public {
onlyAdmin();
require(operators[operator], "not operator");
delete operators[operator];
for (uint256 i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.pop();
emit OperatorAdded(operator, false);
break;
}
}
}
/// @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), "new admin 0");
emit TransferAdminPending(newAdmin);
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), "admin 0");
emit TransferAdminPending(newAdmin);
emit AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
function onlyAdmin() internal view {
require(msg.sender == admin, "only admin");
}
function onlyAlerter() internal view {
require(alerters[msg.sender], "only alerter");
}
function onlyOperator() internal view {
require(operators[msg.sender], "only operator");
}
}
// File: contracts/sol6/utils/WithdrawableNoModifiers.sol
pragma solidity 0.6.6;
contract WithdrawableNoModifiers is PermissionGroupsNoModifiers {
constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {}
event EtherWithdraw(uint256 amount, address sendTo);
event TokenWithdraw(IERC20 token, uint256 amount, address sendTo);
/// @dev Withdraw Ethers
function withdrawEther(uint256 amount, address payable sendTo) external {
onlyAdmin();
(bool success, ) = sendTo.call{value: amount}("");
require(success);
emit EtherWithdraw(amount, sendTo);
}
/// @dev Withdraw all IERC20 compatible tokens
/// @param token IERC20 The address of the token contract
function withdrawToken(
IERC20 token,
uint256 amount,
address sendTo
) external {
onlyAdmin();
token.transfer(sendTo, amount);
emit TokenWithdraw(token, amount, sendTo);
}
}
// File: contracts/sol6/utils/Utils5.sol
pragma solidity 0.6.6;
/**
* @title Kyber utility file
* mostly shared constants and rate calculation helpers
* inherited by most of kyber contracts.
* previous utils implementations are for previous solidity versions.
*/
contract Utils5 {
IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
);
uint256 internal constant PRECISION = (10**18);
uint256 internal constant MAX_QTY = (10**28); // 10B tokens
uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
uint256 internal constant MAX_DECIMALS = 18;
uint256 internal constant ETH_DECIMALS = 18;
uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
mapping(IERC20 => uint256) internal decimals;
function getUpdateDecimals(IERC20 token) internal returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if (tokenDecimals == 0) {
tokenDecimals = token.decimals();
decimals[token] = tokenDecimals;
}
return tokenDecimals;
}
function setDecimals(IERC20 token) internal {
if (decimals[token] != 0) return; //already set
if (token == ETH_TOKEN_ADDRESS) {
decimals[token] = ETH_DECIMALS;
} else {
decimals[token] = token.decimals();
}
}
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(IERC20 token, address user) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) {
return user.balance;
} else {
return token.balanceOf(user);
}
}
function getDecimals(IERC20 token) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// 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 calcDestAmount(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
uint256 rate
) internal view returns (uint256) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(
IERC20 src,
IERC20 dest,
uint256 destAmount,
uint256 rate
) internal view returns (uint256) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcDstQty(
uint256 srcQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(
uint256 dstQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
//source quantity is rounded up. to avoid dest quantity being too low.
uint256 numerator;
uint256 denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
function calcRateFromQty(
uint256 srcAmount,
uint256 destAmount,
uint256 srcDecimals,
uint256 dstDecimals
) internal pure returns (uint256) {
require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
}
}
function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? y : x;
}
}
// File: contracts/sol6/utils/zeppelin/SafeMath.sol
pragma solidity 0.6.6;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
// File: contracts/sol6/utils/zeppelin/Address.sol
pragma solidity 0.6.6;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
// File: contracts/sol6/utils/zeppelin/SafeERC20.sol
pragma solidity 0.6.6;
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// File: contracts/sol6/IKyberNetwork.sol
pragma solidity 0.6.6;
interface IKyberNetwork {
event KyberTrade(
IERC20 indexed src,
IERC20 indexed dest,
uint256 ethWeiValue,
uint256 networkFeeWei,
uint256 customPlatformFeeWei,
bytes32[] t2eIds,
bytes32[] e2tIds,
uint256[] t2eSrcAmounts,
uint256[] e2tSrcAmounts,
uint256[] t2eRates,
uint256[] e2tRates
);
function tradeWithHintAndFee(
address payable trader,
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function listTokenForReserve(
address reserve,
IERC20 token,
bool add
) external;
function enabled() external view returns (bool);
function getExpectedRateWithHintAndFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
)
external
view
returns (
uint256 expectedRateAfterNetworkFee,
uint256 expectedRateAfterAllFees
);
function getNetworkData()
external
view
returns (
uint256 negligibleDiffBps,
uint256 networkFeeBps,
uint256 expiryTimestamp
);
function maxGasPrice() external view returns (uint256);
}
// File: contracts/sol6/IKyberNetworkProxy.sol
pragma solidity 0.6.6;
interface IKyberNetworkProxy {
event ExecuteTrade(
address indexed trader,
IERC20 src,
IERC20 dest,
address destAddress,
uint256 actualSrcAmount,
uint256 actualDestAmount,
address platformWallet,
uint256 platformFeeBps
);
/// @notice backward compatible
function tradeWithHint(
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable returns (uint256);
function tradeWithHintAndFee(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function trade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet
) external payable returns (uint256);
/// @notice backward compatible
/// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view returns (uint256 expectedRate, uint256 worstRate);
function getExpectedRateAfterFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
) external view returns (uint256 expectedRate);
}
// File: contracts/sol6/ISimpleKyberProxy.sol
pragma solidity 0.6.6;
/*
* @title simple Kyber Network proxy interface
* add convenient functions to help with kyber proxy API
*/
interface ISimpleKyberProxy {
function swapTokenToEther(
IERC20 token,
uint256 srcAmount,
uint256 minConversionRate
) external returns (uint256 destAmount);
function swapEtherToToken(IERC20 token, uint256 minConversionRate)
external
payable
returns (uint256 destAmount);
function swapTokenToToken(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
uint256 minConversionRate
) external returns (uint256 destAmount);
}
// File: contracts/sol6/IKyberReserve.sol
pragma solidity 0.6.6;
interface IKyberReserve {
function trade(
IERC20 srcToken,
uint256 srcAmount,
IERC20 destToken,
address payable destAddress,
uint256 conversionRate,
bool validate
) external payable returns (bool);
function getConversionRate(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 blockNumber
) external view returns (uint256);
}
// File: contracts/sol6/IKyberHint.sol
pragma solidity 0.6.6;
interface IKyberHint {
enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
enum HintErrors {
NoError, // Hint is valid
NonEmptyDataError, // reserveIDs and splits must be empty for BestOfAll hint
ReserveIdDupError, // duplicate reserveID found
ReserveIdEmptyError, // reserveIDs array is empty for MaskIn and Split trade type
ReserveIdSplitsError, // reserveIDs and splitBpsValues arrays do not have the same length
ReserveIdSequenceError, // reserveID sequence in array is not in increasing order
ReserveIdNotFound, // reserveID isn't registered or doesn't exist
SplitsNotEmptyError, // splitBpsValues is not empty for MaskIn or MaskOut trade type
TokenListedError, // reserveID not listed for the token
TotalBPSError // total BPS for Split trade type is not 10000 (100%)
}
function buildTokenToEthHint(
IERC20 tokenSrc,
TradeType tokenToEthType,
bytes32[] calldata tokenToEthReserveIds,
uint256[] calldata tokenToEthSplits
) external view returns (bytes memory hint);
function buildEthToTokenHint(
IERC20 tokenDest,
TradeType ethToTokenType,
bytes32[] calldata ethToTokenReserveIds,
uint256[] calldata ethToTokenSplits
) external view returns (bytes memory hint);
function buildTokenToTokenHint(
IERC20 tokenSrc,
TradeType tokenToEthType,
bytes32[] calldata tokenToEthReserveIds,
uint256[] calldata tokenToEthSplits,
IERC20 tokenDest,
TradeType ethToTokenType,
bytes32[] calldata ethToTokenReserveIds,
uint256[] calldata ethToTokenSplits
) external view returns (bytes memory hint);
function parseTokenToEthHint(IERC20 tokenSrc, bytes calldata hint)
external
view
returns (
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
IKyberReserve[] memory tokenToEthAddresses,
uint256[] memory tokenToEthSplits
);
function parseEthToTokenHint(IERC20 tokenDest, bytes calldata hint)
external
view
returns (
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
IKyberReserve[] memory ethToTokenAddresses,
uint256[] memory ethToTokenSplits
);
function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes calldata hint)
external
view
returns (
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
IKyberReserve[] memory tokenToEthAddresses,
uint256[] memory tokenToEthSplits,
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
IKyberReserve[] memory ethToTokenAddresses,
uint256[] memory ethToTokenSplits
);
}
// File: contracts/sol6/KyberNetworkProxy.sol
pragma solidity 0.6.6;
/**
* @title kyberProxy for kyberNetwork contract
* The contract provides the following functions:
* - Get rates
* - Trade execution
* - Simple T2E, E2T and T2T trade APIs
* - Has some checks in place to safeguard takers
*/
contract KyberNetworkProxy is
IKyberNetworkProxy,
ISimpleKyberProxy,
WithdrawableNoModifiers,
Utils5
{
using SafeERC20 for IERC20;
IKyberNetwork public kyberNetwork;
IKyberHint public kyberHintHandler; // kyberHintHhandler pointer for users.
event KyberNetworkSet(IKyberNetwork newKyberNetwork, IKyberNetwork previousKyberNetwork);
event KyberHintHandlerSet(IKyberHint kyberHintHandler);
constructor(address _admin) public WithdrawableNoModifiers(_admin) {
/*empty body*/
}
/// @notice Backward compatible function
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param platformWallet Platform wallet address for receiving fees
/// @return Amount of actual dest tokens in twei
function trade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet
) external payable override returns (uint256) {
bytes memory hint;
return
doTrade(
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
platformWallet,
0,
hint
);
}
/// @notice Backward compatible function
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param walletId Platform wallet address for receiving fees
/// @param hint Advanced instructions for running the trade
/// @return Amount of actual dest tokens in twei
function tradeWithHint(
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable override returns (uint256) {
return
doTrade(
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
walletId,
0,
hint
);
}
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param platformWallet Platform wallet address for receiving fees
/// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 10000 = 100%, 100 = 1%
/// @param hint Advanced instructions for running the trade
/// @return destAmount Amount of actual dest tokens in twei
function tradeWithHintAndFee(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable override returns (uint256 destAmount) {
return
doTrade(
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
platformWallet,
platformFeeBps,
hint
);
}
/// @dev Trade from src to dest token. Sends dest tokens to msg sender
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @return Amount of actual dest tokens in twei
function swapTokenToToken(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
uint256 minConversionRate
) external override returns (uint256) {
bytes memory hint;
return
doTrade(
src,
srcAmount,
dest,
msg.sender,
MAX_QTY,
minConversionRate,
address(0),
0,
hint
);
}
/// @dev Trade from eth -> token. Sends token to msg sender
/// @param token Destination token
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @return Amount of actual dest tokens in twei
function swapEtherToToken(IERC20 token, uint256 minConversionRate)
external
payable
override
returns (uint256)
{
bytes memory hint;
return
doTrade(
ETH_TOKEN_ADDRESS,
msg.value,
token,
msg.sender,
MAX_QTY,
minConversionRate,
address(0),
0,
hint
);
}
/// @dev Trade from token -> eth. Sends eth to msg sender
/// @param token Source token
/// @param srcAmount Amount of src tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @return Amount of actual dest tokens in twei
function swapTokenToEther(
IERC20 token,
uint256 srcAmount,
uint256 minConversionRate
) external override returns (uint256) {
bytes memory hint;
return
doTrade(
token,
srcAmount,
ETH_TOKEN_ADDRESS,
msg.sender,
MAX_QTY,
minConversionRate,
address(0),
0,
hint
);
}
function setKyberNetwork(IKyberNetwork _kyberNetwork) external {
onlyAdmin();
require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0");
emit KyberNetworkSet(_kyberNetwork, kyberNetwork);
kyberNetwork = _kyberNetwork;
}
function setHintHandler(IKyberHint _kyberHintHandler) external {
onlyAdmin();
require(_kyberHintHandler != IKyberHint(0), "kyberHintHandler 0");
emit KyberHintHandlerSet(_kyberHintHandler);
kyberHintHandler = _kyberHintHandler;
}
/// @notice Backward compatible function
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Get expected rate for a trade from src to dest tokens, with amount srcQty (no platform fee)
/// @param src Source token
/// @param dest Destination token
/// @param srcQty Amount of src tokens in twei
/// @return expectedRate for a trade after deducting network fee. Rate = destQty (twei) / srcQty (twei) * 10 ** 18
/// @return worstRate for a trade. Usually expectedRate * 97 / 100
/// Use worstRate value as trade min conversion rate at your own risk
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view override returns (uint256 expectedRate, uint256 worstRate) {
bytes memory hint;
(expectedRate, ) = kyberNetwork.getExpectedRateWithHintAndFee(
src,
dest,
srcQty,
0,
hint
);
// use simple backward compatible optoin.
worstRate = (expectedRate * 97) / 100;
}
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Get expected rate for a trade from src to dest tokens, amount srcQty and fees
/// @param src Source token
/// @param dest Destination token
/// @param srcQty Amount of src tokens in twei
/// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 10000 = 100%, 100 = 1%
/// @param hint Advanced instructions for running the trade
/// @return expectedRate for a trade after deducting network + platform fee
/// Rate = destQty (twei) / srcQty (twei) * 10 ** 18
function getExpectedRateAfterFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
) external view override returns (uint256 expectedRate) {
(, expectedRate) = kyberNetwork.getExpectedRateWithHintAndFee(
src,
dest,
srcQty,
platformFeeBps,
hint
);
}
function maxGasPrice() external view returns (uint256) {
return kyberNetwork.maxGasPrice();
}
function enabled() external view returns (bool) {
return kyberNetwork.enabled();
}
/// helper structure for function doTrade
struct UserBalance {
uint256 srcTok;
uint256 destTok;
}
function doTrade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes memory hint
) internal returns (uint256) {
UserBalance memory balanceBefore = prepareTrade(src, dest, srcAmount, destAddress);
uint256 reportedDestAmount = kyberNetwork.tradeWithHintAndFee{value: msg.value}(
msg.sender,
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
platformWallet,
platformFeeBps,
hint
);
TradeOutcome memory tradeOutcome = calculateTradeOutcome(
src,
dest,
destAddress,
platformFeeBps,
balanceBefore
);
require(
tradeOutcome.userDeltaDestToken == reportedDestAmount,
"kyberNetwork returned wrong amount"
);
require(
tradeOutcome.userDeltaDestToken <= maxDestAmount,
"actual dest amount exceeds maxDestAmount"
);
require(tradeOutcome.actualRate >= minConversionRate, "rate below minConversionRate");
emit ExecuteTrade(
msg.sender,
src,
dest,
destAddress,
tradeOutcome.userDeltaSrcToken,
tradeOutcome.userDeltaDestToken,
platformWallet,
platformFeeBps
);
return tradeOutcome.userDeltaDestToken;
}
/// helper structure for function prepareTrade
struct TradeOutcome {
uint256 userDeltaSrcToken;
uint256 userDeltaDestToken;
uint256 actualRate;
}
function prepareTrade(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
address destAddress
) internal returns (UserBalance memory balanceBefore) {
if (src == ETH_TOKEN_ADDRESS) {
require(msg.value == srcAmount, "sent eth not equal to srcAmount");
} else {
require(msg.value == 0, "sent eth not 0");
}
balanceBefore.srcTok = getBalance(src, msg.sender);
balanceBefore.destTok = getBalance(dest, destAddress);
if (src == ETH_TOKEN_ADDRESS) {
balanceBefore.srcTok += msg.value;
} else {
src.safeTransferFrom(msg.sender, address(kyberNetwork), srcAmount);
}
}
function calculateTradeOutcome(
IERC20 src,
IERC20 dest,
address destAddress,
uint256 platformFeeBps,
UserBalance memory balanceBefore
) internal returns (TradeOutcome memory outcome) {
uint256 srcTokenBalanceAfter;
uint256 destTokenBalanceAfter;
srcTokenBalanceAfter = getBalance(src, msg.sender);
destTokenBalanceAfter = getBalance(dest, destAddress);
//protect from underflow
require(
destTokenBalanceAfter > balanceBefore.destTok,
"wrong amount in destination address"
);
require(balanceBefore.srcTok > srcTokenBalanceAfter, "wrong amount in source address");
outcome.userDeltaSrcToken = balanceBefore.srcTok - srcTokenBalanceAfter;
outcome.userDeltaDestToken = destTokenBalanceAfter - balanceBefore.destTok;
// what would be the src amount after deducting platformFee
// not protecting from platform fee
uint256 srcTokenAmountAfterDeductingFee = (outcome.userDeltaSrcToken *
(BPS - platformFeeBps)) / BPS;
outcome.actualRate = calcRateFromQty(
srcTokenAmountAfterDeductingFee,
outcome.userDeltaDestToken,
getUpdateDecimals(src),
getUpdateDecimals(dest)
);
}
}File 2 of 8: 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 3 of 8: KyberNetwork
// File: contracts/sol6/IERC20.sol
pragma solidity 0.6.6;
interface IERC20 {
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function approve(address _spender, uint256 _value) external returns (bool success);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
function balanceOf(address _owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 digits);
function totalSupply() external view returns (uint256 supply);
}
// to support backward compatible contract name -- so function signature remains same
abstract contract ERC20 is IERC20 {
}
// File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
pragma solidity 0.6.6;
contract PermissionGroupsNoModifiers {
address public admin;
address public pendingAdmin;
mapping(address => bool) internal operators;
mapping(address => bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint256 internal constant MAX_GROUP_SIZE = 50;
event AdminClaimed(address newAdmin, address previousAdmin);
event AlerterAdded(address newAlerter, bool isAdd);
event OperatorAdded(address newOperator, bool isAdd);
event TransferAdminPending(address pendingAdmin);
constructor(address _admin) public {
require(_admin != address(0), "admin 0");
admin = _admin;
}
function getOperators() external view returns (address[] memory) {
return operatorsGroup;
}
function getAlerters() external view returns (address[] memory) {
return alertersGroup;
}
function addAlerter(address newAlerter) public {
onlyAdmin();
require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
emit AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function addOperator(address newOperator) public {
onlyAdmin();
require(!operators[newOperator], "operator exists"); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
emit OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
/// @dev Allows the pendingAdmin address to finalize the change admin process.
function claimAdmin() public {
require(pendingAdmin == msg.sender, "not pending");
emit AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
function removeAlerter(address alerter) public {
onlyAdmin();
require(alerters[alerter], "not alerter");
delete alerters[alerter];
for (uint256 i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.pop();
emit AlerterAdded(alerter, false);
break;
}
}
}
function removeOperator(address operator) public {
onlyAdmin();
require(operators[operator], "not operator");
delete operators[operator];
for (uint256 i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.pop();
emit OperatorAdded(operator, false);
break;
}
}
}
/// @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), "new admin 0");
emit TransferAdminPending(newAdmin);
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), "admin 0");
emit TransferAdminPending(newAdmin);
emit AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
function onlyAdmin() internal view {
require(msg.sender == admin, "only admin");
}
function onlyAlerter() internal view {
require(alerters[msg.sender], "only alerter");
}
function onlyOperator() internal view {
require(operators[msg.sender], "only operator");
}
}
// File: contracts/sol6/utils/WithdrawableNoModifiers.sol
pragma solidity 0.6.6;
contract WithdrawableNoModifiers is PermissionGroupsNoModifiers {
constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {}
event EtherWithdraw(uint256 amount, address sendTo);
event TokenWithdraw(IERC20 token, uint256 amount, address sendTo);
/// @dev Withdraw Ethers
function withdrawEther(uint256 amount, address payable sendTo) external {
onlyAdmin();
(bool success, ) = sendTo.call{value: amount}("");
require(success);
emit EtherWithdraw(amount, sendTo);
}
/// @dev Withdraw all IERC20 compatible tokens
/// @param token IERC20 The address of the token contract
function withdrawToken(
IERC20 token,
uint256 amount,
address sendTo
) external {
onlyAdmin();
token.transfer(sendTo, amount);
emit TokenWithdraw(token, amount, sendTo);
}
}
// File: contracts/sol6/utils/Utils5.sol
pragma solidity 0.6.6;
/**
* @title Kyber utility file
* mostly shared constants and rate calculation helpers
* inherited by most of kyber contracts.
* previous utils implementations are for previous solidity versions.
*/
contract Utils5 {
IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
);
uint256 internal constant PRECISION = (10**18);
uint256 internal constant MAX_QTY = (10**28); // 10B tokens
uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
uint256 internal constant MAX_DECIMALS = 18;
uint256 internal constant ETH_DECIMALS = 18;
uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
mapping(IERC20 => uint256) internal decimals;
function getUpdateDecimals(IERC20 token) internal returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if (tokenDecimals == 0) {
tokenDecimals = token.decimals();
decimals[token] = tokenDecimals;
}
return tokenDecimals;
}
function setDecimals(IERC20 token) internal {
if (decimals[token] != 0) return; //already set
if (token == ETH_TOKEN_ADDRESS) {
decimals[token] = ETH_DECIMALS;
} else {
decimals[token] = token.decimals();
}
}
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(IERC20 token, address user) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) {
return user.balance;
} else {
return token.balanceOf(user);
}
}
function getDecimals(IERC20 token) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// 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 calcDestAmount(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
uint256 rate
) internal view returns (uint256) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(
IERC20 src,
IERC20 dest,
uint256 destAmount,
uint256 rate
) internal view returns (uint256) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcDstQty(
uint256 srcQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(
uint256 dstQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
//source quantity is rounded up. to avoid dest quantity being too low.
uint256 numerator;
uint256 denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
function calcRateFromQty(
uint256 srcAmount,
uint256 destAmount,
uint256 srcDecimals,
uint256 dstDecimals
) internal pure returns (uint256) {
require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
}
}
function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? y : x;
}
}
// File: contracts/sol6/utils/zeppelin/ReentrancyGuard.sol
pragma solidity 0.6.6;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
contract ReentrancyGuard {
bool private _notEntered;
constructor () internal {
// Storing an initial non-zero value makes deployment a bit more
// expensive, but in exchange the refund on every call to nonReentrant
// will be lower in amount. Since refunds are capped to a percetange of
// the total transaction's gas, it is best to keep them low in cases
// like this one, to increase the likelihood of the full refund coming
// into effect.
_notEntered = true;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_notEntered, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_notEntered = false;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
}
// File: contracts/sol6/utils/zeppelin/SafeMath.sol
pragma solidity 0.6.6;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
// File: contracts/sol6/utils/zeppelin/Address.sol
pragma solidity 0.6.6;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
// File: contracts/sol6/utils/zeppelin/SafeERC20.sol
pragma solidity 0.6.6;
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// File: contracts/sol6/IKyberNetwork.sol
pragma solidity 0.6.6;
interface IKyberNetwork {
event KyberTrade(
IERC20 indexed src,
IERC20 indexed dest,
uint256 ethWeiValue,
uint256 networkFeeWei,
uint256 customPlatformFeeWei,
bytes32[] t2eIds,
bytes32[] e2tIds,
uint256[] t2eSrcAmounts,
uint256[] e2tSrcAmounts,
uint256[] t2eRates,
uint256[] e2tRates
);
function tradeWithHintAndFee(
address payable trader,
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function listTokenForReserve(
address reserve,
IERC20 token,
bool add
) external;
function enabled() external view returns (bool);
function getExpectedRateWithHintAndFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
)
external
view
returns (
uint256 expectedRateAfterNetworkFee,
uint256 expectedRateAfterAllFees
);
function getNetworkData()
external
view
returns (
uint256 negligibleDiffBps,
uint256 networkFeeBps,
uint256 expiryTimestamp
);
function maxGasPrice() external view returns (uint256);
}
// File: contracts/sol6/IKyberReserve.sol
pragma solidity 0.6.6;
interface IKyberReserve {
function trade(
IERC20 srcToken,
uint256 srcAmount,
IERC20 destToken,
address payable destAddress,
uint256 conversionRate,
bool validate
) external payable returns (bool);
function getConversionRate(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 blockNumber
) external view returns (uint256);
}
// File: contracts/sol6/IKyberFeeHandler.sol
pragma solidity 0.6.6;
interface IKyberFeeHandler {
event RewardPaid(address indexed staker, uint256 indexed epoch, IERC20 indexed token, uint256 amount);
event RebatePaid(address indexed rebateWallet, IERC20 indexed token, uint256 amount);
event PlatformFeePaid(address indexed platformWallet, IERC20 indexed token, uint256 amount);
event KncBurned(uint256 kncTWei, IERC20 indexed token, uint256 amount);
function handleFees(
IERC20 token,
address[] calldata eligibleWallets,
uint256[] calldata rebatePercentages,
address platformWallet,
uint256 platformFee,
uint256 networkFee
) external payable;
function claimReserveRebate(address rebateWallet) external returns (uint256);
function claimPlatformFee(address platformWallet) external returns (uint256);
function claimStakerReward(
address staker,
uint256 epoch
) external returns(uint amount);
}
// File: contracts/sol6/Dao/IEpochUtils.sol
pragma solidity 0.6.6;
interface IEpochUtils {
function epochPeriodInSeconds() external view returns (uint256);
function firstEpochStartTimestamp() external view returns (uint256);
function getCurrentEpochNumber() external view returns (uint256);
function getEpochNumber(uint256 timestamp) external view returns (uint256);
}
// File: contracts/sol6/IKyberDao.sol
pragma solidity 0.6.6;
interface IKyberDao is IEpochUtils {
event Voted(address indexed staker, uint indexed epoch, uint indexed campaignID, uint option);
function getLatestNetworkFeeDataWithCache()
external
returns (uint256 feeInBps, uint256 expiryTimestamp);
function getLatestBRRDataWithCache()
external
returns (
uint256 burnInBps,
uint256 rewardInBps,
uint256 rebateInBps,
uint256 epoch,
uint256 expiryTimestamp
);
function handleWithdrawal(address staker, uint256 penaltyAmount) external;
function vote(uint256 campaignID, uint256 option) external;
function getLatestNetworkFeeData()
external
view
returns (uint256 feeInBps, uint256 expiryTimestamp);
function shouldBurnRewardForEpoch(uint256 epoch) external view returns (bool);
/**
* @dev return staker's reward percentage in precision for a past epoch only
* fee handler should call this function when a staker wants to claim reward
* return 0 if staker has no votes or stakes
*/
function getPastEpochRewardPercentageInPrecision(address staker, uint256 epoch)
external
view
returns (uint256);
/**
* @dev return staker's reward percentage in precision for the current epoch
* reward percentage is not finalized until the current epoch is ended
*/
function getCurrentEpochRewardPercentageInPrecision(address staker)
external
view
returns (uint256);
}
// File: contracts/sol6/IKyberNetworkProxy.sol
pragma solidity 0.6.6;
interface IKyberNetworkProxy {
event ExecuteTrade(
address indexed trader,
IERC20 src,
IERC20 dest,
address destAddress,
uint256 actualSrcAmount,
uint256 actualDestAmount,
address platformWallet,
uint256 platformFeeBps
);
/// @notice backward compatible
function tradeWithHint(
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable returns (uint256);
function tradeWithHintAndFee(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function trade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet
) external payable returns (uint256);
/// @notice backward compatible
/// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view returns (uint256 expectedRate, uint256 worstRate);
function getExpectedRateAfterFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
) external view returns (uint256 expectedRate);
}
// File: contracts/sol6/IKyberStorage.sol
pragma solidity 0.6.6;
interface IKyberStorage {
enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST}
function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies)
external;
function removeKyberProxy(address kyberProxy) external;
function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external;
function setKyberDaoContract(address _kyberDao) external;
function getReserveId(address reserve) external view returns (bytes32 reserveId);
function getReserveIdsFromAddresses(address[] calldata reserveAddresses)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesFromIds(bytes32[] calldata reserveIds)
external
view
returns (address[] memory reserveAddresses);
function getReserveIdsPerTokenSrc(IERC20 token)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex)
external
view
returns (address[] memory reserveAddresses);
function getReserveIdsPerTokenDest(IERC20 token)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesByReserveId(bytes32 reserveId)
external
view
returns (address[] memory reserveAddresses);
function getRebateWalletsFromIds(bytes32[] calldata reserveIds)
external
view
returns (address[] memory rebateWallets);
function getKyberProxies() external view returns (IKyberNetworkProxy[] memory);
function getReserveDetailsByAddress(address reserve)
external
view
returns (
bytes32 reserveId,
address rebateWallet,
ReserveType resType,
bool isFeeAccountedFlag,
bool isEntitledRebateFlag
);
function getReserveDetailsById(bytes32 reserveId)
external
view
returns (
address reserveAddress,
address rebateWallet,
ReserveType resType,
bool isFeeAccountedFlag,
bool isEntitledRebateFlag
);
function getFeeAccountedData(bytes32[] calldata reserveIds)
external
view
returns (bool[] memory feeAccountedArr);
function getEntitledRebateData(bytes32[] calldata reserveIds)
external
view
returns (bool[] memory entitledRebateArr);
function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest)
external
view
returns (
bool areAllReservesListed,
bool[] memory feeAccountedArr,
bool[] memory entitledRebateArr,
IKyberReserve[] memory reserveAddresses);
function isKyberProxyAdded() external view returns (bool);
}
// File: contracts/sol6/IKyberMatchingEngine.sol
pragma solidity 0.6.6;
interface IKyberMatchingEngine {
enum ProcessWithRate {NotRequired, Required}
function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps) external;
function setKyberStorage(IKyberStorage _kyberStorage) external;
function getNegligibleRateDiffBps() external view returns (uint256);
function getTradingReserves(
IERC20 src,
IERC20 dest,
bool isTokenToToken,
bytes calldata hint
)
external
view
returns (
bytes32[] memory reserveIds,
uint256[] memory splitValuesBps,
ProcessWithRate processWithRate
);
function doMatch(
IERC20 src,
IERC20 dest,
uint256[] calldata srcAmounts,
uint256[] calldata feesAccountedDestBps,
uint256[] calldata rates
) external view returns (uint256[] memory reserveIndexes);
}
// File: contracts/sol6/IGasHelper.sol
pragma solidity 0.6.6;
interface IGasHelper {
function freeGas(
address platformWallet,
IERC20 src,
IERC20 dest,
uint256 tradeWei,
bytes32[] calldata t2eReserveIds,
bytes32[] calldata e2tReserveIds
) external;
}
// File: contracts/sol6/KyberNetwork.sol
pragma solidity 0.6.6;
/**
* @title kyberNetwork main contract
* Interacts with contracts:
* kyberDao: to retrieve fee data
* kyberFeeHandler: accumulates and distributes trade fees
* kyberMatchingEngine: parse user hint and run reserve matching algorithm
* kyberStorage: store / access reserves, token listings and contract addresses
* kyberReserve(s): query rate and trade
*/
contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, ReentrancyGuard {
using SafeERC20 for IERC20;
struct NetworkFeeData {
uint64 expiryTimestamp;
uint16 feeBps;
}
/// @notice Stores work data for reserves (either for token -> eth, or eth -> token)
/// @dev Variables are in-place, ie. reserve with addresses[i] has id of ids[i], offers rate of rates[i], etc.
/// @param addresses List of reserve addresses selected for the trade
/// @param ids List of reserve ids, to be used for KyberTrade event
/// @param rates List of rates that were offered by the reserves
/// @param isFeeAccountedFlags List of reserves requiring users to pay network fee
/// @param isEntitledRebateFlags List of reserves eligible for rebates
/// @param splitsBps List of proportions of trade amount allocated to the reserves.
/// If there is only 1 reserve, then it should have a value of 10000 bps
/// @param srcAmounts Source amount per reserve.
/// @param decimals Token decimals. Src decimals when for src -> eth, dest decimals when eth -> dest
struct ReservesData {
IKyberReserve[] addresses;
bytes32[] ids;
uint256[] rates;
bool[] isFeeAccountedFlags;
bool[] isEntitledRebateFlags;
uint256[] splitsBps;
uint256[] srcAmounts;
uint256 decimals;
}
/// @notice Main trade data structure, is initialised and used for the entire trade flow
/// @param input Initialised when initTradeInput is called. Stores basic trade info
/// @param tokenToEth Stores information about reserves that were selected for src -> eth side of trade
/// @param ethToToken Stores information about reserves that were selected for eth -> dest side of trade
/// @param tradeWei Trade amount in ether wei, before deducting fees.
/// @param networkFeeWei Network fee in ether wei. For t2t trades, it can go up to 200% of networkFeeBps
/// @param platformFeeWei Platform fee in ether wei
/// @param networkFeeBps Network fee bps determined by kyberDao, or default value
/// @param numEntitledRebateReserves No. of reserves that are eligible for rebates
/// @param feeAccountedBps Proportion of this trade that fee is accounted to, in BPS. Up to 2 * BPS
struct TradeData {
TradeInput input;
ReservesData tokenToEth;
ReservesData ethToToken;
uint256 tradeWei;
uint256 networkFeeWei;
uint256 platformFeeWei;
uint256 networkFeeBps;
uint256 numEntitledRebateReserves;
uint256 feeAccountedBps; // what part of this trade is fee paying. for token -> token - up to 200%
}
struct TradeInput {
address payable trader;
IERC20 src;
uint256 srcAmount;
IERC20 dest;
address payable destAddress;
uint256 maxDestAmount;
uint256 minConversionRate;
address platformWallet;
uint256 platformFeeBps;
}
uint256 internal constant PERM_HINT_GET_RATE = 1 << 255; // for backwards compatibility
uint256 internal constant DEFAULT_NETWORK_FEE_BPS = 25; // till we read value from kyberDao
uint256 internal constant MAX_APPROVED_PROXIES = 2; // limit number of proxies that can trade here
IKyberFeeHandler internal kyberFeeHandler;
IKyberDao internal kyberDao;
IKyberMatchingEngine internal kyberMatchingEngine;
IKyberStorage internal kyberStorage;
IGasHelper internal gasHelper;
NetworkFeeData internal networkFeeData; // data is feeBps and expiry timestamp
uint256 internal maxGasPriceValue = 50 * 1000 * 1000 * 1000; // 50 gwei
bool internal isEnabled = false; // is network enabled
mapping(address => bool) internal kyberProxyContracts;
event EtherReceival(address indexed sender, uint256 amount);
event KyberFeeHandlerUpdated(IKyberFeeHandler newKyberFeeHandler);
event KyberMatchingEngineUpdated(IKyberMatchingEngine newKyberMatchingEngine);
event GasHelperUpdated(IGasHelper newGasHelper);
event KyberDaoUpdated(IKyberDao newKyberDao);
event KyberNetworkParamsSet(uint256 maxGasPrice, uint256 negligibleRateDiffBps);
event KyberNetworkSetEnable(bool isEnabled);
event KyberProxyAdded(address kyberProxy);
event KyberProxyRemoved(address kyberProxy);
event ListedReservesForToken(
IERC20 indexed token,
address[] reserves,
bool add
);
constructor(address _admin, IKyberStorage _kyberStorage)
public
WithdrawableNoModifiers(_admin)
{
updateNetworkFee(now, DEFAULT_NETWORK_FEE_BPS);
kyberStorage = _kyberStorage;
}
receive() external payable {
emit EtherReceival(msg.sender, msg.value);
}
/// @notice Backward compatible function
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param trader Address of the taker side of this trade
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param walletId Platform wallet address for receiving fees
/// @param hint Advanced instructions for running the trade
/// @return destAmount Amount of actual dest tokens in twei
function tradeWithHint(
address payable trader,
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable returns (uint256 destAmount) {
TradeData memory tradeData = initTradeInput({
trader: trader,
src: src,
dest: dest,
srcAmount: srcAmount,
destAddress: destAddress,
maxDestAmount: maxDestAmount,
minConversionRate: minConversionRate,
platformWallet: walletId,
platformFeeBps: 0
});
return trade(tradeData, hint);
}
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param trader Address of the taker side of this trade
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param platformWallet Platform wallet address for receiving fees
/// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 1000 = 10%
/// @param hint Advanced instructions for running the trade
/// @return destAmount Amount of actual dest tokens in twei
function tradeWithHintAndFee(
address payable trader,
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable override returns (uint256 destAmount) {
TradeData memory tradeData = initTradeInput({
trader: trader,
src: src,
dest: dest,
srcAmount: srcAmount,
destAddress: destAddress,
maxDestAmount: maxDestAmount,
minConversionRate: minConversionRate,
platformWallet: platformWallet,
platformFeeBps: platformFeeBps
});
return trade(tradeData, hint);
}
/// @notice Can be called only by kyberStorage
/// @dev Allow or prevent to trade token -> eth for a reserve
/// @param reserve The reserve address
/// @param token Token address
/// @param add If true, then give reserve token allowance, otherwise set zero allowance
function listTokenForReserve(
address reserve,
IERC20 token,
bool add
) external override {
require(msg.sender == address(kyberStorage), "only kyberStorage");
if (add) {
token.safeApprove(reserve, MAX_ALLOWANCE);
setDecimals(token);
} else {
token.safeApprove(reserve, 0);
}
}
/// @notice Can be called only by operator
/// @dev Allow or prevent to trade token -> eth for list of reserves
/// Useful for migration to new network contract
/// Call storage to get list of reserves supporting token -> eth
/// @param token Token address
/// @param startIndex start index in reserves list
/// @param endIndex end index in reserves list (can be larger)
/// @param add If true, then give reserve token allowance, otherwise set zero allowance
function listReservesForToken(
IERC20 token,
uint256 startIndex,
uint256 endIndex,
bool add
) external {
onlyOperator();
if (startIndex > endIndex) {
// no need to do anything
return;
}
address[] memory reserves = kyberStorage.getReserveAddressesPerTokenSrc(
token, startIndex, endIndex
);
if (reserves.length == 0) {
// no need to do anything
return;
}
for(uint i = 0; i < reserves.length; i++) {
if (add) {
token.safeApprove(reserves[i], MAX_ALLOWANCE);
setDecimals(token);
} else {
token.safeApprove(reserves[i], 0);
}
}
emit ListedReservesForToken(token, reserves, add);
}
function setContracts(
IKyberFeeHandler _kyberFeeHandler,
IKyberMatchingEngine _kyberMatchingEngine,
IGasHelper _gasHelper
) external virtual {
onlyAdmin();
if (kyberFeeHandler != _kyberFeeHandler) {
kyberFeeHandler = _kyberFeeHandler;
emit KyberFeeHandlerUpdated(_kyberFeeHandler);
}
if (kyberMatchingEngine != _kyberMatchingEngine) {
kyberMatchingEngine = _kyberMatchingEngine;
emit KyberMatchingEngineUpdated(_kyberMatchingEngine);
}
if ((_gasHelper != IGasHelper(0)) && (_gasHelper != gasHelper)) {
gasHelper = _gasHelper;
emit GasHelperUpdated(_gasHelper);
}
kyberStorage.setContracts(address(_kyberFeeHandler), address(_kyberMatchingEngine));
require(_kyberFeeHandler != IKyberFeeHandler(0));
require(_kyberMatchingEngine != IKyberMatchingEngine(0));
}
function setKyberDaoContract(IKyberDao _kyberDao) external {
// enable setting null kyberDao address
onlyAdmin();
if (kyberDao != _kyberDao) {
kyberDao = _kyberDao;
kyberStorage.setKyberDaoContract(address(_kyberDao));
emit KyberDaoUpdated(_kyberDao);
}
}
function setParams(uint256 _maxGasPrice, uint256 _negligibleRateDiffBps) external {
onlyAdmin();
maxGasPriceValue = _maxGasPrice;
kyberMatchingEngine.setNegligibleRateDiffBps(_negligibleRateDiffBps);
emit KyberNetworkParamsSet(maxGasPriceValue, _negligibleRateDiffBps);
}
function setEnable(bool enable) external {
onlyAdmin();
if (enable) {
require(kyberFeeHandler != IKyberFeeHandler(0));
require(kyberMatchingEngine != IKyberMatchingEngine(0));
require(kyberStorage.isKyberProxyAdded());
}
isEnabled = enable;
emit KyberNetworkSetEnable(isEnabled);
}
/// @dev No. of kyberProxies is capped
function addKyberProxy(address kyberProxy) external virtual {
onlyAdmin();
kyberStorage.addKyberProxy(kyberProxy, MAX_APPROVED_PROXIES);
require(kyberProxy != address(0));
require(!kyberProxyContracts[kyberProxy]);
kyberProxyContracts[kyberProxy] = true;
emit KyberProxyAdded(kyberProxy);
}
function removeKyberProxy(address kyberProxy) external virtual {
onlyAdmin();
kyberStorage.removeKyberProxy(kyberProxy);
require(kyberProxyContracts[kyberProxy]);
kyberProxyContracts[kyberProxy] = false;
emit KyberProxyRemoved(kyberProxy);
}
/// @dev gets the expected rates when trading src -> dest token, with / without fees
/// @param src Source token
/// @param dest Destination token
/// @param srcQty Amount of src tokens in twei
/// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 1000 = 10%
/// @param hint Advanced instructions for running the trade
/// @return rateWithNetworkFee Rate after deducting network fee but excluding platform fee
/// @return rateWithAllFees = actual rate. Rate after accounting for both network and platform fees
function getExpectedRateWithHintAndFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
)
external
view
override
returns (
uint256 rateWithNetworkFee,
uint256 rateWithAllFees
)
{
if (src == dest) return (0, 0);
TradeData memory tradeData = initTradeInput({
trader: payable(address(0)),
src: src,
dest: dest,
srcAmount: (srcQty == 0) ? 1 : srcQty,
destAddress: payable(address(0)),
maxDestAmount: 2**255,
minConversionRate: 0,
platformWallet: payable(address(0)),
platformFeeBps: platformFeeBps
});
tradeData.networkFeeBps = getNetworkFee();
uint256 destAmount;
(destAmount, rateWithNetworkFee) = calcRatesAndAmounts(tradeData, hint);
rateWithAllFees = calcRateFromQty(
tradeData.input.srcAmount,
destAmount,
tradeData.tokenToEth.decimals,
tradeData.ethToToken.decimals
);
}
/// @notice Backward compatible API
/// @dev Gets the expected and slippage rate for exchanging src -> dest token
/// @dev worstRate is hardcoded to be 3% lower of expectedRate
/// @param src Source token
/// @param dest Destination token
/// @param srcQty Amount of src tokens in twei
/// @return expectedRate for a trade after deducting network fee.
/// @return worstRate for a trade. Calculated to be expectedRate * 97 / 100
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view returns (uint256 expectedRate, uint256 worstRate) {
if (src == dest) return (0, 0);
uint256 qty = srcQty & ~PERM_HINT_GET_RATE;
TradeData memory tradeData = initTradeInput({
trader: payable(address(0)),
src: src,
dest: dest,
srcAmount: (qty == 0) ? 1 : qty,
destAddress: payable(address(0)),
maxDestAmount: 2**255,
minConversionRate: 0,
platformWallet: payable(address(0)),
platformFeeBps: 0
});
tradeData.networkFeeBps = getNetworkFee();
(, expectedRate) = calcRatesAndAmounts(tradeData, "");
worstRate = (expectedRate * 97) / 100; // backward compatible formula
}
/// @notice Returns some data about the network
/// @param negligibleDiffBps Negligible rate difference (in basis pts) when searching best rate
/// @param networkFeeBps Network fees to be charged (in basis pts)
/// @param expiryTimestamp Timestamp for which networkFeeBps will expire,
/// and needs to be updated by calling kyberDao contract / set to default
function getNetworkData()
external
view
override
returns (
uint256 negligibleDiffBps,
uint256 networkFeeBps,
uint256 expiryTimestamp
)
{
(networkFeeBps, expiryTimestamp) = readNetworkFeeData();
negligibleDiffBps = kyberMatchingEngine.getNegligibleRateDiffBps();
return (negligibleDiffBps, networkFeeBps, expiryTimestamp);
}
function getContracts()
external
view
returns (
IKyberFeeHandler kyberFeeHandlerAddress,
IKyberDao kyberDaoAddress,
IKyberMatchingEngine kyberMatchingEngineAddress,
IKyberStorage kyberStorageAddress,
IGasHelper gasHelperAddress,
IKyberNetworkProxy[] memory kyberProxyAddresses
)
{
return (
kyberFeeHandler,
kyberDao,
kyberMatchingEngine,
kyberStorage,
gasHelper,
kyberStorage.getKyberProxies()
);
}
/// @notice returns the max gas price allowable for trades
function maxGasPrice() external view override returns (uint256) {
return maxGasPriceValue;
}
/// @notice returns status of the network. If disabled, trades cannot happen.
function enabled() external view override returns (bool) {
return isEnabled;
}
/// @notice Gets network fee from the kyberDao (or use default).
/// For trade function, so that data can be updated and cached.
/// @dev Note that this function can be triggered by anyone, so that
/// the first trader of a new epoch can avoid incurring extra gas costs
function getAndUpdateNetworkFee() public returns (uint256 networkFeeBps) {
uint256 expiryTimestamp;
(networkFeeBps, expiryTimestamp) = readNetworkFeeData();
if (expiryTimestamp < now && kyberDao != IKyberDao(0)) {
(networkFeeBps, expiryTimestamp) = kyberDao.getLatestNetworkFeeDataWithCache();
updateNetworkFee(expiryTimestamp, networkFeeBps);
}
}
/// @notice Calculates platform fee and reserve rebate percentages for the trade.
/// Transfers eth and rebate wallet data to kyberFeeHandler
function handleFees(TradeData memory tradeData) internal {
uint256 sentFee = tradeData.networkFeeWei + tradeData.platformFeeWei;
//no need to handle fees if total fee is zero
if (sentFee == 0)
return;
// update reserve eligibility and rebate percentages
(
address[] memory rebateWallets,
uint256[] memory rebatePercentBps
) = calculateRebates(tradeData);
// send total fee amount to fee handler with reserve data
kyberFeeHandler.handleFees{value: sentFee}(
ETH_TOKEN_ADDRESS,
rebateWallets,
rebatePercentBps,
tradeData.input.platformWallet,
tradeData.platformFeeWei,
tradeData.networkFeeWei
);
}
function updateNetworkFee(uint256 expiryTimestamp, uint256 feeBps) internal {
require(expiryTimestamp < 2**64, "expiry overflow");
require(feeBps < BPS / 2, "fees exceed BPS");
networkFeeData.expiryTimestamp = uint64(expiryTimestamp);
networkFeeData.feeBps = uint16(feeBps);
}
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Do one trade with each reserve in reservesData, verifying network balance
/// as expected to ensure reserves take correct src amount
/// @param src Source token
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param reservesData reservesData to trade
/// @param expectedDestAmount Amount to be transferred to destAddress
/// @param srcDecimals Decimals of source token
/// @param destDecimals Decimals of destination token
function doReserveTrades(
IERC20 src,
IERC20 dest,
address payable destAddress,
ReservesData memory reservesData,
uint256 expectedDestAmount,
uint256 srcDecimals,
uint256 destDecimals
) internal virtual {
if (src == dest) {
// eth -> eth, need not do anything except for token -> eth: transfer eth to destAddress
if (destAddress != (address(this))) {
(bool success, ) = destAddress.call{value: expectedDestAmount}("");
require(success, "send dest qty failed");
}
return;
}
tradeAndVerifyNetworkBalance(
reservesData,
src,
dest,
srcDecimals,
destDecimals
);
if (destAddress != address(this)) {
// for eth -> token / token -> token, transfer tokens to destAddress
dest.safeTransfer(destAddress, expectedDestAmount);
}
}
/// @dev call trade from reserves and verify balances
/// @param reservesData reservesData to trade
/// @param src Source token of trade
/// @param dest Destination token of trade
/// @param srcDecimals Decimals of source token
/// @param destDecimals Decimals of destination token
function tradeAndVerifyNetworkBalance(
ReservesData memory reservesData,
IERC20 src,
IERC20 dest,
uint256 srcDecimals,
uint256 destDecimals
) internal
{
// only need to verify src balance if src is not eth
uint256 srcBalanceBefore = (src == ETH_TOKEN_ADDRESS) ? 0 : getBalance(src, address(this));
uint256 destBalanceBefore = getBalance(dest, address(this));
for(uint256 i = 0; i < reservesData.addresses.length; i++) {
uint256 callValue = (src == ETH_TOKEN_ADDRESS) ? reservesData.srcAmounts[i] : 0;
require(
reservesData.addresses[i].trade{value: callValue}(
src,
reservesData.srcAmounts[i],
dest,
address(this),
reservesData.rates[i],
true
),
"reserve trade failed"
);
uint256 balanceAfter;
if (src != ETH_TOKEN_ADDRESS) {
// verify src balance only if it is not eth
balanceAfter = getBalance(src, address(this));
// verify correct src amount is taken
if (srcBalanceBefore >= balanceAfter && srcBalanceBefore - balanceAfter > reservesData.srcAmounts[i]) {
revert("reserve takes high amount");
}
srcBalanceBefore = balanceAfter;
}
// verify correct dest amount is received
uint256 expectedDestAmount = calcDstQty(
reservesData.srcAmounts[i],
srcDecimals,
destDecimals,
reservesData.rates[i]
);
balanceAfter = getBalance(dest, address(this));
if (balanceAfter < destBalanceBefore || balanceAfter - destBalanceBefore < expectedDestAmount) {
revert("reserve returns low amount");
}
destBalanceBefore = balanceAfter;
}
}
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade API for kyberNetwork
/// @param tradeData Main trade data object for trade info to be stored
function trade(TradeData memory tradeData, bytes memory hint)
internal
virtual
nonReentrant
returns (uint256 destAmount)
{
tradeData.networkFeeBps = getAndUpdateNetworkFee();
validateTradeInput(tradeData.input);
uint256 rateWithNetworkFee;
(destAmount, rateWithNetworkFee) = calcRatesAndAmounts(tradeData, hint);
require(rateWithNetworkFee > 0, "trade invalid, if hint involved, try parseHint API");
require(rateWithNetworkFee < MAX_RATE, "rate > MAX_RATE");
require(rateWithNetworkFee >= tradeData.input.minConversionRate, "rate < min rate");
uint256 actualSrcAmount;
if (destAmount > tradeData.input.maxDestAmount) {
// notice tradeData passed by reference and updated
destAmount = tradeData.input.maxDestAmount;
actualSrcAmount = calcTradeSrcAmountFromDest(tradeData);
} else {
actualSrcAmount = tradeData.input.srcAmount;
}
// token -> eth
doReserveTrades(
tradeData.input.src,
ETH_TOKEN_ADDRESS,
address(this),
tradeData.tokenToEth,
tradeData.tradeWei,
tradeData.tokenToEth.decimals,
ETH_DECIMALS
);
// eth -> token
doReserveTrades(
ETH_TOKEN_ADDRESS,
tradeData.input.dest,
tradeData.input.destAddress,
tradeData.ethToToken,
destAmount,
ETH_DECIMALS,
tradeData.ethToToken.decimals
);
handleChange(
tradeData.input.src,
tradeData.input.srcAmount,
actualSrcAmount,
tradeData.input.trader
);
handleFees(tradeData);
emit KyberTrade({
src: tradeData.input.src,
dest: tradeData.input.dest,
ethWeiValue: tradeData.tradeWei,
networkFeeWei: tradeData.networkFeeWei,
customPlatformFeeWei: tradeData.platformFeeWei,
t2eIds: tradeData.tokenToEth.ids,
e2tIds: tradeData.ethToToken.ids,
t2eSrcAmounts: tradeData.tokenToEth.srcAmounts,
e2tSrcAmounts: tradeData.ethToToken.srcAmounts,
t2eRates: tradeData.tokenToEth.rates,
e2tRates: tradeData.ethToToken.rates
});
if (gasHelper != IGasHelper(0)) {
(bool success, ) = address(gasHelper).call(
abi.encodeWithSignature(
"freeGas(address,address,address,uint256,bytes32[],bytes32[])",
tradeData.input.platformWallet,
tradeData.input.src,
tradeData.input.dest,
tradeData.tradeWei,
tradeData.tokenToEth.ids,
tradeData.ethToToken.ids
)
);
// remove compilation warning
success;
}
return (destAmount);
}
/// @notice If user maxDestAmount < actual dest amount, actualSrcAmount will be < srcAmount
/// Calculate the change, and send it back to the user
function handleChange(
IERC20 src,
uint256 srcAmount,
uint256 requiredSrcAmount,
address payable trader
) internal {
if (requiredSrcAmount < srcAmount) {
// if there is "change" send back to trader
if (src == ETH_TOKEN_ADDRESS) {
(bool success, ) = trader.call{value: (srcAmount - requiredSrcAmount)}("");
require(success, "Send change failed");
} else {
src.safeTransfer(trader, (srcAmount - requiredSrcAmount));
}
}
}
function initTradeInput(
address payable trader,
IERC20 src,
IERC20 dest,
uint256 srcAmount,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps
) internal view returns (TradeData memory tradeData) {
tradeData.input.trader = trader;
tradeData.input.src = src;
tradeData.input.srcAmount = srcAmount;
tradeData.input.dest = dest;
tradeData.input.destAddress = destAddress;
tradeData.input.maxDestAmount = maxDestAmount;
tradeData.input.minConversionRate = minConversionRate;
tradeData.input.platformWallet = platformWallet;
tradeData.input.platformFeeBps = platformFeeBps;
tradeData.tokenToEth.decimals = getDecimals(src);
tradeData.ethToToken.decimals = getDecimals(dest);
}
/// @notice This function does all calculations to find trade dest amount without accounting
/// for maxDestAmount. Part of this process includes:
/// - Call kyberMatchingEngine to parse hint and get an optional reserve list to trade.
/// - Query reserve rates and call kyberMatchingEngine to use best reserve.
/// - Calculate trade values and fee values.
/// This function should set all TradeData information so that it can be later used without
/// any ambiguity
/// @param tradeData Main trade data object for trade info to be stored
/// @param hint Advanced user instructions for the trade
function calcRatesAndAmounts(TradeData memory tradeData, bytes memory hint)
internal
view
returns (uint256 destAmount, uint256 rateWithNetworkFee)
{
validateFeeInput(tradeData.input, tradeData.networkFeeBps);
// token -> eth: find best reserves match and calculate wei amount
tradeData.tradeWei = calcDestQtyAndMatchReserves(
tradeData.input.src,
ETH_TOKEN_ADDRESS,
tradeData.input.srcAmount,
tradeData,
tradeData.tokenToEth,
hint
);
require(tradeData.tradeWei <= MAX_QTY, "Trade wei > MAX_QTY");
if (tradeData.tradeWei == 0) {
return (0, 0);
}
// calculate fees
tradeData.platformFeeWei = (tradeData.tradeWei * tradeData.input.platformFeeBps) / BPS;
tradeData.networkFeeWei =
(((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) /
BPS;
assert(tradeData.tradeWei >= (tradeData.networkFeeWei + tradeData.platformFeeWei));
// eth -> token: find best reserves match and calculate trade dest amount
uint256 actualSrcWei = tradeData.tradeWei -
tradeData.networkFeeWei -
tradeData.platformFeeWei;
destAmount = calcDestQtyAndMatchReserves(
ETH_TOKEN_ADDRESS,
tradeData.input.dest,
actualSrcWei,
tradeData,
tradeData.ethToToken,
hint
);
tradeData.networkFeeWei =
(((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) /
BPS;
rateWithNetworkFee = calcRateFromQty(
tradeData.input.srcAmount * (BPS - tradeData.input.platformFeeBps) / BPS,
destAmount,
tradeData.tokenToEth.decimals,
tradeData.ethToToken.decimals
);
}
/// @notice Get trading reserves, source amounts, and calculate dest qty
/// Store information into tradeData
function calcDestQtyAndMatchReserves(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
TradeData memory tradeData,
ReservesData memory reservesData,
bytes memory hint
) internal view returns (uint256 destAmount) {
if (src == dest) {
return srcAmount;
}
IKyberMatchingEngine.ProcessWithRate processWithRate;
// get reserve list from kyberMatchingEngine
(reservesData.ids, reservesData.splitsBps, processWithRate) =
kyberMatchingEngine.getTradingReserves(
src,
dest,
(tradeData.input.src != ETH_TOKEN_ADDRESS) && (tradeData.input.dest != ETH_TOKEN_ADDRESS),
hint
);
bool areAllReservesListed;
(areAllReservesListed, reservesData.isFeeAccountedFlags, reservesData.isEntitledRebateFlags, reservesData.addresses)
= kyberStorage.getReservesData(reservesData.ids, src, dest);
if(!areAllReservesListed) {
return 0;
}
require(reservesData.ids.length == reservesData.splitsBps.length, "bad split array");
require(reservesData.ids.length == reservesData.isFeeAccountedFlags.length, "bad fee array");
require(reservesData.ids.length == reservesData.isEntitledRebateFlags.length, "bad rebate array");
require(reservesData.ids.length == reservesData.addresses.length, "bad addresses array");
// calculate src trade amount per reserve and query rates
// set data in reservesData struct
uint256[] memory feesAccountedDestBps = calcSrcAmountsAndGetRates(
reservesData,
src,
dest,
srcAmount,
tradeData
);
// if matching engine requires processing with rate data. call doMatch and update reserve list
if (processWithRate == IKyberMatchingEngine.ProcessWithRate.Required) {
uint256[] memory selectedIndexes = kyberMatchingEngine.doMatch(
src,
dest,
reservesData.srcAmounts,
feesAccountedDestBps,
reservesData.rates
);
updateReservesList(reservesData, selectedIndexes);
}
// calculate dest amount and fee paying data of this part (t2e or e2t)
destAmount = validateTradeCalcDestQtyAndFeeData(src, reservesData, tradeData);
}
/// @notice Calculates source amounts per reserve. Does get rate call
function calcSrcAmountsAndGetRates(
ReservesData memory reservesData,
IERC20 src,
IERC20 dest,
uint256 srcAmount,
TradeData memory tradeData
) internal view returns (uint256[] memory feesAccountedDestBps) {
uint256 numReserves = reservesData.ids.length;
uint256 srcAmountAfterFee;
uint256 destAmountFeeBps;
if (src == ETH_TOKEN_ADDRESS) {
// @notice srcAmount is after deducting fees from tradeWei
// @notice using tradeWei to calculate fee so eth -> token symmetric to token -> eth
srcAmountAfterFee = srcAmount -
(tradeData.tradeWei * tradeData.networkFeeBps / BPS);
} else {
srcAmountAfterFee = srcAmount;
destAmountFeeBps = tradeData.networkFeeBps;
}
reservesData.srcAmounts = new uint256[](numReserves);
reservesData.rates = new uint256[](numReserves);
feesAccountedDestBps = new uint256[](numReserves);
// iterate reserve list. validate data. calculate srcAmount according to splits and fee data.
for (uint256 i = 0; i < numReserves; i++) {
require(
reservesData.splitsBps[i] > 0 && reservesData.splitsBps[i] <= BPS,
"invalid split bps"
);
if (reservesData.isFeeAccountedFlags[i]) {
reservesData.srcAmounts[i] = srcAmountAfterFee * reservesData.splitsBps[i] / BPS;
feesAccountedDestBps[i] = destAmountFeeBps;
} else {
reservesData.srcAmounts[i] = (srcAmount * reservesData.splitsBps[i]) / BPS;
}
// get rate with calculated src amount
reservesData.rates[i] = reservesData.addresses[i].getConversionRate(
src,
dest,
reservesData.srcAmounts[i],
block.number
);
}
}
function calculateRebates(TradeData memory tradeData)
internal
view
returns (address[] memory rebateWallets, uint256[] memory rebatePercentBps)
{
rebateWallets = new address[](tradeData.numEntitledRebateReserves);
rebatePercentBps = new uint256[](tradeData.numEntitledRebateReserves);
if (tradeData.numEntitledRebateReserves == 0) {
return (rebateWallets, rebatePercentBps);
}
uint256 index;
bytes32[] memory rebateReserveIds = new bytes32[](tradeData.numEntitledRebateReserves);
// token -> eth
index = createRebateEntitledList(
rebateReserveIds,
rebatePercentBps,
tradeData.tokenToEth,
index,
tradeData.feeAccountedBps
);
// eth -> token
createRebateEntitledList(
rebateReserveIds,
rebatePercentBps,
tradeData.ethToToken,
index,
tradeData.feeAccountedBps
);
rebateWallets = kyberStorage.getRebateWalletsFromIds(rebateReserveIds);
}
function createRebateEntitledList(
bytes32[] memory rebateReserveIds,
uint256[] memory rebatePercentBps,
ReservesData memory reservesData,
uint256 index,
uint256 feeAccountedBps
) internal pure returns (uint256) {
uint256 _index = index;
for (uint256 i = 0; i < reservesData.isEntitledRebateFlags.length; i++) {
if (reservesData.isEntitledRebateFlags[i]) {
rebateReserveIds[_index] = reservesData.ids[i];
rebatePercentBps[_index] = (reservesData.splitsBps[i] * BPS) / feeAccountedBps;
_index++;
}
}
return _index;
}
/// @dev Checks a trade input validity, including correct src amounts
/// @param input Trade input structure
function validateTradeInput(TradeInput memory input) internal view
{
require(isEnabled, "network disabled");
require(kyberProxyContracts[msg.sender], "bad sender");
require(tx.gasprice <= maxGasPriceValue, "gas price");
require(input.srcAmount <= MAX_QTY, "srcAmt > MAX_QTY");
require(input.srcAmount != 0, "0 srcAmt");
require(input.destAddress != address(0), "dest add 0");
require(input.src != input.dest, "src = dest");
if (input.src == ETH_TOKEN_ADDRESS) {
require(msg.value == input.srcAmount); // kyberProxy issues message here
} else {
require(msg.value == 0); // kyberProxy issues message here
// funds should have been moved to this contract already.
require(input.src.balanceOf(address(this)) >= input.srcAmount, "no tokens");
}
}
/// @notice Gets the network fee from kyberDao (or use default). View function for getExpectedRate
function getNetworkFee() internal view returns (uint256 networkFeeBps) {
uint256 expiryTimestamp;
(networkFeeBps, expiryTimestamp) = readNetworkFeeData();
if (expiryTimestamp < now && kyberDao != IKyberDao(0)) {
(networkFeeBps, expiryTimestamp) = kyberDao.getLatestNetworkFeeData();
}
}
function readNetworkFeeData() internal view returns (uint256 feeBps, uint256 expiryTimestamp) {
feeBps = uint256(networkFeeData.feeBps);
expiryTimestamp = uint256(networkFeeData.expiryTimestamp);
}
/// @dev Checks fee input validity, including correct src amounts
/// @param input Trade input structure
/// @param networkFeeBps Network fee in bps.
function validateFeeInput(TradeInput memory input, uint256 networkFeeBps) internal pure {
require(input.platformFeeBps < BPS, "platformFee high");
require(input.platformFeeBps + networkFeeBps + networkFeeBps < BPS, "fees high");
}
/// @notice Update reserve data with selected reserves from kyberMatchingEngine
function updateReservesList(ReservesData memory reservesData, uint256[] memory selectedIndexes)
internal
pure
{
uint256 numReserves = selectedIndexes.length;
require(numReserves <= reservesData.addresses.length, "doMatch: too many reserves");
IKyberReserve[] memory reserveAddresses = new IKyberReserve[](numReserves);
bytes32[] memory reserveIds = new bytes32[](numReserves);
uint256[] memory splitsBps = new uint256[](numReserves);
bool[] memory isFeeAccountedFlags = new bool[](numReserves);
bool[] memory isEntitledRebateFlags = new bool[](numReserves);
uint256[] memory srcAmounts = new uint256[](numReserves);
uint256[] memory rates = new uint256[](numReserves);
// update participating resevres and all data (rates, srcAmounts, feeAcounted etc.)
for (uint256 i = 0; i < numReserves; i++) {
reserveAddresses[i] = reservesData.addresses[selectedIndexes[i]];
reserveIds[i] = reservesData.ids[selectedIndexes[i]];
splitsBps[i] = reservesData.splitsBps[selectedIndexes[i]];
isFeeAccountedFlags[i] = reservesData.isFeeAccountedFlags[selectedIndexes[i]];
isEntitledRebateFlags[i] = reservesData.isEntitledRebateFlags[selectedIndexes[i]];
srcAmounts[i] = reservesData.srcAmounts[selectedIndexes[i]];
rates[i] = reservesData.rates[selectedIndexes[i]];
}
// update values
reservesData.addresses = reserveAddresses;
reservesData.ids = reserveIds;
reservesData.splitsBps = splitsBps;
reservesData.isFeeAccountedFlags = isFeeAccountedFlags;
reservesData.isEntitledRebateFlags = isEntitledRebateFlags;
reservesData.rates = rates;
reservesData.srcAmounts = srcAmounts;
}
/// @notice Verify split values bps and reserve ids,
/// then calculate the destQty from srcAmounts and rates
/// @dev Each split bps must be in range (0, BPS]
/// @dev Total split bps must be 100%
/// @dev Reserve ids must be increasing
function validateTradeCalcDestQtyAndFeeData(
IERC20 src,
ReservesData memory reservesData,
TradeData memory tradeData
) internal pure returns (uint256 totalDestAmount) {
uint256 totalBps;
uint256 srcDecimals = (src == ETH_TOKEN_ADDRESS) ? ETH_DECIMALS : reservesData.decimals;
uint256 destDecimals = (src == ETH_TOKEN_ADDRESS) ? reservesData.decimals : ETH_DECIMALS;
for (uint256 i = 0; i < reservesData.addresses.length; i++) {
if (i > 0 && (uint256(reservesData.ids[i]) <= uint256(reservesData.ids[i - 1]))) {
return 0; // ids are not in increasing order
}
totalBps += reservesData.splitsBps[i];
uint256 destAmount = calcDstQty(
reservesData.srcAmounts[i],
srcDecimals,
destDecimals,
reservesData.rates[i]
);
if (destAmount == 0) {
return 0;
}
totalDestAmount += destAmount;
if (reservesData.isFeeAccountedFlags[i]) {
tradeData.feeAccountedBps += reservesData.splitsBps[i];
if (reservesData.isEntitledRebateFlags[i]) {
tradeData.numEntitledRebateReserves++;
}
}
}
if (totalBps != BPS) {
return 0;
}
}
/// @notice Recalculates tradeWei, network and platform fees, and actual source amount needed for the trade
/// in the event actualDestAmount > maxDestAmount
function calcTradeSrcAmountFromDest(TradeData memory tradeData)
internal
pure
virtual
returns (uint256 actualSrcAmount)
{
uint256 weiAfterDeductingFees;
if (tradeData.input.dest != ETH_TOKEN_ADDRESS) {
weiAfterDeductingFees = calcTradeSrcAmount(
tradeData.tradeWei - tradeData.platformFeeWei - tradeData.networkFeeWei,
ETH_DECIMALS,
tradeData.ethToToken.decimals,
tradeData.input.maxDestAmount,
tradeData.ethToToken
);
} else {
weiAfterDeductingFees = tradeData.input.maxDestAmount;
}
// reverse calculation, because we are working backwards
uint256 newTradeWei =
(weiAfterDeductingFees * BPS * BPS) /
((BPS * BPS) -
(tradeData.networkFeeBps *
tradeData.feeAccountedBps +
tradeData.input.platformFeeBps *
BPS));
tradeData.tradeWei = minOf(newTradeWei, tradeData.tradeWei);
// recalculate network and platform fees based on tradeWei
tradeData.networkFeeWei =
(((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) /
BPS;
tradeData.platformFeeWei = (tradeData.tradeWei * tradeData.input.platformFeeBps) / BPS;
if (tradeData.input.src != ETH_TOKEN_ADDRESS) {
actualSrcAmount = calcTradeSrcAmount(
tradeData.input.srcAmount,
tradeData.tokenToEth.decimals,
ETH_DECIMALS,
tradeData.tradeWei,
tradeData.tokenToEth
);
} else {
actualSrcAmount = tradeData.tradeWei;
}
assert(actualSrcAmount <= tradeData.input.srcAmount);
}
/// @notice Recalculates srcAmounts and stores into tradingReserves, given the new destAmount.
/// Uses the original proportion of srcAmounts and rates to determine new split destAmounts,
/// then calculate the respective srcAmounts
/// @dev Due to small rounding errors, will fallback to current src amounts if new src amount is greater
function calcTradeSrcAmount(
uint256 srcAmount,
uint256 srcDecimals,
uint256 destDecimals,
uint256 destAmount,
ReservesData memory reservesData
) internal pure returns (uint256 newSrcAmount) {
uint256 totalWeightedDestAmount;
for (uint256 i = 0; i < reservesData.srcAmounts.length; i++) {
totalWeightedDestAmount += reservesData.srcAmounts[i] * reservesData.rates[i];
}
uint256[] memory newSrcAmounts = new uint256[](reservesData.srcAmounts.length);
uint256 destAmountSoFar;
uint256 currentSrcAmount;
uint256 destAmountSplit;
for (uint256 i = 0; i < reservesData.srcAmounts.length; i++) {
currentSrcAmount = reservesData.srcAmounts[i];
require(destAmount * currentSrcAmount * reservesData.rates[i] / destAmount ==
currentSrcAmount * reservesData.rates[i],
"multiplication overflow");
destAmountSplit = i == (reservesData.srcAmounts.length - 1)
? (destAmount - destAmountSoFar)
: (destAmount * currentSrcAmount * reservesData.rates[i]) /
totalWeightedDestAmount;
destAmountSoFar += destAmountSplit;
newSrcAmounts[i] = calcSrcQty(
destAmountSplit,
srcDecimals,
destDecimals,
reservesData.rates[i]
);
if (newSrcAmounts[i] > currentSrcAmount) {
// revert back to use current src amounts
return srcAmount;
}
newSrcAmount += newSrcAmounts[i];
}
// new src amounts are used only when all of them aren't greater then current srcAmounts
reservesData.srcAmounts = newSrcAmounts;
}
}File 4 of 8: GVToken
pragma solidity ^0.4.11;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
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;
}
}
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/179
*/
contract ERC20Basic {
uint256 public totalSupply;
function balanceOf(address who) constant returns (uint256);
function transfer(address to, uint256 value) returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
/**
* @title Basic token
* @dev Basic version of StandardToken, with no allowances.
*/
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];
}
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
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);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* @dev https://github.com/ethereum/EIPs/issues/20
* @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract StandardToken is 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);
balances[_to] = balances[_to].add(_value);
balances[_from] = balances[_from].sub(_value);
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];
}
}
// Migration Agent interface
contract MigrationAgent {
function migrateFrom(address _from, uint _value);
}
contract GVToken is StandardToken {
// Constants
string public constant name = "Genesis Vision Token";
string public constant symbol = "GVT";
uint public constant decimals = 18;
uint constant TOKEN_LIMIT = 44 * 1e6 * 1e18;
address public ico;
// GVT transfers are blocked until ICO is finished.
bool public isFrozen = true;
// Token migration variables
address public migrationMaster;
address public migrationAgent;
uint public totalMigrated;
event Migrate(address indexed _from, address indexed _to, uint _value);
// Constructor
function GVToken(address _ico, address _migrationMaster) {
require(_ico != 0);
require(_migrationMaster != 0);
ico = _ico;
migrationMaster = _migrationMaster;
}
// Create tokens
function mint(address holder, uint value) {
require(msg.sender == ico);
require(value > 0);
require(totalSupply + value <= TOKEN_LIMIT);
balances[holder] += value;
totalSupply += value;
Transfer(0x0, holder, value);
}
// Allow token transfer.
function unfreeze() {
require(msg.sender == ico);
isFrozen = false;
}
// ERC20 functions
// =========================
function transfer(address _to, uint _value) public returns (bool) {
require(_to != address(0));
require(!isFrozen);
return super.transfer(_to, _value);
}
function transferFrom(address _from, address _to, uint _value) public returns (bool) {
require(!isFrozen);
return super.transferFrom(_from, _to, _value);
}
function approve(address _spender, uint _value) public returns (bool) {
require(!isFrozen);
return super.approve(_spender, _value);
}
// Token migration
function migrate(uint value) external {
require(migrationAgent != 0);
require(value > 0);
require(value <= balances[msg.sender]);
balances[msg.sender] -= value;
totalSupply -= value;
totalMigrated += value;
MigrationAgent(migrationAgent).migrateFrom(msg.sender, value);
Migrate(msg.sender, migrationAgent, value);
}
// Set address of migration contract
function setMigrationAgent(address _agent) external {
require(migrationAgent == 0);
require(msg.sender == migrationMaster);
migrationAgent = _agent;
}
function setMigrationMaster(address _master) external {
require(msg.sender == migrationMaster);
require(_master != 0);
migrationMaster = _master;
}
}File 5 of 8: KyberFeeHandler
// File: contracts/sol6/IERC20.sol
pragma solidity 0.6.6;
interface IERC20 {
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function approve(address _spender, uint256 _value) external returns (bool success);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
function balanceOf(address _owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 digits);
function totalSupply() external view returns (uint256 supply);
}
// to support backward compatible contract name -- so function signature remains same
abstract contract ERC20 is IERC20 {
}
// File: contracts/sol6/utils/Utils5.sol
pragma solidity 0.6.6;
/**
* @title Kyber utility file
* mostly shared constants and rate calculation helpers
* inherited by most of kyber contracts.
* previous utils implementations are for previous solidity versions.
*/
contract Utils5 {
IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
);
uint256 internal constant PRECISION = (10**18);
uint256 internal constant MAX_QTY = (10**28); // 10B tokens
uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
uint256 internal constant MAX_DECIMALS = 18;
uint256 internal constant ETH_DECIMALS = 18;
uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
mapping(IERC20 => uint256) internal decimals;
function getUpdateDecimals(IERC20 token) internal returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if (tokenDecimals == 0) {
tokenDecimals = token.decimals();
decimals[token] = tokenDecimals;
}
return tokenDecimals;
}
function setDecimals(IERC20 token) internal {
if (decimals[token] != 0) return; //already set
if (token == ETH_TOKEN_ADDRESS) {
decimals[token] = ETH_DECIMALS;
} else {
decimals[token] = token.decimals();
}
}
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(IERC20 token, address user) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) {
return user.balance;
} else {
return token.balanceOf(user);
}
}
function getDecimals(IERC20 token) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// 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 calcDestAmount(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
uint256 rate
) internal view returns (uint256) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(
IERC20 src,
IERC20 dest,
uint256 destAmount,
uint256 rate
) internal view returns (uint256) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcDstQty(
uint256 srcQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(
uint256 dstQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
//source quantity is rounded up. to avoid dest quantity being too low.
uint256 numerator;
uint256 denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
function calcRateFromQty(
uint256 srcAmount,
uint256 destAmount,
uint256 srcDecimals,
uint256 dstDecimals
) internal pure returns (uint256) {
require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
}
}
function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? y : x;
}
}
// File: contracts/sol6/utils/zeppelin/ReentrancyGuard.sol
pragma solidity 0.6.6;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
contract ReentrancyGuard {
bool private _notEntered;
constructor () internal {
// Storing an initial non-zero value makes deployment a bit more
// expensive, but in exchange the refund on every call to nonReentrant
// will be lower in amount. Since refunds are capped to a percetange of
// the total transaction's gas, it is best to keep them low in cases
// like this one, to increase the likelihood of the full refund coming
// into effect.
_notEntered = true;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_notEntered, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_notEntered = false;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_notEntered = true;
}
}
// File: contracts/sol6/Dao/IEpochUtils.sol
pragma solidity 0.6.6;
interface IEpochUtils {
function epochPeriodInSeconds() external view returns (uint256);
function firstEpochStartTimestamp() external view returns (uint256);
function getCurrentEpochNumber() external view returns (uint256);
function getEpochNumber(uint256 timestamp) external view returns (uint256);
}
// File: contracts/sol6/IKyberDao.sol
pragma solidity 0.6.6;
interface IKyberDao is IEpochUtils {
event Voted(address indexed staker, uint indexed epoch, uint indexed campaignID, uint option);
function getLatestNetworkFeeDataWithCache()
external
returns (uint256 feeInBps, uint256 expiryTimestamp);
function getLatestBRRDataWithCache()
external
returns (
uint256 burnInBps,
uint256 rewardInBps,
uint256 rebateInBps,
uint256 epoch,
uint256 expiryTimestamp
);
function handleWithdrawal(address staker, uint256 penaltyAmount) external;
function vote(uint256 campaignID, uint256 option) external;
function getLatestNetworkFeeData()
external
view
returns (uint256 feeInBps, uint256 expiryTimestamp);
function shouldBurnRewardForEpoch(uint256 epoch) external view returns (bool);
/**
* @dev return staker's reward percentage in precision for a past epoch only
* fee handler should call this function when a staker wants to claim reward
* return 0 if staker has no votes or stakes
*/
function getPastEpochRewardPercentageInPrecision(address staker, uint256 epoch)
external
view
returns (uint256);
/**
* @dev return staker's reward percentage in precision for the current epoch
* reward percentage is not finalized until the current epoch is ended
*/
function getCurrentEpochRewardPercentageInPrecision(address staker)
external
view
returns (uint256);
}
// File: contracts/sol6/IKyberFeeHandler.sol
pragma solidity 0.6.6;
interface IKyberFeeHandler {
event RewardPaid(address indexed staker, uint256 indexed epoch, IERC20 indexed token, uint256 amount);
event RebatePaid(address indexed rebateWallet, IERC20 indexed token, uint256 amount);
event PlatformFeePaid(address indexed platformWallet, IERC20 indexed token, uint256 amount);
event KncBurned(uint256 kncTWei, IERC20 indexed token, uint256 amount);
function handleFees(
IERC20 token,
address[] calldata eligibleWallets,
uint256[] calldata rebatePercentages,
address platformWallet,
uint256 platformFee,
uint256 networkFee
) external payable;
function claimReserveRebate(address rebateWallet) external returns (uint256);
function claimPlatformFee(address platformWallet) external returns (uint256);
function claimStakerReward(
address staker,
uint256 epoch
) external returns(uint amount);
}
// File: contracts/sol6/IKyberNetworkProxy.sol
pragma solidity 0.6.6;
interface IKyberNetworkProxy {
event ExecuteTrade(
address indexed trader,
IERC20 src,
IERC20 dest,
address destAddress,
uint256 actualSrcAmount,
uint256 actualDestAmount,
address platformWallet,
uint256 platformFeeBps
);
/// @notice backward compatible
function tradeWithHint(
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable returns (uint256);
function tradeWithHintAndFee(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function trade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet
) external payable returns (uint256);
/// @notice backward compatible
/// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view returns (uint256 expectedRate, uint256 worstRate);
function getExpectedRateAfterFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
) external view returns (uint256 expectedRate);
}
// File: contracts/sol6/ISimpleKyberProxy.sol
pragma solidity 0.6.6;
/*
* @title simple Kyber Network proxy interface
* add convenient functions to help with kyber proxy API
*/
interface ISimpleKyberProxy {
function swapTokenToEther(
IERC20 token,
uint256 srcAmount,
uint256 minConversionRate
) external returns (uint256 destAmount);
function swapEtherToToken(IERC20 token, uint256 minConversionRate)
external
payable
returns (uint256 destAmount);
function swapTokenToToken(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
uint256 minConversionRate
) external returns (uint256 destAmount);
}
// File: contracts/sol6/IBurnableToken.sol
pragma solidity 0.6.6;
interface IBurnableToken {
function burn(uint256 _value) external returns (bool);
}
// File: contracts/sol6/Dao/ISanityRate.sol
pragma solidity 0.6.6;
/// @title Sanity Rate check to prevent burning knc with too expensive or cheap price
/// @dev Using ChainLink as the provider for current knc/eth price
interface ISanityRate {
// return latest rate of knc/eth
function latestAnswer() external view returns (uint256);
}
// File: contracts/sol6/utils/zeppelin/SafeMath.sol
pragma solidity 0.6.6;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
// File: contracts/sol6/Dao/DaoOperator.sol
pragma solidity 0.6.6;
contract DaoOperator {
address public daoOperator;
constructor(address _daoOperator) public {
require(_daoOperator != address(0), "daoOperator is 0");
daoOperator = _daoOperator;
}
modifier onlyDaoOperator() {
require(msg.sender == daoOperator, "only daoOperator");
_;
}
}
// File: contracts/sol6/Dao/KyberFeeHandler.sol
pragma solidity 0.6.6;
/**
* @title IKyberProxy
* This interface combines two interfaces.
* It is needed since we use one function from each of the interfaces.
*
*/
interface IKyberProxy is IKyberNetworkProxy, ISimpleKyberProxy {
// empty block
}
/**
* @title kyberFeeHandler
*
* @dev kyberFeeHandler works tightly with contracts kyberNetwork and kyberDao.
* Some events are moved to interface, for easier usage
* @dev Terminology:
* Epoch - Voting campaign time frame in kyberDao.
* kyberDao voting campaigns are in the scope of epochs.
* BRR - Burn / Reward / Rebate. kyberNetwork fee is used for 3 purposes:
* Burning KNC
* Reward an address that staked knc in kyberStaking contract. AKA - stakers
* Rebate reserves for supporting trades.
* @dev Code flow:
* 1. Accumulating && claiming Fees. Per trade on kyberNetwork, it calls handleFees() function which
* internally accounts for network & platform fees from the trade. Fee distribution:
* rewards: accumulated per epoch. can be claimed by the kyberDao after epoch is concluded.
* rebates: accumulated per rebate wallet, can be claimed any time.
* Burn: accumulated in the contract. Burned value and interval limited with safe check using
sanity rate.
* Platfrom fee: accumulated per platform wallet, can be claimed any time.
* 2. Network Fee distribution: Per epoch kyberFeeHandler contract reads BRR distribution percentage
* from kyberDao. When the data expires, kyberFeeHandler reads updated values.
*/
contract KyberFeeHandler is IKyberFeeHandler, Utils5, DaoOperator, ReentrancyGuard {
using SafeMath for uint256;
uint256 internal constant DEFAULT_REWARD_BPS = 3000;
uint256 internal constant DEFAULT_REBATE_BPS = 3000;
uint256 internal constant SANITY_RATE_DIFF_BPS = 1000; // 10%
struct BRRData {
uint64 expiryTimestamp;
uint32 epoch;
uint16 rewardBps;
uint16 rebateBps;
}
struct BRRWei {
uint256 rewardWei;
uint256 fullRebateWei;
uint256 paidRebateWei;
uint256 burnWei;
}
IKyberDao public kyberDao;
IKyberProxy public kyberProxy;
address public kyberNetwork;
IERC20 public immutable knc;
uint256 public immutable burnBlockInterval;
uint256 public lastBurnBlock;
BRRData public brrAndEpochData;
address public daoSetter;
/// @dev amount of eth to burn for each burn knc call
uint256 public weiToBurn = 2 ether;
mapping(address => uint256) public feePerPlatformWallet;
mapping(address => uint256) public rebatePerWallet;
mapping(uint256 => uint256) public rewardsPerEpoch;
mapping(uint256 => uint256) public rewardsPaidPerEpoch;
// hasClaimedReward[staker][epoch]: true/false if the staker has/hasn't claimed the reward for an epoch
mapping(address => mapping (uint256 => bool)) public hasClaimedReward;
uint256 public totalPayoutBalance; // total balance in the contract that is for rebate, reward, platform fee
/// @dev use to get rate of KNC/ETH to check if rate to burn knc is normal
/// @dev index 0 is currently used contract address, indexes > 0 are older versions
ISanityRate[] internal sanityRateContract;
event FeeDistributed(
IERC20 indexed token,
address indexed platformWallet,
uint256 platformFeeWei,
uint256 rewardWei,
uint256 rebateWei,
address[] rebateWallets,
uint256[] rebatePercentBpsPerWallet,
uint256 burnAmtWei
);
event BRRUpdated(
uint256 rewardBps,
uint256 rebateBps,
uint256 burnBps,
uint256 expiryTimestamp,
uint256 indexed epoch
);
event EthReceived(uint256 amount);
event KyberDaoAddressSet(IKyberDao kyberDao);
event BurnConfigSet(ISanityRate sanityRate, uint256 weiToBurn);
event RewardsRemovedToBurn(uint256 indexed epoch, uint256 rewardsWei);
event KyberNetworkUpdated(address kyberNetwork);
event KyberProxyUpdated(IKyberProxy kyberProxy);
constructor(
address _daoSetter,
IKyberProxy _kyberProxy,
address _kyberNetwork,
IERC20 _knc,
uint256 _burnBlockInterval,
address _daoOperator
) public DaoOperator(_daoOperator) {
require(_daoSetter != address(0), "daoSetter 0");
require(_kyberProxy != IKyberProxy(0), "kyberNetworkProxy 0");
require(_kyberNetwork != address(0), "kyberNetwork 0");
require(_knc != IERC20(0), "knc 0");
require(_burnBlockInterval != 0, "_burnBlockInterval 0");
daoSetter = _daoSetter;
kyberProxy = _kyberProxy;
kyberNetwork = _kyberNetwork;
knc = _knc;
burnBlockInterval = _burnBlockInterval;
//start with epoch 0
updateBRRData(DEFAULT_REWARD_BPS, DEFAULT_REBATE_BPS, now, 0);
}
modifier onlyKyberDao {
require(msg.sender == address(kyberDao), "only kyberDao");
_;
}
modifier onlyKyberNetwork {
require(msg.sender == address(kyberNetwork), "only kyberNetwork");
_;
}
modifier onlyNonContract {
require(tx.origin == msg.sender, "only non-contract");
_;
}
receive() external payable {
emit EthReceived(msg.value);
}
/// @dev handleFees function is called per trade on kyberNetwork. unless the trade is not involving any fees.
/// @param token Token currency of fees
/// @param rebateWallets a list of rebate wallets that will get rebate for this trade.
/// @param rebateBpsPerWallet percentage of rebate for each wallet, out of total rebate.
/// @param platformWallet Wallet address that will receive the platfrom fee.
/// @param platformFee Fee amount (in wei) the platfrom wallet is entitled to.
/// @param networkFee Fee amount (in wei) to be allocated for BRR
function handleFees(
IERC20 token,
address[] calldata rebateWallets,
uint256[] calldata rebateBpsPerWallet,
address platformWallet,
uint256 platformFee,
uint256 networkFee
) external payable override onlyKyberNetwork nonReentrant {
require(token == ETH_TOKEN_ADDRESS, "token not eth");
require(msg.value == platformFee.add(networkFee), "msg.value not equal to total fees");
// handle platform fee
feePerPlatformWallet[platformWallet] = feePerPlatformWallet[platformWallet].add(
platformFee
);
if (networkFee == 0) {
// only platform fee paid
totalPayoutBalance = totalPayoutBalance.add(platformFee);
emit FeeDistributed(
ETH_TOKEN_ADDRESS,
platformWallet,
platformFee,
0,
0,
rebateWallets,
rebateBpsPerWallet,
0
);
return;
}
BRRWei memory brrAmounts;
uint256 epoch;
// Decoding BRR data
(brrAmounts.rewardWei, brrAmounts.fullRebateWei, epoch) = getRRWeiValues(networkFee);
brrAmounts.paidRebateWei = updateRebateValues(
brrAmounts.fullRebateWei, rebateWallets, rebateBpsPerWallet
);
brrAmounts.rewardWei = brrAmounts.rewardWei.add(
brrAmounts.fullRebateWei.sub(brrAmounts.paidRebateWei)
);
rewardsPerEpoch[epoch] = rewardsPerEpoch[epoch].add(brrAmounts.rewardWei);
// update total balance of rewards, rebates, fee
totalPayoutBalance = totalPayoutBalance.add(
platformFee).add(brrAmounts.rewardWei).add(brrAmounts.paidRebateWei
);
brrAmounts.burnWei = networkFee.sub(brrAmounts.rewardWei).sub(brrAmounts.paidRebateWei);
emit FeeDistributed(
ETH_TOKEN_ADDRESS,
platformWallet,
platformFee,
brrAmounts.rewardWei,
brrAmounts.paidRebateWei,
rebateWallets,
rebateBpsPerWallet,
brrAmounts.burnWei
);
}
/// @notice WARNING When staker address is a contract,
/// it should be able to receive claimed reward in ETH whenever anyone calls this function.
/// @dev not revert if already claimed or reward percentage is 0
/// allow writing a wrapper to claim for multiple epochs
/// @param staker address.
/// @param epoch for which epoch the staker is claiming the reward
function claimStakerReward(
address staker,
uint256 epoch
) external override nonReentrant returns(uint256 amountWei) {
if (hasClaimedReward[staker][epoch]) {
// staker has already claimed reward for the epoch
return 0;
}
// the relative part of the reward the staker is entitled to for the epoch.
// units Precision: 10 ** 18 = 100%
// if the epoch is current or in the future, kyberDao will return 0 as result
uint256 percentageInPrecision = kyberDao.getPastEpochRewardPercentageInPrecision(staker, epoch);
if (percentageInPrecision == 0) {
return 0; // not revert, in case a wrapper wants to claim reward for multiple epochs
}
require(percentageInPrecision <= PRECISION, "percentage too high");
// Amount of reward to be sent to staker
amountWei = rewardsPerEpoch[epoch].mul(percentageInPrecision).div(PRECISION);
// redundant check, can't happen
assert(totalPayoutBalance >= amountWei);
assert(rewardsPaidPerEpoch[epoch].add(amountWei) <= rewardsPerEpoch[epoch]);
rewardsPaidPerEpoch[epoch] = rewardsPaidPerEpoch[epoch].add(amountWei);
totalPayoutBalance = totalPayoutBalance.sub(amountWei);
hasClaimedReward[staker][epoch] = true;
// send reward to staker
(bool success, ) = staker.call{value: amountWei}("");
require(success, "staker rewards transfer failed");
emit RewardPaid(staker, epoch, ETH_TOKEN_ADDRESS, amountWei);
}
/// @dev claim rebate per reserve wallet. called by any address
/// @param rebateWallet the wallet to claim rebates for. Total accumulated rebate sent to this wallet.
/// @return amountWei amount of rebate claimed
function claimReserveRebate(address rebateWallet)
external
override
nonReentrant
returns (uint256 amountWei)
{
require(rebatePerWallet[rebateWallet] > 1, "no rebate to claim");
// Get total amount of rebate accumulated
amountWei = rebatePerWallet[rebateWallet].sub(1);
// redundant check, can't happen
assert(totalPayoutBalance >= amountWei);
totalPayoutBalance = totalPayoutBalance.sub(amountWei);
rebatePerWallet[rebateWallet] = 1; // avoid zero to non zero storage cost
// send rebate to rebate wallet
(bool success, ) = rebateWallet.call{value: amountWei}("");
require(success, "rebate transfer failed");
emit RebatePaid(rebateWallet, ETH_TOKEN_ADDRESS, amountWei);
return amountWei;
}
/// @dev claim accumulated fee per platform wallet. Called by any address
/// @param platformWallet the wallet to claim fee for. Total accumulated fee sent to this wallet.
/// @return amountWei amount of fee claimed
function claimPlatformFee(address platformWallet)
external
override
nonReentrant
returns (uint256 amountWei)
{
require(feePerPlatformWallet[platformWallet] > 1, "no fee to claim");
// Get total amount of fees accumulated
amountWei = feePerPlatformWallet[platformWallet].sub(1);
// redundant check, can't happen
assert(totalPayoutBalance >= amountWei);
totalPayoutBalance = totalPayoutBalance.sub(amountWei);
feePerPlatformWallet[platformWallet] = 1; // avoid zero to non zero storage cost
(bool success, ) = platformWallet.call{value: amountWei}("");
require(success, "platform fee transfer failed");
emit PlatformFeePaid(platformWallet, ETH_TOKEN_ADDRESS, amountWei);
return amountWei;
}
/// @dev set kyberDao contract address once and set setter address to zero.
/// @param _kyberDao kyberDao address.
function setDaoContract(IKyberDao _kyberDao) external {
require(msg.sender == daoSetter, "only daoSetter");
require(_kyberDao != IKyberDao(0));
kyberDao = _kyberDao;
emit KyberDaoAddressSet(kyberDao);
daoSetter = address(0);
}
/// @dev set new kyberNetwork address by daoOperator
/// @param _kyberNetwork new kyberNetwork contract
function setNetworkContract(address _kyberNetwork) external onlyDaoOperator {
require(_kyberNetwork != address(0), "kyberNetwork 0");
if (_kyberNetwork != kyberNetwork) {
kyberNetwork = _kyberNetwork;
emit KyberNetworkUpdated(kyberNetwork);
}
}
/// @dev Allow to set kyberNetworkProxy address by daoOperator
/// @param _newProxy new kyberNetworkProxy contract
function setKyberProxy(IKyberProxy _newProxy) external onlyDaoOperator {
require(_newProxy != IKyberProxy(0), "kyberNetworkProxy 0");
if (_newProxy != kyberProxy) {
kyberProxy = _newProxy;
emit KyberProxyUpdated(_newProxy);
}
}
/// @dev set knc sanity rate contract and amount wei to burn
/// @param _sanityRate new sanity rate contract
/// @param _weiToBurn new amount of wei to burn
function setBurnConfigParams(ISanityRate _sanityRate, uint256 _weiToBurn)
external
onlyDaoOperator
{
require(_weiToBurn > 0, "_weiToBurn is 0");
if (sanityRateContract.length == 0 || (_sanityRate != sanityRateContract[0])) {
// it is a new sanity rate contract
if (sanityRateContract.length == 0) {
sanityRateContract.push(_sanityRate);
} else {
sanityRateContract.push(sanityRateContract[0]);
sanityRateContract[0] = _sanityRate;
}
}
weiToBurn = _weiToBurn;
emit BurnConfigSet(_sanityRate, _weiToBurn);
}
/// @dev Burn knc. The burn amount is limited. Forces block delay between burn calls.
/// @dev only none ontract can call this function
/// @return kncBurnAmount amount of knc burned
function burnKnc() external onlyNonContract returns (uint256 kncBurnAmount) {
// check if current block > last burn block number + num block interval
require(block.number > lastBurnBlock + burnBlockInterval, "wait more blocks to burn");
// update last burn block number
lastBurnBlock = block.number;
// Get amount to burn, if greater than weiToBurn, burn only weiToBurn per function call.
uint256 balance = address(this).balance;
// redundant check, can't happen
assert(balance >= totalPayoutBalance);
uint256 srcAmount = balance.sub(totalPayoutBalance);
srcAmount = srcAmount > weiToBurn ? weiToBurn : srcAmount;
// Get rate
uint256 kyberEthKncRate = kyberProxy.getExpectedRateAfterFee(
ETH_TOKEN_ADDRESS,
knc,
srcAmount,
0,
""
);
validateEthToKncRateToBurn(kyberEthKncRate);
// Buy some knc and burn
kncBurnAmount = kyberProxy.swapEtherToToken{value: srcAmount}(
knc,
kyberEthKncRate
);
require(IBurnableToken(address(knc)).burn(kncBurnAmount), "knc burn failed");
emit KncBurned(kncBurnAmount, ETH_TOKEN_ADDRESS, srcAmount);
return kncBurnAmount;
}
/// @dev if no one voted for an epoch (like epoch 0), no one gets rewards - should burn it.
/// Will move the epoch reward amount to burn amount. So can later be burned.
/// calls kyberDao contract to check if there were any votes for this epoch.
/// @param epoch epoch number to check.
function makeEpochRewardBurnable(uint256 epoch) external {
require(kyberDao != IKyberDao(0), "kyberDao not set");
require(kyberDao.shouldBurnRewardForEpoch(epoch), "should not burn reward");
uint256 rewardAmount = rewardsPerEpoch[epoch];
require(rewardAmount > 0, "reward is 0");
// redundant check, can't happen
require(totalPayoutBalance >= rewardAmount, "total reward less than epoch reward");
totalPayoutBalance = totalPayoutBalance.sub(rewardAmount);
rewardsPerEpoch[epoch] = 0;
emit RewardsRemovedToBurn(epoch, rewardAmount);
}
/// @notice should be called off chain
/// @dev returns list of sanity rate contracts
/// @dev index 0 is currently used contract address, indexes > 0 are older versions
function getSanityRateContracts() external view returns (ISanityRate[] memory sanityRates) {
sanityRates = sanityRateContract;
}
/// @dev return latest knc/eth rate from sanity rate contract
function getLatestSanityRate() external view returns (uint256 kncToEthSanityRate) {
if (sanityRateContract.length > 0 && sanityRateContract[0] != ISanityRate(0)) {
kncToEthSanityRate = sanityRateContract[0].latestAnswer();
} else {
kncToEthSanityRate = 0;
}
}
function getBRR()
public
returns (
uint256 rewardBps,
uint256 rebateBps,
uint256 epoch
)
{
uint256 expiryTimestamp;
(rewardBps, rebateBps, expiryTimestamp, epoch) = readBRRData();
// Check current timestamp
if (now > expiryTimestamp && kyberDao != IKyberDao(0)) {
uint256 burnBps;
(burnBps, rewardBps, rebateBps, epoch, expiryTimestamp) = kyberDao
.getLatestBRRDataWithCache();
require(burnBps.add(rewardBps).add(rebateBps) == BPS, "Bad BRR values");
emit BRRUpdated(rewardBps, rebateBps, burnBps, expiryTimestamp, epoch);
// Update brrAndEpochData
updateBRRData(rewardBps, rebateBps, expiryTimestamp, epoch);
}
}
function readBRRData()
public
view
returns (
uint256 rewardBps,
uint256 rebateBps,
uint256 expiryTimestamp,
uint256 epoch
)
{
rewardBps = uint256(brrAndEpochData.rewardBps);
rebateBps = uint256(brrAndEpochData.rebateBps);
epoch = uint256(brrAndEpochData.epoch);
expiryTimestamp = uint256(brrAndEpochData.expiryTimestamp);
}
function updateBRRData(
uint256 reward,
uint256 rebate,
uint256 expiryTimestamp,
uint256 epoch
) internal {
// reward and rebate combined values <= BPS. Tested in getBRR.
require(expiryTimestamp < 2**64, "expiry timestamp overflow");
require(epoch < 2**32, "epoch overflow");
brrAndEpochData.rewardBps = uint16(reward);
brrAndEpochData.rebateBps = uint16(rebate);
brrAndEpochData.expiryTimestamp = uint64(expiryTimestamp);
brrAndEpochData.epoch = uint32(epoch);
}
function getRRWeiValues(uint256 RRAmountWei)
internal
returns (
uint256 rewardWei,
uint256 rebateWei,
uint256 epoch
)
{
// Decoding BRR data
uint256 rewardInBps;
uint256 rebateInBps;
(rewardInBps, rebateInBps, epoch) = getBRR();
rebateWei = RRAmountWei.mul(rebateInBps).div(BPS);
rewardWei = RRAmountWei.mul(rewardInBps).div(BPS);
}
function updateRebateValues(
uint256 rebateWei,
address[] memory rebateWallets,
uint256[] memory rebateBpsPerWallet
) internal returns (uint256 totalRebatePaidWei) {
uint256 totalRebateBps;
uint256 walletRebateWei;
for (uint256 i = 0; i < rebateWallets.length; i++) {
require(rebateWallets[i] != address(0), "rebate wallet address 0");
walletRebateWei = rebateWei.mul(rebateBpsPerWallet[i]).div(BPS);
rebatePerWallet[rebateWallets[i]] = rebatePerWallet[rebateWallets[i]].add(
walletRebateWei
);
// a few wei could be left out due to rounding down. so count only paid wei
totalRebatePaidWei = totalRebatePaidWei.add(walletRebateWei);
totalRebateBps = totalRebateBps.add(rebateBpsPerWallet[i]);
}
require(totalRebateBps <= BPS, "rebates more then 100%");
}
function validateEthToKncRateToBurn(uint256 rateEthToKnc) internal view {
require(rateEthToKnc <= MAX_RATE, "ethToKnc rate out of bounds");
require(rateEthToKnc > 0, "ethToKnc rate is 0");
require(sanityRateContract.length > 0, "no sanity rate contract");
require(sanityRateContract[0] != ISanityRate(0), "sanity rate is 0x0, burning is blocked");
// get latest knc/eth rate from sanity contract
uint256 kncToEthRate = sanityRateContract[0].latestAnswer();
require(kncToEthRate > 0, "sanity rate is 0");
require(kncToEthRate <= MAX_RATE, "sanity rate out of bounds");
uint256 sanityEthToKncRate = PRECISION.mul(PRECISION).div(kncToEthRate);
// rate shouldn't be SANITY_RATE_DIFF_BPS lower than sanity rate
require(
rateEthToKnc.mul(BPS) >= sanityEthToKncRate.mul(BPS.sub(SANITY_RATE_DIFF_BPS)),
"kyberNetwork eth to knc rate too low"
);
}
}File 6 of 8: KyberMatchingEngine
// File: contracts/sol6/IERC20.sol
pragma solidity 0.6.6;
interface IERC20 {
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function approve(address _spender, uint256 _value) external returns (bool success);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
function balanceOf(address _owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 digits);
function totalSupply() external view returns (uint256 supply);
}
// to support backward compatible contract name -- so function signature remains same
abstract contract ERC20 is IERC20 {
}
// File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
pragma solidity 0.6.6;
contract PermissionGroupsNoModifiers {
address public admin;
address public pendingAdmin;
mapping(address => bool) internal operators;
mapping(address => bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint256 internal constant MAX_GROUP_SIZE = 50;
event AdminClaimed(address newAdmin, address previousAdmin);
event AlerterAdded(address newAlerter, bool isAdd);
event OperatorAdded(address newOperator, bool isAdd);
event TransferAdminPending(address pendingAdmin);
constructor(address _admin) public {
require(_admin != address(0), "admin 0");
admin = _admin;
}
function getOperators() external view returns (address[] memory) {
return operatorsGroup;
}
function getAlerters() external view returns (address[] memory) {
return alertersGroup;
}
function addAlerter(address newAlerter) public {
onlyAdmin();
require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
emit AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function addOperator(address newOperator) public {
onlyAdmin();
require(!operators[newOperator], "operator exists"); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
emit OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
/// @dev Allows the pendingAdmin address to finalize the change admin process.
function claimAdmin() public {
require(pendingAdmin == msg.sender, "not pending");
emit AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
function removeAlerter(address alerter) public {
onlyAdmin();
require(alerters[alerter], "not alerter");
delete alerters[alerter];
for (uint256 i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.pop();
emit AlerterAdded(alerter, false);
break;
}
}
}
function removeOperator(address operator) public {
onlyAdmin();
require(operators[operator], "not operator");
delete operators[operator];
for (uint256 i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.pop();
emit OperatorAdded(operator, false);
break;
}
}
}
/// @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), "new admin 0");
emit TransferAdminPending(newAdmin);
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), "admin 0");
emit TransferAdminPending(newAdmin);
emit AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
function onlyAdmin() internal view {
require(msg.sender == admin, "only admin");
}
function onlyAlerter() internal view {
require(alerters[msg.sender], "only alerter");
}
function onlyOperator() internal view {
require(operators[msg.sender], "only operator");
}
}
// File: contracts/sol6/utils/WithdrawableNoModifiers.sol
pragma solidity 0.6.6;
contract WithdrawableNoModifiers is PermissionGroupsNoModifiers {
constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {}
event EtherWithdraw(uint256 amount, address sendTo);
event TokenWithdraw(IERC20 token, uint256 amount, address sendTo);
/// @dev Withdraw Ethers
function withdrawEther(uint256 amount, address payable sendTo) external {
onlyAdmin();
(bool success, ) = sendTo.call{value: amount}("");
require(success);
emit EtherWithdraw(amount, sendTo);
}
/// @dev Withdraw all IERC20 compatible tokens
/// @param token IERC20 The address of the token contract
function withdrawToken(
IERC20 token,
uint256 amount,
address sendTo
) external {
onlyAdmin();
token.transfer(sendTo, amount);
emit TokenWithdraw(token, amount, sendTo);
}
}
// File: contracts/sol6/IKyberReserve.sol
pragma solidity 0.6.6;
interface IKyberReserve {
function trade(
IERC20 srcToken,
uint256 srcAmount,
IERC20 destToken,
address payable destAddress,
uint256 conversionRate,
bool validate
) external payable returns (bool);
function getConversionRate(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 blockNumber
) external view returns (uint256);
}
// File: contracts/sol6/IKyberNetwork.sol
pragma solidity 0.6.6;
interface IKyberNetwork {
event KyberTrade(
IERC20 indexed src,
IERC20 indexed dest,
uint256 ethWeiValue,
uint256 networkFeeWei,
uint256 customPlatformFeeWei,
bytes32[] t2eIds,
bytes32[] e2tIds,
uint256[] t2eSrcAmounts,
uint256[] e2tSrcAmounts,
uint256[] t2eRates,
uint256[] e2tRates
);
function tradeWithHintAndFee(
address payable trader,
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function listTokenForReserve(
address reserve,
IERC20 token,
bool add
) external;
function enabled() external view returns (bool);
function getExpectedRateWithHintAndFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
)
external
view
returns (
uint256 expectedRateAfterNetworkFee,
uint256 expectedRateAfterAllFees
);
function getNetworkData()
external
view
returns (
uint256 negligibleDiffBps,
uint256 networkFeeBps,
uint256 expiryTimestamp
);
function maxGasPrice() external view returns (uint256);
}
// File: contracts/sol6/IKyberNetworkProxy.sol
pragma solidity 0.6.6;
interface IKyberNetworkProxy {
event ExecuteTrade(
address indexed trader,
IERC20 src,
IERC20 dest,
address destAddress,
uint256 actualSrcAmount,
uint256 actualDestAmount,
address platformWallet,
uint256 platformFeeBps
);
/// @notice backward compatible
function tradeWithHint(
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable returns (uint256);
function tradeWithHintAndFee(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function trade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet
) external payable returns (uint256);
/// @notice backward compatible
/// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view returns (uint256 expectedRate, uint256 worstRate);
function getExpectedRateAfterFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
) external view returns (uint256 expectedRate);
}
// File: contracts/sol6/IKyberStorage.sol
pragma solidity 0.6.6;
interface IKyberStorage {
enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST}
function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies)
external;
function removeKyberProxy(address kyberProxy) external;
function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external;
function setKyberDaoContract(address _kyberDao) external;
function getReserveId(address reserve) external view returns (bytes32 reserveId);
function getReserveIdsFromAddresses(address[] calldata reserveAddresses)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesFromIds(bytes32[] calldata reserveIds)
external
view
returns (address[] memory reserveAddresses);
function getReserveIdsPerTokenSrc(IERC20 token)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex)
external
view
returns (address[] memory reserveAddresses);
function getReserveIdsPerTokenDest(IERC20 token)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesByReserveId(bytes32 reserveId)
external
view
returns (address[] memory reserveAddresses);
function getRebateWalletsFromIds(bytes32[] calldata reserveIds)
external
view
returns (address[] memory rebateWallets);
function getKyberProxies() external view returns (IKyberNetworkProxy[] memory);
function getReserveDetailsByAddress(address reserve)
external
view
returns (
bytes32 reserveId,
address rebateWallet,
ReserveType resType,
bool isFeeAccountedFlag,
bool isEntitledRebateFlag
);
function getReserveDetailsById(bytes32 reserveId)
external
view
returns (
address reserveAddress,
address rebateWallet,
ReserveType resType,
bool isFeeAccountedFlag,
bool isEntitledRebateFlag
);
function getFeeAccountedData(bytes32[] calldata reserveIds)
external
view
returns (bool[] memory feeAccountedArr);
function getEntitledRebateData(bytes32[] calldata reserveIds)
external
view
returns (bool[] memory entitledRebateArr);
function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest)
external
view
returns (
bool areAllReservesListed,
bool[] memory feeAccountedArr,
bool[] memory entitledRebateArr,
IKyberReserve[] memory reserveAddresses);
function isKyberProxyAdded() external view returns (bool);
}
// File: contracts/sol6/IKyberMatchingEngine.sol
pragma solidity 0.6.6;
interface IKyberMatchingEngine {
enum ProcessWithRate {NotRequired, Required}
function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps) external;
function setKyberStorage(IKyberStorage _kyberStorage) external;
function getNegligibleRateDiffBps() external view returns (uint256);
function getTradingReserves(
IERC20 src,
IERC20 dest,
bool isTokenToToken,
bytes calldata hint
)
external
view
returns (
bytes32[] memory reserveIds,
uint256[] memory splitValuesBps,
ProcessWithRate processWithRate
);
function doMatch(
IERC20 src,
IERC20 dest,
uint256[] calldata srcAmounts,
uint256[] calldata feesAccountedDestBps,
uint256[] calldata rates
) external view returns (uint256[] memory reserveIndexes);
}
// File: contracts/sol6/utils/Utils5.sol
pragma solidity 0.6.6;
/**
* @title Kyber utility file
* mostly shared constants and rate calculation helpers
* inherited by most of kyber contracts.
* previous utils implementations are for previous solidity versions.
*/
contract Utils5 {
IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
);
uint256 internal constant PRECISION = (10**18);
uint256 internal constant MAX_QTY = (10**28); // 10B tokens
uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
uint256 internal constant MAX_DECIMALS = 18;
uint256 internal constant ETH_DECIMALS = 18;
uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
mapping(IERC20 => uint256) internal decimals;
function getUpdateDecimals(IERC20 token) internal returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if (tokenDecimals == 0) {
tokenDecimals = token.decimals();
decimals[token] = tokenDecimals;
}
return tokenDecimals;
}
function setDecimals(IERC20 token) internal {
if (decimals[token] != 0) return; //already set
if (token == ETH_TOKEN_ADDRESS) {
decimals[token] = ETH_DECIMALS;
} else {
decimals[token] = token.decimals();
}
}
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(IERC20 token, address user) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) {
return user.balance;
} else {
return token.balanceOf(user);
}
}
function getDecimals(IERC20 token) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// 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 calcDestAmount(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
uint256 rate
) internal view returns (uint256) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(
IERC20 src,
IERC20 dest,
uint256 destAmount,
uint256 rate
) internal view returns (uint256) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcDstQty(
uint256 srcQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(
uint256 dstQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
//source quantity is rounded up. to avoid dest quantity being too low.
uint256 numerator;
uint256 denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
function calcRateFromQty(
uint256 srcAmount,
uint256 destAmount,
uint256 srcDecimals,
uint256 dstDecimals
) internal pure returns (uint256) {
require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
}
}
function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? y : x;
}
}
// File: contracts/sol6/IKyberHint.sol
pragma solidity 0.6.6;
interface IKyberHint {
enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
enum HintErrors {
NoError, // Hint is valid
NonEmptyDataError, // reserveIDs and splits must be empty for BestOfAll hint
ReserveIdDupError, // duplicate reserveID found
ReserveIdEmptyError, // reserveIDs array is empty for MaskIn and Split trade type
ReserveIdSplitsError, // reserveIDs and splitBpsValues arrays do not have the same length
ReserveIdSequenceError, // reserveID sequence in array is not in increasing order
ReserveIdNotFound, // reserveID isn't registered or doesn't exist
SplitsNotEmptyError, // splitBpsValues is not empty for MaskIn or MaskOut trade type
TokenListedError, // reserveID not listed for the token
TotalBPSError // total BPS for Split trade type is not 10000 (100%)
}
function buildTokenToEthHint(
IERC20 tokenSrc,
TradeType tokenToEthType,
bytes32[] calldata tokenToEthReserveIds,
uint256[] calldata tokenToEthSplits
) external view returns (bytes memory hint);
function buildEthToTokenHint(
IERC20 tokenDest,
TradeType ethToTokenType,
bytes32[] calldata ethToTokenReserveIds,
uint256[] calldata ethToTokenSplits
) external view returns (bytes memory hint);
function buildTokenToTokenHint(
IERC20 tokenSrc,
TradeType tokenToEthType,
bytes32[] calldata tokenToEthReserveIds,
uint256[] calldata tokenToEthSplits,
IERC20 tokenDest,
TradeType ethToTokenType,
bytes32[] calldata ethToTokenReserveIds,
uint256[] calldata ethToTokenSplits
) external view returns (bytes memory hint);
function parseTokenToEthHint(IERC20 tokenSrc, bytes calldata hint)
external
view
returns (
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
IKyberReserve[] memory tokenToEthAddresses,
uint256[] memory tokenToEthSplits
);
function parseEthToTokenHint(IERC20 tokenDest, bytes calldata hint)
external
view
returns (
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
IKyberReserve[] memory ethToTokenAddresses,
uint256[] memory ethToTokenSplits
);
function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes calldata hint)
external
view
returns (
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
IKyberReserve[] memory tokenToEthAddresses,
uint256[] memory tokenToEthSplits,
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
IKyberReserve[] memory ethToTokenAddresses,
uint256[] memory ethToTokenSplits
);
}
// File: contracts/sol6/KyberHintHandler.sol
pragma solidity 0.6.6;
/**
* @title kyberHintHandler contract
* The contract provides the following functionality:
* - building hints
* - parsing hints
*
* All external functions, build*Hint() and parse*Hint:
* - Will revert with error message if an error is found
* - parse*Hint() returns both reserveIds and reserveAddresses
* Internal functions unpackT2THint() and parseHint():
* - Are part of get rate && trade flow
* - Don't revert if an error is found
* - If an error is found, return no data such that the trade flow
* returns 0 rate for bad hint values
*/
abstract contract KyberHintHandler is IKyberHint, Utils5 {
/// @notice Parses the hint for a token -> eth trade
/// @param tokenSrc source token to trade
/// @param hint The ABI encoded hint, built using the build*Hint functions
/// @return tokenToEthType Decoded hint type
/// @return tokenToEthReserveIds Decoded reserve IDs
/// @return tokenToEthAddresses Reserve addresses corresponding to reserve IDs
/// @return tokenToEthSplits Decoded splits
function parseTokenToEthHint(IERC20 tokenSrc, bytes memory hint)
public
view
override
returns (
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
IKyberReserve[] memory tokenToEthAddresses,
uint256[] memory tokenToEthSplits
)
{
HintErrors error;
(tokenToEthType, tokenToEthReserveIds, tokenToEthSplits, error) = parseHint(hint);
if (error != HintErrors.NoError) throwHintError(error);
if (tokenToEthType == TradeType.MaskIn || tokenToEthType == TradeType.Split) {
checkTokenListedForReserve(tokenSrc, tokenToEthReserveIds, true);
}
tokenToEthAddresses = new IKyberReserve[](tokenToEthReserveIds.length);
for (uint256 i = 0; i < tokenToEthReserveIds.length; i++) {
checkReserveIdsExists(tokenToEthReserveIds[i]);
checkDuplicateReserveIds(tokenToEthReserveIds, i);
if (i > 0 && tokenToEthType == TradeType.Split) {
checkSplitReserveIdSeq(tokenToEthReserveIds[i], tokenToEthReserveIds[i - 1]);
}
tokenToEthAddresses[i] = IKyberReserve(
getReserveAddress(tokenToEthReserveIds[i])
);
}
}
/// @notice Parses the hint for a eth -> token trade
/// @param tokenDest destination token to trade
/// @param hint The ABI encoded hint, built using the build*Hint functions
/// @return ethToTokenType Decoded hint type
/// @return ethToTokenReserveIds Decoded reserve IDs
/// @return ethToTokenAddresses Reserve addresses corresponding to reserve IDs
/// @return ethToTokenSplits Decoded splits
function parseEthToTokenHint(IERC20 tokenDest, bytes memory hint)
public
view
override
returns (
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
IKyberReserve[] memory ethToTokenAddresses,
uint256[] memory ethToTokenSplits
)
{
HintErrors error;
(ethToTokenType, ethToTokenReserveIds, ethToTokenSplits, error) = parseHint(hint);
if (error != HintErrors.NoError) throwHintError(error);
if (ethToTokenType == TradeType.MaskIn || ethToTokenType == TradeType.Split) {
checkTokenListedForReserve(tokenDest, ethToTokenReserveIds, false);
}
ethToTokenAddresses = new IKyberReserve[](ethToTokenReserveIds.length);
for (uint256 i = 0; i < ethToTokenReserveIds.length; i++) {
checkReserveIdsExists(ethToTokenReserveIds[i]);
checkDuplicateReserveIds(ethToTokenReserveIds, i);
if (i > 0 && ethToTokenType == TradeType.Split) {
checkSplitReserveIdSeq(ethToTokenReserveIds[i], ethToTokenReserveIds[i - 1]);
}
ethToTokenAddresses[i] = IKyberReserve(
getReserveAddress(ethToTokenReserveIds[i])
);
}
}
/// @notice Parses the hint for a token to token trade
/// @param tokenSrc source token to trade
/// @param tokenDest destination token to trade
/// @param hint The ABI encoded hint, built using the build*Hint functions
/// @return tokenToEthType Decoded hint type
/// @return tokenToEthReserveIds Decoded reserve IDs
/// @return tokenToEthAddresses Reserve addresses corresponding to reserve IDs
/// @return tokenToEthSplits Decoded splits
/// @return ethToTokenType Decoded hint type
/// @return ethToTokenReserveIds Decoded reserve IDs
/// @return ethToTokenAddresses Reserve addresses corresponding to reserve IDs
/// @return ethToTokenSplits Decoded splits
function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes memory hint)
public
view
override
returns (
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
IKyberReserve[] memory tokenToEthAddresses,
uint256[] memory tokenToEthSplits,
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
IKyberReserve[] memory ethToTokenAddresses,
uint256[] memory ethToTokenSplits
)
{
bytes memory t2eHint;
bytes memory e2tHint;
(t2eHint, e2tHint) = unpackT2THint(hint);
(
tokenToEthType,
tokenToEthReserveIds,
tokenToEthAddresses,
tokenToEthSplits
) = parseTokenToEthHint(tokenSrc, t2eHint);
(
ethToTokenType,
ethToTokenReserveIds,
ethToTokenAddresses,
ethToTokenSplits
) = parseEthToTokenHint(tokenDest, e2tHint);
}
/// @notice Builds the hint for a token -> eth trade
/// @param tokenSrc source token to trade
/// @param tokenToEthType token -> eth trade hint type
/// @param tokenToEthReserveIds token -> eth reserve IDs
/// @param tokenToEthSplits token -> eth reserve splits
/// @return hint The ABI encoded hint
function buildTokenToEthHint(
IERC20 tokenSrc,
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
uint256[] memory tokenToEthSplits
) public view override returns (bytes memory hint) {
for (uint256 i = 0; i < tokenToEthReserveIds.length; i++) {
checkReserveIdsExists(tokenToEthReserveIds[i]);
}
HintErrors valid = verifyData(
tokenToEthType,
tokenToEthReserveIds,
tokenToEthSplits
);
if (valid != HintErrors.NoError) throwHintError(valid);
if (tokenToEthType == TradeType.MaskIn || tokenToEthType == TradeType.Split) {
checkTokenListedForReserve(tokenSrc, tokenToEthReserveIds, true);
}
if (tokenToEthType == TradeType.Split) {
bytes32[] memory seqT2EReserveIds;
uint256[] memory seqT2ESplits;
(seqT2EReserveIds, seqT2ESplits) = ensureSplitSeq(
tokenToEthReserveIds,
tokenToEthSplits
);
hint = abi.encode(tokenToEthType, seqT2EReserveIds, seqT2ESplits);
} else {
hint = abi.encode(tokenToEthType, tokenToEthReserveIds, tokenToEthSplits);
}
}
/// @notice Builds the hint for a eth -> token trade
/// @param tokenDest destination token to trade
/// @param ethToTokenType eth -> token trade hint type
/// @param ethToTokenReserveIds eth -> token reserve IDs
/// @param ethToTokenSplits eth -> token reserve splits
/// @return hint The ABI encoded hint
function buildEthToTokenHint(
IERC20 tokenDest,
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
uint256[] memory ethToTokenSplits
) public view override returns (bytes memory hint) {
for (uint256 i = 0; i < ethToTokenReserveIds.length; i++) {
checkReserveIdsExists(ethToTokenReserveIds[i]);
}
HintErrors valid = verifyData(
ethToTokenType,
ethToTokenReserveIds,
ethToTokenSplits
);
if (valid != HintErrors.NoError) throwHintError(valid);
if (ethToTokenType == TradeType.MaskIn || ethToTokenType == TradeType.Split) {
checkTokenListedForReserve(tokenDest, ethToTokenReserveIds, false);
}
if (ethToTokenType == TradeType.Split) {
bytes32[] memory seqE2TReserveIds;
uint256[] memory seqE2TSplits;
(seqE2TReserveIds, seqE2TSplits) = ensureSplitSeq(
ethToTokenReserveIds,
ethToTokenSplits
);
hint = abi.encode(ethToTokenType, seqE2TReserveIds, seqE2TSplits);
} else {
hint = abi.encode(ethToTokenType, ethToTokenReserveIds, ethToTokenSplits);
}
}
/// @notice Builds the hint for a token to token trade
/// @param tokenSrc source token to trade
/// @param tokenToEthType token -> eth trade hint type
/// @param tokenToEthReserveIds token -> eth reserve IDs
/// @param tokenToEthSplits token -> eth reserve splits
/// @param tokenDest destination token to trade
/// @param ethToTokenType eth -> token trade hint type
/// @param ethToTokenReserveIds eth -> token reserve IDs
/// @param ethToTokenSplits eth -> token reserve splits
/// @return hint The ABI encoded hint
function buildTokenToTokenHint(
IERC20 tokenSrc,
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
uint256[] memory tokenToEthSplits,
IERC20 tokenDest,
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
uint256[] memory ethToTokenSplits
) public view override returns (bytes memory hint) {
bytes memory t2eHint = buildTokenToEthHint(
tokenSrc,
tokenToEthType,
tokenToEthReserveIds,
tokenToEthSplits
);
bytes memory e2tHint = buildEthToTokenHint(
tokenDest,
ethToTokenType,
ethToTokenReserveIds,
ethToTokenSplits
);
hint = abi.encode(t2eHint, e2tHint);
}
/// @notice Parses or decodes the token -> eth or eth -> token bytes hint
/// @param hint token -> eth or eth -> token trade hint
/// @return tradeType Decoded hint type
/// @return reserveIds Decoded reserve IDs
/// @return splits Reserve addresses corresponding to reserve IDs
/// @return valid Whether the decoded is valid
function parseHint(bytes memory hint)
internal
pure
returns (
TradeType tradeType,
bytes32[] memory reserveIds,
uint256[] memory splits,
HintErrors valid
)
{
(tradeType, reserveIds, splits) = abi.decode(hint, (TradeType, bytes32[], uint256[])); // solhint-disable
valid = verifyData(tradeType, reserveIds, splits);
if (valid != HintErrors.NoError) {
reserveIds = new bytes32[](0);
splits = new uint256[](0);
}
}
/// @notice Unpacks the token to token hint to token -> eth and eth -> token hints
/// @param hint token to token trade hint
/// @return t2eHint The ABI encoded token -> eth hint
/// @return e2tHint The ABI encoded eth -> token hint
function unpackT2THint(bytes memory hint)
internal
pure
returns (bytes memory t2eHint, bytes memory e2tHint)
{
(t2eHint, e2tHint) = abi.decode(hint, (bytes, bytes));
}
/// @notice Checks if the reserveId exists
/// @param reserveId Reserve ID to check
function checkReserveIdsExists(bytes32 reserveId)
internal
view
{
if (getReserveAddress(reserveId) == address(0))
throwHintError(HintErrors.ReserveIdNotFound);
}
/// @notice Checks that the token is listed for the reserves
/// @param token ERC20 token
/// @param reserveIds Reserve IDs
/// @param isTokenToEth Flag to indicate token -> eth or eth -> token
function checkTokenListedForReserve(
IERC20 token,
bytes32[] memory reserveIds,
bool isTokenToEth
) internal view {
IERC20 src = (isTokenToEth) ? token : ETH_TOKEN_ADDRESS;
IERC20 dest = (isTokenToEth) ? ETH_TOKEN_ADDRESS : token;
if (!areAllReservesListed(reserveIds, src, dest))
throwHintError(HintErrors.TokenListedError);
}
/// @notice Ensures that the reserveIds in the hint to be parsed has no duplicates
/// and applies to all trade types
/// @param reserveIds Array of reserve IDs
/// @param i Starting index from outer loop
function checkDuplicateReserveIds(bytes32[] memory reserveIds, uint256 i)
internal
pure
{
for (uint256 j = i + 1; j < reserveIds.length; j++) {
if (uint256(reserveIds[i]) == uint256(reserveIds[j])) {
throwHintError(HintErrors.ReserveIdDupError);
}
}
}
/// @notice Ensures that the reserveIds in the hint to be parsed is in
/// sequence for and applies to only Split trade type
/// @param reserveId Current index Reserve ID in array
/// @param prevReserveId Previous index Reserve ID in array
function checkSplitReserveIdSeq(bytes32 reserveId, bytes32 prevReserveId)
internal
pure
{
if (uint256(reserveId) <= uint256(prevReserveId)) {
throwHintError(HintErrors.ReserveIdSequenceError);
}
}
/// @notice Ensures that the reserveIds and splits passed when building Split hints are in increasing sequence
/// @param reserveIds Reserve IDs
/// @param splits Reserve splits
/// @return Returns a bytes32[] with reserveIds in increasing sequence and respective arranged splits
function ensureSplitSeq(
bytes32[] memory reserveIds,
uint256[] memory splits
)
internal
pure
returns (bytes32[] memory, uint256[] memory)
{
for (uint256 i = 0; i < reserveIds.length; i++) {
for (uint256 j = i + 1; j < reserveIds.length; j++) {
if (uint256(reserveIds[i]) > (uint256(reserveIds[j]))) {
bytes32 tempId = reserveIds[i];
uint256 tempSplit = splits[i];
reserveIds[i] = reserveIds[j];
reserveIds[j] = tempId;
splits[i] = splits[j];
splits[j] = tempSplit;
} else if (reserveIds[i] == reserveIds[j]) {
throwHintError(HintErrors.ReserveIdDupError);
}
}
}
return (reserveIds, splits);
}
/// @notice Ensures that the data passed when building/parsing hints is valid
/// @param tradeType Trade hint type
/// @param reserveIds Reserve IDs
/// @param splits Reserve splits
/// @return Returns a HintError enum to indicate valid or invalid hint data
function verifyData(
TradeType tradeType,
bytes32[] memory reserveIds,
uint256[] memory splits
) internal pure returns (HintErrors) {
if (tradeType == TradeType.BestOfAll) {
if (reserveIds.length != 0 || splits.length != 0) return HintErrors.NonEmptyDataError;
}
if (
(tradeType == TradeType.MaskIn || tradeType == TradeType.Split) &&
reserveIds.length == 0
) return HintErrors.ReserveIdEmptyError;
if (tradeType == TradeType.Split) {
if (reserveIds.length != splits.length) return HintErrors.ReserveIdSplitsError;
uint256 bpsSoFar;
for (uint256 i = 0; i < splits.length; i++) {
bpsSoFar += splits[i];
}
if (bpsSoFar != BPS) return HintErrors.TotalBPSError;
} else {
if (splits.length != 0) return HintErrors.SplitsNotEmptyError;
}
return HintErrors.NoError;
}
/// @notice Throws error message to user to indicate error on hint
/// @param error Error type from HintErrors enum
function throwHintError(HintErrors error) internal pure {
if (error == HintErrors.NonEmptyDataError) revert("reserveIds and splits must be empty");
if (error == HintErrors.ReserveIdDupError) revert("duplicate reserveId");
if (error == HintErrors.ReserveIdEmptyError) revert("reserveIds cannot be empty");
if (error == HintErrors.ReserveIdSplitsError) revert("reserveIds.length != splits.length");
if (error == HintErrors.ReserveIdSequenceError) revert("reserveIds not in increasing order");
if (error == HintErrors.ReserveIdNotFound) revert("reserveId not found");
if (error == HintErrors.SplitsNotEmptyError) revert("splits must be empty");
if (error == HintErrors.TokenListedError) revert("token is not listed for reserveId");
if (error == HintErrors.TotalBPSError) revert("total BPS != 10000");
}
function getReserveAddress(bytes32 reserveId) internal view virtual returns (address);
function areAllReservesListed(
bytes32[] memory reserveIds,
IERC20 src,
IERC20 dest
) internal virtual view returns (bool);
}
// File: contracts/sol6/KyberMatchingEngine.sol
pragma solidity 0.6.6;
/**
* @title kyberMatchingEngine contract
* During getExpectedRate flow and trade flow this contract is called for:
* - parsing hint and returning reserve list (function getTradingReserves)
* - matching best reserves to trade with (function doMatch)
*/
contract KyberMatchingEngine is KyberHintHandler, IKyberMatchingEngine, WithdrawableNoModifiers {
struct BestReserveInfo {
uint256 index;
uint256 destAmount;
uint256 numRelevantReserves;
}
IKyberNetwork public kyberNetwork;
IKyberStorage public kyberStorage;
uint256 negligibleRateDiffBps = 5; // 1 bps is 0.01%
event KyberStorageUpdated(IKyberStorage newKyberStorage);
event KyberNetworkUpdated(IKyberNetwork newKyberNetwork);
constructor(address _admin) public WithdrawableNoModifiers(_admin) {
/* empty body */
}
function setKyberStorage(IKyberStorage _kyberStorage) external virtual override {
onlyAdmin();
emit KyberStorageUpdated(_kyberStorage);
kyberStorage = _kyberStorage;
}
function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps)
external
virtual
override
{
onlyNetwork();
require(_negligibleRateDiffBps <= BPS, "rateDiffBps exceed BPS"); // at most 100%
negligibleRateDiffBps = _negligibleRateDiffBps;
}
function setNetworkContract(IKyberNetwork _kyberNetwork) external {
onlyAdmin();
require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0");
emit KyberNetworkUpdated(_kyberNetwork);
kyberNetwork = _kyberNetwork;
}
/// @dev Returns trading reserves info for a trade
/// @param src Source token
/// @param dest Destination token
/// @param isTokenToToken Whether the trade is token -> token
/// @param hint Advanced instructions for running the trade
/// @return reserveIds Array of reserve IDs for the trade, each being 32 bytes
/// @return splitValuesBps Array of split values (in basis points) for the trade
/// @return processWithRate Enum ProcessWithRate, whether extra processing is required or not
function getTradingReserves(
IERC20 src,
IERC20 dest,
bool isTokenToToken,
bytes calldata hint
)
external
view
override
returns (
bytes32[] memory reserveIds,
uint256[] memory splitValuesBps,
ProcessWithRate processWithRate
)
{
HintErrors error;
if (hint.length == 0 || hint.length == 4) {
reserveIds = (dest == ETH_TOKEN_ADDRESS)
? kyberStorage.getReserveIdsPerTokenSrc(src)
: kyberStorage.getReserveIdsPerTokenDest(dest);
splitValuesBps = populateSplitValuesBps(reserveIds.length);
processWithRate = ProcessWithRate.Required;
return (reserveIds, splitValuesBps, processWithRate);
}
TradeType tradeType;
if (isTokenToToken) {
bytes memory unpackedHint;
if (src == ETH_TOKEN_ADDRESS) {
(, unpackedHint) = unpackT2THint(hint);
(tradeType, reserveIds, splitValuesBps, error) = parseHint(unpackedHint);
}
if (dest == ETH_TOKEN_ADDRESS) {
(unpackedHint, ) = unpackT2THint(hint);
(tradeType, reserveIds, splitValuesBps, error) = parseHint(unpackedHint);
}
} else {
(tradeType, reserveIds, splitValuesBps, error) = parseHint(hint);
}
if (error != HintErrors.NoError)
return (new bytes32[](0), new uint256[](0), ProcessWithRate.NotRequired);
if (tradeType == TradeType.MaskIn) {
splitValuesBps = populateSplitValuesBps(reserveIds.length);
} else if (tradeType == TradeType.BestOfAll || tradeType == TradeType.MaskOut) {
bytes32[] memory allReserves = (dest == ETH_TOKEN_ADDRESS)
? kyberStorage.getReserveIdsPerTokenSrc(src)
: kyberStorage.getReserveIdsPerTokenDest(dest);
// if bestOfAll, reserveIds = allReserves
// if mask out, apply masking out logic
reserveIds = (tradeType == TradeType.BestOfAll) ?
allReserves :
maskOutReserves(allReserves, reserveIds);
splitValuesBps = populateSplitValuesBps(reserveIds.length);
}
// for split no need to search for best rate. User defines full trade details in advance.
processWithRate = (tradeType == TradeType.Split)
? ProcessWithRate.NotRequired
: ProcessWithRate.Required;
}
function getNegligibleRateDiffBps() external view override returns (uint256) {
return negligibleRateDiffBps;
}
/// @dev Returns the indexes of the best rate from the rates array
/// for token -> eth or eth -> token side of trade
/// @param src Source token (not needed in this kyberMatchingEngine version)
/// @param dest Destination token (not needed in this kyberMatchingEngine version)
/// @param srcAmounts Array of srcAmounts after deducting fees.
/// @param feesAccountedDestBps Fees charged in BPS, to be deducted from calculated destAmount
/// @param rates Rates queried from reserves
/// @return reserveIndexes An array of the indexes most suited for the trade
function doMatch(
IERC20 src,
IERC20 dest,
uint256[] calldata srcAmounts,
uint256[] calldata feesAccountedDestBps, // 0 for no fee, networkFeeBps when has fee
uint256[] calldata rates
) external view override returns (uint256[] memory reserveIndexes) {
src;
dest;
reserveIndexes = new uint256[](1);
// use destAmounts for comparison, but return the best rate
BestReserveInfo memory bestReserve;
bestReserve.numRelevantReserves = 1; // assume always best reserve will be relevant
// return empty array for unlisted tokens
if (rates.length == 0) {
reserveIndexes = new uint256[](0);
return reserveIndexes;
}
uint256[] memory reserveCandidates = new uint256[](rates.length);
uint256[] memory destAmounts = new uint256[](rates.length);
uint256 destAmount;
for (uint256 i = 0; i < rates.length; i++) {
// if fee is accounted on dest amount of this reserve, should deduct it
destAmount = (srcAmounts[i] * rates[i] * (BPS - feesAccountedDestBps[i])) / BPS;
if (destAmount > bestReserve.destAmount) {
// best rate is highest rate
bestReserve.destAmount = destAmount;
bestReserve.index = i;
}
destAmounts[i] = destAmount;
}
if (bestReserve.destAmount == 0) {
reserveIndexes[0] = bestReserve.index;
return reserveIndexes;
}
reserveCandidates[0] = bestReserve.index;
// update best reserve destAmount to be its destAmount after deducting negligible diff.
// if any reserve has better or equal dest amount it can be considred to be chosen as best
bestReserve.destAmount = (bestReserve.destAmount * BPS) / (BPS + negligibleRateDiffBps);
for (uint256 i = 0; i < rates.length; i++) {
if (i == bestReserve.index) continue;
if (destAmounts[i] > bestReserve.destAmount) {
reserveCandidates[bestReserve.numRelevantReserves++] = i;
}
}
if (bestReserve.numRelevantReserves > 1) {
// when encountering small rate diff from bestRate. draw from relevant reserves
bestReserve.index = reserveCandidates[uint256(blockhash(block.number - 1)) %
bestReserve.numRelevantReserves];
} else {
bestReserve.index = reserveCandidates[0];
}
reserveIndexes[0] = bestReserve.index;
}
function getReserveAddress(bytes32 reserveId) internal view override returns (address reserveAddress) {
(reserveAddress, , , ,) = kyberStorage.getReserveDetailsById(reserveId);
}
function areAllReservesListed(
bytes32[] memory reserveIds,
IERC20 src,
IERC20 dest
) internal override view returns (bool allReservesListed) {
(allReservesListed, , ,) = kyberStorage.getReservesData(reserveIds, src, dest);
}
/// @notice Logic for masking out reserves
/// @param allReservesPerToken Array of reserveIds that support
/// the token -> eth or eth -> token side of the trade
/// @param maskedOutReserves Array of reserveIds to be excluded from allReservesPerToken
/// @return filteredReserves An array of reserveIds that can be used for the trade
function maskOutReserves(
bytes32[] memory allReservesPerToken,
bytes32[] memory maskedOutReserves
) internal pure returns (bytes32[] memory filteredReserves) {
require(
allReservesPerToken.length >= maskedOutReserves.length,
"mask out exceeds available reserves"
);
filteredReserves = new bytes32[](allReservesPerToken.length - maskedOutReserves.length);
uint256 currentResultIndex = 0;
for (uint256 i = 0; i < allReservesPerToken.length; i++) {
bytes32 reserveId = allReservesPerToken[i];
bool notMaskedOut = true;
for (uint256 j = 0; j < maskedOutReserves.length; j++) {
bytes32 maskedOutReserveId = maskedOutReserves[j];
if (reserveId == maskedOutReserveId) {
notMaskedOut = false;
break;
}
}
if (notMaskedOut) filteredReserves[currentResultIndex++] = reserveId;
}
}
function onlyNetwork() internal view {
require(msg.sender == address(kyberNetwork), "only kyberNetwork");
}
function populateSplitValuesBps(uint256 length)
internal
pure
returns (uint256[] memory splitValuesBps)
{
splitValuesBps = new uint256[](length);
for (uint256 i = 0; i < length; i++) {
splitValuesBps[i] = BPS;
}
}
}File 7 of 8: KyberStorage
// File: contracts/sol6/IKyberHistory.sol
pragma solidity 0.6.6;
interface IKyberHistory {
function saveContract(address _contract) external;
function getContracts() external view returns (address[] memory);
}
// File: contracts/sol6/IERC20.sol
pragma solidity 0.6.6;
interface IERC20 {
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function approve(address _spender, uint256 _value) external returns (bool success);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
function balanceOf(address _owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 digits);
function totalSupply() external view returns (uint256 supply);
}
// to support backward compatible contract name -- so function signature remains same
abstract contract ERC20 is IERC20 {
}
// File: contracts/sol6/IKyberNetworkProxy.sol
pragma solidity 0.6.6;
interface IKyberNetworkProxy {
event ExecuteTrade(
address indexed trader,
IERC20 src,
IERC20 dest,
address destAddress,
uint256 actualSrcAmount,
uint256 actualDestAmount,
address platformWallet,
uint256 platformFeeBps
);
/// @notice backward compatible
function tradeWithHint(
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable returns (uint256);
function tradeWithHintAndFee(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function trade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet
) external payable returns (uint256);
/// @notice backward compatible
/// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view returns (uint256 expectedRate, uint256 worstRate);
function getExpectedRateAfterFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
) external view returns (uint256 expectedRate);
}
// File: contracts/sol6/IKyberReserve.sol
pragma solidity 0.6.6;
interface IKyberReserve {
function trade(
IERC20 srcToken,
uint256 srcAmount,
IERC20 destToken,
address payable destAddress,
uint256 conversionRate,
bool validate
) external payable returns (bool);
function getConversionRate(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 blockNumber
) external view returns (uint256);
}
// File: contracts/sol6/IKyberStorage.sol
pragma solidity 0.6.6;
interface IKyberStorage {
enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST}
function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies)
external;
function removeKyberProxy(address kyberProxy) external;
function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external;
function setKyberDaoContract(address _kyberDao) external;
function getReserveId(address reserve) external view returns (bytes32 reserveId);
function getReserveIdsFromAddresses(address[] calldata reserveAddresses)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesFromIds(bytes32[] calldata reserveIds)
external
view
returns (address[] memory reserveAddresses);
function getReserveIdsPerTokenSrc(IERC20 token)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex)
external
view
returns (address[] memory reserveAddresses);
function getReserveIdsPerTokenDest(IERC20 token)
external
view
returns (bytes32[] memory reserveIds);
function getReserveAddressesByReserveId(bytes32 reserveId)
external
view
returns (address[] memory reserveAddresses);
function getRebateWalletsFromIds(bytes32[] calldata reserveIds)
external
view
returns (address[] memory rebateWallets);
function getKyberProxies() external view returns (IKyberNetworkProxy[] memory);
function getReserveDetailsByAddress(address reserve)
external
view
returns (
bytes32 reserveId,
address rebateWallet,
ReserveType resType,
bool isFeeAccountedFlag,
bool isEntitledRebateFlag
);
function getReserveDetailsById(bytes32 reserveId)
external
view
returns (
address reserveAddress,
address rebateWallet,
ReserveType resType,
bool isFeeAccountedFlag,
bool isEntitledRebateFlag
);
function getFeeAccountedData(bytes32[] calldata reserveIds)
external
view
returns (bool[] memory feeAccountedArr);
function getEntitledRebateData(bytes32[] calldata reserveIds)
external
view
returns (bool[] memory entitledRebateArr);
function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest)
external
view
returns (
bool areAllReservesListed,
bool[] memory feeAccountedArr,
bool[] memory entitledRebateArr,
IKyberReserve[] memory reserveAddresses);
function isKyberProxyAdded() external view returns (bool);
}
// File: contracts/sol6/IKyberNetwork.sol
pragma solidity 0.6.6;
interface IKyberNetwork {
event KyberTrade(
IERC20 indexed src,
IERC20 indexed dest,
uint256 ethWeiValue,
uint256 networkFeeWei,
uint256 customPlatformFeeWei,
bytes32[] t2eIds,
bytes32[] e2tIds,
uint256[] t2eSrcAmounts,
uint256[] e2tSrcAmounts,
uint256[] t2eRates,
uint256[] e2tRates
);
function tradeWithHintAndFee(
address payable trader,
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function listTokenForReserve(
address reserve,
IERC20 token,
bool add
) external;
function enabled() external view returns (bool);
function getExpectedRateWithHintAndFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
)
external
view
returns (
uint256 expectedRateAfterNetworkFee,
uint256 expectedRateAfterAllFees
);
function getNetworkData()
external
view
returns (
uint256 negligibleDiffBps,
uint256 networkFeeBps,
uint256 expiryTimestamp
);
function maxGasPrice() external view returns (uint256);
}
// File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
pragma solidity 0.6.6;
contract PermissionGroupsNoModifiers {
address public admin;
address public pendingAdmin;
mapping(address => bool) internal operators;
mapping(address => bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint256 internal constant MAX_GROUP_SIZE = 50;
event AdminClaimed(address newAdmin, address previousAdmin);
event AlerterAdded(address newAlerter, bool isAdd);
event OperatorAdded(address newOperator, bool isAdd);
event TransferAdminPending(address pendingAdmin);
constructor(address _admin) public {
require(_admin != address(0), "admin 0");
admin = _admin;
}
function getOperators() external view returns (address[] memory) {
return operatorsGroup;
}
function getAlerters() external view returns (address[] memory) {
return alertersGroup;
}
function addAlerter(address newAlerter) public {
onlyAdmin();
require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
emit AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function addOperator(address newOperator) public {
onlyAdmin();
require(!operators[newOperator], "operator exists"); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
emit OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
/// @dev Allows the pendingAdmin address to finalize the change admin process.
function claimAdmin() public {
require(pendingAdmin == msg.sender, "not pending");
emit AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
function removeAlerter(address alerter) public {
onlyAdmin();
require(alerters[alerter], "not alerter");
delete alerters[alerter];
for (uint256 i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.pop();
emit AlerterAdded(alerter, false);
break;
}
}
}
function removeOperator(address operator) public {
onlyAdmin();
require(operators[operator], "not operator");
delete operators[operator];
for (uint256 i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.pop();
emit OperatorAdded(operator, false);
break;
}
}
}
/// @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), "new admin 0");
emit TransferAdminPending(newAdmin);
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), "admin 0");
emit TransferAdminPending(newAdmin);
emit AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
function onlyAdmin() internal view {
require(msg.sender == admin, "only admin");
}
function onlyAlerter() internal view {
require(alerters[msg.sender], "only alerter");
}
function onlyOperator() internal view {
require(operators[msg.sender], "only operator");
}
}
// File: contracts/sol6/utils/Utils5.sol
pragma solidity 0.6.6;
/**
* @title Kyber utility file
* mostly shared constants and rate calculation helpers
* inherited by most of kyber contracts.
* previous utils implementations are for previous solidity versions.
*/
contract Utils5 {
IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
);
uint256 internal constant PRECISION = (10**18);
uint256 internal constant MAX_QTY = (10**28); // 10B tokens
uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
uint256 internal constant MAX_DECIMALS = 18;
uint256 internal constant ETH_DECIMALS = 18;
uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
mapping(IERC20 => uint256) internal decimals;
function getUpdateDecimals(IERC20 token) internal returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if (tokenDecimals == 0) {
tokenDecimals = token.decimals();
decimals[token] = tokenDecimals;
}
return tokenDecimals;
}
function setDecimals(IERC20 token) internal {
if (decimals[token] != 0) return; //already set
if (token == ETH_TOKEN_ADDRESS) {
decimals[token] = ETH_DECIMALS;
} else {
decimals[token] = token.decimals();
}
}
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(IERC20 token, address user) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) {
return user.balance;
} else {
return token.balanceOf(user);
}
}
function getDecimals(IERC20 token) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// 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 calcDestAmount(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
uint256 rate
) internal view returns (uint256) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(
IERC20 src,
IERC20 dest,
uint256 destAmount,
uint256 rate
) internal view returns (uint256) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcDstQty(
uint256 srcQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(
uint256 dstQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
//source quantity is rounded up. to avoid dest quantity being too low.
uint256 numerator;
uint256 denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
function calcRateFromQty(
uint256 srcAmount,
uint256 destAmount,
uint256 srcDecimals,
uint256 dstDecimals
) internal pure returns (uint256) {
require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
}
}
function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? y : x;
}
}
// File: contracts/sol6/KyberStorage.sol
pragma solidity 0.6.6;
/**
* @title kyberStorage contract
* The contract provides the following functions for kyberNetwork contract:
* - Stores reserve and token listing information by the kyberNetwork
* - Stores feeAccounted data for reserve types
* - Record contract changes for reserves and kyberProxies
* - Points to historical contracts that record contract changes for kyberNetwork,
* kyberFeeHandler, kyberDao and kyberMatchingEngine
*/
contract KyberStorage is IKyberStorage, PermissionGroupsNoModifiers, Utils5 {
// store current and previous contracts
IKyberHistory public kyberNetworkHistory;
IKyberHistory public kyberFeeHandlerHistory;
IKyberHistory public kyberDaoHistory;
IKyberHistory public kyberMatchingEngineHistory;
IKyberReserve[] internal reserves;
IKyberNetworkProxy[] internal kyberProxyArray;
mapping(bytes32 => address[]) internal reserveIdToAddresses;
mapping(bytes32 => address) internal reserveRebateWallet;
mapping(address => bytes32) internal reserveAddressToId;
mapping(IERC20 => bytes32[]) internal reservesPerTokenSrc; // reserves supporting token to eth
mapping(IERC20 => bytes32[]) internal reservesPerTokenDest; // reserves support eth to token
mapping(bytes32 => IERC20[]) internal srcTokensPerReserve;
mapping(bytes32 => IERC20[]) internal destTokensPerReserve;
mapping(IERC20 => mapping(bytes32 => bool)) internal isListedReserveWithTokenSrc;
mapping(IERC20 => mapping(bytes32 => bool)) internal isListedReserveWithTokenDest;
uint256 internal feeAccountedPerType = 0xffffffff;
uint256 internal entitledRebatePerType = 0xffffffff;
mapping(bytes32 => uint256) internal reserveType; // type from enum ReserveType
mapping(ReserveType => bytes32[]) internal reservesPerType;
IKyberNetwork public kyberNetwork;
constructor(
address _admin,
IKyberHistory _kyberNetworkHistory,
IKyberHistory _kyberFeeHandlerHistory,
IKyberHistory _kyberDaoHistory,
IKyberHistory _kyberMatchingEngineHistory
) public PermissionGroupsNoModifiers(_admin) {
require(_kyberNetworkHistory != IKyberHistory(0), "kyberNetworkHistory 0");
require(_kyberFeeHandlerHistory != IKyberHistory(0), "kyberFeeHandlerHistory 0");
require(_kyberDaoHistory != IKyberHistory(0), "kyberDaoHistory 0");
require(_kyberMatchingEngineHistory != IKyberHistory(0), "kyberMatchingEngineHistory 0");
kyberNetworkHistory = _kyberNetworkHistory;
kyberFeeHandlerHistory = _kyberFeeHandlerHistory;
kyberDaoHistory = _kyberDaoHistory;
kyberMatchingEngineHistory = _kyberMatchingEngineHistory;
}
event KyberNetworkUpdated(IKyberNetwork newKyberNetwork);
event RemoveReserveFromStorage(address indexed reserve, bytes32 indexed reserveId);
event AddReserveToStorage(
address indexed reserve,
bytes32 indexed reserveId,
IKyberStorage.ReserveType reserveType,
address indexed rebateWallet
);
event ReserveRebateWalletSet(
bytes32 indexed reserveId,
address indexed rebateWallet
);
event ListReservePairs(
bytes32 indexed reserveId,
address reserve,
IERC20 indexed src,
IERC20 indexed dest,
bool add
);
function setNetworkContract(IKyberNetwork _kyberNetwork) external {
onlyAdmin();
require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0");
emit KyberNetworkUpdated(_kyberNetwork);
kyberNetworkHistory.saveContract(address(_kyberNetwork));
kyberNetwork = _kyberNetwork;
}
function setRebateWallet(bytes32 reserveId, address rebateWallet) external {
onlyOperator();
require(rebateWallet != address(0), "rebate wallet is 0");
require(reserveId != bytes32(0), "reserveId = 0");
require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found");
require(reserveIdToAddresses[reserveId][0] != address(0), "no reserve associated");
reserveRebateWallet[reserveId] = rebateWallet;
emit ReserveRebateWalletSet(reserveId, rebateWallet);
}
function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine)
external
override
{
onlyNetwork();
require(_kyberFeeHandler != address(0), "kyberFeeHandler 0");
require(_kyberMatchingEngine != address(0), "kyberMatchingEngine 0");
kyberFeeHandlerHistory.saveContract(_kyberFeeHandler);
kyberMatchingEngineHistory.saveContract(_kyberMatchingEngine);
}
function setKyberDaoContract(address _kyberDao) external override {
onlyNetwork();
kyberDaoHistory.saveContract(_kyberDao);
}
/// @notice Can be called only by operator
/// @dev Adds a reserve to the storage
/// @param reserve The reserve address
/// @param reserveId The reserve ID in 32 bytes.
/// @param resType Type of the reserve out of enum ReserveType
/// @param rebateWallet Rebate wallet address for this reserve
function addReserve(
address reserve,
bytes32 reserveId,
ReserveType resType,
address payable rebateWallet
) external {
onlyOperator();
require(reserveAddressToId[reserve] == bytes32(0), "reserve has id");
require(reserveId != bytes32(0), "reserveId = 0");
require(
(resType != ReserveType.NONE) && (uint256(resType) < uint256(ReserveType.LAST)),
"bad reserve type"
);
require(feeAccountedPerType != 0xffffffff, "fee accounted data not set");
require(entitledRebatePerType != 0xffffffff, "entitled rebate data not set");
require(rebateWallet != address(0), "rebate wallet is 0");
reserveRebateWallet[reserveId] = rebateWallet;
if (reserveIdToAddresses[reserveId].length == 0) {
reserveIdToAddresses[reserveId].push(reserve);
} else {
require(reserveIdToAddresses[reserveId][0] == address(0), "reserveId taken");
reserveIdToAddresses[reserveId][0] = reserve;
}
reserves.push(IKyberReserve(reserve));
reservesPerType[resType].push(reserveId);
reserveAddressToId[reserve] = reserveId;
reserveType[reserveId] = uint256(resType);
emit AddReserveToStorage(reserve, reserveId, resType, rebateWallet);
emit ReserveRebateWalletSet(reserveId, rebateWallet);
}
/// @notice Can be called only by operator
/// @dev Removes a reserve from the storage
/// @param reserveId The reserve id
/// @param startIndex Index to start searching from in reserve array
function removeReserve(bytes32 reserveId, uint256 startIndex)
external
{
onlyOperator();
require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found");
address reserve = reserveIdToAddresses[reserveId][0];
// delist all token pairs for reserve
delistTokensOfReserve(reserveId);
uint256 reserveIndex = 2**255;
for (uint256 i = startIndex; i < reserves.length; i++) {
if (reserves[i] == IKyberReserve(reserve)) {
reserveIndex = i;
break;
}
}
require(reserveIndex != 2**255, "reserve not found");
reserves[reserveIndex] = reserves[reserves.length - 1];
reserves.pop();
// remove reserve from mapping to address
require(reserveAddressToId[reserve] != bytes32(0), "reserve's existing reserveId is 0");
reserveId = reserveAddressToId[reserve];
// update reserve mappings
reserveIdToAddresses[reserveId].push(reserveIdToAddresses[reserveId][0]);
reserveIdToAddresses[reserveId][0] = address(0);
// remove reserveId from reservesPerType
bytes32[] storage reservesOfType = reservesPerType[ReserveType(reserveType[reserveId])];
for (uint256 i = 0; i < reservesOfType.length; i++) {
if (reserveId == reservesOfType[i]) {
reservesOfType[i] = reservesOfType[reservesOfType.length - 1];
reservesOfType.pop();
break;
}
}
delete reserveAddressToId[reserve];
delete reserveType[reserveId];
delete reserveRebateWallet[reserveId];
emit RemoveReserveFromStorage(reserve, reserveId);
}
/// @notice Can be called only by operator
/// @dev Allow or prevent a specific reserve to trade a pair of tokens
/// @param reserveId The reserve id
/// @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(
bytes32 reserveId,
IERC20 token,
bool ethToToken,
bool tokenToEth,
bool add
) public {
onlyOperator();
require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found");
address reserve = reserveIdToAddresses[reserveId][0];
require(reserve != address(0), "reserve = 0");
if (ethToToken) {
listPairs(reserveId, token, false, add);
emit ListReservePairs(reserveId, reserve, ETH_TOKEN_ADDRESS, token, add);
}
if (tokenToEth) {
kyberNetwork.listTokenForReserve(reserve, token, add);
listPairs(reserveId, token, true, add);
emit ListReservePairs(reserveId, reserve, token, ETH_TOKEN_ADDRESS, add);
}
}
/// @dev No. of kyberProxies are capped
function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies)
external
override
{
onlyNetwork();
require(kyberProxy != address(0), "kyberProxy 0");
require(kyberProxyArray.length < maxApprovedProxies, "max kyberProxies limit reached");
kyberProxyArray.push(IKyberNetworkProxy(kyberProxy));
}
function removeKyberProxy(address kyberProxy) external override {
onlyNetwork();
uint256 proxyIndex = 2**255;
for (uint256 i = 0; i < kyberProxyArray.length; i++) {
if (kyberProxyArray[i] == IKyberNetworkProxy(kyberProxy)) {
proxyIndex = i;
break;
}
}
require(proxyIndex != 2**255, "kyberProxy not found");
kyberProxyArray[proxyIndex] = kyberProxyArray[kyberProxyArray.length - 1];
kyberProxyArray.pop();
}
function setFeeAccountedPerReserveType(
bool fpr,
bool apr,
bool bridge,
bool utility,
bool custom,
bool orderbook
) external {
onlyAdmin();
uint256 feeAccountedData;
if (fpr) feeAccountedData |= 1 << uint256(ReserveType.FPR);
if (apr) feeAccountedData |= 1 << uint256(ReserveType.APR);
if (bridge) feeAccountedData |= 1 << uint256(ReserveType.BRIDGE);
if (utility) feeAccountedData |= 1 << uint256(ReserveType.UTILITY);
if (custom) feeAccountedData |= 1 << uint256(ReserveType.CUSTOM);
if (orderbook) feeAccountedData |= 1 << uint256(ReserveType.ORDERBOOK);
feeAccountedPerType = feeAccountedData;
}
function setEntitledRebatePerReserveType(
bool fpr,
bool apr,
bool bridge,
bool utility,
bool custom,
bool orderbook
) external {
onlyAdmin();
require(feeAccountedPerType != 0xffffffff, "fee accounted data not set");
uint256 entitledRebateData;
if (fpr) {
require(feeAccountedPerType & (1 << uint256(ReserveType.FPR)) > 0, "fpr not fee accounted");
entitledRebateData |= 1 << uint256(ReserveType.FPR);
}
if (apr) {
require(feeAccountedPerType & (1 << uint256(ReserveType.APR)) > 0, "apr not fee accounted");
entitledRebateData |= 1 << uint256(ReserveType.APR);
}
if (bridge) {
require(feeAccountedPerType & (1 << uint256(ReserveType.BRIDGE)) > 0, "bridge not fee accounted");
entitledRebateData |= 1 << uint256(ReserveType.BRIDGE);
}
if (utility) {
require(feeAccountedPerType & (1 << uint256(ReserveType.UTILITY)) > 0, "utility not fee accounted");
entitledRebateData |= 1 << uint256(ReserveType.UTILITY);
}
if (custom) {
require(feeAccountedPerType & (1 << uint256(ReserveType.CUSTOM)) > 0, "custom not fee accounted");
entitledRebateData |= 1 << uint256(ReserveType.CUSTOM);
}
if (orderbook) {
require(feeAccountedPerType & (1 << uint256(ReserveType.ORDERBOOK)) > 0, "orderbook not fee accounted");
entitledRebateData |= 1 << uint256(ReserveType.ORDERBOOK);
}
entitledRebatePerType = entitledRebateData;
}
/// @notice Should be called off chain
/// @return An array of all reserves
function getReserves() external view returns (IKyberReserve[] memory) {
return reserves;
}
function getReservesPerType(ReserveType resType) external view returns (bytes32[] memory) {
return reservesPerType[resType];
}
function getReserveId(address reserve) external view override returns (bytes32) {
return reserveAddressToId[reserve];
}
function getReserveIdsFromAddresses(address[] calldata reserveAddresses)
external
override
view
returns (bytes32[] memory reserveIds)
{
reserveIds = new bytes32[](reserveAddresses.length);
for (uint256 i = 0; i < reserveAddresses.length; i++) {
reserveIds[i] = reserveAddressToId[reserveAddresses[i]];
}
}
function getReserveAddressesFromIds(bytes32[] calldata reserveIds)
external
view
override
returns (address[] memory reserveAddresses)
{
reserveAddresses = new address[](reserveIds.length);
for (uint256 i = 0; i < reserveIds.length; i++) {
reserveAddresses[i] = reserveIdToAddresses[reserveIds[i]][0];
}
}
function getRebateWalletsFromIds(bytes32[] calldata reserveIds)
external
view
override
returns (address[] memory rebateWallets)
{
rebateWallets = new address[](reserveIds.length);
for (uint256 i = 0; i < rebateWallets.length; i++) {
rebateWallets[i] = reserveRebateWallet[reserveIds[i]];
}
}
function getReserveIdsPerTokenSrc(IERC20 token)
external
view
override
returns (bytes32[] memory reserveIds)
{
reserveIds = reservesPerTokenSrc[token];
}
/// @dev kyberNetwork is calling this function to approve (allowance) for list of reserves for a token
/// in case we have a long list of reserves, approving all of them could run out of gas
/// using startIndex and endIndex to prevent above scenario
/// also enable us to approve reserve one by one
function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex)
external
view
override
returns (address[] memory reserveAddresses)
{
bytes32[] memory reserveIds = reservesPerTokenSrc[token];
if (reserveIds.length == 0) {
return reserveAddresses;
}
uint256 endId = (endIndex >= reserveIds.length) ? (reserveIds.length - 1) : endIndex;
if (endId < startIndex) {
return reserveAddresses;
}
reserveAddresses = new address[](endId - startIndex + 1);
for(uint256 i = startIndex; i <= endId; i++) {
reserveAddresses[i - startIndex] = reserveIdToAddresses[reserveIds[i]][0];
}
}
function getReserveIdsPerTokenDest(IERC20 token)
external
view
override
returns (bytes32[] memory reserveIds)
{
reserveIds = reservesPerTokenDest[token];
}
function getReserveAddressesByReserveId(bytes32 reserveId)
external
view
override
returns (address[] memory reserveAddresses)
{
reserveAddresses = reserveIdToAddresses[reserveId];
}
/// @notice Should be called off chain
/// @dev Returns list of kyberDao, kyberFeeHandler, kyberMatchingEngine and kyberNetwork contracts
/// @dev Index 0 is currently used contract address, indexes > 0 are older versions
function getContracts()
external
view
returns (
address[] memory kyberDaoAddresses,
address[] memory kyberFeeHandlerAddresses,
address[] memory kyberMatchingEngineAddresses,
address[] memory kyberNetworkAddresses
)
{
kyberDaoAddresses = kyberDaoHistory.getContracts();
kyberFeeHandlerAddresses = kyberFeeHandlerHistory.getContracts();
kyberMatchingEngineAddresses = kyberMatchingEngineHistory.getContracts();
kyberNetworkAddresses = kyberNetworkHistory.getContracts();
}
/// @notice Should be called off chain
/// @return An array of KyberNetworkProxies
function getKyberProxies() external view override returns (IKyberNetworkProxy[] memory) {
return kyberProxyArray;
}
function isKyberProxyAdded() external view override returns (bool) {
return (kyberProxyArray.length > 0);
}
/// @notice Returns information about a reserve given its reserve ID
/// @return reserveAddress Address of the reserve
/// @return rebateWallet address of rebate wallet of this reserve
/// @return resType Reserve type from enum ReserveType
/// @return isFeeAccountedFlag Whether fees are to be charged for the trade for this reserve
/// @return isEntitledRebateFlag Whether reserve is entitled rebate from the trade fees
function getReserveDetailsById(bytes32 reserveId)
external
view
override
returns (
address reserveAddress,
address rebateWallet,
ReserveType resType,
bool isFeeAccountedFlag,
bool isEntitledRebateFlag
)
{
address[] memory reserveAddresses = reserveIdToAddresses[reserveId];
if (reserveAddresses.length != 0) {
reserveAddress = reserveIdToAddresses[reserveId][0];
rebateWallet = reserveRebateWallet[reserveId];
uint256 resTypeUint = reserveType[reserveId];
resType = ReserveType(resTypeUint);
isFeeAccountedFlag = (feeAccountedPerType & (1 << resTypeUint)) > 0;
isEntitledRebateFlag = (entitledRebatePerType & (1 << resTypeUint)) > 0;
}
}
/// @notice Returns information about a reserve given its reserve ID
/// @return reserveId The reserve ID in 32 bytes.
/// @return rebateWallet address of rebate wallet of this reserve
/// @return resType Reserve type from enum ReserveType
/// @return isFeeAccountedFlag Whether fees are to be charged for the trade for this reserve
/// @return isEntitledRebateFlag Whether reserve is entitled rebate from the trade fees
function getReserveDetailsByAddress(address reserve)
external
view
override
returns (
bytes32 reserveId,
address rebateWallet,
ReserveType resType,
bool isFeeAccountedFlag,
bool isEntitledRebateFlag
)
{
reserveId = reserveAddressToId[reserve];
rebateWallet = reserveRebateWallet[reserveId];
uint256 resTypeUint = reserveType[reserveId];
resType = ReserveType(resTypeUint);
isFeeAccountedFlag = (feeAccountedPerType & (1 << resTypeUint)) > 0;
isEntitledRebateFlag = (entitledRebatePerType & (1 << resTypeUint)) > 0;
}
function getListedTokensByReserveId(bytes32 reserveId)
external
view
returns (
IERC20[] memory srcTokens,
IERC20[] memory destTokens
)
{
srcTokens = srcTokensPerReserve[reserveId];
destTokens = destTokensPerReserve[reserveId];
}
function getFeeAccountedData(bytes32[] calldata reserveIds)
external
view
override
returns (bool[] memory feeAccountedArr)
{
feeAccountedArr = new bool[](reserveIds.length);
uint256 feeAccountedData = feeAccountedPerType;
for (uint256 i = 0; i < reserveIds.length; i++) {
feeAccountedArr[i] = (feeAccountedData & (1 << reserveType[reserveIds[i]]) > 0);
}
}
function getEntitledRebateData(bytes32[] calldata reserveIds)
external
view
override
returns (bool[] memory entitledRebateArr)
{
entitledRebateArr = new bool[](reserveIds.length);
uint256 entitledRebateData = entitledRebatePerType;
for (uint256 i = 0; i < reserveIds.length; i++) {
entitledRebateArr[i] = (entitledRebateData & (1 << reserveType[reserveIds[i]]) > 0);
}
}
/// @dev Returns information about reserves given their reserve IDs
/// Also check if these reserve IDs are listed for token
/// Network calls this function to retrive information about fee, address and rebate information
function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest)
external
view
override
returns (
bool areAllReservesListed,
bool[] memory feeAccountedArr,
bool[] memory entitledRebateArr,
IKyberReserve[] memory reserveAddresses)
{
feeAccountedArr = new bool[](reserveIds.length);
entitledRebateArr = new bool[](reserveIds.length);
reserveAddresses = new IKyberReserve[](reserveIds.length);
areAllReservesListed = true;
uint256 entitledRebateData = entitledRebatePerType;
uint256 feeAccountedData = feeAccountedPerType;
mapping(bytes32 => bool) storage isListedReserveWithToken = (dest == ETH_TOKEN_ADDRESS) ?
isListedReserveWithTokenSrc[src]:
isListedReserveWithTokenDest[dest];
for (uint256 i = 0; i < reserveIds.length; i++) {
uint256 resType = reserveType[reserveIds[i]];
entitledRebateArr[i] = (entitledRebateData & (1 << resType) > 0);
feeAccountedArr[i] = (feeAccountedData & (1 << resType) > 0);
reserveAddresses[i] = IKyberReserve(reserveIdToAddresses[reserveIds[i]][0]);
if (!isListedReserveWithToken[reserveIds[i]]){
areAllReservesListed = false;
break;
}
}
}
function delistTokensOfReserve(bytes32 reserveId) internal {
// token to ether
// memory declaration instead of storage because we are modifying the storage array
IERC20[] memory tokensArr = srcTokensPerReserve[reserveId];
for (uint256 i = 0; i < tokensArr.length; i++) {
listPairForReserve(reserveId, tokensArr[i], false, true, false);
}
// ether to token
tokensArr = destTokensPerReserve[reserveId];
for (uint256 i = 0; i < tokensArr.length; i++) {
listPairForReserve(reserveId, tokensArr[i], true, false, false);
}
}
function listPairs(
bytes32 reserveId,
IERC20 token,
bool isTokenToEth,
bool add
) internal {
uint256 i;
bytes32[] storage reserveArr = reservesPerTokenDest[token];
IERC20[] storage tokensArr = destTokensPerReserve[reserveId];
mapping(bytes32 => bool) storage isListedReserveWithToken = isListedReserveWithTokenDest[token];
if (isTokenToEth) {
reserveArr = reservesPerTokenSrc[token];
tokensArr = srcTokensPerReserve[reserveId];
isListedReserveWithToken = isListedReserveWithTokenSrc[token];
}
for (i = 0; i < reserveArr.length; i++) {
if (reserveId == reserveArr[i]) {
if (add) {
return; // reserve already added, no further action needed
} else {
// remove reserve from reserveArr
reserveArr[i] = reserveArr[reserveArr.length - 1];
reserveArr.pop();
break;
}
}
}
if (add) {
// add reserve and token to reserveArr and tokensArr respectively
reserveArr.push(reserveId);
tokensArr.push(token);
isListedReserveWithToken[reserveId] = true;
} else {
// remove token from tokenArr
for (i = 0; i < tokensArr.length; i++) {
if (token == tokensArr[i]) {
tokensArr[i] = tokensArr[tokensArr.length - 1];
tokensArr.pop();
break;
}
}
delete isListedReserveWithToken[reserveId];
}
}
function onlyNetwork() internal view {
require(msg.sender == address(kyberNetwork), "only kyberNetwork");
}
}File 8 of 8: LiquidityConversionRates
/**
*Submitted for verification at Etherscan.io on 2020-04-14
*/
// File: contracts\ERC20Interface.sol
pragma solidity 0.4.18;
// https://github.com/ethereum/EIPs/issues/20
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
// File: contracts\ConversionRatesInterface.sol
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);
}
// 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\reserves\aprConversionRate\LiquidityFormula.sol
contract UtilMath {
uint public constant BIG_NUMBER = (uint(1)<<uint(200));
function checkMultOverflow(uint x, uint y) public pure returns(bool) {
if (y == 0) return false;
return (((x*y) / y) != x);
}
function compactFraction(uint p, uint q, uint precision) public pure returns (uint, uint) {
if (q < precision * precision) return (p, q);
return compactFraction(p/precision, q/precision, precision);
}
/* solhint-disable code-complexity */
function exp(uint p, uint q, uint precision) public pure returns (uint) {
uint n = 0;
uint nFact = 1;
uint currentP = 1;
uint currentQ = 1;
uint sum = 0;
uint prevSum = 0;
while (true) {
if (checkMultOverflow(currentP, precision)) return sum;
if (checkMultOverflow(currentQ, nFact)) return sum;
sum += (currentP * precision) / (currentQ * nFact);
if (sum == prevSum) return sum;
prevSum = sum;
n++;
if (checkMultOverflow(currentP, p)) return sum;
if (checkMultOverflow(currentQ, q)) return sum;
if (checkMultOverflow(nFact, n)) return sum;
currentP *= p;
currentQ *= q;
nFact *= n;
(currentP, currentQ) = compactFraction(currentP, currentQ, precision);
}
}
/* solhint-enable code-complexity */
function countLeadingZeros(uint p, uint q) public pure returns (uint) {
uint denomator = (uint(1)<<255);
for (int i = 255; i >= 0; i--) {
if ((q*denomator)/denomator != q) {
// overflow
denomator = denomator/2;
continue;
}
if (p/(q*denomator) > 0) return uint(i);
denomator = denomator/2;
}
return uint(-1);
}
// log2 for a number that it in [1,2)
function log2ForSmallNumber(uint x, uint numPrecisionBits) public pure returns (uint) {
uint res = 0;
uint one = (uint(1)<<numPrecisionBits);
uint two = 2 * one;
uint addition = one;
require((x >= one) && (x <= two));
require(numPrecisionBits < 125);
for (uint i = numPrecisionBits; i > 0; i--) {
x = (x*x) / one;
addition = addition/2;
if (x >= two) {
x = x/2;
res += addition;
}
}
return res;
}
function logBase2 (uint p, uint q, uint numPrecisionBits) public pure returns (uint) {
uint n = 0;
uint precision = (uint(1)<<numPrecisionBits);
if (p > q) {
n = countLeadingZeros(p, q);
}
require(!checkMultOverflow(p, precision));
require(!checkMultOverflow(n, precision));
require(!checkMultOverflow(uint(1)<<n, q));
uint y = p * precision / (q * (uint(1)<<n));
uint log2Small = log2ForSmallNumber(y, numPrecisionBits);
require(n*precision <= BIG_NUMBER);
require(log2Small <= BIG_NUMBER);
return n * precision + log2Small;
}
function ln(uint p, uint q, uint numPrecisionBits) public pure returns (uint) {
uint ln2Numerator = 6931471805599453094172;
uint ln2Denomerator = 10000000000000000000000;
uint log2x = logBase2(p, q, numPrecisionBits);
require(!checkMultOverflow(ln2Numerator, log2x));
return ln2Numerator * log2x / ln2Denomerator;
}
}
contract LiquidityFormula is UtilMath {
function pE(uint r, uint pMIn, uint e, uint precision) public pure returns (uint) {
require(!checkMultOverflow(r, e));
uint expRE = exp(r*e, precision*precision, precision);
require(!checkMultOverflow(expRE, pMIn));
return pMIn*expRE / precision;
}
function deltaTFunc(uint r, uint pMIn, uint e, uint deltaE, uint precision) public pure returns (uint) {
uint pe = pE(r, pMIn, e, precision);
uint rpe = r * pe;
require(!checkMultOverflow(r, deltaE));
uint erdeltaE = exp(r*deltaE, precision*precision, precision);
require(erdeltaE >= precision);
require(!checkMultOverflow(erdeltaE - precision, precision));
require(!checkMultOverflow((erdeltaE - precision)*precision, precision));
require(!checkMultOverflow((erdeltaE - precision)*precision*precision, precision));
require(!checkMultOverflow(rpe, erdeltaE));
require(!checkMultOverflow(r, pe));
return (erdeltaE - precision) * precision * precision * precision / (rpe*erdeltaE);
}
function deltaEFunc(uint r, uint pMIn, uint e, uint deltaT, uint precision, uint numPrecisionBits)
public pure
returns (uint)
{
uint pe = pE(r, pMIn, e, precision);
uint rpe = r * pe;
require(!checkMultOverflow(rpe, deltaT));
require(precision * precision + rpe * deltaT/precision > precision * precision);
uint lnPart = ln(precision*precision + rpe*deltaT/precision, precision*precision, numPrecisionBits);
require(!checkMultOverflow(r, pe));
require(!checkMultOverflow(precision, precision));
require(!checkMultOverflow(rpe, deltaT));
require(!checkMultOverflow(lnPart, precision));
return lnPart * precision / r;
}
}
// File: contracts\reserves\aprConversionRate\LiquidityConversionRates.sol
contract LiquidityConversionRates is ConversionRatesInterface, LiquidityFormula, Withdrawable, Utils {
uint constant FORMULA_PRECISION_BITS = 40;
ERC20 public token;
address public reserveContract;
uint public numFpBits;
uint public formulaPrecision;
uint public rInFp;
uint public pMinInFp;
uint public maxEthCapBuyInFp;
uint public maxEthCapSellInFp;
uint public maxQtyInFp;
uint public feeInBps;
uint public collectedFeesInTwei = 0;
uint public maxBuyRateInPrecision;
uint public minBuyRateInPrecision;
uint public maxSellRateInPrecision;
uint public minSellRateInPrecision;
function LiquidityConversionRates(address _admin, ERC20 _token) public {
transferAdminQuickly(_admin);
token = _token;
setDecimals(token);
require(getDecimals(token) <= MAX_DECIMALS);
}
event ReserveAddressSet(address reserve);
function setReserveAddress(address reserve) public onlyAdmin {
reserveContract = reserve;
ReserveAddressSet(reserve);
}
event LiquidityParamsSet(
uint rInFp,
uint pMinInFp,
uint numFpBits,
uint maxCapBuyInFp,
uint maxEthCapSellInFp,
uint feeInBps,
uint formulaPrecision,
uint maxQtyInFp,
uint maxBuyRateInPrecision,
uint minBuyRateInPrecision,
uint maxSellRateInPrecision,
uint minSellRateInPrecision
);
function setLiquidityParams(
uint _rInFp,
uint _pMinInFp,
uint _numFpBits,
uint _maxCapBuyInWei,
uint _maxCapSellInWei,
uint _feeInBps,
uint _maxTokenToEthRateInPrecision,
uint _minTokenToEthRateInPrecision
) public onlyAdmin {
require(_numFpBits == FORMULA_PRECISION_BITS); // only used config, but keep in API
formulaPrecision = uint(1)<<_numFpBits; // require(formulaPrecision <= MAX_QTY)
require(_feeInBps < 10000);
require(_minTokenToEthRateInPrecision < _maxTokenToEthRateInPrecision);
require(_minTokenToEthRateInPrecision > 0);
require(_rInFp > 0);
require(_pMinInFp > 0);
rInFp = _rInFp;
pMinInFp = _pMinInFp;
maxQtyInFp = fromWeiToFp(MAX_QTY);
numFpBits = _numFpBits;
maxEthCapBuyInFp = fromWeiToFp(_maxCapBuyInWei);
maxEthCapSellInFp = fromWeiToFp(_maxCapSellInWei);
feeInBps = _feeInBps;
maxBuyRateInPrecision = PRECISION * PRECISION / _minTokenToEthRateInPrecision;
minBuyRateInPrecision = PRECISION * PRECISION / _maxTokenToEthRateInPrecision;
maxSellRateInPrecision = _maxTokenToEthRateInPrecision;
minSellRateInPrecision = _minTokenToEthRateInPrecision;
LiquidityParamsSet(
rInFp,
pMinInFp,
numFpBits,
maxEthCapBuyInFp,
maxEthCapSellInFp,
feeInBps,
formulaPrecision,
maxQtyInFp,
maxBuyRateInPrecision,
minBuyRateInPrecision,
maxSellRateInPrecision,
minSellRateInPrecision
);
}
function recordImbalance(
ERC20 conversionToken,
int buyAmountInTwei,
uint rateUpdateBlock,
uint currentBlock
)
public
{
conversionToken;
rateUpdateBlock;
currentBlock;
require(msg.sender == reserveContract);
if (buyAmountInTwei > 0) {
// Buy case
collectedFeesInTwei += calcCollectedFee(abs(buyAmountInTwei));
} else {
// Sell case
collectedFeesInTwei += abs(buyAmountInTwei) * feeInBps / 10000;
}
}
event CollectedFeesReset(uint resetFeesInTwei);
function resetCollectedFees() public onlyAdmin {
uint resetFeesInTwei = collectedFeesInTwei;
collectedFeesInTwei = 0;
CollectedFeesReset(resetFeesInTwei);
}
function getRate(
ERC20 conversionToken,
uint currentBlockNumber,
bool buy,
uint qtyInSrcWei
) public view returns(uint) {
currentBlockNumber;
require(qtyInSrcWei <= MAX_QTY);
uint eInFp = fromWeiToFp(reserveContract.balance);
uint rateInPrecision = getRateWithE(conversionToken, buy, qtyInSrcWei, eInFp);
require(rateInPrecision <= MAX_RATE);
return rateInPrecision;
}
function getRateWithE(ERC20 conversionToken, bool buy, uint qtyInSrcWei, uint eInFp) public view returns(uint) {
uint deltaEInFp;
uint sellInputTokenQtyInFp;
uint deltaTInFp;
uint rateInPrecision;
require(qtyInSrcWei <= MAX_QTY);
require(eInFp <= maxQtyInFp);
if (conversionToken != token) return 0;
if (buy) {
// ETH goes in, token goes out
deltaEInFp = fromWeiToFp(qtyInSrcWei);
if (deltaEInFp > maxEthCapBuyInFp) return 0;
if (deltaEInFp == 0) {
rateInPrecision = buyRateZeroQuantity(eInFp);
} else {
rateInPrecision = buyRate(eInFp, deltaEInFp);
}
} else {
sellInputTokenQtyInFp = fromTweiToFp(qtyInSrcWei);
deltaTInFp = valueAfterReducingFee(sellInputTokenQtyInFp);
if (deltaTInFp == 0) {
rateInPrecision = sellRateZeroQuantity(eInFp);
deltaEInFp = 0;
} else {
(rateInPrecision, deltaEInFp) = sellRate(eInFp, sellInputTokenQtyInFp, deltaTInFp);
}
if (deltaEInFp > maxEthCapSellInFp) return 0;
}
rateInPrecision = rateAfterValidation(rateInPrecision, buy);
return rateInPrecision;
}
function rateAfterValidation(uint rateInPrecision, bool buy) public view returns(uint) {
uint minAllowRateInPrecision;
uint maxAllowedRateInPrecision;
if (buy) {
minAllowRateInPrecision = minBuyRateInPrecision;
maxAllowedRateInPrecision = maxBuyRateInPrecision;
} else {
minAllowRateInPrecision = minSellRateInPrecision;
maxAllowedRateInPrecision = maxSellRateInPrecision;
}
if ((rateInPrecision > maxAllowedRateInPrecision) || (rateInPrecision < minAllowRateInPrecision)) {
return 0;
} else if (rateInPrecision > MAX_RATE) {
return 0;
} else {
return rateInPrecision;
}
}
function buyRate(uint eInFp, uint deltaEInFp) public view returns(uint) {
uint deltaTInFp = deltaTFunc(rInFp, pMinInFp, eInFp, deltaEInFp, formulaPrecision);
require(deltaTInFp <= maxQtyInFp);
deltaTInFp = valueAfterReducingFee(deltaTInFp);
return deltaTInFp * PRECISION / deltaEInFp;
}
function buyRateZeroQuantity(uint eInFp) public view returns(uint) {
uint ratePreReductionInPrecision = formulaPrecision * PRECISION / pE(rInFp, pMinInFp, eInFp, formulaPrecision);
return valueAfterReducingFee(ratePreReductionInPrecision);
}
function sellRate(
uint eInFp,
uint sellInputTokenQtyInFp,
uint deltaTInFp
) public view returns(uint rateInPrecision, uint deltaEInFp) {
deltaEInFp = deltaEFunc(rInFp, pMinInFp, eInFp, deltaTInFp, formulaPrecision, numFpBits);
require(deltaEInFp <= maxQtyInFp);
rateInPrecision = deltaEInFp * PRECISION / sellInputTokenQtyInFp;
}
function sellRateZeroQuantity(uint eInFp) public view returns(uint) {
uint ratePreReductionInPrecision = pE(rInFp, pMinInFp, eInFp, formulaPrecision) * PRECISION / formulaPrecision;
return valueAfterReducingFee(ratePreReductionInPrecision);
}
function fromTweiToFp(uint qtyInTwei) public view returns(uint) {
require(qtyInTwei <= MAX_QTY);
return qtyInTwei * formulaPrecision / (10 ** getDecimals(token));
}
function fromWeiToFp(uint qtyInwei) public view returns(uint) {
require(qtyInwei <= MAX_QTY);
return qtyInwei * formulaPrecision / (10 ** ETH_DECIMALS);
}
function valueAfterReducingFee(uint val) public view returns(uint) {
require(val <= BIG_NUMBER);
return ((10000 - feeInBps) * val) / 10000;
}
function calcCollectedFee(uint val) public view returns(uint) {
require(val <= MAX_QTY);
return val * feeInBps / (10000 - feeInBps);
}
function abs(int val) public pure returns(uint) {
if (val < 0) {
return uint(val * (-1));
} else {
return uint(val);
}
}
}