Transaction Hash:
Block:
11627487 at Jan-10-2021 01:52:33 PM +UTC
Transaction Fee:
0.02055459 ETH
$46.48
Gas Used:
293,637 Gas / 70 Gwei
Emitted Events:
18 |
LoomToken.Transfer( from=[Sender] 0xd35e72be751a821cc1f2bda1c164190370553a2b, to=KyberNetwork, value=150000000000000000000000 )
|
19 |
LoomToken.Transfer( from=KyberNetwork, to=KyberFprReserveV2, value=150000000000000000000000 )
|
20 |
KyberNetwork.EtherReceival( sender=KyberFprReserveV2, amount=4945148280615900000 )
|
21 |
KyberFprReserveV2.TradeExecute( origin=KyberNetwork, src=LoomToken, srcAmount=150000000000000000000000, destToken=0xEeeeeEee...eeeeeEEeE, destAmount=4945148280615900000, destAddress=KyberNetwork )
|
22 |
KyberFeeHandler.FeeDistributed( token=0xEeeeeEee...eeeeeEEeE, platformWallet=0x440bBd6a...16874faa9, platformFeeWei=3956118624492720, rewardWei=3329073822510623, rebateWei=1309475264707090, rebateWallets=[0xBC33a1F908612640F2849b56b67a4De4d179C151], rebatePercentBpsPerWallet=[10000], burnAmtWei=306599193398187 )
|
23 |
KyberNetwork.KyberTrade( 0x30bbea603a7b36858fe5e3ec6ba5ff59dde039d02120d758eacfaed01520577d, 0x000000000000000000000000a4e8c3ec456107ea67d3075bf9e3df3a75823db0, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 00000000000000000000000000000000000000000000000044a0b22923be9360, 000000000000000000000000000000000000000000000000001191960ea0b3dc, 000000000000000000000000000000000000000000000000000e0e11a54d5cb0, 0000000000000000000000000000000000000000000000000000000000000120, 0000000000000000000000000000000000000000000000000000000000000160, 0000000000000000000000000000000000000000000000000000000000000180, 00000000000000000000000000000000000000000000000000000000000001c0, 00000000000000000000000000000000000000000000000000000000000001e0, 0000000000000000000000000000000000000000000000000000000000000220, 0000000000000000000000000000000000000000000000000000000000000001, ff4b796265722046707256320000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000001fc3842bd1f071c00000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000000001dfbe160a50a, 0000000000000000000000000000000000000000000000000000000000000000 )
|
24 |
KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0xd35e72be751a821cc1f2bda1c164190370553a2b, src=LoomToken, dest=0xEeeeeEee...eeeeeEEeE, destAddress=[Sender] 0xd35e72be751a821cc1f2bda1c164190370553a2b, actualSrcAmount=150000000000000000000000, actualDestAmount=4936247013710791380, platformWallet=0x440bBd6a...16874faa9, platformFeeBps=8 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x15a91A09...35fd779e9 | |||||
0xA4e8C3Ec...A75823DB0 | |||||
0xAa448eFF...9eac15a0E | 870.715290729085558993 Eth | 865.770142448469658993 Eth | 4.9451482806159 | ||
0xB3b7874F...A3505D8d4
Miner
| (Babel Pool) | 6,857.704396220086628642 Eth | 6,857.724950810086628642 Eth | 0.02055459 | |
0xd35E72be...370553a2B |
25.179556985072677595 Eth
Nonce: 7
|
30.095249408783468975 Eth
Nonce: 8
| 4.91569242371079138 | ||
0xd3d2b564...a941114fe | (Kyber: Fee Handler) | 1,023.054008848699367254 Eth | 1,023.062910115604475874 Eth | 0.00890126690510862 |
Execution Trace
KyberNetworkProxy.tradeWithHintAndFee( src=0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0, srcAmount=150000000000000000000000, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0xd35E72be751A821CC1f2bda1c164190370553a2B, maxDestAmount=115792089237316195423570985008687907853269984665640564039457584007913129639935, minConversionRate=31921064021996, platformWallet=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, platformFeeBps=8, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FF4B79626572204670725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 ) => ( destAmount=4936247013710791380 )
-
LoomToken.balanceOf( _owner=0xd35E72be751A821CC1f2bda1c164190370553a2B ) => ( balance=177802663204782620977135 )
-
LoomToken.transferFrom( _from=0xd35E72be751A821CC1f2bda1c164190370553a2B, _to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, _value=150000000000000000000000 ) => ( True )
KyberNetwork.tradeWithHintAndFee( trader=0xd35E72be751A821CC1f2bda1c164190370553a2B, src=0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0, srcAmount=150000000000000000000000, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0xd35E72be751A821CC1f2bda1c164190370553a2B, maxDestAmount=115792089237316195423570985008687907853269984665640564039457584007913129639935, minConversionRate=31921064021996, platformWallet=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, platformFeeBps=8, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FF4B79626572204670725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 ) => ( destAmount=4936247013710791380 )
-
LoomToken.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( balance=150000000000000000019841 )
-
KyberMatchingEngine.getTradingReserves( src=0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, isTokenToToken=False, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FF4B79626572204670725632000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 )
-
KyberStorage.getReservesData( ) => ( areAllReservesListed=True, feeAccountedArr=[true], entitledRebateArr=[true], reserveAddresses=[0xAa448eFF88B1E752D50b87220B543d79eac15a0E] )
KyberFprReserveV2.getConversionRate( src=0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0, dest=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcQty=150000000000000000000000, blockNumber=11627487 ) => ( 32967655204106 )
-
ConversionRateEnhancedSteps.getRate( token=0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0, currentBlockNumber=11627487, buy=False, qty=150000000000000000000000 ) => ( 32967655204106 )
-
0xa2d951a22d5c0256fc2daee7e2b3ede75ebfa22d.a58092b7( )
-
-
LoomToken.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( balance=150000000000000000019841 )
KyberFprReserveV2.trade( srcToken=0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0, srcAmount=150000000000000000000000, destToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, destAddress=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, conversionRate=32967655204106, True ) => ( True )
-
ConversionRateEnhancedSteps.recordImbalance( token=0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0, buyAmount=-150000000000000000000000, rateUpdateBlock=0, currentBlock=11627487 )
-
LoomToken.transferFrom( _from=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, _to=0xAa448eFF88B1E752D50b87220B543d79eac15a0E, _value=150000000000000000000000 ) => ( True )
- ETH 4.9451482806159
KyberNetwork.CALL( )
-
-
LoomToken.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( balance=19841 )
- ETH 4.93624701371079138
0xd35e72be751a821cc1f2bda1c164190370553a2b.CALL( )
-
KyberStorage.getRebateWalletsFromIds( ) => ( rebateWallets=[0xBC33a1F908612640F2849b56b67a4De4d179C151] )
- ETH 0.00890126690510862
KyberFeeHandler.handleFees( token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, rebateWallets=[0xBC33a1F908612640F2849b56b67a4De4d179C151], rebateBpsPerWallet=[10000], platformWallet=0x440bBd6a888a36DE6e2F6A25f65bc4e16874faa9, platformFee=3956118624492720, networkFee=4945148280615900 )
-
-
LoomToken.balanceOf( _owner=0xd35E72be751A821CC1f2bda1c164190370553a2B ) => ( balance=27802663204782620977135 )
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: KyberNetwork
File 3 of 8: LoomToken
File 4 of 8: KyberFprReserveV2
File 5 of 8: KyberFeeHandler
File 6 of 8: KyberMatchingEngine
File 7 of 8: KyberStorage
File 8 of 8: ConversionRateEnhancedSteps
// 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: 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 3 of 8: LoomToken
pragma solidity ^0.4.13; library SafeMath { /** * @dev Multiplies two numbers, throws on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; assert(c / a == b); return c; } /** * @dev Integer division of two numbers, truncating the quotient. */ function div(uint256 a, uint256 b) internal pure 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; } /** * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } contract ERC20Basic { function totalSupply() public view returns (uint256); function balanceOf(address who) public view returns (uint256); function transfer(address to, uint256 value) public returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); } contract BasicToken is ERC20Basic { using SafeMath for uint256; mapping(address => uint256) balances; uint256 totalSupply_; /** * @dev total number of tokens in existence */ function totalSupply() public view returns (uint256) { return totalSupply_; } /** * @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) public returns (bool) { require(_to != address(0)); require(_value <= balances[msg.sender]); // SafeMath.sub will throw if there is not enough balance. 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) public view returns (uint256 balance) { return balances[_owner]; } } contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public view returns (uint256); function transferFrom(address from, address to, uint256 value) public returns (bool); function approve(address spender, uint256 value) public returns (bool); event Approval(address indexed owner, address indexed spender, uint256 value); } contract StandardToken is ERC20, BasicToken { mapping (address => mapping (address => uint256)) internal 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 amount of tokens to be transferred */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { require(_to != address(0)); require(_value <= balances[_from]); require(_value <= allowed[_from][msg.sender]); balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); Transfer(_from, _to, _value); return true; } /** * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. * * Beware that changing an allowance with this method brings the risk that someone may use both the old * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * @param _spender The address which will spend the funds. * @param _value The amount of tokens to be spent. */ function approve(address _spender, uint256 _value) public returns (bool) { 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 specifying the amount of tokens still available for the spender. */ function allowance(address _owner, address _spender) public view returns (uint256) { return allowed[_owner][_spender]; } /** * @dev Increase the amount of tokens that an owner allowed to a spender. * * approve should be called when allowed[_spender] == 0. To increment * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * @param _spender The address which will spend the funds. * @param _addedValue The amount of tokens to increase the allowance by. */ function increaseApproval(address _spender, uint _addedValue) public returns (bool) { allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } /** * @dev Decrease the amount of tokens that an owner allowed to a spender. * * approve should be called when allowed[_spender] == 0. To decrement * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From MonolithDAO Token.sol * @param _spender The address which will spend the funds. * @param _subtractedValue The amount of tokens to decrease the allowance by. */ function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) { uint oldValue = allowed[msg.sender][_spender]; if (_subtractedValue > oldValue) { allowed[msg.sender][_spender] = 0; } else { allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); } Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } } contract LoomToken is StandardToken { string public name = "LoomToken"; string public symbol = "LOOM"; uint8 public decimals = 18; // one billion in initial supply uint256 public constant INITIAL_SUPPLY = 1000000000; function LoomToken() public { totalSupply_ = INITIAL_SUPPLY * (10 ** uint256(decimals)); balances[msg.sender] = totalSupply_; } }
File 4 of 8: KyberFprReserveV2
// File: @openzeppelin/contracts/token/ERC20/IERC20.sol // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); } // File: @kyber.network/utils-sc/contracts/IERC20Ext.sol pragma solidity 0.6.6; /** * @dev Interface extending ERC20 standard to include decimals() as * it is optional in the OpenZeppelin IERC20 interface. */ interface IERC20Ext is IERC20 { /** * @dev This function is required as Kyber requires to interact * with token.decimals() with many of its operations. */ function decimals() external view returns (uint8 digits); } // File: contracts/sol6/IKyberReserve.sol pragma solidity 0.6.6; interface IKyberReserve { function trade( IERC20Ext srcToken, uint256 srcAmount, IERC20Ext destToken, address payable destAddress, uint256 conversionRate, bool validate ) external payable returns (bool); function getConversionRate( IERC20Ext src, IERC20Ext dest, uint256 srcQty, uint256 blockNumber ) external view returns (uint256); } // File: contracts/sol6/IWeth.sol pragma solidity 0.6.6; interface IWeth is IERC20Ext { function deposit() external payable; function withdraw(uint256 wad) external; } // File: contracts/sol6/IKyberSanity.sol pragma solidity 0.6.6; interface IKyberSanity { function getSanityRate(IERC20Ext src, IERC20Ext dest) external view returns (uint256); } // File: contracts/sol6/IConversionRates.sol pragma solidity 0.6.6; interface IConversionRates { function recordImbalance( IERC20Ext token, int buyAmount, uint256 rateUpdateBlock, uint256 currentBlock ) external; function getRate( IERC20Ext token, uint256 currentBlockNumber, bool buy, uint256 qty ) external view returns(uint256); } // File: @kyber.network/utils-sc/contracts/Utils.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 Utils { /// Declared constants below to be used in tandem with /// getDecimalsConstant(), for gas optimization purposes /// which return decimals from a constant list of popular /// tokens. IERC20Ext internal constant ETH_TOKEN_ADDRESS = IERC20Ext( 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ); IERC20Ext internal constant USDT_TOKEN_ADDRESS = IERC20Ext( 0xdAC17F958D2ee523a2206206994597C13D831ec7 ); IERC20Ext internal constant DAI_TOKEN_ADDRESS = IERC20Ext( 0x6B175474E89094C44Da98b954EedeAC495271d0F ); IERC20Ext internal constant USDC_TOKEN_ADDRESS = IERC20Ext( 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 ); IERC20Ext internal constant WBTC_TOKEN_ADDRESS = IERC20Ext( 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599 ); IERC20Ext internal constant KNC_TOKEN_ADDRESS = IERC20Ext( 0xdd974D5C2e2928deA5F71b9825b8b646686BD200 ); uint256 public constant BPS = 10000; // Basic Price Steps. 1 step = 0.01% 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 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite mapping(IERC20Ext => uint256) internal decimals; /// @dev Sets the decimals of a token to storage if not already set, and returns /// the decimals value of the token. Prefer using this function over /// getDecimals(), to avoid forgetting to set decimals in local storage. /// @param token The token type /// @return tokenDecimals The decimals of the token function getSetDecimals(IERC20Ext token) internal returns (uint256 tokenDecimals) { tokenDecimals = getDecimalsConstant(token); if (tokenDecimals > 0) return tokenDecimals; tokenDecimals = decimals[token]; if (tokenDecimals == 0) { tokenDecimals = token.decimals(); decimals[token] = tokenDecimals; } } /// @dev Get the balance of a user /// @param token The token type /// @param user The user's address /// @return The balance function getBalance(IERC20Ext token, address user) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) { return user.balance; } else { return token.balanceOf(user); } } /// @dev Get the decimals of a token, read from the constant list, storage, /// or from token.decimals(). Prefer using getSetDecimals when possible. /// @param token The token type /// @return tokenDecimals The decimals of the token function getDecimals(IERC20Ext token) internal view returns (uint256 tokenDecimals) { // return token decimals if has constant value tokenDecimals = getDecimalsConstant(token); if (tokenDecimals > 0) return tokenDecimals; // handle case where token decimals is not a declared decimal constant tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. return (tokenDecimals > 0) ? tokenDecimals : token.decimals(); } function calcDestAmount( IERC20Ext src, IERC20Ext dest, uint256 srcAmount, uint256 rate ) internal view returns (uint256) { return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate); } function calcSrcAmount( IERC20Ext src, IERC20Ext 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); } } /// @dev save storage access by declaring token decimal constants /// @param token The token type /// @return token decimals function getDecimalsConstant(IERC20Ext token) internal pure returns (uint256) { if (token == ETH_TOKEN_ADDRESS) { return ETH_DECIMALS; } else if (token == USDT_TOKEN_ADDRESS) { return 6; } else if (token == DAI_TOKEN_ADDRESS) { return 18; } else if (token == USDC_TOKEN_ADDRESS) { return 6; } else if (token == WBTC_TOKEN_ADDRESS) { return 8; } else if (token == KNC_TOKEN_ADDRESS) { return 18; } else { return 0; } } function minOf(uint256 x, uint256 y) internal pure returns (uint256) { return x > y ? y : x; } } // File: @openzeppelin/contracts/math/SafeMath.sol // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** * @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) { 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; } } // File: @openzeppelin/contracts/utils/Address.sol // SPDX-License-Identifier: MIT pragma solidity ^0.6.2; /** * @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) { // This method relies in extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } /** * @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"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain`call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return _functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); return _functionCallWithValue(target, data, value, errorMessage); } function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; /** * @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 IERC20;` 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)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ 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. We use {Address.functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "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: @kyber.network/utils-sc/contracts/PermissionGroups.sol pragma solidity 0.6.6; contract PermissionGroups { uint256 internal constant MAX_GROUP_SIZE = 50; address public admin; address public pendingAdmin; mapping(address => bool) internal operators; mapping(address => bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; event AdminClaimed(address newAdmin, address previousAdmin); event TransferAdminPending(address pendingAdmin); event OperatorAdded(address newOperator, bool isAdd); event AlerterAdded(address newAlerter, bool isAdd); constructor(address _admin) public { require(_admin != address(0), "admin 0"); admin = _admin; } modifier onlyAdmin() { require(msg.sender == admin, "only admin"); _; } modifier onlyOperator() { require(operators[msg.sender], "only operator"); _; } modifier onlyAlerter() { require(alerters[msg.sender], "only alerter"); _; } function getOperators() external view returns (address[] memory) { return operatorsGroup; } function getAlerters() external view returns (address[] memory) { return alertersGroup; } /** * @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; } /** * @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 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 removeAlerter(address alerter) public onlyAdmin { require(alerters[alerter], "not alerter"); alerters[alerter] = false; 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 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); } function removeOperator(address operator) public onlyAdmin { require(operators[operator], "not operator"); operators[operator] = false; 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; } } } } // File: @kyber.network/utils-sc/contracts/Withdrawable.sol pragma solidity 0.6.6; contract Withdrawable is PermissionGroups { using SafeERC20 for IERC20Ext; event TokenWithdraw(IERC20Ext token, uint256 amount, address sendTo); event EtherWithdraw(uint256 amount, address sendTo); constructor(address _admin) public PermissionGroups(_admin) {} /** * @dev Withdraw all IERC20Ext compatible tokens * @param token IERC20Ext The address of the token contract */ function withdrawToken( IERC20Ext token, uint256 amount, address sendTo ) external onlyAdmin { token.safeTransfer(sendTo, amount); emit TokenWithdraw(token, amount, sendTo); } /** * @dev Withdraw Ethers */ function withdrawEther(uint256 amount, address payable sendTo) external onlyAdmin { (bool success, ) = sendTo.call{value: amount}(""); require(success, "withdraw failed"); emit EtherWithdraw(amount, sendTo); } } // File: contracts/sol6/KyberFprReserveV2.sol // SPDX-License-Identifier: MIT pragma solidity 0.6.6; /// @title KyberFprReserve version 2 /// Allow Reserve to work work with either weth or eth. /// for working with weth should specify external address to hold weth. /// Allow Reserve to set maxGasPriceWei to trade with contract KyberFprReserveV2 is IKyberReserve, Utils, Withdrawable { using SafeERC20 for IERC20Ext; using SafeMath for uint256; mapping(bytes32 => bool) public approvedWithdrawAddresses; // sha3(token,address)=>bool mapping(address => address) public tokenWallet; struct ConfigData { bool tradeEnabled; bool doRateValidation; // whether to do rate validation in trade func uint128 maxGasPriceWei; } address public kyberNetwork; ConfigData internal configData; IConversionRates public conversionRatesContract; IKyberSanity public sanityRatesContract; IWeth public weth; event DepositToken(IERC20Ext indexed token, uint256 amount); event TradeExecute( address indexed origin, IERC20Ext indexed src, uint256 srcAmount, IERC20Ext indexed destToken, uint256 destAmount, address payable destAddress ); event TradeEnabled(bool enable); event MaxGasPriceUpdated(uint128 newMaxGasPrice); event DoRateValidationUpdated(bool doRateValidation); event WithdrawAddressApproved(IERC20Ext indexed token, address indexed addr, bool approve); event NewTokenWallet(IERC20Ext indexed token, address indexed wallet); event WithdrawFunds(IERC20Ext indexed token, uint256 amount, address indexed destination); event SetKyberNetworkAddress(address indexed network); event SetConversionRateAddress(IConversionRates indexed rate); event SetWethAddress(IWeth indexed weth); event SetSanityRateAddress(IKyberSanity indexed sanity); constructor( address _kyberNetwork, IConversionRates _ratesContract, IWeth _weth, uint128 _maxGasPriceWei, bool _doRateValidation, address _admin ) public Withdrawable(_admin) { require(_kyberNetwork != address(0), "kyberNetwork 0"); require(_ratesContract != IConversionRates(0), "ratesContract 0"); require(_weth != IWeth(0), "weth 0"); kyberNetwork = _kyberNetwork; conversionRatesContract = _ratesContract; weth = _weth; configData = ConfigData({ tradeEnabled: true, maxGasPriceWei: _maxGasPriceWei, doRateValidation: _doRateValidation }); } receive() external payable { emit DepositToken(ETH_TOKEN_ADDRESS, msg.value); } function trade( IERC20Ext srcToken, uint256 srcAmount, IERC20Ext destToken, address payable destAddress, uint256 conversionRate, bool /* validate */ ) external override payable returns (bool) { require(msg.sender == kyberNetwork, "wrong sender"); ConfigData memory data = configData; require(data.tradeEnabled, "trade not enable"); require(tx.gasprice <= uint256(data.maxGasPriceWei), "gas price too high"); doTrade( srcToken, srcAmount, destToken, destAddress, conversionRate, data.doRateValidation ); return true; } function enableTrade() external onlyAdmin { configData.tradeEnabled = true; emit TradeEnabled(true); } function disableTrade() external onlyAlerter { configData.tradeEnabled = false; emit TradeEnabled(false); } function setMaxGasPrice(uint128 newMaxGasPrice) external onlyOperator { configData.maxGasPriceWei = newMaxGasPrice; emit MaxGasPriceUpdated(newMaxGasPrice); } function setDoRateValidation(bool _doRateValidation) external onlyAdmin { configData.doRateValidation = _doRateValidation; emit DoRateValidationUpdated(_doRateValidation); } function approveWithdrawAddress( IERC20Ext token, address addr, bool approve ) external onlyAdmin { approvedWithdrawAddresses[keccak256(abi.encodePacked(address(token), addr))] = approve; getSetDecimals(token); emit WithdrawAddressApproved(token, addr, approve); } /// @dev allow set tokenWallet[token] back to 0x0 address /// @dev in case of using weth from external wallet, must call set token wallet for weth /// tokenWallet for weth must be different from this reserve address function setTokenWallet(IERC20Ext token, address wallet) external onlyAdmin { tokenWallet[address(token)] = wallet; getSetDecimals(token); emit NewTokenWallet(token, wallet); } /// @dev withdraw amount of token to an approved destination /// if reserve is using weth instead of eth, should call withdraw weth /// @param token token to withdraw /// @param amount amount to withdraw /// @param destination address to transfer fund to function withdraw( IERC20Ext token, uint256 amount, address destination ) external onlyOperator { require( approvedWithdrawAddresses[keccak256(abi.encodePacked(address(token), destination))], "destination is not approved" ); if (token == ETH_TOKEN_ADDRESS) { (bool success, ) = destination.call{value: amount}(""); require(success, "withdraw eth failed"); } else { address wallet = getTokenWallet(token); if (wallet == address(this)) { token.safeTransfer(destination, amount); } else { token.safeTransferFrom(wallet, destination, amount); } } emit WithdrawFunds(token, amount, destination); } function setKyberNetwork(address _newNetwork) external onlyAdmin { require(_newNetwork != address(0), "kyberNetwork 0"); kyberNetwork = _newNetwork; emit SetKyberNetworkAddress(_newNetwork); } function setConversionRate(IConversionRates _newConversionRate) external onlyAdmin { require(_newConversionRate != IConversionRates(0), "conversionRates 0"); conversionRatesContract = _newConversionRate; emit SetConversionRateAddress(_newConversionRate); } /// @dev weth is unlikely to be changed, but added this function to keep the flexibilty function setWeth(IWeth _newWeth) external onlyAdmin { require(_newWeth != IWeth(0), "weth 0"); weth = _newWeth; emit SetWethAddress(_newWeth); } /// @dev sanity rate can be set to 0x0 address to disable sanity rate check function setSanityRate(IKyberSanity _newSanity) external onlyAdmin { sanityRatesContract = _newSanity; emit SetSanityRateAddress(_newSanity); } function getConversionRate( IERC20Ext src, IERC20Ext dest, uint256 srcQty, uint256 blockNumber ) external override view returns (uint256) { ConfigData memory data = configData; if (!data.tradeEnabled) return 0; if (tx.gasprice > uint256(data.maxGasPriceWei)) return 0; if (srcQty == 0) return 0; IERC20Ext token; bool isBuy; 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 } uint256 rate; try conversionRatesContract.getRate(token, blockNumber, isBuy, srcQty) returns(uint256 r) { rate = r; } catch { return 0; } uint256 destQty = calcDestAmount(src, dest, srcQty, rate); if (getBalance(dest) < destQty) return 0; if (sanityRatesContract != IKyberSanity(0)) { uint256 sanityRate = sanityRatesContract.getSanityRate(src, dest); if (rate > sanityRate) return 0; } return rate; } function isAddressApprovedForWithdrawal(IERC20Ext token, address addr) external view returns (bool) { return approvedWithdrawAddresses[keccak256(abi.encodePacked(address(token), addr))]; } function tradeEnabled() external view returns (bool) { return configData.tradeEnabled; } function maxGasPriceWei() external view returns (uint128) { return configData.maxGasPriceWei; } function doRateValidation() external view returns (bool) { return configData.doRateValidation; } /// @dev return available balance of a token that reserve can use /// if using weth, call getBalance(eth) will return weth balance /// if using wallet for token, will return min of balance and allowance /// @param token token to get available balance that reserve can use function getBalance(IERC20Ext token) public view returns (uint256) { address wallet = getTokenWallet(token); IERC20Ext usingToken; if (token == ETH_TOKEN_ADDRESS) { if (wallet == address(this)) { // reserve should be using eth instead of weth return address(this).balance; } // reserve is using weth instead of eth usingToken = weth; } else { if (wallet == address(this)) { // not set token wallet or reserve is the token wallet, no need to check allowance return token.balanceOf(address(this)); } usingToken = token; } uint256 balanceOfWallet = usingToken.balanceOf(wallet); uint256 allowanceOfWallet = usingToken.allowance(wallet, address(this)); return minOf(balanceOfWallet, allowanceOfWallet); } /// @dev return wallet that holds the token /// if token is ETH, check tokenWallet of WETH instead /// if wallet is 0x0, consider as this reserve address function getTokenWallet(IERC20Ext token) public view returns (address wallet) { wallet = (token == ETH_TOKEN_ADDRESS) ? tokenWallet[address(weth)] : tokenWallet[address(token)]; if (wallet == address(0)) { wallet = address(this); } } /// @dev do a trade, re-validate the conversion rate, remove trust assumption with network /// @param srcToken Src token /// @param srcAmount Amount of src token /// @param destToken Destination token /// @param destAddress Destination address to send tokens to /// @param validateRate re-validate rate or not function doTrade( IERC20Ext srcToken, uint256 srcAmount, IERC20Ext destToken, address payable destAddress, uint256 conversionRate, bool validateRate ) internal { require(conversionRate > 0, "rate is 0"); bool isBuy = srcToken == ETH_TOKEN_ADDRESS; if (isBuy) { require(msg.value == srcAmount, "wrong msg value"); } else { require(msg.value == 0, "bad msg value"); } if (validateRate) { uint256 rate = conversionRatesContract.getRate( isBuy ? destToken : srcToken, block.number, isBuy, srcAmount ); // re-validate conversion rate require(rate >= conversionRate, "reserve rate lower then network requested rate"); if (sanityRatesContract != IKyberSanity(0)) { // sanity rate check uint256 sanityRate = sanityRatesContract.getSanityRate(srcToken, destToken); require(rate <= sanityRate, "rate should not be greater than sanity rate" ); } } uint256 destAmount = calcDestAmount(srcToken, destToken, srcAmount, conversionRate); require(destAmount > 0, "dest amount is 0"); address srcTokenWallet = getTokenWallet(srcToken); address destTokenWallet = getTokenWallet(destToken); if (isBuy) { // add to imbalance conversionRatesContract.recordImbalance( destToken, int256(destAmount), 0, block.number ); // if reserve is using weth, convert eth to weth and transfer weth to its' tokenWallet if (srcTokenWallet != address(this)) { weth.deposit{value: msg.value}(); IERC20Ext(weth).safeTransfer(srcTokenWallet, msg.value); } // transfer dest token from tokenWallet to destAddress if (destTokenWallet == address(this)) { destToken.safeTransfer(destAddress, destAmount); } else { destToken.safeTransferFrom(destTokenWallet, destAddress, destAmount); } } else { // add to imbalance conversionRatesContract.recordImbalance( srcToken, -1 * int256(srcAmount), 0, block.number ); // collect src token from sender srcToken.safeTransferFrom(msg.sender, srcTokenWallet, srcAmount); // if reserve is using weth, reserve needs to collect weth from tokenWallet, // then convert it to eth if (destTokenWallet != address(this)) { IERC20Ext(weth).safeTransferFrom(destTokenWallet, address(this), destAmount); weth.withdraw(destAmount); } // transfer eth to destAddress (bool success, ) = destAddress.call{value: destAmount}(""); require(success, "transfer eth from reserve to destAddress failed"); } emit TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress); } }
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: ConversionRateEnhancedSteps
// File: contracts/sol4/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/sol4/Utils.sol pragma solidity 0.4.18; /// @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/sol4/ConversionRatesInterface.sol pragma solidity 0.4.18; interface ConversionRatesInterface { function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public; function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint); } // File: contracts/sol4/PermissionGroups.sol pragma solidity 0.4.18; contract PermissionGroups { address public admin; address public pendingAdmin; mapping(address=>bool) internal operators; mapping(address=>bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint constant internal MAX_GROUP_SIZE = 50; function PermissionGroups() public { admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin); _; } modifier onlyOperator() { require(operators[msg.sender]); _; } modifier onlyAlerter() { require(alerters[msg.sender]); _; } function getOperators () external view returns(address[]) { return operatorsGroup; } function getAlerters () external view returns(address[]) { return alertersGroup; } event TransferAdminPending(address pendingAdmin); /** * @dev Allows the current admin to set the pendingAdmin address. * @param newAdmin The address to transfer ownership to. */ function transferAdmin(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(pendingAdmin); pendingAdmin = newAdmin; } /** * @dev Allows the current admin to set the admin in one tx. Useful initial deployment. * @param newAdmin The address to transfer ownership to. */ function transferAdminQuickly(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(newAdmin); AdminClaimed(newAdmin, admin); admin = newAdmin; } event AdminClaimed( address newAdmin, address previousAdmin); /** * @dev Allows the pendingAdmin address to finalize the change admin process. */ function claimAdmin() public { require(pendingAdmin == msg.sender); AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } event AlerterAdded (address newAlerter, bool isAdd); function addAlerter(address newAlerter) public onlyAdmin { require(!alerters[newAlerter]); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE); AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function removeAlerter (address alerter) public onlyAdmin { require(alerters[alerter]); alerters[alerter] = false; for (uint i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.length--; AlerterAdded(alerter, false); break; } } } event OperatorAdded(address newOperator, bool isAdd); function addOperator(address newOperator) public onlyAdmin { require(!operators[newOperator]); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE); OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } function removeOperator (address operator) public onlyAdmin { require(operators[operator]); operators[operator] = false; for (uint i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.length -= 1; OperatorAdded(operator, false); break; } } } } // File: contracts/sol4/Withdrawable.sol pragma solidity 0.4.18; /** * @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/sol4/VolumeImbalanceRecorder.sol pragma solidity 0.4.18; contract VolumeImbalanceRecorder is Withdrawable { uint constant internal SLIDING_WINDOW_SIZE = 5; uint constant internal POW_2_64 = 2 ** 64; struct TokenControlInfo { uint minimalRecordResolution; // can be roughly 1 cent uint maxPerBlockImbalance; // in twei resolution uint maxTotalImbalance; // max total imbalance (between rate updates) // before halting trade } mapping(address => TokenControlInfo) internal tokenControlInfo; struct TokenImbalanceData { int lastBlockBuyUnitsImbalance; uint lastBlock; int totalBuyUnitsImbalance; uint lastRateUpdateBlock; } mapping(address => mapping(uint=>uint)) public tokenImbalanceData; function VolumeImbalanceRecorder(address _admin) public { require(_admin != address(0)); admin = _admin; } function setTokenControlInfo( ERC20 token, uint minimalRecordResolution, uint maxPerBlockImbalance, uint maxTotalImbalance ) public onlyAdmin { tokenControlInfo[token] = TokenControlInfo( minimalRecordResolution, maxPerBlockImbalance, maxTotalImbalance ); } function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) { return (tokenControlInfo[token].minimalRecordResolution, tokenControlInfo[token].maxPerBlockImbalance, tokenControlInfo[token].maxTotalImbalance); } function addImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) internal { uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE; int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution)); int prevImbalance = 0; TokenImbalanceData memory currentBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]); // first scenario - this is not the first tx in the current block if (currentBlockData.lastBlock == currentBlock) { if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) { // just increase imbalance currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount; currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount; } else { // imbalance was changed in the middle of the block prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock); currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount; currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount; currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock); } } else { // first tx in the current block int currentBlockImbalance; (prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock); currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount; currentBlockData.lastBlock = uint(currentBlock); currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock); currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount; } tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData); } function setGarbageToVolumeRecorder(ERC20 token) internal { for (uint i = 0; i < SLIDING_WINDOW_SIZE; i++) { tokenImbalanceData[token][i] = 0x1; } } function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) { // check the imbalance in the sliding window require(startBlock <= endBlock); buyImbalance = 0; for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) { TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]); if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) { buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance); } } } function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int buyImbalance, int currentBlockImbalance) { buyImbalance = 0; currentBlockImbalance = 0; uint latestBlock = 0; int imbalanceInRange = 0; uint startBlock = rateUpdateBlock; uint endBlock = currentBlock; for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) { TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]); if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) { imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance; } if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue; if (perBlockData.lastBlock < latestBlock) continue; latestBlock = perBlockData.lastBlock; buyImbalance = perBlockData.totalBuyUnitsImbalance; if (uint(perBlockData.lastBlock) == currentBlock) { currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance; } } if (buyImbalance == 0) { buyImbalance = imbalanceInRange; } } function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int totalImbalance, int currentBlockImbalance) { int resolution = int(tokenControlInfo[token].minimalRecordResolution); (totalImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate( token, rateUpdateBlock, currentBlock); totalImbalance *= resolution; currentBlockImbalance *= resolution; } function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) { return tokenControlInfo[token].maxPerBlockImbalance; } function getMaxTotalImbalance(ERC20 token) internal view returns(uint) { return tokenControlInfo[token].maxTotalImbalance; } function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) { // check for overflows require(data.lastBlockBuyUnitsImbalance < int(POW_2_64 / 2)); require(data.lastBlockBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2)); require(data.lastBlock < POW_2_64); require(data.totalBuyUnitsImbalance < int(POW_2_64 / 2)); require(data.totalBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2)); require(data.lastRateUpdateBlock < POW_2_64); // do encoding uint result = uint(data.lastBlockBuyUnitsImbalance) & (POW_2_64 - 1); result |= data.lastBlock * POW_2_64; result |= (uint(data.totalBuyUnitsImbalance) & (POW_2_64 - 1)) * POW_2_64 * POW_2_64; result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64; return result; } function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) { TokenImbalanceData memory data; data.lastBlockBuyUnitsImbalance = int(int64(input & (POW_2_64 - 1))); data.lastBlock = uint(uint64((input / POW_2_64) & (POW_2_64 - 1))); data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) & (POW_2_64 - 1))); data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64)))); return data; } } // File: contracts/sol4/fprConversionRate/ConversionRates.sol pragma solidity 0.4.18; contract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils { // bps - basic rate steps. one step is 1 / 10000 of the rate. struct StepFunction { int[] x; // quantity for each step. Quantity of each step includes previous steps. int[] y; // rate change per quantity step in bps. } struct TokenData { bool listed; // was added to reserve bool enabled; // whether trade is enabled // position in the compact data uint compactDataArrayIndex; uint compactDataFieldIndex; // rate data. base and changes according to quantity and reserve balance. // generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction. uint baseBuyRate; // in PRECISION units. see KyberConstants uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate. StepFunction sellRateQtyStepFunction;// in bps. higher the qua StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate. StepFunction sellRateImbalanceStepFunction; } /* this is the data for tokenRatesCompactData but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write so we represent it as bytes32 and do the byte tricks ourselves. struct TokenRatesCompactData { bytes14 buy; // change buy rate of token from baseBuyRate in 10 bps bytes14 sell; // change sell rate of token from baseSellRate in 10 bps uint32 blockNumber; } */ uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks ERC20[] internal listedTokens; mapping(address=>TokenData) internal tokenData; bytes32[] internal tokenRatesCompactData; uint public numTokensInCurrentCompactData = 0; address public reserveContract; uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14; uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA)); uint constant internal MAX_STEPS_IN_FUNCTION = 10; int constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B % int constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100% function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin) { } // solhint-disable-line no-empty-blocks function addToken(ERC20 token) public onlyAdmin { require(!tokenData[token].listed); tokenData[token].listed = true; listedTokens.push(token); if (numTokensInCurrentCompactData == 0) { tokenRatesCompactData.length++; // add new structure } tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1; tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData; numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA; setGarbageToVolumeRecorder(token); setDecimals(token); } function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator { require(buy.length == sell.length); require(indices.length == buy.length); require(blockNumber <= 0xFFFFFFFF); uint bytes14Offset = BYTES_14_OFFSET; for (uint i = 0; i < indices.length; i++) { require(indices[i] < tokenRatesCompactData.length); uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset)); tokenRatesCompactData[indices[i]] = bytes32(data); } } function setBaseRate( ERC20[] tokens, uint[] baseBuy, uint[] baseSell, bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices ) public onlyOperator { require(tokens.length == baseBuy.length); require(tokens.length == baseSell.length); require(sell.length == buy.length); require(sell.length == indices.length); for (uint ind = 0; ind < tokens.length; ind++) { require(tokenData[tokens[ind]].listed); tokenData[tokens[ind]].baseBuyRate = baseBuy[ind]; tokenData[tokens[ind]].baseSellRate = baseSell[ind]; } setCompactData(buy, sell, blockNumber, indices); } function setQtyStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length == yBuy.length); require(xSell.length == ySell.length); require(xBuy.length <= MAX_STEPS_IN_FUNCTION); require(xSell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy); tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell); } function setImbalanceStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length == yBuy.length); require(xSell.length == ySell.length); require(xBuy.length <= MAX_STEPS_IN_FUNCTION); require(xSell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy); tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell); } function setValidRateDurationInBlocks(uint duration) public onlyAdmin { validRateDurationInBlocks = duration; } function enableTokenTrade(ERC20 token) public onlyAdmin { require(tokenData[token].listed); require(tokenControlInfo[token].minimalRecordResolution != 0); tokenData[token].enabled = true; } function disableTokenTrade(ERC20 token) public onlyAlerter { require(tokenData[token].listed); tokenData[token].enabled = false; } function setReserveAddress(address reserve) public onlyAdmin { reserveContract = reserve; } function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public { require(msg.sender == reserveContract); if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token); return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock); } /* solhint-disable function-max-lines */ function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) { // check if trade is enabled if (!tokenData[token].enabled) return 0; if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set // get rate update block bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; uint updateRateBlock = getLast4Bytes(compactData); if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired // check imbalance int totalImbalance; int blockImbalance; (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber); // calculate actual rate int imbalanceQty; int extraBps; int8 rateUpdate; uint rate; if (buy) { // start with base rate rate = tokenData[token].baseBuyRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, true); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty qty = getTokenQty(token, rate, qty); imbalanceQty = int(qty); totalImbalance += imbalanceQty; // add qty overhead extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty)); rate = addBps(rate, extraBps); // add imbalance overhead extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance); rate = addBps(rate, extraBps); } else { // start with base rate rate = tokenData[token].baseSellRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, false); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty imbalanceQty = -1 * int(qty); totalImbalance += imbalanceQty; // add qty overhead extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty)); rate = addBps(rate, extraBps); // add imbalance overhead extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance); rate = addBps(rate, extraBps); } if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0; if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0; return rate; } /* solhint-enable function-max-lines */ function getBasicRate(ERC20 token, bool buy) public view returns(uint) { if (buy) return tokenData[token].baseBuyRate; else return tokenData[token].baseSellRate; } function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) { require(tokenData[token].listed); uint arrayIndex = tokenData[token].compactDataArrayIndex; uint fieldOffset = tokenData[token].compactDataFieldIndex; return ( arrayIndex, fieldOffset, byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)), byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false)) ); } function getTokenBasicData(ERC20 token) public view returns(bool, bool) { return (tokenData[token].listed, tokenData[token].enabled); } /* solhint-disable code-complexity */ function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) { if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length); if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param]; if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length); if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param]; if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length); if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param]; if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length); if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param]; if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length); if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param]; if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length); if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param]; if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length); if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param]; if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length); if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param]; revert(); } /* solhint-enable code-complexity */ function getRateUpdateBlock(ERC20 token) public view returns(uint) { bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; return getLast4Bytes(compactData); } function getListedTokens() public view returns(ERC20[]) { return listedTokens; } function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) { uint dstDecimals = getDecimals(token); uint srcDecimals = ETH_DECIMALS; return calcDstQty(ethQty, srcDecimals, dstDecimals, rate); } function getLast4Bytes(bytes32 b) internal pure returns(uint) { // cannot trust compiler with not turning bit operations into EXP opcode return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET); } function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) { uint fieldOffset = tokenData[token].compactDataFieldIndex; uint byteOffset; if (buy) byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset; else byteOffset = 4 + fieldOffset; return int8(data[byteOffset]); } function executeStepFunction(StepFunction f, int x) internal pure returns(int) { uint len = f.y.length; for (uint ind = 0; ind < len; ind++) { if (x <= f.x[ind]) return f.y[ind]; } return f.y[len-1]; } function addBps(uint rate, int bps) internal pure returns(uint) { require(rate <= MAX_RATE); require(bps >= MIN_BPS_ADJUSTMENT); require(bps <= MAX_BPS_ADJUSTMENT); uint maxBps = 100 * 100; return (rate * uint(int(maxBps) + bps)) / maxBps; } function abs(int x) internal pure returns(uint) { if (x < 0) return uint(-1 * x); else return uint(x); } } // File: contracts/sol4/fprConversionRate/ConversionRateEnhancedSteps.sol pragma solidity 0.4.18; /// @title ConversionRateEnhancedSteps contract - new ConversionRates contract with step function enhancement /// Removed qty step function overhead /// Also fixed following issues: /// https://github.com/KyberNetwork/smart-contracts/issues/291 /// https://github.com/KyberNetwork/smart-contracts/issues/241 /// https://github.com/KyberNetwork/smart-contracts/issues/240 contract ConversionRateEnhancedSteps is ConversionRates { uint constant internal MAX_RATE = (PRECISION * 10 ** 7); // up to 10M tokens per ETH uint constant internal MAX_STEPS_IN_FUNCTION = 16; int constant internal MAX_IMBALANCE = 2 ** 255 - 1; uint constant internal POW_2_128 = 2 ** 128; int128 constant internal MAX_STEP_VALUE = 2 ** 127 - 1; int128 constant internal MIN_STEP_VALUE = -1 * 2 ** 127; int constant internal MAX_BPS_ADJUSTMENT = 100 * 100; function ConversionRateEnhancedSteps(address _admin) public ConversionRates(_admin) { } // solhint-disable-line no-empty-blocks // Blocking set qty step func as we won't use function setQtyStepFunction( ERC20, int[], int[], int[], int[] ) public onlyOperator { revert(); } function setImbalanceStepFunction( ERC20 token, int[] xBuy, int[] yBuy, int[] xSell, int[] ySell ) public onlyOperator { require(xBuy.length + 1 == yBuy.length); require(xSell.length + 1 == ySell.length); require(yBuy.length <= MAX_STEPS_IN_FUNCTION); require(ySell.length <= MAX_STEPS_IN_FUNCTION); require(tokenData[token].listed); uint i; if (xBuy.length > 1) { // verify qty are increasing for(i = 0; i < xBuy.length - 1; i++) { require(xBuy[i] < xBuy[i + 1]); } } // only need to check last value as it's sorted array require(xBuy.length == 0 || xBuy[xBuy.length - 1] < MAX_STEP_VALUE); // verify yBuy for(i = 0; i < yBuy.length; i++) { require(yBuy[i] >= MIN_BPS_ADJUSTMENT); require(yBuy[i] <= MAX_BPS_ADJUSTMENT); } if (xSell.length > 1) { // verify qty are increasing for(i = 0; i < xSell.length - 1; i++) { require(xSell[i] < xSell[i + 1]); } } // only need to check last value as it's sorted array require(xSell.length == 0 || xSell[xSell.length - 1] < MAX_STEP_VALUE); // verify ySell for(i = 0; i < ySell.length; i++) { require(ySell[i] >= MIN_BPS_ADJUSTMENT); require(ySell[i] <= MAX_BPS_ADJUSTMENT); } int[] memory buyArray = new int[](yBuy.length); for(i = 0; i < yBuy.length; i++) { int128 xBuyVal = (i == yBuy.length - 1) ? MAX_STEP_VALUE : int128(xBuy[i]); buyArray[i] = encodeStepFunctionData(xBuyVal, int128(yBuy[i])); } int[] memory sellArray = new int[](ySell.length); for(i = 0; i < ySell.length; i++) { int128 xSellVal = (i == ySell.length - 1) ? MAX_STEP_VALUE : int128(xSell[i]); sellArray[i] = encodeStepFunctionData(xSellVal, int128(ySell[i])); } int[] memory emptyArr = new int[](0); tokenData[token].buyRateImbalanceStepFunction = StepFunction(buyArray, emptyArr); tokenData[token].sellRateImbalanceStepFunction = StepFunction(sellArray, emptyArr); } /* solhint-disable code-complexity */ function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) { if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length - 1); int stepXValue; int stepYValue; if (command == 9) { (stepXValue, stepYValue) = decodeStepFunctionData(tokenData[token].buyRateImbalanceStepFunction.x[param]); return stepXValue; } if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.x.length); if (command == 11) { (stepXValue, stepYValue) = decodeStepFunctionData(tokenData[token].buyRateImbalanceStepFunction.x[param]); return stepYValue; } if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length - 1); if (command == 13) { (stepXValue, stepYValue) = decodeStepFunctionData(tokenData[token].sellRateImbalanceStepFunction.x[param]); return stepXValue; } if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.x.length); if (command == 15) { (stepXValue, stepYValue) = decodeStepFunctionData(tokenData[token].sellRateImbalanceStepFunction.x[param]); return stepYValue; } revert(); } /* solhint-disable function-max-lines */ function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) { // check if trade is enabled if (!tokenData[token].enabled) return 0; if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set // get rate update block bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex]; uint updateRateBlock = getLast4Bytes(compactData); if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired // check imbalance int totalImbalance; int blockImbalance; (totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber); // calculate actual rate int imbalanceQty; int extraBps; int8 rateUpdate; uint rate; if (buy) { // start with base rate rate = tokenData[token].baseBuyRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, true); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty qty = getTokenQty(token, qty, rate); imbalanceQty = int(qty); // add imbalance overhead extraBps = executeStepFunction( tokenData[token].buyRateImbalanceStepFunction, totalImbalance, totalImbalance + imbalanceQty ); rate = addBps(rate, extraBps); totalImbalance += imbalanceQty; } else { // start with base rate rate = tokenData[token].baseSellRate; // add rate update rateUpdate = getRateByteFromCompactData(compactData, token, false); extraBps = int(rateUpdate) * 10; rate = addBps(rate, extraBps); // compute token qty imbalanceQty = -1 * int(qty); // add imbalance overhead extraBps = executeStepFunction( tokenData[token].sellRateImbalanceStepFunction, totalImbalance + imbalanceQty, totalImbalance ); rate = addBps(rate, extraBps); totalImbalance += imbalanceQty; } if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0; if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0; return rate; } // Override function getImbalance to fix #240 function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock) internal view returns(int totalImbalance, int currentBlockImbalance) { int resolution = int(tokenControlInfo[token].minimalRecordResolution); (totalImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate( token, rateUpdateBlock, currentBlock); if (!checkMultOverflow(totalImbalance, resolution)) { totalImbalance *= resolution; } else { totalImbalance = MAX_IMBALANCE; } if (!checkMultOverflow(currentBlockImbalance, resolution)) { currentBlockImbalance *= resolution; } else { currentBlockImbalance = MAX_IMBALANCE; } } function getImbalancePerToken(ERC20 token, uint whichBlock) public view returns(int totalImbalance, int currentBlockImbalance) { uint rateUpdateBlock = getRateUpdateBlock(token); // if whichBlock = 0, use latest block, otherwise use whichBlock uint usedBlock = whichBlock == 0 ? block.number : whichBlock; return getImbalance(token, rateUpdateBlock, usedBlock); } function addBps(uint rate, int bps) internal pure returns(uint) { require(rate <= MAX_RATE); require(bps >= MIN_BPS_ADJUSTMENT); require(bps <= MAX_BPS_ADJUSTMENT); uint maxBps = 100 * 100; return (rate * uint(int(maxBps) + bps)) / maxBps; } function executeStepFunction(StepFunction storage f, int from, int to) internal view returns(int) { uint len = f.x.length; if (len == 0 || from == to) { return 0; } int fromVal = from; // avoid modifying function parameters int change = 0; // amount change from initial amount when applying bps for each step int stepXValue; int stepYValue; for(uint ind = 0; ind < len; ind++) { (stepXValue, stepYValue) = decodeStepFunctionData(f.x[ind]); if (stepXValue <= fromVal) { continue; } // if it falls into step with y <= -10000, rate must be 0 if (stepYValue == MIN_BPS_ADJUSTMENT) { return MIN_BPS_ADJUSTMENT; } // from here, from < stepXValue, // if from < to <= stepXValue, take [from, to] and return, else take [from, stepXValue] if (stepXValue >= to) { change += (to - fromVal) * stepYValue; return change / (to - from); } else { change += (stepXValue - fromVal) * stepYValue; fromVal = stepXValue; } // max change < MAX_QTY * BPS * 16 = (10 ** 28) * 10000 * 16 = 1.6e33 } return change / (to - from); } // first 128 bits is value for x, next 128 bits is value for y function encodeStepFunctionData(int128 x, int128 y) internal pure returns(int data) { require(x <= MAX_STEP_VALUE && x >= MIN_STEP_VALUE); require(y <= MAX_STEP_VALUE && y >= MIN_STEP_VALUE); data = int(uint(y) & (POW_2_128 - 1)); data |= int((uint(x) & (POW_2_128 - 1)) * POW_2_128); } function decodeStepFunctionData(int val) internal pure returns (int x, int y) { y = int(int128(uint(val) & (POW_2_128 - 1))); x = int(int128((uint(val) / POW_2_128) & (POW_2_128 - 1))); // default to be max value if (x == int(MAX_STEP_VALUE)) { x = MAX_IMBALANCE; } } function checkMultOverflow(int x, int y) internal pure returns(bool) { if (y == 0) return false; return (((x*y) / y) != x); } }