Transaction Hash:
Block:
10736777 at Aug-26-2020 03:02:57 PM +UTC
Transaction Fee:
0.055493351970101028 ETH
$119.70
Gas Used:
542,457 Gas / 102.300001604 Gwei
Emitted Events:
| 68 |
WETH9.Deposit( dst=UniswapV2Router02, wad=3959726889739771376 )
|
| 69 |
WETH9.Transfer( src=UniswapV2Router02, dst=UniswapV2Pair, wad=3959726889739771376 )
|
| 70 |
BZRXToken.Transfer( from=UniswapV2Pair, to=KyberNetwork, value=2071658179887328085500 )
|
| 71 |
UniswapV2Pair.Sync( reserve0=1420905974607521107755383, reserve1=2711703915153485553667 )
|
| 72 |
UniswapV2Pair.Swap( sender=UniswapV2Router02, amount0In=0, amount1In=3959726889739771376, amount0Out=2071658179887328085500, amount1Out=0, to=KyberNetwork )
|
| 73 |
0x10908c875d865c66f271f5d3949848971c9595c9.0xea9415385bae08fe9f6dc457b02577166790cde83bb18cc340aac6cb81b824de( 0xea9415385bae08fe9f6dc457b02577166790cde83bb18cc340aac6cb81b824de, 0x0000000000000000000000007c66550c9c730b6fdd4c03bc2e73c5462c5f7acc, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 0x00000000000000000000000056d811088235f11c8920698a204a5010a788f4b3, 00000000000000000000000000000000000000000000000036f3c6a06e805df0, 0000000000000000000000000000000000000000000000704db66f32256c0149, 0000000000000000000000007c66550c9c730b6fdd4c03bc2e73c5462c5f7acc )
|
| 74 |
BZRXToken.Transfer( from=KyberNetwork, to=[Sender] 0xde281ce468a4f01eebb4407037c0a7dcded0ddc2, value=2071635121703318454272 )
|
| 75 |
KyberFeeHandler.FeeDistributed( token=0xEeeeeEee...eeeeeEEeE, platformWallet=0xF1AA99C6...E35Cc504d, platformFeeWei=9924127543207447, rewardWei=0, rebateWei=0, rebateWallets=[], rebatePercentBpsPerWallet=[], burnAmtWei=0 )
|
| 76 |
KyberNetwork.KyberTrade( 0x30bbea603a7b36858fe5e3ec6ba5ff59dde039d02120d758eacfaed01520577d, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 0x00000000000000000000000056d811088235f11c8920698a204a5010a788f4b3, 000000000000000000000000000000000000000000000000371708916f6b8407, 0000000000000000000000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000000002341f100eb2617, 0000000000000000000000000000000000000000000000000000000000000120, 0000000000000000000000000000000000000000000000000000000000000140, 0000000000000000000000000000000000000000000000000000000000000180, 00000000000000000000000000000000000000000000000000000000000001a0, 00000000000000000000000000000000000000000000000000000000000001e0, 0000000000000000000000000000000000000000000000000000000000000200, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, bb756e6973776170563200000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000036f3c6a06e805df0, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000001c5c8778726635bc9d )
|
| 77 |
KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0xde281ce468a4f01eebb4407037c0a7dcded0ddc2, src=0xEeeeeEee...eeeeeEEeE, dest=BZRXToken, destAddress=[Sender] 0xde281ce468a4f01eebb4407037c0a7dcded0ddc2, actualSrcAmount=3969651017282978823, actualDestAmount=2071635121703318454272, platformWallet=0xF1AA99C6...E35Cc504d, platformFeeBps=25 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x56d81108...0a788f4b3 | |||||
|
0x5A0b54D5...D3E029c4c
Miner
| (Spark Pool) | 7.239415686547103052 Eth | 7.29490903851720408 Eth | 0.055493351970101028 | |
| 0xB9b752F7...66763A796 | |||||
| 0xC02aaA39...83C756Cc2 | 3,724,275.271119409509647984 Eth | 3,724,279.23084629924941936 Eth | 3.959726889739771376 | ||
| 0xd3d2b564...a941114fe | (Kyber: Fee Handler) | 1,153.000041139315212272 Eth | 1,153.009965266858419719 Eth | 0.009924127543207447 | |
| 0xDe281CE4...cdED0dDc2 |
8.671794429204442012 Eth
Nonce: 11
|
4.646650059951362161 Eth
Nonce: 12
| 4.025144369253079851 |
Execution Trace
ETH 4
KyberNetworkProxy.tradeWithHintAndFee( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=4000000000000000000, dest=0x56d811088235F11C8920698a204A5010a788f4b3, destAddress=0xDe281CE468a4f01eEbB4407037c0A7DcdED0dDc2, maxDestAmount=2071635121703318454272, minConversionRate=502332576503248322560, platformWallet=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d, platformFeeBps=25, hint=0x ) => ( destAmount=2071635121703318454272 )
-
BZRXToken.balanceOf( _owner=0xDe281CE468a4f01eEbB4407037c0A7DcdED0dDc2 ) => ( 2062347158672354050048 )
ETH 4
KyberNetwork.tradeWithHintAndFee( trader=0xDe281CE468a4f01eEbB4407037c0A7DcdED0dDc2, src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=4000000000000000000, dest=0x56d811088235F11C8920698a204A5010a788f4b3, destAddress=0xDe281CE468a4f01eEbB4407037c0A7DcdED0dDc2, maxDestAmount=2071635121703318454272, minConversionRate=502332576503248322560, platformWallet=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d, platformFeeBps=25, hint=0x ) => ( destAmount=2071635121703318454272 )KyberMatchingEngine.getTradingReserves( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x56d811088235F11C8920698a204A5010a788f4b3, isTokenToToken=False, hint=0x )-
KyberStorage.getReserveIdsPerTokenDest( token=0x56d811088235F11C8920698a204A5010a788f4b3 )
-
-
KyberStorage.getReservesData( ) => ( areAllReservesListed=True, feeAccountedArr=[true, false], entitledRebateArr=[false, false], reserveAddresses=[0xcF76b605484Cd4bD46237c05B7De98d538ff44AE, 0x10908C875D865C66f271F5d3949848971c9595C9] ) KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x56d811088235F11C8920698a204A5010a788f4b3, srcQty=3982000000000000000, blockNumber=10736777 ) => ( 511140396976854621486 )-
LiquidityConversionRates.getRate( conversionToken=0x56d811088235F11C8920698a204A5010a788f4b3, currentBlockNumber=10736777, buy=True, qtyInSrcWei=3982000000000000000 ) => ( 511140396976854621486 ) -
BZRXToken.balanceOf( _owner=0xcF76b605484Cd4bD46237c05B7De98d538ff44AE ) => ( 199344333961317458388308 )
-
BZRXToken.allowance( _owner=0xcF76b605484Cd4bD46237c05B7De98d538ff44AE, _spender=0xcF76b605484Cd4bD46237c05B7De98d538ff44AE ) => ( 57896044618658097711785492504343953926634992332820282019728792003956564819968 )
-
Kyber: Reserve Uniswap V2.7cd44272( )UniswapV2Router02.getAmountsOut( amountIn=3990000000000000000, path=[0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 0x56d811088235F11C8920698a204A5010a788f4b3] ) => ( amounts=[3990000000000000000, 2087473294437097134473] )-
UniswapV2Pair.STATICCALL( )
-
-
KyberMatchingEngine.doMatch( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x56d811088235F11C8920698a204A5010a788f4b3, srcAmounts=[3982000000000000000, 3990000000000000000], feesAccountedDestBps=[0, 0], rates=[511140396976854621486, 523176264269949156509] ) => ( reserveIndexes=[1] ) -
BZRXToken.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 44571118782448514 )
ETH 3.959726889739771376
Kyber: Reserve Uniswap V2.6cf69811( )UniswapV2Router02.getAmountsOut( amountIn=3959726889739771376, path=[0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 0x56d811088235F11C8920698a204A5010a788f4b3] ) => ( amounts=[3959726889739771376, 2071658179887328085500] )-
UniswapV2Pair.STATICCALL( )
-
ETH 3.959726889739771376
UniswapV2Router02.swapExactETHForTokens( amountOutMin=2071635121703318454601, path=[0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 0x56d811088235F11C8920698a204A5010a788f4b3], to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, deadline=57896044618658097711785492504343953926634992332820282019728792003956564819968 ) => ( amounts=[3959726889739771376, 2071658179887328085500] )-
UniswapV2Pair.STATICCALL( )
- ETH 3.959726889739771376
WETH9.CALL( )
-
WETH9.transfer( dst=0xB9b752F7f4a4680eEb327ffe728f46666763A796, wad=3959726889739771376 ) => ( True )
UniswapV2Pair.swap( amount0Out=2071658179887328085500, amount1Out=0, to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, data=0x )
-
BZRXToken.transfer( _to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, _value=2071658179887328085500 ) => ( True )
-
BZRXToken.balanceOf( _owner=0xB9b752F7f4a4680eEb327ffe728f46666763A796 ) => ( 1420905974607521107755383 )
-
WETH9.balanceOf( 0xB9b752F7f4a4680eEb327ffe728f46666763A796 ) => ( 2711703915153485553667 )
-
-
-
BZRXToken.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 2071702751006110534014 )
-
BZRXToken.transfer( _to=0xDe281CE468a4f01eEbB4407037c0A7DcdED0dDc2, _value=2071635121703318454272 ) => ( True )
- ETH 0.030348982717021177
0xde281ce468a4f01eebb4407037c0a7dcded0ddc2.CALL( ) - ETH 0.009924127543207447
KyberFeeHandler.handleFees( token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, rebateWallets=[], rebateBpsPerWallet=[], platformWallet=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d, platformFee=9924127543207447, networkFee=0 )
-
BZRXToken.balanceOf( _owner=0xDe281CE468a4f01eEbB4407037c0A7DcdED0dDc2 ) => ( 4133982280375672504320 )
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 11: KyberNetworkProxy
File 2 of 11: WETH9
File 3 of 11: UniswapV2Router02
File 4 of 11: UniswapV2Pair
File 5 of 11: KyberNetwork
File 6 of 11: BZRXToken
File 7 of 11: KyberFeeHandler
File 8 of 11: KyberMatchingEngine
File 9 of 11: KyberStorage
File 10 of 11: KyberReserve
File 11 of 11: LiquidityConversionRates
// File: contracts/sol6/IERC20.sol
pragma solidity 0.6.6;
interface IERC20 {
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
function approve(address _spender, uint256 _value) external returns (bool success);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
function balanceOf(address _owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 digits);
function totalSupply() external view returns (uint256 supply);
}
// to support backward compatible contract name -- so function signature remains same
abstract contract ERC20 is IERC20 {
}
// File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol
pragma solidity 0.6.6;
contract PermissionGroupsNoModifiers {
address public admin;
address public pendingAdmin;
mapping(address => bool) internal operators;
mapping(address => bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint256 internal constant MAX_GROUP_SIZE = 50;
event AdminClaimed(address newAdmin, address previousAdmin);
event AlerterAdded(address newAlerter, bool isAdd);
event OperatorAdded(address newOperator, bool isAdd);
event TransferAdminPending(address pendingAdmin);
constructor(address _admin) public {
require(_admin != address(0), "admin 0");
admin = _admin;
}
function getOperators() external view returns (address[] memory) {
return operatorsGroup;
}
function getAlerters() external view returns (address[] memory) {
return alertersGroup;
}
function addAlerter(address newAlerter) public {
onlyAdmin();
require(!alerters[newAlerter], "alerter exists"); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters");
emit AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function addOperator(address newOperator) public {
onlyAdmin();
require(!operators[newOperator], "operator exists"); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators");
emit OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
/// @dev Allows the pendingAdmin address to finalize the change admin process.
function claimAdmin() public {
require(pendingAdmin == msg.sender, "not pending");
emit AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
function removeAlerter(address alerter) public {
onlyAdmin();
require(alerters[alerter], "not alerter");
delete alerters[alerter];
for (uint256 i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.pop();
emit AlerterAdded(alerter, false);
break;
}
}
}
function removeOperator(address operator) public {
onlyAdmin();
require(operators[operator], "not operator");
delete operators[operator];
for (uint256 i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.pop();
emit OperatorAdded(operator, false);
break;
}
}
}
/// @dev Allows the current admin to set the pendingAdmin address
/// @param newAdmin The address to transfer ownership to
function transferAdmin(address newAdmin) public {
onlyAdmin();
require(newAdmin != address(0), "new admin 0");
emit TransferAdminPending(newAdmin);
pendingAdmin = newAdmin;
}
/// @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
/// @param newAdmin The address to transfer ownership to.
function transferAdminQuickly(address newAdmin) public {
onlyAdmin();
require(newAdmin != address(0), "admin 0");
emit TransferAdminPending(newAdmin);
emit AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
function onlyAdmin() internal view {
require(msg.sender == admin, "only admin");
}
function onlyAlerter() internal view {
require(alerters[msg.sender], "only alerter");
}
function onlyOperator() internal view {
require(operators[msg.sender], "only operator");
}
}
// File: contracts/sol6/utils/WithdrawableNoModifiers.sol
pragma solidity 0.6.6;
contract WithdrawableNoModifiers is PermissionGroupsNoModifiers {
constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {}
event EtherWithdraw(uint256 amount, address sendTo);
event TokenWithdraw(IERC20 token, uint256 amount, address sendTo);
/// @dev Withdraw Ethers
function withdrawEther(uint256 amount, address payable sendTo) external {
onlyAdmin();
(bool success, ) = sendTo.call{value: amount}("");
require(success);
emit EtherWithdraw(amount, sendTo);
}
/// @dev Withdraw all IERC20 compatible tokens
/// @param token IERC20 The address of the token contract
function withdrawToken(
IERC20 token,
uint256 amount,
address sendTo
) external {
onlyAdmin();
token.transfer(sendTo, amount);
emit TokenWithdraw(token, amount, sendTo);
}
}
// File: contracts/sol6/utils/Utils5.sol
pragma solidity 0.6.6;
/**
* @title Kyber utility file
* mostly shared constants and rate calculation helpers
* inherited by most of kyber contracts.
* previous utils implementations are for previous solidity versions.
*/
contract Utils5 {
IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20(
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
);
uint256 internal constant PRECISION = (10**18);
uint256 internal constant MAX_QTY = (10**28); // 10B tokens
uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth
uint256 internal constant MAX_DECIMALS = 18;
uint256 internal constant ETH_DECIMALS = 18;
uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01%
uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite
mapping(IERC20 => uint256) internal decimals;
function getUpdateDecimals(IERC20 token) internal returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if (tokenDecimals == 0) {
tokenDecimals = token.decimals();
decimals[token] = tokenDecimals;
}
return tokenDecimals;
}
function setDecimals(IERC20 token) internal {
if (decimals[token] != 0) return; //already set
if (token == ETH_TOKEN_ADDRESS) {
decimals[token] = ETH_DECIMALS;
} else {
decimals[token] = token.decimals();
}
}
/// @dev get the balance of a user.
/// @param token The token type
/// @return The balance
function getBalance(IERC20 token, address user) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) {
return user.balance;
} else {
return token.balanceOf(user);
}
}
function getDecimals(IERC20 token) internal view returns (uint256) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint256 tokenDecimals = decimals[token];
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if (tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDestAmount(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
uint256 rate
) internal view returns (uint256) {
return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcSrcAmount(
IERC20 src,
IERC20 dest,
uint256 destAmount,
uint256 rate
) internal view returns (uint256) {
return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate);
}
function calcDstQty(
uint256 srcQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(srcQty <= MAX_QTY, "srcQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(
uint256 dstQty,
uint256 srcDecimals,
uint256 dstDecimals,
uint256 rate
) internal pure returns (uint256) {
require(dstQty <= MAX_QTY, "dstQty > MAX_QTY");
require(rate <= MAX_RATE, "rate > MAX_RATE");
//source quantity is rounded up. to avoid dest quantity being too low.
uint256 numerator;
uint256 denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
function calcRateFromQty(
uint256 srcAmount,
uint256 destAmount,
uint256 srcDecimals,
uint256 dstDecimals
) internal pure returns (uint256) {
require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY");
require(destAmount <= MAX_QTY, "destAmount > MAX_QTY");
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS");
return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount));
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS");
return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount);
}
}
function minOf(uint256 x, uint256 y) internal pure returns (uint256) {
return x > y ? y : x;
}
}
// File: contracts/sol6/utils/zeppelin/SafeMath.sol
pragma solidity 0.6.6;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMath {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting with custom message on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
return div(a, b, "SafeMath: division by zero");
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts with custom message on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts with custom message when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
// File: contracts/sol6/utils/zeppelin/Address.sol
pragma solidity 0.6.6;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*/
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly { codehash := extcodehash(account) }
return (codehash != accountHash && codehash != 0x0);
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
// File: contracts/sol6/utils/zeppelin/SafeERC20.sol
pragma solidity 0.6.6;
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
using Address for address;
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) { // Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// File: contracts/sol6/IKyberNetwork.sol
pragma solidity 0.6.6;
interface IKyberNetwork {
event KyberTrade(
IERC20 indexed src,
IERC20 indexed dest,
uint256 ethWeiValue,
uint256 networkFeeWei,
uint256 customPlatformFeeWei,
bytes32[] t2eIds,
bytes32[] e2tIds,
uint256[] t2eSrcAmounts,
uint256[] e2tSrcAmounts,
uint256[] t2eRates,
uint256[] e2tRates
);
function tradeWithHintAndFee(
address payable trader,
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function listTokenForReserve(
address reserve,
IERC20 token,
bool add
) external;
function enabled() external view returns (bool);
function getExpectedRateWithHintAndFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
)
external
view
returns (
uint256 expectedRateAfterNetworkFee,
uint256 expectedRateAfterAllFees
);
function getNetworkData()
external
view
returns (
uint256 negligibleDiffBps,
uint256 networkFeeBps,
uint256 expiryTimestamp
);
function maxGasPrice() external view returns (uint256);
}
// File: contracts/sol6/IKyberNetworkProxy.sol
pragma solidity 0.6.6;
interface IKyberNetworkProxy {
event ExecuteTrade(
address indexed trader,
IERC20 src,
IERC20 dest,
address destAddress,
uint256 actualSrcAmount,
uint256 actualDestAmount,
address platformWallet,
uint256 platformFeeBps
);
/// @notice backward compatible
function tradeWithHint(
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable returns (uint256);
function tradeWithHintAndFee(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable returns (uint256 destAmount);
function trade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet
) external payable returns (uint256);
/// @notice backward compatible
/// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view returns (uint256 expectedRate, uint256 worstRate);
function getExpectedRateAfterFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
) external view returns (uint256 expectedRate);
}
// File: contracts/sol6/ISimpleKyberProxy.sol
pragma solidity 0.6.6;
/*
* @title simple Kyber Network proxy interface
* add convenient functions to help with kyber proxy API
*/
interface ISimpleKyberProxy {
function swapTokenToEther(
IERC20 token,
uint256 srcAmount,
uint256 minConversionRate
) external returns (uint256 destAmount);
function swapEtherToToken(IERC20 token, uint256 minConversionRate)
external
payable
returns (uint256 destAmount);
function swapTokenToToken(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
uint256 minConversionRate
) external returns (uint256 destAmount);
}
// File: contracts/sol6/IKyberReserve.sol
pragma solidity 0.6.6;
interface IKyberReserve {
function trade(
IERC20 srcToken,
uint256 srcAmount,
IERC20 destToken,
address payable destAddress,
uint256 conversionRate,
bool validate
) external payable returns (bool);
function getConversionRate(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 blockNumber
) external view returns (uint256);
}
// File: contracts/sol6/IKyberHint.sol
pragma solidity 0.6.6;
interface IKyberHint {
enum TradeType {BestOfAll, MaskIn, MaskOut, Split}
enum HintErrors {
NoError, // Hint is valid
NonEmptyDataError, // reserveIDs and splits must be empty for BestOfAll hint
ReserveIdDupError, // duplicate reserveID found
ReserveIdEmptyError, // reserveIDs array is empty for MaskIn and Split trade type
ReserveIdSplitsError, // reserveIDs and splitBpsValues arrays do not have the same length
ReserveIdSequenceError, // reserveID sequence in array is not in increasing order
ReserveIdNotFound, // reserveID isn't registered or doesn't exist
SplitsNotEmptyError, // splitBpsValues is not empty for MaskIn or MaskOut trade type
TokenListedError, // reserveID not listed for the token
TotalBPSError // total BPS for Split trade type is not 10000 (100%)
}
function buildTokenToEthHint(
IERC20 tokenSrc,
TradeType tokenToEthType,
bytes32[] calldata tokenToEthReserveIds,
uint256[] calldata tokenToEthSplits
) external view returns (bytes memory hint);
function buildEthToTokenHint(
IERC20 tokenDest,
TradeType ethToTokenType,
bytes32[] calldata ethToTokenReserveIds,
uint256[] calldata ethToTokenSplits
) external view returns (bytes memory hint);
function buildTokenToTokenHint(
IERC20 tokenSrc,
TradeType tokenToEthType,
bytes32[] calldata tokenToEthReserveIds,
uint256[] calldata tokenToEthSplits,
IERC20 tokenDest,
TradeType ethToTokenType,
bytes32[] calldata ethToTokenReserveIds,
uint256[] calldata ethToTokenSplits
) external view returns (bytes memory hint);
function parseTokenToEthHint(IERC20 tokenSrc, bytes calldata hint)
external
view
returns (
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
IKyberReserve[] memory tokenToEthAddresses,
uint256[] memory tokenToEthSplits
);
function parseEthToTokenHint(IERC20 tokenDest, bytes calldata hint)
external
view
returns (
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
IKyberReserve[] memory ethToTokenAddresses,
uint256[] memory ethToTokenSplits
);
function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes calldata hint)
external
view
returns (
TradeType tokenToEthType,
bytes32[] memory tokenToEthReserveIds,
IKyberReserve[] memory tokenToEthAddresses,
uint256[] memory tokenToEthSplits,
TradeType ethToTokenType,
bytes32[] memory ethToTokenReserveIds,
IKyberReserve[] memory ethToTokenAddresses,
uint256[] memory ethToTokenSplits
);
}
// File: contracts/sol6/KyberNetworkProxy.sol
pragma solidity 0.6.6;
/**
* @title kyberProxy for kyberNetwork contract
* The contract provides the following functions:
* - Get rates
* - Trade execution
* - Simple T2E, E2T and T2T trade APIs
* - Has some checks in place to safeguard takers
*/
contract KyberNetworkProxy is
IKyberNetworkProxy,
ISimpleKyberProxy,
WithdrawableNoModifiers,
Utils5
{
using SafeERC20 for IERC20;
IKyberNetwork public kyberNetwork;
IKyberHint public kyberHintHandler; // kyberHintHhandler pointer for users.
event KyberNetworkSet(IKyberNetwork newKyberNetwork, IKyberNetwork previousKyberNetwork);
event KyberHintHandlerSet(IKyberHint kyberHintHandler);
constructor(address _admin) public WithdrawableNoModifiers(_admin) {
/*empty body*/
}
/// @notice Backward compatible function
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param platformWallet Platform wallet address for receiving fees
/// @return Amount of actual dest tokens in twei
function trade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet
) external payable override returns (uint256) {
bytes memory hint;
return
doTrade(
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
platformWallet,
0,
hint
);
}
/// @notice Backward compatible function
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param walletId Platform wallet address for receiving fees
/// @param hint Advanced instructions for running the trade
/// @return Amount of actual dest tokens in twei
function tradeWithHint(
ERC20 src,
uint256 srcAmount,
ERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable walletId,
bytes calldata hint
) external payable override returns (uint256) {
return
doTrade(
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
walletId,
0,
hint
);
}
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Trade from src to dest token and sends dest token to destAddress
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param destAddress Address to send tokens to
/// @param maxDestAmount A limit on the amount of dest tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @param platformWallet Platform wallet address for receiving fees
/// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 10000 = 100%, 100 = 1%
/// @param hint Advanced instructions for running the trade
/// @return destAmount Amount of actual dest tokens in twei
function tradeWithHintAndFee(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes calldata hint
) external payable override returns (uint256 destAmount) {
return
doTrade(
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
platformWallet,
platformFeeBps,
hint
);
}
/// @dev Trade from src to dest token. Sends dest tokens to msg sender
/// @param src Source token
/// @param srcAmount Amount of src tokens in twei
/// @param dest Destination token
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @return Amount of actual dest tokens in twei
function swapTokenToToken(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
uint256 minConversionRate
) external override returns (uint256) {
bytes memory hint;
return
doTrade(
src,
srcAmount,
dest,
msg.sender,
MAX_QTY,
minConversionRate,
address(0),
0,
hint
);
}
/// @dev Trade from eth -> token. Sends token to msg sender
/// @param token Destination token
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @return Amount of actual dest tokens in twei
function swapEtherToToken(IERC20 token, uint256 minConversionRate)
external
payable
override
returns (uint256)
{
bytes memory hint;
return
doTrade(
ETH_TOKEN_ADDRESS,
msg.value,
token,
msg.sender,
MAX_QTY,
minConversionRate,
address(0),
0,
hint
);
}
/// @dev Trade from token -> eth. Sends eth to msg sender
/// @param token Source token
/// @param srcAmount Amount of src tokens in twei
/// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts
/// @return Amount of actual dest tokens in twei
function swapTokenToEther(
IERC20 token,
uint256 srcAmount,
uint256 minConversionRate
) external override returns (uint256) {
bytes memory hint;
return
doTrade(
token,
srcAmount,
ETH_TOKEN_ADDRESS,
msg.sender,
MAX_QTY,
minConversionRate,
address(0),
0,
hint
);
}
function setKyberNetwork(IKyberNetwork _kyberNetwork) external {
onlyAdmin();
require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0");
emit KyberNetworkSet(_kyberNetwork, kyberNetwork);
kyberNetwork = _kyberNetwork;
}
function setHintHandler(IKyberHint _kyberHintHandler) external {
onlyAdmin();
require(_kyberHintHandler != IKyberHint(0), "kyberHintHandler 0");
emit KyberHintHandlerSet(_kyberHintHandler);
kyberHintHandler = _kyberHintHandler;
}
/// @notice Backward compatible function
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Get expected rate for a trade from src to dest tokens, with amount srcQty (no platform fee)
/// @param src Source token
/// @param dest Destination token
/// @param srcQty Amount of src tokens in twei
/// @return expectedRate for a trade after deducting network fee. Rate = destQty (twei) / srcQty (twei) * 10 ** 18
/// @return worstRate for a trade. Usually expectedRate * 97 / 100
/// Use worstRate value as trade min conversion rate at your own risk
function getExpectedRate(
ERC20 src,
ERC20 dest,
uint256 srcQty
) external view override returns (uint256 expectedRate, uint256 worstRate) {
bytes memory hint;
(expectedRate, ) = kyberNetwork.getExpectedRateWithHintAndFee(
src,
dest,
srcQty,
0,
hint
);
// use simple backward compatible optoin.
worstRate = (expectedRate * 97) / 100;
}
/// @notice Use token address ETH_TOKEN_ADDRESS for ether
/// @dev Get expected rate for a trade from src to dest tokens, amount srcQty and fees
/// @param src Source token
/// @param dest Destination token
/// @param srcQty Amount of src tokens in twei
/// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 10000 = 100%, 100 = 1%
/// @param hint Advanced instructions for running the trade
/// @return expectedRate for a trade after deducting network + platform fee
/// Rate = destQty (twei) / srcQty (twei) * 10 ** 18
function getExpectedRateAfterFee(
IERC20 src,
IERC20 dest,
uint256 srcQty,
uint256 platformFeeBps,
bytes calldata hint
) external view override returns (uint256 expectedRate) {
(, expectedRate) = kyberNetwork.getExpectedRateWithHintAndFee(
src,
dest,
srcQty,
platformFeeBps,
hint
);
}
function maxGasPrice() external view returns (uint256) {
return kyberNetwork.maxGasPrice();
}
function enabled() external view returns (bool) {
return kyberNetwork.enabled();
}
/// helper structure for function doTrade
struct UserBalance {
uint256 srcTok;
uint256 destTok;
}
function doTrade(
IERC20 src,
uint256 srcAmount,
IERC20 dest,
address payable destAddress,
uint256 maxDestAmount,
uint256 minConversionRate,
address payable platformWallet,
uint256 platformFeeBps,
bytes memory hint
) internal returns (uint256) {
UserBalance memory balanceBefore = prepareTrade(src, dest, srcAmount, destAddress);
uint256 reportedDestAmount = kyberNetwork.tradeWithHintAndFee{value: msg.value}(
msg.sender,
src,
srcAmount,
dest,
destAddress,
maxDestAmount,
minConversionRate,
platformWallet,
platformFeeBps,
hint
);
TradeOutcome memory tradeOutcome = calculateTradeOutcome(
src,
dest,
destAddress,
platformFeeBps,
balanceBefore
);
require(
tradeOutcome.userDeltaDestToken == reportedDestAmount,
"kyberNetwork returned wrong amount"
);
require(
tradeOutcome.userDeltaDestToken <= maxDestAmount,
"actual dest amount exceeds maxDestAmount"
);
require(tradeOutcome.actualRate >= minConversionRate, "rate below minConversionRate");
emit ExecuteTrade(
msg.sender,
src,
dest,
destAddress,
tradeOutcome.userDeltaSrcToken,
tradeOutcome.userDeltaDestToken,
platformWallet,
platformFeeBps
);
return tradeOutcome.userDeltaDestToken;
}
/// helper structure for function prepareTrade
struct TradeOutcome {
uint256 userDeltaSrcToken;
uint256 userDeltaDestToken;
uint256 actualRate;
}
function prepareTrade(
IERC20 src,
IERC20 dest,
uint256 srcAmount,
address destAddress
) internal returns (UserBalance memory balanceBefore) {
if (src == ETH_TOKEN_ADDRESS) {
require(msg.value == srcAmount, "sent eth not equal to srcAmount");
} else {
require(msg.value == 0, "sent eth not 0");
}
balanceBefore.srcTok = getBalance(src, msg.sender);
balanceBefore.destTok = getBalance(dest, destAddress);
if (src == ETH_TOKEN_ADDRESS) {
balanceBefore.srcTok += msg.value;
} else {
src.safeTransferFrom(msg.sender, address(kyberNetwork), srcAmount);
}
}
function calculateTradeOutcome(
IERC20 src,
IERC20 dest,
address destAddress,
uint256 platformFeeBps,
UserBalance memory balanceBefore
) internal returns (TradeOutcome memory outcome) {
uint256 srcTokenBalanceAfter;
uint256 destTokenBalanceAfter;
srcTokenBalanceAfter = getBalance(src, msg.sender);
destTokenBalanceAfter = getBalance(dest, destAddress);
//protect from underflow
require(
destTokenBalanceAfter > balanceBefore.destTok,
"wrong amount in destination address"
);
require(balanceBefore.srcTok > srcTokenBalanceAfter, "wrong amount in source address");
outcome.userDeltaSrcToken = balanceBefore.srcTok - srcTokenBalanceAfter;
outcome.userDeltaDestToken = destTokenBalanceAfter - balanceBefore.destTok;
// what would be the src amount after deducting platformFee
// not protecting from platform fee
uint256 srcTokenAmountAfterDeductingFee = (outcome.userDeltaSrcToken *
(BPS - platformFeeBps)) / BPS;
outcome.actualRate = calcRateFromQty(
srcTokenAmountAfterDeductingFee,
outcome.userDeltaDestToken,
getUpdateDecimals(src),
getUpdateDecimals(dest)
);
}
}File 2 of 11: WETH9
// Copyright (C) 2015, 2016, 2017 Dapphub
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity ^0.4.18;
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;
event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;
function() public payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
msg.sender.transfer(wad);
Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return this.balance;
}
function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
Approval(msg.sender, guy, wad);
return true;
}
function transfer(address dst, uint wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(address src, address dst, uint wad)
public
returns (bool)
{
require(balanceOf[src] >= wad);
if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
Transfer(src, dst, wad);
return true;
}
}
/*
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
*/File 3 of 11: UniswapV2Router02
pragma solidity =0.6.6;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
interface IUniswapV2Router01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB);
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountToken, uint amountETH);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
returns (uint[] memory amounts);
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
payable
returns (uint[] memory amounts);
function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external;
}
interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
}
interface IWETH {
function deposit() external payable;
function transfer(address to, uint value) external returns (bool);
function withdraw(uint) external;
}
contract UniswapV2Router02 is IUniswapV2Router02 {
using SafeMath for uint;
address public immutable override factory;
address public immutable override WETH;
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}
// **** ADD LIQUIDITY ****
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal virtual returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to);
}
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
assert(IWETH(WETH).transfer(pair, amountETH));
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
}
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountToken, uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
// **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH) {
(, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token, liquidity, amountTokenMin, amountETHMin, to, deadline
);
}
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
// refund dust eth, if any
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
}
// **** SWAP (supporting fee-on-transfer tokens) ****
// requires the initial amount to have already been sent to the first pair
function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
uint amountInput;
uint amountOutput;
{ // scope to avoid stack too deep errors
(uint reserve0, uint reserve1,) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
}
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
pair.swap(amount0Out, amount1Out, to, new bytes(0));
}
}
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) {
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
payable
ensure(deadline)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
uint amountIn = msg.value;
IWETH(WETH).deposit{value: amountIn}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint amountOut = IERC20(WETH).balanceOf(address(this));
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).withdraw(amountOut);
TransferHelper.safeTransferETH(to, amountOut);
}
// **** LIBRARY FUNCTIONS ****
function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountIn)
{
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint amountIn, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(uint amountOut, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
library SafeMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x, 'ds-math-add-overflow');
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x, 'ds-math-sub-underflow');
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
}
}
library UniswapV2Library {
using SafeMath for uint;
// returns sorted token addresses, used to handle return values from pairs sorted in this order
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}
// calculates the CREATE2 address for a pair without making any external calls
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
}
// fetches and sorts the reserves for a pair
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}
// performs chained getAmountOut calculations on any number of pairs
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
// performs chained getAmountIn calculations on any number of pairs
function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
}
}
}
// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(address token, address to, uint value) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');
}
function safeTransfer(address token, address to, uint value) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
}
function safeTransferFrom(address token, address from, address to, uint value) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
}
function safeTransferETH(address to, uint value) internal {
(bool success,) = to.call{value:value}(new bytes(0));
require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
}
}File 4 of 11: UniswapV2Pair
// File: contracts/interfaces/IUniswapV2Pair.sol
pragma solidity >=0.5.0;
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint);
function price1CumulativeLast() external view returns (uint);
function kLast() external view returns (uint);
function mint(address to) external returns (uint liquidity);
function burn(address to) external returns (uint amount0, uint amount1);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
// File: contracts/interfaces/IUniswapV2ERC20.sol
pragma solidity >=0.5.0;
interface IUniswapV2ERC20 {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint);
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
}
// File: contracts/libraries/SafeMath.sol
pragma solidity =0.5.16;
// a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math)
library SafeMath {
function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x, 'ds-math-add-overflow');
}
function sub(uint x, uint y) internal pure returns (uint z) {
require((z = x - y) <= x, 'ds-math-sub-underflow');
}
function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow');
}
}
// File: contracts/UniswapV2ERC20.sol
pragma solidity =0.5.16;
contract UniswapV2ERC20 is IUniswapV2ERC20 {
using SafeMath for uint;
string public constant name = 'Uniswap V2';
string public constant symbol = 'UNI-V2';
uint8 public constant decimals = 18;
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public nonces;
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
constructor() public {
uint chainId;
assembly {
chainId := chainid
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}
function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}
function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(from, address(0), value);
}
function _approve(address owner, address spender, uint value) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}
function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint value) external returns (bool) {
if (allowance[from][msg.sender] != uint(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}
_transfer(from, to, value);
return true;
}
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
}
// File: contracts/libraries/Math.sol
pragma solidity =0.5.16;
// a library for performing various math operations
library Math {
function min(uint x, uint y) internal pure returns (uint z) {
z = x < y ? x : y;
}
// babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}
// File: contracts/libraries/UQ112x112.sol
pragma solidity =0.5.16;
// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format))
// range: [0, 2**112 - 1]
// resolution: 1 / 2**112
library UQ112x112 {
uint224 constant Q112 = 2**112;
// encode a uint112 as a UQ112x112
function encode(uint112 y) internal pure returns (uint224 z) {
z = uint224(y) * Q112; // never overflows
}
// divide a UQ112x112 by a uint112, returning a UQ112x112
function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
z = x / uint224(y);
}
}
// File: contracts/interfaces/IERC20.sol
pragma solidity >=0.5.0;
interface IERC20 {
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address owner) external view returns (uint);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint value) external returns (bool);
function transfer(address to, uint value) external returns (bool);
function transferFrom(address from, address to, uint value) external returns (bool);
}
// File: contracts/interfaces/IUniswapV2Factory.sol
pragma solidity >=0.5.0;
interface IUniswapV2Factory {
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
function feeTo() external view returns (address);
function feeToSetter() external view returns (address);
function getPair(address tokenA, address tokenB) external view returns (address pair);
function allPairs(uint) external view returns (address pair);
function allPairsLength() external view returns (uint);
function createPair(address tokenA, address tokenB) external returns (address pair);
function setFeeTo(address) external;
function setFeeToSetter(address) external;
}
// File: contracts/interfaces/IUniswapV2Callee.sol
pragma solidity >=0.5.0;
interface IUniswapV2Callee {
function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external;
}
// File: contracts/UniswapV2Pair.sol
pragma solidity =0.5.16;
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
using SafeMath for uint;
using UQ112x112 for uint224;
uint public constant MINIMUM_LIQUIDITY = 10**3;
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
address public factory;
address public token0;
address public token1;
uint112 private reserve0; // uses single storage slot, accessible via getReserves
uint112 private reserve1; // uses single storage slot, accessible via getReserves
uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves
uint public price0CumulativeLast;
uint public price1CumulativeLast;
uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
uint private unlocked = 1;
modifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
function _safeTransfer(address token, address to, uint value) private {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
constructor() public {
factory = msg.sender;
}
// called once by the factory at time of deployment
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
token0 = _token0;
token1 = _token1;
}
// update reserves and, on the first call per block, price accumulators
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
// if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
address feeTo = IUniswapV2Factory(factory).feeTo();
feeOn = feeTo != address(0);
uint _kLast = kLast; // gas savings
if (feeOn) {
if (_kLast != 0) {
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
uint denominator = rootK.mul(5).add(rootKLast);
uint liquidity = numerator / denominator;
if (liquidity > 0) _mint(feeTo, liquidity);
}
}
} else if (_kLast != 0) {
kLast = 0;
}
}
// this low-level function should be called from a contract which performs important safety checks
function mint(address to) external lock returns (uint liquidity) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity);
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}
// this low-level function should be called from a contract which performs important safety checks
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}
// this low-level function should be called from a contract which performs important safety checks
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
// force balances to match reserves
function skim(address to) external lock {
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}
// force reserves to match balances
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}File 5 of 11: 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 6 of 11: BZRXToken
/**
* Copyright 2017-2020, bZeroX, LLC <https://bzx.network/>. All Rights Reserved.
* Licensed under the Apache License, Version 2.0.
*/
pragma solidity 0.5.17;
contract IERC20 {
string public name;
uint8 public decimals;
string public symbol;
function totalSupply() public view returns (uint256);
function balanceOf(address _who) public view returns (uint256);
function allowance(address _owner, address _spender) public view returns (uint256);
function approve(address _spender, uint256 _value) public returns (bool);
function transfer(address _to, uint256 _value) public returns (bool);
function transferFrom(address _from, address _to, uint256 _value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* Copyright (C) 2019 Aragon One <https://aragon.one/>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @title Checkpointing
* @notice Checkpointing library for keeping track of historical values based on an arbitrary time
* unit (e.g. seconds or block numbers).
* @dev Adapted from:
* - Checkpointing (https://github.com/aragonone/voting-connectors/blob/master/shared/contract-utils/contracts/Checkpointing.sol)
*/
library Checkpointing {
struct Checkpoint {
uint256 time;
uint256 value;
}
struct History {
Checkpoint[] history;
}
function addCheckpoint(
History storage _self,
uint256 _time,
uint256 _value)
internal
{
uint256 length = _self.history.length;
if (length == 0) {
_self.history.push(Checkpoint(_time, _value));
} else {
Checkpoint storage currentCheckpoint = _self.history[length - 1];
uint256 currentCheckpointTime = currentCheckpoint.time;
if (_time > currentCheckpointTime) {
_self.history.push(Checkpoint(_time, _value));
} else if (_time == currentCheckpointTime) {
currentCheckpoint.value = _value;
} else { // ensure list ordering
revert("past-checkpoint");
}
}
}
function getValueAt(
History storage _self,
uint256 _time)
internal
view
returns (uint256)
{
return _getValueAt(_self, _time);
}
function lastUpdated(
History storage _self)
internal
view
returns (uint256)
{
uint256 length = _self.history.length;
if (length != 0) {
return _self.history[length - 1].time;
}
}
function latestValue(
History storage _self)
internal
view
returns (uint256)
{
uint256 length = _self.history.length;
if (length != 0) {
return _self.history[length - 1].value;
}
}
function _getValueAt(
History storage _self,
uint256 _time)
private
view
returns (uint256)
{
uint256 length = _self.history.length;
// Short circuit if there's no checkpoints yet
// Note that this also lets us avoid using SafeMath later on, as we've established that
// there must be at least one checkpoint
if (length == 0) {
return 0;
}
// Check last checkpoint
uint256 lastIndex = length - 1;
Checkpoint storage lastCheckpoint = _self.history[lastIndex];
if (_time >= lastCheckpoint.time) {
return lastCheckpoint.value;
}
// Check first checkpoint (if not already checked with the above check on last)
if (length == 1 || _time < _self.history[0].time) {
return 0;
}
// Do binary search
// As we've already checked both ends, we don't need to check the last checkpoint again
uint256 low = 0;
uint256 high = lastIndex - 1;
while (high != low) {
uint256 mid = (high + low + 1) / 2; // average, ceil round
Checkpoint storage checkpoint = _self.history[mid];
uint256 midTime = checkpoint.time;
if (_time > midTime) {
low = mid;
} else if (_time < midTime) {
// Note that we don't need SafeMath here because mid must always be greater than 0
// from the while condition
high = mid - 1;
} else {
// _time == midTime
return checkpoint.value;
}
}
return _self.history[low].value;
}
}
contract CheckpointingToken is IERC20 {
using Checkpointing for Checkpointing.History;
mapping (address => mapping (address => uint256)) internal allowances_;
mapping (address => Checkpointing.History) internal balancesHistory_;
struct Checkpoint {
uint256 time;
uint256 value;
}
struct History {
Checkpoint[] history;
}
// override this function if a totalSupply should be tracked
function totalSupply()
public
view
returns (uint256)
{
return 0;
}
function balanceOf(
address _owner)
public
view
returns (uint256)
{
return balanceOfAt(_owner, block.number);
}
function balanceOfAt(
address _owner,
uint256 _blockNumber)
public
view
returns (uint256)
{
return balancesHistory_[_owner].getValueAt(_blockNumber);
}
function allowance(
address _owner,
address _spender)
public
view
returns (uint256)
{
return allowances_[_owner][_spender];
}
function approve(
address _spender,
uint256 _value)
public
returns (bool)
{
allowances_[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transfer(
address _to,
uint256 _value)
public
returns (bool)
{
return transferFrom(
msg.sender,
_to,
_value
);
}
function transferFrom(
address _from,
address _to,
uint256 _value)
public
returns (bool)
{
uint256 previousBalanceFrom = balanceOfAt(_from, block.number);
require(previousBalanceFrom >= _value, "insufficient-balance");
if (_from != msg.sender && allowances_[_from][msg.sender] != uint(-1)) {
require(allowances_[_from][msg.sender] >= _value, "insufficient-allowance");
allowances_[_from][msg.sender] = allowances_[_from][msg.sender] - _value; // overflow not possible
}
balancesHistory_[_from].addCheckpoint(
block.number,
previousBalanceFrom - _value // overflow not possible
);
balancesHistory_[_to].addCheckpoint(
block.number,
add(
balanceOfAt(_to, block.number),
_value
)
);
emit Transfer(_from, _to, _value);
return true;
}
function _getBlockNumber()
internal
view
returns (uint256)
{
return block.number;
}
function _getTimestamp()
internal
view
returns (uint256)
{
return block.timestamp;
}
function add(
uint256 x,
uint256 y)
internal
pure
returns (uint256 c)
{
require((c = x + y) >= x, "addition-overflow");
}
function sub(
uint256 x,
uint256 y)
internal
pure
returns (uint256 c)
{
require((c = x - y) <= x, "subtraction-overflow");
}
function mul(
uint256 a,
uint256 b)
internal
pure
returns (uint256 c)
{
if (a == 0) {
return 0;
}
require((c = a * b) / a == b, "multiplication-overflow");
}
function div(
uint256 a,
uint256 b)
internal
pure
returns (uint256 c)
{
require(b != 0, "division by zero");
c = a / b;
}
}
contract BZRXToken is CheckpointingToken {
string public constant name = "bZx Protocol Token";
string public constant symbol = "BZRX";
uint8 public constant decimals = 18;
uint256 internal constant totalSupply_ = 1030000000e18; // 1,030,000,000 BZRX
constructor(
address _to)
public
{
balancesHistory_[_to].addCheckpoint(_getBlockNumber(), totalSupply_);
emit Transfer(address(0), _to, totalSupply_);
}
function totalSupply()
public
view
returns (uint256)
{
return totalSupply_;
}
}File 7 of 11: 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 8 of 11: 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 9 of 11: 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 10 of 11: KyberReserve
// File: contracts/ERC20Interface.sol
pragma solidity 0.4.18;
// https://github.com/ethereum/EIPs/issues/20
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
// File: contracts/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/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/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/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/SanityRatesInterface.sol
pragma solidity 0.4.18;
interface SanityRatesInterface {
function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint);
}
// File: contracts/KyberReserveInterface.sol
pragma solidity 0.4.18;
/// @title Kyber Reserve contract
interface KyberReserveInterface {
function trade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
public
payable
returns(bool);
function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint);
}
// File: contracts/reserves/KyberReserve.sol
pragma solidity 0.4.18;
/// @title Kyber Reserve contract
contract KyberReserve is KyberReserveInterface, Withdrawable, Utils {
address public kyberNetwork;
bool public tradeEnabled;
ConversionRatesInterface public conversionRatesContract;
SanityRatesInterface public sanityRatesContract;
mapping(bytes32=>bool) public approvedWithdrawAddresses; // sha3(token,address)=>bool
mapping(address=>address) public tokenWallet;
function KyberReserve(address _kyberNetwork, ConversionRatesInterface _ratesContract, address _admin) public {
require(_admin != address(0));
require(_ratesContract != address(0));
require(_kyberNetwork != address(0));
kyberNetwork = _kyberNetwork;
conversionRatesContract = _ratesContract;
admin = _admin;
tradeEnabled = true;
}
event DepositToken(ERC20 token, uint amount);
function() public payable {
DepositToken(ETH_TOKEN_ADDRESS, msg.value);
}
event TradeExecute(
address indexed origin,
address src,
uint srcAmount,
address destToken,
uint destAmount,
address destAddress
);
function trade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
public
payable
returns(bool)
{
require(tradeEnabled);
require(msg.sender == kyberNetwork);
require(doTrade(srcToken, srcAmount, destToken, destAddress, conversionRate, validate));
return true;
}
event TradeEnabled(bool enable);
function enableTrade() public onlyAdmin returns(bool) {
tradeEnabled = true;
TradeEnabled(true);
return true;
}
function disableTrade() public onlyAlerter returns(bool) {
tradeEnabled = false;
TradeEnabled(false);
return true;
}
event WithdrawAddressApproved(ERC20 token, address addr, bool approve);
function approveWithdrawAddress(ERC20 token, address addr, bool approve) public onlyAdmin {
approvedWithdrawAddresses[keccak256(token, addr)] = approve;
WithdrawAddressApproved(token, addr, approve);
setDecimals(token);
if ((tokenWallet[token] == address(0x0)) && (token != ETH_TOKEN_ADDRESS)) {
tokenWallet[token] = this; // by default
require(token.approve(this, 2 ** 255));
}
}
event NewTokenWallet(ERC20 token, address wallet);
function setTokenWallet(ERC20 token, address wallet) public onlyAdmin {
require(wallet != address(0x0));
tokenWallet[token] = wallet;
NewTokenWallet(token, wallet);
}
event WithdrawFunds(ERC20 token, uint amount, address destination);
function withdraw(ERC20 token, uint amount, address destination) public onlyOperator returns(bool) {
require(approvedWithdrawAddresses[keccak256(token, destination)]);
if (token == ETH_TOKEN_ADDRESS) {
destination.transfer(amount);
} else {
require(token.transferFrom(tokenWallet[token], destination, amount));
}
WithdrawFunds(token, amount, destination);
return true;
}
event SetContractAddresses(address network, address rate, address sanity);
function setContracts(
address _kyberNetwork,
ConversionRatesInterface _conversionRates,
SanityRatesInterface _sanityRates
)
public
onlyAdmin
{
require(_kyberNetwork != address(0));
require(_conversionRates != address(0));
kyberNetwork = _kyberNetwork;
conversionRatesContract = _conversionRates;
sanityRatesContract = _sanityRates;
SetContractAddresses(kyberNetwork, conversionRatesContract, sanityRatesContract);
}
////////////////////////////////////////////////////////////////////////////
/// status functions ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
function getBalance(ERC20 token) public view returns(uint) {
if (token == ETH_TOKEN_ADDRESS)
return this.balance;
else {
address wallet = tokenWallet[token];
uint balanceOfWallet = token.balanceOf(wallet);
uint allowanceOfWallet = token.allowance(wallet, this);
return (balanceOfWallet < allowanceOfWallet) ? balanceOfWallet : allowanceOfWallet;
}
}
function getDestQty(ERC20 src, ERC20 dest, uint srcQty, uint rate) public view returns(uint) {
uint dstDecimals = getDecimals(dest);
uint srcDecimals = getDecimals(src);
return calcDstQty(srcQty, srcDecimals, dstDecimals, rate);
}
function getSrcQty(ERC20 src, ERC20 dest, uint dstQty, uint rate) public view returns(uint) {
uint dstDecimals = getDecimals(dest);
uint srcDecimals = getDecimals(src);
return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate);
}
function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint) {
ERC20 token;
bool isBuy;
if (!tradeEnabled) return 0;
if (ETH_TOKEN_ADDRESS == src) {
isBuy = true;
token = dest;
} else if (ETH_TOKEN_ADDRESS == dest) {
isBuy = false;
token = src;
} else {
return 0; // pair is not listed
}
uint rate = conversionRatesContract.getRate(token, blockNumber, isBuy, srcQty);
uint destQty = getDestQty(src, dest, srcQty, rate);
if (getBalance(dest) < destQty) return 0;
if (sanityRatesContract != address(0)) {
uint sanityRate = sanityRatesContract.getSanityRate(src, dest);
if (rate > sanityRate) return 0;
}
return rate;
}
/// @dev do a trade
/// @param srcToken Src token
/// @param srcAmount Amount of src token
/// @param destToken Destination token
/// @param destAddress Destination address to send tokens to
/// @param validate If true, additional validations are applicable
/// @return true iff trade is successful
function doTrade(
ERC20 srcToken,
uint srcAmount,
ERC20 destToken,
address destAddress,
uint conversionRate,
bool validate
)
internal
returns(bool)
{
// can skip validation if done at kyber network level
if (validate) {
require(conversionRate > 0);
if (srcToken == ETH_TOKEN_ADDRESS)
require(msg.value == srcAmount);
else
require(msg.value == 0);
}
uint destAmount = getDestQty(srcToken, destToken, srcAmount, conversionRate);
// sanity check
require(destAmount > 0);
// add to imbalance
ERC20 token;
int tradeAmount;
if (srcToken == ETH_TOKEN_ADDRESS) {
tradeAmount = int(destAmount);
token = destToken;
} else {
tradeAmount = -1 * int(srcAmount);
token = srcToken;
}
conversionRatesContract.recordImbalance(
token,
tradeAmount,
0,
block.number
);
// collect src tokens
if (srcToken != ETH_TOKEN_ADDRESS) {
require(srcToken.transferFrom(msg.sender, tokenWallet[srcToken], srcAmount));
}
// send dest tokens
if (destToken == ETH_TOKEN_ADDRESS) {
destAddress.transfer(destAmount);
} else {
require(destToken.transferFrom(tokenWallet[destToken], destAddress, destAmount));
}
TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress);
return true;
}
}File 11 of 11: LiquidityConversionRates
{"ConversionRatesInterface.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\ninterface ConversionRatesInterface {\n\n function recordImbalance(\n ERC20 token,\n int buyAmount,\n uint rateUpdateBlock,\n uint currentBlock\n )\n public;\n\n function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);\n}\n"},"ERC20Interface.sol":{"content":"pragma solidity 0.4.18;\n\n\n// https://github.com/ethereum/EIPs/issues/20\ninterface ERC20 {\n function totalSupply() public view returns (uint supply);\n function balanceOf(address _owner) public view returns (uint balance);\n function transfer(address _to, uint _value) public returns (bool success);\n function transferFrom(address _from, address _to, uint _value) public returns (bool success);\n function approve(address _spender, uint _value) public returns (bool success);\n function allowance(address _owner, address _spender) public view returns (uint remaining);\n function decimals() public view returns(uint digits);\n event Approval(address indexed _owner, address indexed _spender, uint _value);\n}\n"},"LiquidityConversionRates.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"../../ConversionRatesInterface.sol\";\nimport \"../../Withdrawable.sol\";\nimport \"../../Utils.sol\";\nimport \"./LiquidityFormula.sol\";\n\n\ncontract LiquidityConversionRates is ConversionRatesInterface, LiquidityFormula, Withdrawable, Utils {\n\n uint constant FORMULA_PRECISION_BITS = 40;\n\n ERC20 public token;\n address public reserveContract;\n\n uint public numFpBits;\n uint public formulaPrecision;\n\n uint public rInFp;\n uint public pMinInFp;\n\n uint public maxEthCapBuyInFp;\n uint public maxEthCapSellInFp;\n uint public maxQtyInFp;\n\n uint public feeInBps;\n uint public collectedFeesInTwei = 0;\n\n uint public maxBuyRateInPrecision;\n uint public minBuyRateInPrecision;\n uint public maxSellRateInPrecision;\n uint public minSellRateInPrecision;\n\n function LiquidityConversionRates(address _admin, ERC20 _token) public {\n transferAdminQuickly(_admin);\n token = _token;\n setDecimals(token);\n require(getDecimals(token) \u003c= MAX_DECIMALS);\n }\n\n event ReserveAddressSet(address reserve);\n\n function setReserveAddress(address reserve) public onlyAdmin {\n reserveContract = reserve;\n ReserveAddressSet(reserve);\n }\n\n event LiquidityParamsSet(\n uint rInFp,\n uint pMinInFp,\n uint numFpBits,\n uint maxCapBuyInFp,\n uint maxEthCapSellInFp,\n uint feeInBps,\n uint formulaPrecision,\n uint maxQtyInFp,\n uint maxBuyRateInPrecision,\n uint minBuyRateInPrecision,\n uint maxSellRateInPrecision,\n uint minSellRateInPrecision\n );\n\n function setLiquidityParams(\n uint _rInFp,\n uint _pMinInFp,\n uint _numFpBits,\n uint _maxCapBuyInWei,\n uint _maxCapSellInWei,\n uint _feeInBps,\n uint _maxTokenToEthRateInPrecision,\n uint _minTokenToEthRateInPrecision\n ) public onlyAdmin {\n require(_numFpBits == FORMULA_PRECISION_BITS); // only used config, but keep in API\n formulaPrecision = uint(1)\u003c\u003c_numFpBits; // require(formulaPrecision \u003c= MAX_QTY)\n require(_feeInBps \u003c 10000);\n require(_minTokenToEthRateInPrecision \u003c _maxTokenToEthRateInPrecision);\n require(_minTokenToEthRateInPrecision \u003e 0);\n require(_rInFp \u003e 0);\n require(_pMinInFp \u003e 0);\n\n rInFp = _rInFp;\n pMinInFp = _pMinInFp;\n maxQtyInFp = fromWeiToFp(MAX_QTY);\n numFpBits = _numFpBits;\n maxEthCapBuyInFp = fromWeiToFp(_maxCapBuyInWei);\n maxEthCapSellInFp = fromWeiToFp(_maxCapSellInWei);\n feeInBps = _feeInBps;\n maxBuyRateInPrecision = PRECISION * PRECISION / _minTokenToEthRateInPrecision;\n minBuyRateInPrecision = PRECISION * PRECISION / _maxTokenToEthRateInPrecision;\n maxSellRateInPrecision = _maxTokenToEthRateInPrecision;\n minSellRateInPrecision = _minTokenToEthRateInPrecision;\n\n LiquidityParamsSet(\n rInFp,\n pMinInFp,\n numFpBits,\n maxEthCapBuyInFp,\n maxEthCapSellInFp,\n feeInBps,\n formulaPrecision,\n maxQtyInFp,\n maxBuyRateInPrecision,\n minBuyRateInPrecision,\n maxSellRateInPrecision,\n minSellRateInPrecision\n );\n }\n\n function recordImbalance(\n ERC20 conversionToken,\n int buyAmountInTwei,\n uint rateUpdateBlock,\n uint currentBlock\n )\n public\n {\n conversionToken;\n rateUpdateBlock;\n currentBlock;\n\n require(msg.sender == reserveContract);\n if (buyAmountInTwei \u003e 0) {\n // Buy case\n collectedFeesInTwei += calcCollectedFee(abs(buyAmountInTwei));\n } else {\n // Sell case\n collectedFeesInTwei += abs(buyAmountInTwei) * feeInBps / 10000;\n }\n }\n\n event CollectedFeesReset(uint resetFeesInTwei);\n\n function resetCollectedFees() public onlyAdmin {\n uint resetFeesInTwei = collectedFeesInTwei;\n collectedFeesInTwei = 0;\n\n CollectedFeesReset(resetFeesInTwei);\n }\n\n function getRate(\n ERC20 conversionToken,\n uint currentBlockNumber,\n bool buy,\n uint qtyInSrcWei\n ) public view returns(uint) {\n\n currentBlockNumber;\n\n require(qtyInSrcWei \u003c= MAX_QTY);\n uint eInFp = fromWeiToFp(reserveContract.balance);\n uint rateInPrecision = getRateWithE(conversionToken, buy, qtyInSrcWei, eInFp);\n require(rateInPrecision \u003c= MAX_RATE);\n return rateInPrecision;\n }\n\n function getRateWithE(ERC20 conversionToken, bool buy, uint qtyInSrcWei, uint eInFp) public view returns(uint) {\n uint deltaEInFp;\n uint sellInputTokenQtyInFp;\n uint deltaTInFp;\n uint rateInPrecision;\n\n require(qtyInSrcWei \u003c= MAX_QTY);\n require(eInFp \u003c= maxQtyInFp);\n if (conversionToken != token) return 0;\n\n if (buy) {\n // ETH goes in, token goes out\n deltaEInFp = fromWeiToFp(qtyInSrcWei);\n if (deltaEInFp \u003e maxEthCapBuyInFp) return 0;\n\n if (deltaEInFp == 0) {\n rateInPrecision = buyRateZeroQuantity(eInFp);\n } else {\n rateInPrecision = buyRate(eInFp, deltaEInFp);\n }\n } else {\n sellInputTokenQtyInFp = fromTweiToFp(qtyInSrcWei);\n deltaTInFp = valueAfterReducingFee(sellInputTokenQtyInFp);\n if (deltaTInFp == 0) {\n rateInPrecision = sellRateZeroQuantity(eInFp);\n deltaEInFp = 0;\n } else {\n (rateInPrecision, deltaEInFp) = sellRate(eInFp, sellInputTokenQtyInFp, deltaTInFp);\n }\n\n if (deltaEInFp \u003e maxEthCapSellInFp) return 0;\n }\n\n rateInPrecision = rateAfterValidation(rateInPrecision, buy);\n return rateInPrecision;\n }\n\n function rateAfterValidation(uint rateInPrecision, bool buy) public view returns(uint) {\n uint minAllowRateInPrecision;\n uint maxAllowedRateInPrecision;\n\n if (buy) {\n minAllowRateInPrecision = minBuyRateInPrecision;\n maxAllowedRateInPrecision = maxBuyRateInPrecision;\n } else {\n minAllowRateInPrecision = minSellRateInPrecision;\n maxAllowedRateInPrecision = maxSellRateInPrecision;\n }\n\n if ((rateInPrecision \u003e maxAllowedRateInPrecision) || (rateInPrecision \u003c minAllowRateInPrecision)) {\n return 0;\n } else if (rateInPrecision \u003e MAX_RATE) {\n return 0;\n } else {\n return rateInPrecision;\n }\n }\n\n function buyRate(uint eInFp, uint deltaEInFp) public view returns(uint) {\n uint deltaTInFp = deltaTFunc(rInFp, pMinInFp, eInFp, deltaEInFp, formulaPrecision);\n require(deltaTInFp \u003c= maxQtyInFp);\n deltaTInFp = valueAfterReducingFee(deltaTInFp);\n return deltaTInFp * PRECISION / deltaEInFp;\n }\n\n function buyRateZeroQuantity(uint eInFp) public view returns(uint) {\n uint ratePreReductionInPrecision = formulaPrecision * PRECISION / pE(rInFp, pMinInFp, eInFp, formulaPrecision);\n return valueAfterReducingFee(ratePreReductionInPrecision);\n }\n\n function sellRate(\n uint eInFp,\n uint sellInputTokenQtyInFp,\n uint deltaTInFp\n ) public view returns(uint rateInPrecision, uint deltaEInFp) {\n deltaEInFp = deltaEFunc(rInFp, pMinInFp, eInFp, deltaTInFp, formulaPrecision, numFpBits);\n require(deltaEInFp \u003c= maxQtyInFp);\n rateInPrecision = deltaEInFp * PRECISION / sellInputTokenQtyInFp;\n }\n\n function sellRateZeroQuantity(uint eInFp) public view returns(uint) {\n uint ratePreReductionInPrecision = pE(rInFp, pMinInFp, eInFp, formulaPrecision) * PRECISION / formulaPrecision;\n return valueAfterReducingFee(ratePreReductionInPrecision);\n }\n\n function fromTweiToFp(uint qtyInTwei) public view returns(uint) {\n require(qtyInTwei \u003c= MAX_QTY);\n return qtyInTwei * formulaPrecision / (10 ** getDecimals(token));\n }\n\n function fromWeiToFp(uint qtyInwei) public view returns(uint) {\n require(qtyInwei \u003c= MAX_QTY);\n return qtyInwei * formulaPrecision / (10 ** ETH_DECIMALS);\n }\n\n function valueAfterReducingFee(uint val) public view returns(uint) {\n require(val \u003c= BIG_NUMBER);\n return ((10000 - feeInBps) * val) / 10000;\n }\n\n function calcCollectedFee(uint val) public view returns(uint) {\n require(val \u003c= MAX_QTY);\n return val * feeInBps / (10000 - feeInBps);\n }\n\n function abs(int val) public pure returns(uint) {\n if (val \u003c 0) {\n return uint(val * (-1));\n } else {\n return uint(val);\n }\n }\n}\n"},"LiquidityFormula.sol":{"content":"pragma solidity 0.4.18;\n\n\ncontract UtilMath {\n uint public constant BIG_NUMBER = (uint(1)\u003c\u003cuint(200));\n\n function checkMultOverflow(uint x, uint y) public pure returns(bool) {\n if (y == 0) return false;\n return (((x*y) / y) != x);\n }\n\n function compactFraction(uint p, uint q, uint precision) public pure returns (uint, uint) {\n if (q \u003c precision * precision) return (p, q);\n return compactFraction(p/precision, q/precision, precision);\n }\n\n /* solhint-disable code-complexity */\n function exp(uint p, uint q, uint precision) public pure returns (uint) {\n uint n = 0;\n uint nFact = 1;\n uint currentP = 1;\n uint currentQ = 1;\n\n uint sum = 0;\n uint prevSum = 0;\n\n while (true) {\n if (checkMultOverflow(currentP, precision)) return sum;\n if (checkMultOverflow(currentQ, nFact)) return sum;\n\n sum += (currentP * precision) / (currentQ * nFact);\n\n if (sum == prevSum) return sum;\n prevSum = sum;\n\n n++;\n\n if (checkMultOverflow(currentP, p)) return sum;\n if (checkMultOverflow(currentQ, q)) return sum;\n if (checkMultOverflow(nFact, n)) return sum;\n\n currentP *= p;\n currentQ *= q;\n nFact *= n;\n\n (currentP, currentQ) = compactFraction(currentP, currentQ, precision);\n }\n }\n /* solhint-enable code-complexity */\n\n function countLeadingZeros(uint p, uint q) public pure returns (uint) {\n uint denomator = (uint(1)\u003c\u003c255);\n for (int i = 255; i \u003e= 0; i--) {\n if ((q*denomator)/denomator != q) {\n // overflow\n denomator = denomator/2;\n continue;\n }\n if (p/(q*denomator) \u003e 0) return uint(i);\n denomator = denomator/2;\n }\n\n return uint(-1);\n }\n\n // log2 for a number that it in [1,2)\n function log2ForSmallNumber(uint x, uint numPrecisionBits) public pure returns (uint) {\n uint res = 0;\n uint one = (uint(1)\u003c\u003cnumPrecisionBits);\n uint two = 2 * one;\n uint addition = one;\n\n require((x \u003e= one) \u0026\u0026 (x \u003c= two));\n require(numPrecisionBits \u003c 125);\n\n for (uint i = numPrecisionBits; i \u003e 0; i--) {\n x = (x*x) / one;\n addition = addition/2;\n if (x \u003e= two) {\n x = x/2;\n res += addition;\n }\n }\n\n return res;\n }\n\n function logBase2 (uint p, uint q, uint numPrecisionBits) public pure returns (uint) {\n uint n = 0;\n uint precision = (uint(1)\u003c\u003cnumPrecisionBits);\n\n if (p \u003e q) {\n n = countLeadingZeros(p, q);\n }\n\n require(!checkMultOverflow(p, precision));\n require(!checkMultOverflow(n, precision));\n require(!checkMultOverflow(uint(1)\u003c\u003cn, q));\n\n uint y = p * precision / (q * (uint(1)\u003c\u003cn));\n uint log2Small = log2ForSmallNumber(y, numPrecisionBits);\n\n require(n*precision \u003c= BIG_NUMBER);\n require(log2Small \u003c= BIG_NUMBER);\n\n return n * precision + log2Small;\n }\n\n function ln(uint p, uint q, uint numPrecisionBits) public pure returns (uint) {\n uint ln2Numerator = 6931471805599453094172;\n uint ln2Denomerator = 10000000000000000000000;\n\n uint log2x = logBase2(p, q, numPrecisionBits);\n\n require(!checkMultOverflow(ln2Numerator, log2x));\n\n return ln2Numerator * log2x / ln2Denomerator;\n }\n}\n\n\ncontract LiquidityFormula is UtilMath {\n function pE(uint r, uint pMIn, uint e, uint precision) public pure returns (uint) {\n require(!checkMultOverflow(r, e));\n uint expRE = exp(r*e, precision*precision, precision);\n require(!checkMultOverflow(expRE, pMIn));\n return pMIn*expRE / precision;\n }\n\n function deltaTFunc(uint r, uint pMIn, uint e, uint deltaE, uint precision) public pure returns (uint) {\n uint pe = pE(r, pMIn, e, precision);\n uint rpe = r * pe;\n\n require(!checkMultOverflow(r, deltaE));\n uint erdeltaE = exp(r*deltaE, precision*precision, precision);\n\n require(erdeltaE \u003e= precision);\n require(!checkMultOverflow(erdeltaE - precision, precision));\n require(!checkMultOverflow((erdeltaE - precision)*precision, precision));\n require(!checkMultOverflow((erdeltaE - precision)*precision*precision, precision));\n require(!checkMultOverflow(rpe, erdeltaE));\n require(!checkMultOverflow(r, pe));\n\n return (erdeltaE - precision) * precision * precision * precision / (rpe*erdeltaE);\n }\n\n function deltaEFunc(uint r, uint pMIn, uint e, uint deltaT, uint precision, uint numPrecisionBits)\n public pure\n returns (uint)\n {\n uint pe = pE(r, pMIn, e, precision);\n uint rpe = r * pe;\n\n require(!checkMultOverflow(rpe, deltaT));\n require(precision * precision + rpe * deltaT/precision \u003e precision * precision);\n uint lnPart = ln(precision*precision + rpe*deltaT/precision, precision*precision, numPrecisionBits);\n\n require(!checkMultOverflow(r, pe));\n require(!checkMultOverflow(precision, precision));\n require(!checkMultOverflow(rpe, deltaT));\n require(!checkMultOverflow(lnPart, precision));\n\n return lnPart * precision / r;\n }\n}\n"},"PermissionGroups.sol":{"content":"pragma solidity 0.4.18;\n\n\ncontract PermissionGroups {\n\n address public admin;\n address public pendingAdmin;\n mapping(address=\u003ebool) internal operators;\n mapping(address=\u003ebool) internal alerters;\n address[] internal operatorsGroup;\n address[] internal alertersGroup;\n uint constant internal MAX_GROUP_SIZE = 50;\n\n function PermissionGroups() public {\n admin = msg.sender;\n }\n\n modifier onlyAdmin() {\n require(msg.sender == admin);\n _;\n }\n\n modifier onlyOperator() {\n require(operators[msg.sender]);\n _;\n }\n\n modifier onlyAlerter() {\n require(alerters[msg.sender]);\n _;\n }\n\n function getOperators () external view returns(address[]) {\n return operatorsGroup;\n }\n\n function getAlerters () external view returns(address[]) {\n return alertersGroup;\n }\n\n event TransferAdminPending(address pendingAdmin);\n\n /**\n * @dev Allows the current admin to set the pendingAdmin address.\n * @param newAdmin The address to transfer ownership to.\n */\n function transferAdmin(address newAdmin) public onlyAdmin {\n require(newAdmin != address(0));\n TransferAdminPending(pendingAdmin);\n pendingAdmin = newAdmin;\n }\n\n /**\n * @dev Allows the current admin to set the admin in one tx. Useful initial deployment.\n * @param newAdmin The address to transfer ownership to.\n */\n function transferAdminQuickly(address newAdmin) public onlyAdmin {\n require(newAdmin != address(0));\n TransferAdminPending(newAdmin);\n AdminClaimed(newAdmin, admin);\n admin = newAdmin;\n }\n\n event AdminClaimed( address newAdmin, address previousAdmin);\n\n /**\n * @dev Allows the pendingAdmin address to finalize the change admin process.\n */\n function claimAdmin() public {\n require(pendingAdmin == msg.sender);\n AdminClaimed(pendingAdmin, admin);\n admin = pendingAdmin;\n pendingAdmin = address(0);\n }\n\n event AlerterAdded (address newAlerter, bool isAdd);\n\n function addAlerter(address newAlerter) public onlyAdmin {\n require(!alerters[newAlerter]); // prevent duplicates.\n require(alertersGroup.length \u003c MAX_GROUP_SIZE);\n\n AlerterAdded(newAlerter, true);\n alerters[newAlerter] = true;\n alertersGroup.push(newAlerter);\n }\n\n function removeAlerter (address alerter) public onlyAdmin {\n require(alerters[alerter]);\n alerters[alerter] = false;\n\n for (uint i = 0; i \u003c alertersGroup.length; ++i) {\n if (alertersGroup[i] == alerter) {\n alertersGroup[i] = alertersGroup[alertersGroup.length - 1];\n alertersGroup.length--;\n AlerterAdded(alerter, false);\n break;\n }\n }\n }\n\n event OperatorAdded(address newOperator, bool isAdd);\n\n function addOperator(address newOperator) public onlyAdmin {\n require(!operators[newOperator]); // prevent duplicates.\n require(operatorsGroup.length \u003c MAX_GROUP_SIZE);\n\n OperatorAdded(newOperator, true);\n operators[newOperator] = true;\n operatorsGroup.push(newOperator);\n }\n\n function removeOperator (address operator) public onlyAdmin {\n require(operators[operator]);\n operators[operator] = false;\n\n for (uint i = 0; i \u003c operatorsGroup.length; ++i) {\n if (operatorsGroup[i] == operator) {\n operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];\n operatorsGroup.length -= 1;\n OperatorAdded(operator, false);\n break;\n }\n }\n }\n}\n"},"Utils.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\n\n\n/// @title Kyber constants contract\ncontract Utils {\n\n ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);\n uint constant internal PRECISION = (10**18);\n uint constant internal MAX_QTY = (10**28); // 10B tokens\n uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH\n uint constant internal MAX_DECIMALS = 18;\n uint constant internal ETH_DECIMALS = 18;\n mapping(address=\u003euint) internal decimals;\n\n function setDecimals(ERC20 token) internal {\n if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;\n else decimals[token] = token.decimals();\n }\n\n function getDecimals(ERC20 token) internal view returns(uint) {\n if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access\n uint tokenDecimals = decimals[token];\n // technically, there might be token with decimals 0\n // moreover, very possible that old tokens have decimals 0\n // these tokens will just have higher gas fees.\n if(tokenDecimals == 0) return token.decimals();\n\n return tokenDecimals;\n }\n\n function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {\n require(srcQty \u003c= MAX_QTY);\n require(rate \u003c= MAX_RATE);\n\n if (dstDecimals \u003e= srcDecimals) {\n require((dstDecimals - srcDecimals) \u003c= MAX_DECIMALS);\n return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;\n } else {\n require((srcDecimals - dstDecimals) \u003c= MAX_DECIMALS);\n return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));\n }\n }\n\n function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {\n require(dstQty \u003c= MAX_QTY);\n require(rate \u003c= MAX_RATE);\n \n //source quantity is rounded up. to avoid dest quantity being too low.\n uint numerator;\n uint denominator;\n if (srcDecimals \u003e= dstDecimals) {\n require((srcDecimals - dstDecimals) \u003c= MAX_DECIMALS);\n numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));\n denominator = rate;\n } else {\n require((dstDecimals - srcDecimals) \u003c= MAX_DECIMALS);\n numerator = (PRECISION * dstQty);\n denominator = (rate * (10**(dstDecimals - srcDecimals)));\n }\n return (numerator + denominator - 1) / denominator; //avoid rounding down errors\n }\n}\n"},"Withdrawable.sol":{"content":"pragma solidity 0.4.18;\n\n\nimport \"./ERC20Interface.sol\";\nimport \"./PermissionGroups.sol\";\n\n\n/**\n * @title Contracts that should be able to recover tokens or ethers\n * @author Ilan Doron\n * @dev This allows to recover any tokens or Ethers received in a contract.\n * This will prevent any accidental loss of tokens.\n */\ncontract Withdrawable is PermissionGroups {\n\n event TokenWithdraw(ERC20 token, uint amount, address sendTo);\n\n /**\n * @dev Withdraw all ERC20 compatible tokens\n * @param token ERC20 The address of the token contract\n */\n function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {\n require(token.transfer(sendTo, amount));\n TokenWithdraw(token, amount, sendTo);\n }\n\n event EtherWithdraw(uint amount, address sendTo);\n\n /**\n * @dev Withdraw Ethers\n */\n function withdrawEther(uint amount, address sendTo) external onlyAdmin {\n sendTo.transfer(amount);\n EtherWithdraw(amount, sendTo);\n }\n}\n"}}