Transaction Hash:
Block:
10757542 at Aug-29-2020 07:20:36 PM +UTC
Transaction Fee:
0.024826074 ETH
$56.49
Gas Used:
318,283 Gas / 78 Gwei
Emitted Events:
64 |
ABYSS.Transfer( _from=KyberReserve, _to=KyberNetwork, _value=9977382029938074109396 )
|
65 |
KyberReserve.TradeExecute( origin=KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=557068540359495527, destToken=ABYSS, destAmount=9977382029938074109396, destAddress=KyberNetwork )
|
66 |
ABYSS.Transfer( _from=KyberNetwork, _to=[Sender] 0x6f2c254674e6439fa8b85abe29d8fd13bf5d6d7b, _value=9977382029938074109396 )
|
67 |
KyberFeeHandler.FeeDistributed( token=0xEeeeeEee...eeeeeEEeE, platformWallet=0xF1AA99C6...E35Cc504d, platformFeeWei=446906169562371, rewardWei=1016711535754393, rebateWei=0, rebateWallets=[], rebatePercentBpsPerWallet=[], burnAmtWei=100553888151534 )
|
68 |
KyberNetwork.KyberTrade( 0x30bbea603a7b36858fe5e3ec6ba5ff59dde039d02120d758eacfaed01520577d, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 0x0000000000000000000000000e8d6b471e332f140e7d9dbb99e5e3822f728da6, 00000000000000000000000000000000000000000000000007c0a97de3de98f1, 0000000000000000000000000000000000000000000000000003f825a0b63487, 00000000000000000000000000000000000000000000000000019675737c1503, 0000000000000000000000000000000000000000000000000000000000000120, 0000000000000000000000000000000000000000000000000000000000000140, 0000000000000000000000000000000000000000000000000000000000000180, 00000000000000000000000000000000000000000000000000000000000001a0, 00000000000000000000000000000000000000000000000000000000000001e0, 0000000000000000000000000000000000000000000000000000000000000200, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, aa41627973730000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000007bb1ae2cfac4f67, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000003caee443677686c9336 )
|
69 |
KyberNetworkProxy.ExecuteTrade( trader=[Sender] 0x6f2c254674e6439fa8b85abe29d8fd13bf5d6d7b, src=0xEeeeeEee...eeeeeEEeE, dest=ABYSS, destAddress=[Sender] 0x6f2c254674e6439fa8b85abe29d8fd13bf5d6d7b, actualSrcAmount=558632711952963825, actualDestAmount=9977382029938074109396, platformWallet=0xF1AA99C6...E35Cc504d, platformFeeBps=8 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x0E8d6b47...22F728DA6 | |||||
0x3e9FFBA3...de2d3163B | (Kyber: Reserve ABYSS) | 112.001799681652993659 Eth | 112.558868222012489186 Eth | 0.557068540359495527 | |
0x6F2c2546...3BF5D6D7B |
1.1042718205 Eth
Nonce: 6
|
0.520813034547036175 Eth
Nonce: 7
| 0.583458785952963825 | ||
0x829BD824...93333A830
Miner
| (F2Pool Old) | 4,413.657373480270523098 Eth | 4,413.682199554270523098 Eth | 0.024826074 | |
0xc7A784C3...01878B0ce | |||||
0xd3d2b564...a941114fe | (Kyber: Fee Handler) | 1,306.257882818407640089 Eth | 1,306.259446990001108387 Eth | 0.001564171593468298 |
Execution Trace
ETH 0.558632711952963825
KyberNetworkProxy.tradeWithHintAndFee( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=558632711952963825, dest=0x0E8d6b471e332F140e7d9dbB99E5E3822F728DA6, destAddress=0x6F2c254674e6439fA8b85aBE29D8fD13BF5D6D7B, maxDestAmount=115792089237316195423570985008687907853269984665640564039457584007913129639935, minConversionRate=17324550392342245203436, platformWallet=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d, platformFeeBps=8, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4162797373000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 ) => ( destAmount=9977382029938074109396 )
-
ABYSS.balanceOf( _owner=0x6F2c254674e6439fA8b85aBE29D8fD13BF5D6D7B ) => ( 0 )
ETH 0.558632711952963825
KyberNetwork.tradeWithHintAndFee( trader=0x6F2c254674e6439fA8b85aBE29D8fD13BF5D6D7B, src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=558632711952963825, dest=0x0E8d6b471e332F140e7d9dbB99E5E3822F728DA6, destAddress=0x6F2c254674e6439fA8b85aBE29D8fD13BF5D6D7B, maxDestAmount=115792089237316195423570985008687907853269984665640564039457584007913129639935, minConversionRate=17324550392342245203436, platformWallet=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d, platformFeeBps=8, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4162797373000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 ) => ( destAmount=9977382029938074109396 )
-
KyberMatchingEngine.getTradingReserves( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x0E8d6b471e332F140e7d9dbB99E5E3822F728DA6, isTokenToToken=False, hint=0x0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001AA4162797373000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000002710 )
-
KyberStorage.getReservesData( ) => ( areAllReservesListed=True, feeAccountedArr=[true], entitledRebateArr=[false], reserveAddresses=[0x3e9FFBA3C3eB91f501817b031031a71de2d3163B] )
KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x0E8d6b471e332F140e7d9dbB99E5E3822F728DA6, srcQty=557068540359495527, blockNumber=10757542 ) => ( 17910510659064189195062 )
-
LiquidityConversionRates.getRate( conversionToken=0x0E8d6b471e332F140e7d9dbB99E5E3822F728DA6, currentBlockNumber=10757542, buy=True, qtyInSrcWei=557068540359495527 ) => ( 17910510659064189195062 )
-
ABYSS.balanceOf( _owner=0x3e9FFBA3C3eB91f501817b031031a71de2d3163B ) => ( 1169645853719758122946808 )
-
ABYSS.allowance( _owner=0x3e9FFBA3C3eB91f501817b031031a71de2d3163B, _spender=0x3e9FFBA3C3eB91f501817b031031a71de2d3163B ) => ( 57896044618658097711785492504343953926634992332820203826869080975611861933972 )
-
-
ABYSS.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 81675 )
ETH 0.557068540359495527
KyberReserve.trade( srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=557068540359495527, destToken=0x0E8d6b471e332F140e7d9dbB99E5E3822F728DA6, destAddress=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, conversionRate=17910510659064189195062, validate=True ) => ( True )
-
LiquidityConversionRates.recordImbalance( conversionToken=0x0E8d6b471e332F140e7d9dbB99E5E3822F728DA6, buyAmountInTwei=9977382029938074109396, rateUpdateBlock=0, currentBlock=10757542 )
ABYSS.transferFrom( _from=0x3e9FFBA3C3eB91f501817b031031a71de2d3163B, _to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, _value=9977382029938074109396 ) => ( True )
PollManagedFund.onTokenTransfer( _from=0x3e9FFBA3C3eB91f501817b031031a71de2d3163B, 0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, _value=9977382029938074109396 )
-
0x95a333080b46c65798161c3605b5caf10ba0394a.CALL( )
-
0x95a333080b46c65798161c3605b5caf10ba0394a.c734f917( )
-
-
-
ABYSS.balanceOf( _owner=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 9977382029938074191071 )
ABYSS.transfer( _to=0x6F2c254674e6439fA8b85aBE29D8fD13BF5D6D7B, _value=9977382029938074109396 ) => ( True )
PollManagedFund.onTokenTransfer( _from=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, 0x6F2c254674e6439fA8b85aBE29D8fD13BF5D6D7B, _value=9977382029938074109396 )
-
0x95a333080b46c65798161c3605b5caf10ba0394a.CALL( )
-
0x95a333080b46c65798161c3605b5caf10ba0394a.c734f917( )
-
- ETH 0.001564171593468298
KyberFeeHandler.handleFees( token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, rebateWallets=[], rebateBpsPerWallet=[], platformWallet=0xF1AA99C69715F423086008eB9D06Dc1E35Cc504d, platformFee=446906169562371, networkFee=1117265423905927 )
-
-
ABYSS.balanceOf( _owner=0x6F2c254674e6439fA8b85aBE29D8fD13BF5D6D7B ) => ( 9977382029938074109396 )
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 9: KyberNetworkProxy
File 2 of 9: KyberReserve
File 3 of 9: KyberNetwork
File 4 of 9: ABYSS
File 5 of 9: KyberFeeHandler
File 6 of 9: KyberMatchingEngine
File 7 of 9: KyberStorage
File 8 of 9: LiquidityConversionRates
File 9 of 9: PollManagedFund
// 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 9: KyberReserve
pragma solidity ^0.4.13; interface ConversionRatesInterface { function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public; function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint); } interface ERC20 { function totalSupply() public view returns (uint supply); function balanceOf(address _owner) public view returns (uint balance); function transfer(address _to, uint _value) public returns (bool success); function transferFrom(address _from, address _to, uint _value) public returns (bool success); function approve(address _spender, uint _value) public returns (bool success); function allowance(address _owner, address _spender) public view returns (uint remaining); function decimals() public view returns(uint digits); event Approval(address indexed _owner, address indexed _spender, uint _value); } interface KyberReserveInterface { function trade( ERC20 srcToken, uint srcAmount, ERC20 destToken, address destAddress, uint conversionRate, bool validate ) public payable returns(bool); function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint); } contract PermissionGroups { address public admin; address public pendingAdmin; mapping(address=>bool) internal operators; mapping(address=>bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint constant internal MAX_GROUP_SIZE = 50; function PermissionGroups() public { admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin); _; } modifier onlyOperator() { require(operators[msg.sender]); _; } modifier onlyAlerter() { require(alerters[msg.sender]); _; } function getOperators () external view returns(address[]) { return operatorsGroup; } function getAlerters () external view returns(address[]) { return alertersGroup; } event TransferAdminPending(address pendingAdmin); /** * @dev Allows the current admin to set the pendingAdmin address. * @param newAdmin The address to transfer ownership to. */ function transferAdmin(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(pendingAdmin); pendingAdmin = newAdmin; } /** * @dev Allows the current admin to set the admin in one tx. Useful initial deployment. * @param newAdmin The address to transfer ownership to. */ function transferAdminQuickly(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(newAdmin); AdminClaimed(newAdmin, admin); admin = newAdmin; } event AdminClaimed( address newAdmin, address previousAdmin); /** * @dev Allows the pendingAdmin address to finalize the change admin process. */ function claimAdmin() public { require(pendingAdmin == msg.sender); AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } event AlerterAdded (address newAlerter, bool isAdd); function addAlerter(address newAlerter) public onlyAdmin { require(!alerters[newAlerter]); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE); AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function removeAlerter (address alerter) public onlyAdmin { require(alerters[alerter]); alerters[alerter] = false; for (uint i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.length--; AlerterAdded(alerter, false); break; } } } event OperatorAdded(address newOperator, bool isAdd); function addOperator(address newOperator) public onlyAdmin { require(!operators[newOperator]); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE); OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } function removeOperator (address operator) public onlyAdmin { require(operators[operator]); operators[operator] = false; for (uint i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.length -= 1; OperatorAdded(operator, false); break; } } } } interface SanityRatesInterface { function getSanityRate(ERC20 src, ERC20 dest) public view returns(uint); } contract Utils { ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee); uint constant internal PRECISION = (10**18); uint constant internal MAX_QTY = (10**28); // 10B tokens uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH uint constant internal MAX_DECIMALS = 18; uint constant internal ETH_DECIMALS = 18; mapping(address=>uint) internal decimals; function setDecimals(ERC20 token) internal { if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS; else decimals[token] = token.decimals(); } function getDecimals(ERC20 token) internal view returns(uint) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint tokenDecimals = decimals[token]; // technically, there might be token with decimals 0 // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if(tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(srcQty <= MAX_QTY); require(rate <= MAX_RATE); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(dstQty <= MAX_QTY); require(rate <= MAX_RATE); //source quantity is rounded up. to avoid dest quantity being too low. uint numerator; uint denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } } contract Withdrawable is PermissionGroups { event TokenWithdraw(ERC20 token, uint amount, address sendTo); /** * @dev Withdraw all ERC20 compatible tokens * @param token ERC20 The address of the token contract */ function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin { require(token.transfer(sendTo, amount)); TokenWithdraw(token, amount, sendTo); } event EtherWithdraw(uint amount, address sendTo); /** * @dev Withdraw Ethers */ function withdrawEther(uint amount, address sendTo) external onlyAdmin { sendTo.transfer(amount); EtherWithdraw(amount, sendTo); } } contract KyberReserve is KyberReserveInterface, Withdrawable, Utils { address public kyberNetwork; bool public tradeEnabled; ConversionRatesInterface public conversionRatesContract; SanityRatesInterface public sanityRatesContract; mapping(bytes32=>bool) public approvedWithdrawAddresses; // sha3(token,address)=>bool mapping(address=>address) public tokenWallet; function KyberReserve(address _kyberNetwork, ConversionRatesInterface _ratesContract, address _admin) public { require(_admin != address(0)); require(_ratesContract != address(0)); require(_kyberNetwork != address(0)); kyberNetwork = _kyberNetwork; conversionRatesContract = _ratesContract; admin = _admin; tradeEnabled = true; } event DepositToken(ERC20 token, uint amount); function() public payable { DepositToken(ETH_TOKEN_ADDRESS, msg.value); } event TradeExecute( address indexed origin, address src, uint srcAmount, address destToken, uint destAmount, address destAddress ); function trade( ERC20 srcToken, uint srcAmount, ERC20 destToken, address destAddress, uint conversionRate, bool validate ) public payable returns(bool) { require(tradeEnabled); require(msg.sender == kyberNetwork); require(doTrade(srcToken, srcAmount, destToken, destAddress, conversionRate, validate)); return true; } event TradeEnabled(bool enable); function enableTrade() public onlyAdmin returns(bool) { tradeEnabled = true; TradeEnabled(true); return true; } function disableTrade() public onlyAlerter returns(bool) { tradeEnabled = false; TradeEnabled(false); return true; } event WithdrawAddressApproved(ERC20 token, address addr, bool approve); function approveWithdrawAddress(ERC20 token, address addr, bool approve) public onlyAdmin { approvedWithdrawAddresses[keccak256(token, addr)] = approve; WithdrawAddressApproved(token, addr, approve); setDecimals(token); if ((tokenWallet[token] == address(0x0)) && (token != ETH_TOKEN_ADDRESS)) { tokenWallet[token] = this; // by default require(token.approve(this, 2 ** 255)); } } event NewTokenWallet(ERC20 token, address wallet); function setTokenWallet(ERC20 token, address wallet) public onlyAdmin { require(wallet != address(0x0)); tokenWallet[token] = wallet; NewTokenWallet(token, wallet); } event WithdrawFunds(ERC20 token, uint amount, address destination); function withdraw(ERC20 token, uint amount, address destination) public onlyOperator returns(bool) { require(approvedWithdrawAddresses[keccak256(token, destination)]); if (token == ETH_TOKEN_ADDRESS) { destination.transfer(amount); } else { require(token.transferFrom(tokenWallet[token], destination, amount)); } WithdrawFunds(token, amount, destination); return true; } event SetContractAddresses(address network, address rate, address sanity); function setContracts( address _kyberNetwork, ConversionRatesInterface _conversionRates, SanityRatesInterface _sanityRates ) public onlyAdmin { require(_kyberNetwork != address(0)); require(_conversionRates != address(0)); kyberNetwork = _kyberNetwork; conversionRatesContract = _conversionRates; sanityRatesContract = _sanityRates; SetContractAddresses(kyberNetwork, conversionRatesContract, sanityRatesContract); } //////////////////////////////////////////////////////////////////////////// /// status functions /////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// function getBalance(ERC20 token) public view returns(uint) { if (token == ETH_TOKEN_ADDRESS) return this.balance; else { address wallet = tokenWallet[token]; uint balanceOfWallet = token.balanceOf(wallet); uint allowanceOfWallet = token.allowance(wallet, this); return (balanceOfWallet < allowanceOfWallet) ? balanceOfWallet : allowanceOfWallet; } } function getDestQty(ERC20 src, ERC20 dest, uint srcQty, uint rate) public view returns(uint) { uint dstDecimals = getDecimals(dest); uint srcDecimals = getDecimals(src); return calcDstQty(srcQty, srcDecimals, dstDecimals, rate); } function getSrcQty(ERC20 src, ERC20 dest, uint dstQty, uint rate) public view returns(uint) { uint dstDecimals = getDecimals(dest); uint srcDecimals = getDecimals(src); return calcSrcQty(dstQty, srcDecimals, dstDecimals, rate); } function getConversionRate(ERC20 src, ERC20 dest, uint srcQty, uint blockNumber) public view returns(uint) { ERC20 token; bool isBuy; if (!tradeEnabled) return 0; if (ETH_TOKEN_ADDRESS == src) { isBuy = true; token = dest; } else if (ETH_TOKEN_ADDRESS == dest) { isBuy = false; token = src; } else { return 0; // pair is not listed } uint rate = conversionRatesContract.getRate(token, blockNumber, isBuy, srcQty); uint destQty = getDestQty(src, dest, srcQty, rate); if (getBalance(dest) < destQty) return 0; if (sanityRatesContract != address(0)) { uint sanityRate = sanityRatesContract.getSanityRate(src, dest); if (rate > sanityRate) return 0; } return rate; } /// @dev do a trade /// @param srcToken Src token /// @param srcAmount Amount of src token /// @param destToken Destination token /// @param destAddress Destination address to send tokens to /// @param validate If true, additional validations are applicable /// @return true iff trade is successful function doTrade( ERC20 srcToken, uint srcAmount, ERC20 destToken, address destAddress, uint conversionRate, bool validate ) internal returns(bool) { // can skip validation if done at kyber network level if (validate) { require(conversionRate > 0); if (srcToken == ETH_TOKEN_ADDRESS) require(msg.value == srcAmount); else require(msg.value == 0); } uint destAmount = getDestQty(srcToken, destToken, srcAmount, conversionRate); // sanity check require(destAmount > 0); // add to imbalance ERC20 token; int tradeAmount; if (srcToken == ETH_TOKEN_ADDRESS) { tradeAmount = int(destAmount); token = destToken; } else { tradeAmount = -1 * int(srcAmount); token = srcToken; } conversionRatesContract.recordImbalance( token, tradeAmount, 0, block.number ); // collect src tokens if (srcToken != ETH_TOKEN_ADDRESS) { require(srcToken.transferFrom(msg.sender, tokenWallet[srcToken], srcAmount)); } // send dest tokens if (destToken == ETH_TOKEN_ADDRESS) { destAddress.transfer(destAmount); } else { require(destToken.transferFrom(tokenWallet[destToken], destAddress, destAmount)); } TradeExecute(msg.sender, srcToken, srcAmount, destToken, destAmount, destAddress); return true; } }
File 3 of 9: KyberNetwork
// File: contracts/sol6/IERC20.sol pragma solidity 0.6.6; interface IERC20 { event Approval(address indexed _owner, address indexed _spender, uint256 _value); function approve(address _spender, uint256 _value) external returns (bool success); function transfer(address _to, uint256 _value) external returns (bool success); function transferFrom( address _from, address _to, uint256 _value ) external returns (bool success); function allowance(address _owner, address _spender) external view returns (uint256 remaining); function balanceOf(address _owner) external view returns (uint256 balance); function decimals() external view returns (uint8 digits); function totalSupply() external view returns (uint256 supply); } // to support backward compatible contract name -- so function signature remains same abstract contract ERC20 is IERC20 { } // File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol pragma solidity 0.6.6; contract PermissionGroupsNoModifiers { address public admin; address public pendingAdmin; mapping(address => bool) internal operators; mapping(address => bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint256 internal constant MAX_GROUP_SIZE = 50; event AdminClaimed(address newAdmin, address previousAdmin); event AlerterAdded(address newAlerter, bool isAdd); event OperatorAdded(address newOperator, bool isAdd); event TransferAdminPending(address pendingAdmin); constructor(address _admin) public { require(_admin != address(0), "admin 0"); admin = _admin; } function getOperators() external view returns (address[] memory) { return operatorsGroup; } function getAlerters() external view returns (address[] memory) { return alertersGroup; } function addAlerter(address newAlerter) public { onlyAdmin(); require(!alerters[newAlerter], "alerter exists"); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters"); emit AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function addOperator(address newOperator) public { onlyAdmin(); require(!operators[newOperator], "operator exists"); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators"); emit OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } /// @dev Allows the pendingAdmin address to finalize the change admin process. function claimAdmin() public { require(pendingAdmin == msg.sender, "not pending"); emit AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } function removeAlerter(address alerter) public { onlyAdmin(); require(alerters[alerter], "not alerter"); delete alerters[alerter]; for (uint256 i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.pop(); emit AlerterAdded(alerter, false); break; } } } function removeOperator(address operator) public { onlyAdmin(); require(operators[operator], "not operator"); delete operators[operator]; for (uint256 i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.pop(); emit OperatorAdded(operator, false); break; } } } /// @dev Allows the current admin to set the pendingAdmin address /// @param newAdmin The address to transfer ownership to function transferAdmin(address newAdmin) public { onlyAdmin(); require(newAdmin != address(0), "new admin 0"); emit TransferAdminPending(newAdmin); pendingAdmin = newAdmin; } /// @dev Allows the current admin to set the admin in one tx. Useful initial deployment. /// @param newAdmin The address to transfer ownership to. function transferAdminQuickly(address newAdmin) public { onlyAdmin(); require(newAdmin != address(0), "admin 0"); emit TransferAdminPending(newAdmin); emit AdminClaimed(newAdmin, admin); admin = newAdmin; } function onlyAdmin() internal view { require(msg.sender == admin, "only admin"); } function onlyAlerter() internal view { require(alerters[msg.sender], "only alerter"); } function onlyOperator() internal view { require(operators[msg.sender], "only operator"); } } // File: contracts/sol6/utils/WithdrawableNoModifiers.sol pragma solidity 0.6.6; contract WithdrawableNoModifiers is PermissionGroupsNoModifiers { constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {} event EtherWithdraw(uint256 amount, address sendTo); event TokenWithdraw(IERC20 token, uint256 amount, address sendTo); /// @dev Withdraw Ethers function withdrawEther(uint256 amount, address payable sendTo) external { onlyAdmin(); (bool success, ) = sendTo.call{value: amount}(""); require(success); emit EtherWithdraw(amount, sendTo); } /// @dev Withdraw all IERC20 compatible tokens /// @param token IERC20 The address of the token contract function withdrawToken( IERC20 token, uint256 amount, address sendTo ) external { onlyAdmin(); token.transfer(sendTo, amount); emit TokenWithdraw(token, amount, sendTo); } } // File: contracts/sol6/utils/Utils5.sol pragma solidity 0.6.6; /** * @title Kyber utility file * mostly shared constants and rate calculation helpers * inherited by most of kyber contracts. * previous utils implementations are for previous solidity versions. */ contract Utils5 { IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20( 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ); uint256 internal constant PRECISION = (10**18); uint256 internal constant MAX_QTY = (10**28); // 10B tokens uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth uint256 internal constant MAX_DECIMALS = 18; uint256 internal constant ETH_DECIMALS = 18; uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01% uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite mapping(IERC20 => uint256) internal decimals; function getUpdateDecimals(IERC20 token) internal returns (uint256) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint256 tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if (tokenDecimals == 0) { tokenDecimals = token.decimals(); decimals[token] = tokenDecimals; } return tokenDecimals; } function setDecimals(IERC20 token) internal { if (decimals[token] != 0) return; //already set if (token == ETH_TOKEN_ADDRESS) { decimals[token] = ETH_DECIMALS; } else { decimals[token] = token.decimals(); } } /// @dev get the balance of a user. /// @param token The token type /// @return The balance function getBalance(IERC20 token, address user) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) { return user.balance; } else { return token.balanceOf(user); } } function getDecimals(IERC20 token) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint256 tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if (tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDestAmount( IERC20 src, IERC20 dest, uint256 srcAmount, uint256 rate ) internal view returns (uint256) { return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate); } function calcSrcAmount( IERC20 src, IERC20 dest, uint256 destAmount, uint256 rate ) internal view returns (uint256) { return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate); } function calcDstQty( uint256 srcQty, uint256 srcDecimals, uint256 dstDecimals, uint256 rate ) internal pure returns (uint256) { require(srcQty <= MAX_QTY, "srcQty > MAX_QTY"); require(rate <= MAX_RATE, "rate > MAX_RATE"); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty( uint256 dstQty, uint256 srcDecimals, uint256 dstDecimals, uint256 rate ) internal pure returns (uint256) { require(dstQty <= MAX_QTY, "dstQty > MAX_QTY"); require(rate <= MAX_RATE, "rate > MAX_RATE"); //source quantity is rounded up. to avoid dest quantity being too low. uint256 numerator; uint256 denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } function calcRateFromQty( uint256 srcAmount, uint256 destAmount, uint256 srcDecimals, uint256 dstDecimals ) internal pure returns (uint256) { require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY"); require(destAmount <= MAX_QTY, "destAmount > MAX_QTY"); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount)); } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount); } } function minOf(uint256 x, uint256 y) internal pure returns (uint256) { return x > y ? y : x; } } // File: contracts/sol6/utils/zeppelin/ReentrancyGuard.sol pragma solidity 0.6.6; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ contract ReentrancyGuard { bool private _notEntered; constructor () internal { // Storing an initial non-zero value makes deployment a bit more // expensive, but in exchange the refund on every call to nonReentrant // will be lower in amount. Since refunds are capped to a percetange of // the total transaction's gas, it is best to keep them low in cases // like this one, to increase the likelihood of the full refund coming // into effect. _notEntered = true; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true require(_notEntered, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _notEntered = false; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _notEntered = true; } } // File: contracts/sol6/utils/zeppelin/SafeMath.sol pragma solidity 0.6.6; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } } // File: contracts/sol6/utils/zeppelin/Address.sol pragma solidity 0.6.6; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // According to EIP-1052, 0x0 is the value returned for not-yet created accounts // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned // for accounts without code, i.e. `keccak256('')` bytes32 codehash; bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; // solhint-disable-next-line no-inline-assembly assembly { codehash := extcodehash(account) } return (codehash != accountHash && codehash != 0x0); } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); require(success, "Address: unable to send value, recipient may have reverted"); } } // File: contracts/sol6/utils/zeppelin/SafeERC20.sol pragma solidity 0.6.6; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using SafeMath for uint256; using Address for address; function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' // solhint-disable-next-line max-line-length require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).add(value); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. // A Solidity high level call has three parts: // 1. The target address is checked to verify it contains contract code // 2. The call itself is made, and success asserted // 3. The return value is decoded, which in turn checks the size of the returned data. // solhint-disable-next-line max-line-length require(address(token).isContract(), "SafeERC20: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = address(token).call(data); require(success, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional // solhint-disable-next-line max-line-length require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } } // File: contracts/sol6/IKyberNetwork.sol pragma solidity 0.6.6; interface IKyberNetwork { event KyberTrade( IERC20 indexed src, IERC20 indexed dest, uint256 ethWeiValue, uint256 networkFeeWei, uint256 customPlatformFeeWei, bytes32[] t2eIds, bytes32[] e2tIds, uint256[] t2eSrcAmounts, uint256[] e2tSrcAmounts, uint256[] t2eRates, uint256[] e2tRates ); function tradeWithHintAndFee( address payable trader, IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps, bytes calldata hint ) external payable returns (uint256 destAmount); function listTokenForReserve( address reserve, IERC20 token, bool add ) external; function enabled() external view returns (bool); function getExpectedRateWithHintAndFee( IERC20 src, IERC20 dest, uint256 srcQty, uint256 platformFeeBps, bytes calldata hint ) external view returns ( uint256 expectedRateAfterNetworkFee, uint256 expectedRateAfterAllFees ); function getNetworkData() external view returns ( uint256 negligibleDiffBps, uint256 networkFeeBps, uint256 expiryTimestamp ); function maxGasPrice() external view returns (uint256); } // File: contracts/sol6/IKyberReserve.sol pragma solidity 0.6.6; interface IKyberReserve { function trade( IERC20 srcToken, uint256 srcAmount, IERC20 destToken, address payable destAddress, uint256 conversionRate, bool validate ) external payable returns (bool); function getConversionRate( IERC20 src, IERC20 dest, uint256 srcQty, uint256 blockNumber ) external view returns (uint256); } // File: contracts/sol6/IKyberFeeHandler.sol pragma solidity 0.6.6; interface IKyberFeeHandler { event RewardPaid(address indexed staker, uint256 indexed epoch, IERC20 indexed token, uint256 amount); event RebatePaid(address indexed rebateWallet, IERC20 indexed token, uint256 amount); event PlatformFeePaid(address indexed platformWallet, IERC20 indexed token, uint256 amount); event KncBurned(uint256 kncTWei, IERC20 indexed token, uint256 amount); function handleFees( IERC20 token, address[] calldata eligibleWallets, uint256[] calldata rebatePercentages, address platformWallet, uint256 platformFee, uint256 networkFee ) external payable; function claimReserveRebate(address rebateWallet) external returns (uint256); function claimPlatformFee(address platformWallet) external returns (uint256); function claimStakerReward( address staker, uint256 epoch ) external returns(uint amount); } // File: contracts/sol6/Dao/IEpochUtils.sol pragma solidity 0.6.6; interface IEpochUtils { function epochPeriodInSeconds() external view returns (uint256); function firstEpochStartTimestamp() external view returns (uint256); function getCurrentEpochNumber() external view returns (uint256); function getEpochNumber(uint256 timestamp) external view returns (uint256); } // File: contracts/sol6/IKyberDao.sol pragma solidity 0.6.6; interface IKyberDao is IEpochUtils { event Voted(address indexed staker, uint indexed epoch, uint indexed campaignID, uint option); function getLatestNetworkFeeDataWithCache() external returns (uint256 feeInBps, uint256 expiryTimestamp); function getLatestBRRDataWithCache() external returns ( uint256 burnInBps, uint256 rewardInBps, uint256 rebateInBps, uint256 epoch, uint256 expiryTimestamp ); function handleWithdrawal(address staker, uint256 penaltyAmount) external; function vote(uint256 campaignID, uint256 option) external; function getLatestNetworkFeeData() external view returns (uint256 feeInBps, uint256 expiryTimestamp); function shouldBurnRewardForEpoch(uint256 epoch) external view returns (bool); /** * @dev return staker's reward percentage in precision for a past epoch only * fee handler should call this function when a staker wants to claim reward * return 0 if staker has no votes or stakes */ function getPastEpochRewardPercentageInPrecision(address staker, uint256 epoch) external view returns (uint256); /** * @dev return staker's reward percentage in precision for the current epoch * reward percentage is not finalized until the current epoch is ended */ function getCurrentEpochRewardPercentageInPrecision(address staker) external view returns (uint256); } // File: contracts/sol6/IKyberNetworkProxy.sol pragma solidity 0.6.6; interface IKyberNetworkProxy { event ExecuteTrade( address indexed trader, IERC20 src, IERC20 dest, address destAddress, uint256 actualSrcAmount, uint256 actualDestAmount, address platformWallet, uint256 platformFeeBps ); /// @notice backward compatible function tradeWithHint( ERC20 src, uint256 srcAmount, ERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable walletId, bytes calldata hint ) external payable returns (uint256); function tradeWithHintAndFee( IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps, bytes calldata hint ) external payable returns (uint256 destAmount); function trade( IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet ) external payable returns (uint256); /// @notice backward compatible /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18 function getExpectedRate( ERC20 src, ERC20 dest, uint256 srcQty ) external view returns (uint256 expectedRate, uint256 worstRate); function getExpectedRateAfterFee( IERC20 src, IERC20 dest, uint256 srcQty, uint256 platformFeeBps, bytes calldata hint ) external view returns (uint256 expectedRate); } // File: contracts/sol6/IKyberStorage.sol pragma solidity 0.6.6; interface IKyberStorage { enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST} function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies) external; function removeKyberProxy(address kyberProxy) external; function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external; function setKyberDaoContract(address _kyberDao) external; function getReserveId(address reserve) external view returns (bytes32 reserveId); function getReserveIdsFromAddresses(address[] calldata reserveAddresses) external view returns (bytes32[] memory reserveIds); function getReserveAddressesFromIds(bytes32[] calldata reserveIds) external view returns (address[] memory reserveAddresses); function getReserveIdsPerTokenSrc(IERC20 token) external view returns (bytes32[] memory reserveIds); function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex) external view returns (address[] memory reserveAddresses); function getReserveIdsPerTokenDest(IERC20 token) external view returns (bytes32[] memory reserveIds); function getReserveAddressesByReserveId(bytes32 reserveId) external view returns (address[] memory reserveAddresses); function getRebateWalletsFromIds(bytes32[] calldata reserveIds) external view returns (address[] memory rebateWallets); function getKyberProxies() external view returns (IKyberNetworkProxy[] memory); function getReserveDetailsByAddress(address reserve) external view returns ( bytes32 reserveId, address rebateWallet, ReserveType resType, bool isFeeAccountedFlag, bool isEntitledRebateFlag ); function getReserveDetailsById(bytes32 reserveId) external view returns ( address reserveAddress, address rebateWallet, ReserveType resType, bool isFeeAccountedFlag, bool isEntitledRebateFlag ); function getFeeAccountedData(bytes32[] calldata reserveIds) external view returns (bool[] memory feeAccountedArr); function getEntitledRebateData(bytes32[] calldata reserveIds) external view returns (bool[] memory entitledRebateArr); function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest) external view returns ( bool areAllReservesListed, bool[] memory feeAccountedArr, bool[] memory entitledRebateArr, IKyberReserve[] memory reserveAddresses); function isKyberProxyAdded() external view returns (bool); } // File: contracts/sol6/IKyberMatchingEngine.sol pragma solidity 0.6.6; interface IKyberMatchingEngine { enum ProcessWithRate {NotRequired, Required} function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps) external; function setKyberStorage(IKyberStorage _kyberStorage) external; function getNegligibleRateDiffBps() external view returns (uint256); function getTradingReserves( IERC20 src, IERC20 dest, bool isTokenToToken, bytes calldata hint ) external view returns ( bytes32[] memory reserveIds, uint256[] memory splitValuesBps, ProcessWithRate processWithRate ); function doMatch( IERC20 src, IERC20 dest, uint256[] calldata srcAmounts, uint256[] calldata feesAccountedDestBps, uint256[] calldata rates ) external view returns (uint256[] memory reserveIndexes); } // File: contracts/sol6/IGasHelper.sol pragma solidity 0.6.6; interface IGasHelper { function freeGas( address platformWallet, IERC20 src, IERC20 dest, uint256 tradeWei, bytes32[] calldata t2eReserveIds, bytes32[] calldata e2tReserveIds ) external; } // File: contracts/sol6/KyberNetwork.sol pragma solidity 0.6.6; /** * @title kyberNetwork main contract * Interacts with contracts: * kyberDao: to retrieve fee data * kyberFeeHandler: accumulates and distributes trade fees * kyberMatchingEngine: parse user hint and run reserve matching algorithm * kyberStorage: store / access reserves, token listings and contract addresses * kyberReserve(s): query rate and trade */ contract KyberNetwork is WithdrawableNoModifiers, Utils5, IKyberNetwork, ReentrancyGuard { using SafeERC20 for IERC20; struct NetworkFeeData { uint64 expiryTimestamp; uint16 feeBps; } /// @notice Stores work data for reserves (either for token -> eth, or eth -> token) /// @dev Variables are in-place, ie. reserve with addresses[i] has id of ids[i], offers rate of rates[i], etc. /// @param addresses List of reserve addresses selected for the trade /// @param ids List of reserve ids, to be used for KyberTrade event /// @param rates List of rates that were offered by the reserves /// @param isFeeAccountedFlags List of reserves requiring users to pay network fee /// @param isEntitledRebateFlags List of reserves eligible for rebates /// @param splitsBps List of proportions of trade amount allocated to the reserves. /// If there is only 1 reserve, then it should have a value of 10000 bps /// @param srcAmounts Source amount per reserve. /// @param decimals Token decimals. Src decimals when for src -> eth, dest decimals when eth -> dest struct ReservesData { IKyberReserve[] addresses; bytes32[] ids; uint256[] rates; bool[] isFeeAccountedFlags; bool[] isEntitledRebateFlags; uint256[] splitsBps; uint256[] srcAmounts; uint256 decimals; } /// @notice Main trade data structure, is initialised and used for the entire trade flow /// @param input Initialised when initTradeInput is called. Stores basic trade info /// @param tokenToEth Stores information about reserves that were selected for src -> eth side of trade /// @param ethToToken Stores information about reserves that were selected for eth -> dest side of trade /// @param tradeWei Trade amount in ether wei, before deducting fees. /// @param networkFeeWei Network fee in ether wei. For t2t trades, it can go up to 200% of networkFeeBps /// @param platformFeeWei Platform fee in ether wei /// @param networkFeeBps Network fee bps determined by kyberDao, or default value /// @param numEntitledRebateReserves No. of reserves that are eligible for rebates /// @param feeAccountedBps Proportion of this trade that fee is accounted to, in BPS. Up to 2 * BPS struct TradeData { TradeInput input; ReservesData tokenToEth; ReservesData ethToToken; uint256 tradeWei; uint256 networkFeeWei; uint256 platformFeeWei; uint256 networkFeeBps; uint256 numEntitledRebateReserves; uint256 feeAccountedBps; // what part of this trade is fee paying. for token -> token - up to 200% } struct TradeInput { address payable trader; IERC20 src; uint256 srcAmount; IERC20 dest; address payable destAddress; uint256 maxDestAmount; uint256 minConversionRate; address platformWallet; uint256 platformFeeBps; } uint256 internal constant PERM_HINT_GET_RATE = 1 << 255; // for backwards compatibility uint256 internal constant DEFAULT_NETWORK_FEE_BPS = 25; // till we read value from kyberDao uint256 internal constant MAX_APPROVED_PROXIES = 2; // limit number of proxies that can trade here IKyberFeeHandler internal kyberFeeHandler; IKyberDao internal kyberDao; IKyberMatchingEngine internal kyberMatchingEngine; IKyberStorage internal kyberStorage; IGasHelper internal gasHelper; NetworkFeeData internal networkFeeData; // data is feeBps and expiry timestamp uint256 internal maxGasPriceValue = 50 * 1000 * 1000 * 1000; // 50 gwei bool internal isEnabled = false; // is network enabled mapping(address => bool) internal kyberProxyContracts; event EtherReceival(address indexed sender, uint256 amount); event KyberFeeHandlerUpdated(IKyberFeeHandler newKyberFeeHandler); event KyberMatchingEngineUpdated(IKyberMatchingEngine newKyberMatchingEngine); event GasHelperUpdated(IGasHelper newGasHelper); event KyberDaoUpdated(IKyberDao newKyberDao); event KyberNetworkParamsSet(uint256 maxGasPrice, uint256 negligibleRateDiffBps); event KyberNetworkSetEnable(bool isEnabled); event KyberProxyAdded(address kyberProxy); event KyberProxyRemoved(address kyberProxy); event ListedReservesForToken( IERC20 indexed token, address[] reserves, bool add ); constructor(address _admin, IKyberStorage _kyberStorage) public WithdrawableNoModifiers(_admin) { updateNetworkFee(now, DEFAULT_NETWORK_FEE_BPS); kyberStorage = _kyberStorage; } receive() external payable { emit EtherReceival(msg.sender, msg.value); } /// @notice Backward compatible function /// @notice Use token address ETH_TOKEN_ADDRESS for ether /// @dev Trade from src to dest token and sends dest token to destAddress /// @param trader Address of the taker side of this trade /// @param src Source token /// @param srcAmount Amount of src tokens in twei /// @param dest Destination token /// @param destAddress Address to send tokens to /// @param maxDestAmount A limit on the amount of dest tokens in twei /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts /// @param walletId Platform wallet address for receiving fees /// @param hint Advanced instructions for running the trade /// @return destAmount Amount of actual dest tokens in twei function tradeWithHint( address payable trader, ERC20 src, uint256 srcAmount, ERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable walletId, bytes calldata hint ) external payable returns (uint256 destAmount) { TradeData memory tradeData = initTradeInput({ trader: trader, src: src, dest: dest, srcAmount: srcAmount, destAddress: destAddress, maxDestAmount: maxDestAmount, minConversionRate: minConversionRate, platformWallet: walletId, platformFeeBps: 0 }); return trade(tradeData, hint); } /// @notice Use token address ETH_TOKEN_ADDRESS for ether /// @dev Trade from src to dest token and sends dest token to destAddress /// @param trader Address of the taker side of this trade /// @param src Source token /// @param srcAmount Amount of src tokens in twei /// @param dest Destination token /// @param destAddress Address to send tokens to /// @param maxDestAmount A limit on the amount of dest tokens in twei /// @param minConversionRate The minimal conversion rate. If actual rate is lower, trade reverts /// @param platformWallet Platform wallet address for receiving fees /// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 1000 = 10% /// @param hint Advanced instructions for running the trade /// @return destAmount Amount of actual dest tokens in twei function tradeWithHintAndFee( address payable trader, IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps, bytes calldata hint ) external payable override returns (uint256 destAmount) { TradeData memory tradeData = initTradeInput({ trader: trader, src: src, dest: dest, srcAmount: srcAmount, destAddress: destAddress, maxDestAmount: maxDestAmount, minConversionRate: minConversionRate, platformWallet: platformWallet, platformFeeBps: platformFeeBps }); return trade(tradeData, hint); } /// @notice Can be called only by kyberStorage /// @dev Allow or prevent to trade token -> eth for a reserve /// @param reserve The reserve address /// @param token Token address /// @param add If true, then give reserve token allowance, otherwise set zero allowance function listTokenForReserve( address reserve, IERC20 token, bool add ) external override { require(msg.sender == address(kyberStorage), "only kyberStorage"); if (add) { token.safeApprove(reserve, MAX_ALLOWANCE); setDecimals(token); } else { token.safeApprove(reserve, 0); } } /// @notice Can be called only by operator /// @dev Allow or prevent to trade token -> eth for list of reserves /// Useful for migration to new network contract /// Call storage to get list of reserves supporting token -> eth /// @param token Token address /// @param startIndex start index in reserves list /// @param endIndex end index in reserves list (can be larger) /// @param add If true, then give reserve token allowance, otherwise set zero allowance function listReservesForToken( IERC20 token, uint256 startIndex, uint256 endIndex, bool add ) external { onlyOperator(); if (startIndex > endIndex) { // no need to do anything return; } address[] memory reserves = kyberStorage.getReserveAddressesPerTokenSrc( token, startIndex, endIndex ); if (reserves.length == 0) { // no need to do anything return; } for(uint i = 0; i < reserves.length; i++) { if (add) { token.safeApprove(reserves[i], MAX_ALLOWANCE); setDecimals(token); } else { token.safeApprove(reserves[i], 0); } } emit ListedReservesForToken(token, reserves, add); } function setContracts( IKyberFeeHandler _kyberFeeHandler, IKyberMatchingEngine _kyberMatchingEngine, IGasHelper _gasHelper ) external virtual { onlyAdmin(); if (kyberFeeHandler != _kyberFeeHandler) { kyberFeeHandler = _kyberFeeHandler; emit KyberFeeHandlerUpdated(_kyberFeeHandler); } if (kyberMatchingEngine != _kyberMatchingEngine) { kyberMatchingEngine = _kyberMatchingEngine; emit KyberMatchingEngineUpdated(_kyberMatchingEngine); } if ((_gasHelper != IGasHelper(0)) && (_gasHelper != gasHelper)) { gasHelper = _gasHelper; emit GasHelperUpdated(_gasHelper); } kyberStorage.setContracts(address(_kyberFeeHandler), address(_kyberMatchingEngine)); require(_kyberFeeHandler != IKyberFeeHandler(0)); require(_kyberMatchingEngine != IKyberMatchingEngine(0)); } function setKyberDaoContract(IKyberDao _kyberDao) external { // enable setting null kyberDao address onlyAdmin(); if (kyberDao != _kyberDao) { kyberDao = _kyberDao; kyberStorage.setKyberDaoContract(address(_kyberDao)); emit KyberDaoUpdated(_kyberDao); } } function setParams(uint256 _maxGasPrice, uint256 _negligibleRateDiffBps) external { onlyAdmin(); maxGasPriceValue = _maxGasPrice; kyberMatchingEngine.setNegligibleRateDiffBps(_negligibleRateDiffBps); emit KyberNetworkParamsSet(maxGasPriceValue, _negligibleRateDiffBps); } function setEnable(bool enable) external { onlyAdmin(); if (enable) { require(kyberFeeHandler != IKyberFeeHandler(0)); require(kyberMatchingEngine != IKyberMatchingEngine(0)); require(kyberStorage.isKyberProxyAdded()); } isEnabled = enable; emit KyberNetworkSetEnable(isEnabled); } /// @dev No. of kyberProxies is capped function addKyberProxy(address kyberProxy) external virtual { onlyAdmin(); kyberStorage.addKyberProxy(kyberProxy, MAX_APPROVED_PROXIES); require(kyberProxy != address(0)); require(!kyberProxyContracts[kyberProxy]); kyberProxyContracts[kyberProxy] = true; emit KyberProxyAdded(kyberProxy); } function removeKyberProxy(address kyberProxy) external virtual { onlyAdmin(); kyberStorage.removeKyberProxy(kyberProxy); require(kyberProxyContracts[kyberProxy]); kyberProxyContracts[kyberProxy] = false; emit KyberProxyRemoved(kyberProxy); } /// @dev gets the expected rates when trading src -> dest token, with / without fees /// @param src Source token /// @param dest Destination token /// @param srcQty Amount of src tokens in twei /// @param platformFeeBps Part of the trade that is allocated as fee to platform wallet. Ex: 1000 = 10% /// @param hint Advanced instructions for running the trade /// @return rateWithNetworkFee Rate after deducting network fee but excluding platform fee /// @return rateWithAllFees = actual rate. Rate after accounting for both network and platform fees function getExpectedRateWithHintAndFee( IERC20 src, IERC20 dest, uint256 srcQty, uint256 platformFeeBps, bytes calldata hint ) external view override returns ( uint256 rateWithNetworkFee, uint256 rateWithAllFees ) { if (src == dest) return (0, 0); TradeData memory tradeData = initTradeInput({ trader: payable(address(0)), src: src, dest: dest, srcAmount: (srcQty == 0) ? 1 : srcQty, destAddress: payable(address(0)), maxDestAmount: 2**255, minConversionRate: 0, platformWallet: payable(address(0)), platformFeeBps: platformFeeBps }); tradeData.networkFeeBps = getNetworkFee(); uint256 destAmount; (destAmount, rateWithNetworkFee) = calcRatesAndAmounts(tradeData, hint); rateWithAllFees = calcRateFromQty( tradeData.input.srcAmount, destAmount, tradeData.tokenToEth.decimals, tradeData.ethToToken.decimals ); } /// @notice Backward compatible API /// @dev Gets the expected and slippage rate for exchanging src -> dest token /// @dev worstRate is hardcoded to be 3% lower of expectedRate /// @param src Source token /// @param dest Destination token /// @param srcQty Amount of src tokens in twei /// @return expectedRate for a trade after deducting network fee. /// @return worstRate for a trade. Calculated to be expectedRate * 97 / 100 function getExpectedRate( ERC20 src, ERC20 dest, uint256 srcQty ) external view returns (uint256 expectedRate, uint256 worstRate) { if (src == dest) return (0, 0); uint256 qty = srcQty & ~PERM_HINT_GET_RATE; TradeData memory tradeData = initTradeInput({ trader: payable(address(0)), src: src, dest: dest, srcAmount: (qty == 0) ? 1 : qty, destAddress: payable(address(0)), maxDestAmount: 2**255, minConversionRate: 0, platformWallet: payable(address(0)), platformFeeBps: 0 }); tradeData.networkFeeBps = getNetworkFee(); (, expectedRate) = calcRatesAndAmounts(tradeData, ""); worstRate = (expectedRate * 97) / 100; // backward compatible formula } /// @notice Returns some data about the network /// @param negligibleDiffBps Negligible rate difference (in basis pts) when searching best rate /// @param networkFeeBps Network fees to be charged (in basis pts) /// @param expiryTimestamp Timestamp for which networkFeeBps will expire, /// and needs to be updated by calling kyberDao contract / set to default function getNetworkData() external view override returns ( uint256 negligibleDiffBps, uint256 networkFeeBps, uint256 expiryTimestamp ) { (networkFeeBps, expiryTimestamp) = readNetworkFeeData(); negligibleDiffBps = kyberMatchingEngine.getNegligibleRateDiffBps(); return (negligibleDiffBps, networkFeeBps, expiryTimestamp); } function getContracts() external view returns ( IKyberFeeHandler kyberFeeHandlerAddress, IKyberDao kyberDaoAddress, IKyberMatchingEngine kyberMatchingEngineAddress, IKyberStorage kyberStorageAddress, IGasHelper gasHelperAddress, IKyberNetworkProxy[] memory kyberProxyAddresses ) { return ( kyberFeeHandler, kyberDao, kyberMatchingEngine, kyberStorage, gasHelper, kyberStorage.getKyberProxies() ); } /// @notice returns the max gas price allowable for trades function maxGasPrice() external view override returns (uint256) { return maxGasPriceValue; } /// @notice returns status of the network. If disabled, trades cannot happen. function enabled() external view override returns (bool) { return isEnabled; } /// @notice Gets network fee from the kyberDao (or use default). /// For trade function, so that data can be updated and cached. /// @dev Note that this function can be triggered by anyone, so that /// the first trader of a new epoch can avoid incurring extra gas costs function getAndUpdateNetworkFee() public returns (uint256 networkFeeBps) { uint256 expiryTimestamp; (networkFeeBps, expiryTimestamp) = readNetworkFeeData(); if (expiryTimestamp < now && kyberDao != IKyberDao(0)) { (networkFeeBps, expiryTimestamp) = kyberDao.getLatestNetworkFeeDataWithCache(); updateNetworkFee(expiryTimestamp, networkFeeBps); } } /// @notice Calculates platform fee and reserve rebate percentages for the trade. /// Transfers eth and rebate wallet data to kyberFeeHandler function handleFees(TradeData memory tradeData) internal { uint256 sentFee = tradeData.networkFeeWei + tradeData.platformFeeWei; //no need to handle fees if total fee is zero if (sentFee == 0) return; // update reserve eligibility and rebate percentages ( address[] memory rebateWallets, uint256[] memory rebatePercentBps ) = calculateRebates(tradeData); // send total fee amount to fee handler with reserve data kyberFeeHandler.handleFees{value: sentFee}( ETH_TOKEN_ADDRESS, rebateWallets, rebatePercentBps, tradeData.input.platformWallet, tradeData.platformFeeWei, tradeData.networkFeeWei ); } function updateNetworkFee(uint256 expiryTimestamp, uint256 feeBps) internal { require(expiryTimestamp < 2**64, "expiry overflow"); require(feeBps < BPS / 2, "fees exceed BPS"); networkFeeData.expiryTimestamp = uint64(expiryTimestamp); networkFeeData.feeBps = uint16(feeBps); } /// @notice Use token address ETH_TOKEN_ADDRESS for ether /// @dev Do one trade with each reserve in reservesData, verifying network balance /// as expected to ensure reserves take correct src amount /// @param src Source token /// @param dest Destination token /// @param destAddress Address to send tokens to /// @param reservesData reservesData to trade /// @param expectedDestAmount Amount to be transferred to destAddress /// @param srcDecimals Decimals of source token /// @param destDecimals Decimals of destination token function doReserveTrades( IERC20 src, IERC20 dest, address payable destAddress, ReservesData memory reservesData, uint256 expectedDestAmount, uint256 srcDecimals, uint256 destDecimals ) internal virtual { if (src == dest) { // eth -> eth, need not do anything except for token -> eth: transfer eth to destAddress if (destAddress != (address(this))) { (bool success, ) = destAddress.call{value: expectedDestAmount}(""); require(success, "send dest qty failed"); } return; } tradeAndVerifyNetworkBalance( reservesData, src, dest, srcDecimals, destDecimals ); if (destAddress != address(this)) { // for eth -> token / token -> token, transfer tokens to destAddress dest.safeTransfer(destAddress, expectedDestAmount); } } /// @dev call trade from reserves and verify balances /// @param reservesData reservesData to trade /// @param src Source token of trade /// @param dest Destination token of trade /// @param srcDecimals Decimals of source token /// @param destDecimals Decimals of destination token function tradeAndVerifyNetworkBalance( ReservesData memory reservesData, IERC20 src, IERC20 dest, uint256 srcDecimals, uint256 destDecimals ) internal { // only need to verify src balance if src is not eth uint256 srcBalanceBefore = (src == ETH_TOKEN_ADDRESS) ? 0 : getBalance(src, address(this)); uint256 destBalanceBefore = getBalance(dest, address(this)); for(uint256 i = 0; i < reservesData.addresses.length; i++) { uint256 callValue = (src == ETH_TOKEN_ADDRESS) ? reservesData.srcAmounts[i] : 0; require( reservesData.addresses[i].trade{value: callValue}( src, reservesData.srcAmounts[i], dest, address(this), reservesData.rates[i], true ), "reserve trade failed" ); uint256 balanceAfter; if (src != ETH_TOKEN_ADDRESS) { // verify src balance only if it is not eth balanceAfter = getBalance(src, address(this)); // verify correct src amount is taken if (srcBalanceBefore >= balanceAfter && srcBalanceBefore - balanceAfter > reservesData.srcAmounts[i]) { revert("reserve takes high amount"); } srcBalanceBefore = balanceAfter; } // verify correct dest amount is received uint256 expectedDestAmount = calcDstQty( reservesData.srcAmounts[i], srcDecimals, destDecimals, reservesData.rates[i] ); balanceAfter = getBalance(dest, address(this)); if (balanceAfter < destBalanceBefore || balanceAfter - destBalanceBefore < expectedDestAmount) { revert("reserve returns low amount"); } destBalanceBefore = balanceAfter; } } /// @notice Use token address ETH_TOKEN_ADDRESS for ether /// @dev Trade API for kyberNetwork /// @param tradeData Main trade data object for trade info to be stored function trade(TradeData memory tradeData, bytes memory hint) internal virtual nonReentrant returns (uint256 destAmount) { tradeData.networkFeeBps = getAndUpdateNetworkFee(); validateTradeInput(tradeData.input); uint256 rateWithNetworkFee; (destAmount, rateWithNetworkFee) = calcRatesAndAmounts(tradeData, hint); require(rateWithNetworkFee > 0, "trade invalid, if hint involved, try parseHint API"); require(rateWithNetworkFee < MAX_RATE, "rate > MAX_RATE"); require(rateWithNetworkFee >= tradeData.input.minConversionRate, "rate < min rate"); uint256 actualSrcAmount; if (destAmount > tradeData.input.maxDestAmount) { // notice tradeData passed by reference and updated destAmount = tradeData.input.maxDestAmount; actualSrcAmount = calcTradeSrcAmountFromDest(tradeData); } else { actualSrcAmount = tradeData.input.srcAmount; } // token -> eth doReserveTrades( tradeData.input.src, ETH_TOKEN_ADDRESS, address(this), tradeData.tokenToEth, tradeData.tradeWei, tradeData.tokenToEth.decimals, ETH_DECIMALS ); // eth -> token doReserveTrades( ETH_TOKEN_ADDRESS, tradeData.input.dest, tradeData.input.destAddress, tradeData.ethToToken, destAmount, ETH_DECIMALS, tradeData.ethToToken.decimals ); handleChange( tradeData.input.src, tradeData.input.srcAmount, actualSrcAmount, tradeData.input.trader ); handleFees(tradeData); emit KyberTrade({ src: tradeData.input.src, dest: tradeData.input.dest, ethWeiValue: tradeData.tradeWei, networkFeeWei: tradeData.networkFeeWei, customPlatformFeeWei: tradeData.platformFeeWei, t2eIds: tradeData.tokenToEth.ids, e2tIds: tradeData.ethToToken.ids, t2eSrcAmounts: tradeData.tokenToEth.srcAmounts, e2tSrcAmounts: tradeData.ethToToken.srcAmounts, t2eRates: tradeData.tokenToEth.rates, e2tRates: tradeData.ethToToken.rates }); if (gasHelper != IGasHelper(0)) { (bool success, ) = address(gasHelper).call( abi.encodeWithSignature( "freeGas(address,address,address,uint256,bytes32[],bytes32[])", tradeData.input.platformWallet, tradeData.input.src, tradeData.input.dest, tradeData.tradeWei, tradeData.tokenToEth.ids, tradeData.ethToToken.ids ) ); // remove compilation warning success; } return (destAmount); } /// @notice If user maxDestAmount < actual dest amount, actualSrcAmount will be < srcAmount /// Calculate the change, and send it back to the user function handleChange( IERC20 src, uint256 srcAmount, uint256 requiredSrcAmount, address payable trader ) internal { if (requiredSrcAmount < srcAmount) { // if there is "change" send back to trader if (src == ETH_TOKEN_ADDRESS) { (bool success, ) = trader.call{value: (srcAmount - requiredSrcAmount)}(""); require(success, "Send change failed"); } else { src.safeTransfer(trader, (srcAmount - requiredSrcAmount)); } } } function initTradeInput( address payable trader, IERC20 src, IERC20 dest, uint256 srcAmount, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps ) internal view returns (TradeData memory tradeData) { tradeData.input.trader = trader; tradeData.input.src = src; tradeData.input.srcAmount = srcAmount; tradeData.input.dest = dest; tradeData.input.destAddress = destAddress; tradeData.input.maxDestAmount = maxDestAmount; tradeData.input.minConversionRate = minConversionRate; tradeData.input.platformWallet = platformWallet; tradeData.input.platformFeeBps = platformFeeBps; tradeData.tokenToEth.decimals = getDecimals(src); tradeData.ethToToken.decimals = getDecimals(dest); } /// @notice This function does all calculations to find trade dest amount without accounting /// for maxDestAmount. Part of this process includes: /// - Call kyberMatchingEngine to parse hint and get an optional reserve list to trade. /// - Query reserve rates and call kyberMatchingEngine to use best reserve. /// - Calculate trade values and fee values. /// This function should set all TradeData information so that it can be later used without /// any ambiguity /// @param tradeData Main trade data object for trade info to be stored /// @param hint Advanced user instructions for the trade function calcRatesAndAmounts(TradeData memory tradeData, bytes memory hint) internal view returns (uint256 destAmount, uint256 rateWithNetworkFee) { validateFeeInput(tradeData.input, tradeData.networkFeeBps); // token -> eth: find best reserves match and calculate wei amount tradeData.tradeWei = calcDestQtyAndMatchReserves( tradeData.input.src, ETH_TOKEN_ADDRESS, tradeData.input.srcAmount, tradeData, tradeData.tokenToEth, hint ); require(tradeData.tradeWei <= MAX_QTY, "Trade wei > MAX_QTY"); if (tradeData.tradeWei == 0) { return (0, 0); } // calculate fees tradeData.platformFeeWei = (tradeData.tradeWei * tradeData.input.platformFeeBps) / BPS; tradeData.networkFeeWei = (((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) / BPS; assert(tradeData.tradeWei >= (tradeData.networkFeeWei + tradeData.platformFeeWei)); // eth -> token: find best reserves match and calculate trade dest amount uint256 actualSrcWei = tradeData.tradeWei - tradeData.networkFeeWei - tradeData.platformFeeWei; destAmount = calcDestQtyAndMatchReserves( ETH_TOKEN_ADDRESS, tradeData.input.dest, actualSrcWei, tradeData, tradeData.ethToToken, hint ); tradeData.networkFeeWei = (((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) / BPS; rateWithNetworkFee = calcRateFromQty( tradeData.input.srcAmount * (BPS - tradeData.input.platformFeeBps) / BPS, destAmount, tradeData.tokenToEth.decimals, tradeData.ethToToken.decimals ); } /// @notice Get trading reserves, source amounts, and calculate dest qty /// Store information into tradeData function calcDestQtyAndMatchReserves( IERC20 src, IERC20 dest, uint256 srcAmount, TradeData memory tradeData, ReservesData memory reservesData, bytes memory hint ) internal view returns (uint256 destAmount) { if (src == dest) { return srcAmount; } IKyberMatchingEngine.ProcessWithRate processWithRate; // get reserve list from kyberMatchingEngine (reservesData.ids, reservesData.splitsBps, processWithRate) = kyberMatchingEngine.getTradingReserves( src, dest, (tradeData.input.src != ETH_TOKEN_ADDRESS) && (tradeData.input.dest != ETH_TOKEN_ADDRESS), hint ); bool areAllReservesListed; (areAllReservesListed, reservesData.isFeeAccountedFlags, reservesData.isEntitledRebateFlags, reservesData.addresses) = kyberStorage.getReservesData(reservesData.ids, src, dest); if(!areAllReservesListed) { return 0; } require(reservesData.ids.length == reservesData.splitsBps.length, "bad split array"); require(reservesData.ids.length == reservesData.isFeeAccountedFlags.length, "bad fee array"); require(reservesData.ids.length == reservesData.isEntitledRebateFlags.length, "bad rebate array"); require(reservesData.ids.length == reservesData.addresses.length, "bad addresses array"); // calculate src trade amount per reserve and query rates // set data in reservesData struct uint256[] memory feesAccountedDestBps = calcSrcAmountsAndGetRates( reservesData, src, dest, srcAmount, tradeData ); // if matching engine requires processing with rate data. call doMatch and update reserve list if (processWithRate == IKyberMatchingEngine.ProcessWithRate.Required) { uint256[] memory selectedIndexes = kyberMatchingEngine.doMatch( src, dest, reservesData.srcAmounts, feesAccountedDestBps, reservesData.rates ); updateReservesList(reservesData, selectedIndexes); } // calculate dest amount and fee paying data of this part (t2e or e2t) destAmount = validateTradeCalcDestQtyAndFeeData(src, reservesData, tradeData); } /// @notice Calculates source amounts per reserve. Does get rate call function calcSrcAmountsAndGetRates( ReservesData memory reservesData, IERC20 src, IERC20 dest, uint256 srcAmount, TradeData memory tradeData ) internal view returns (uint256[] memory feesAccountedDestBps) { uint256 numReserves = reservesData.ids.length; uint256 srcAmountAfterFee; uint256 destAmountFeeBps; if (src == ETH_TOKEN_ADDRESS) { // @notice srcAmount is after deducting fees from tradeWei // @notice using tradeWei to calculate fee so eth -> token symmetric to token -> eth srcAmountAfterFee = srcAmount - (tradeData.tradeWei * tradeData.networkFeeBps / BPS); } else { srcAmountAfterFee = srcAmount; destAmountFeeBps = tradeData.networkFeeBps; } reservesData.srcAmounts = new uint256[](numReserves); reservesData.rates = new uint256[](numReserves); feesAccountedDestBps = new uint256[](numReserves); // iterate reserve list. validate data. calculate srcAmount according to splits and fee data. for (uint256 i = 0; i < numReserves; i++) { require( reservesData.splitsBps[i] > 0 && reservesData.splitsBps[i] <= BPS, "invalid split bps" ); if (reservesData.isFeeAccountedFlags[i]) { reservesData.srcAmounts[i] = srcAmountAfterFee * reservesData.splitsBps[i] / BPS; feesAccountedDestBps[i] = destAmountFeeBps; } else { reservesData.srcAmounts[i] = (srcAmount * reservesData.splitsBps[i]) / BPS; } // get rate with calculated src amount reservesData.rates[i] = reservesData.addresses[i].getConversionRate( src, dest, reservesData.srcAmounts[i], block.number ); } } function calculateRebates(TradeData memory tradeData) internal view returns (address[] memory rebateWallets, uint256[] memory rebatePercentBps) { rebateWallets = new address[](tradeData.numEntitledRebateReserves); rebatePercentBps = new uint256[](tradeData.numEntitledRebateReserves); if (tradeData.numEntitledRebateReserves == 0) { return (rebateWallets, rebatePercentBps); } uint256 index; bytes32[] memory rebateReserveIds = new bytes32[](tradeData.numEntitledRebateReserves); // token -> eth index = createRebateEntitledList( rebateReserveIds, rebatePercentBps, tradeData.tokenToEth, index, tradeData.feeAccountedBps ); // eth -> token createRebateEntitledList( rebateReserveIds, rebatePercentBps, tradeData.ethToToken, index, tradeData.feeAccountedBps ); rebateWallets = kyberStorage.getRebateWalletsFromIds(rebateReserveIds); } function createRebateEntitledList( bytes32[] memory rebateReserveIds, uint256[] memory rebatePercentBps, ReservesData memory reservesData, uint256 index, uint256 feeAccountedBps ) internal pure returns (uint256) { uint256 _index = index; for (uint256 i = 0; i < reservesData.isEntitledRebateFlags.length; i++) { if (reservesData.isEntitledRebateFlags[i]) { rebateReserveIds[_index] = reservesData.ids[i]; rebatePercentBps[_index] = (reservesData.splitsBps[i] * BPS) / feeAccountedBps; _index++; } } return _index; } /// @dev Checks a trade input validity, including correct src amounts /// @param input Trade input structure function validateTradeInput(TradeInput memory input) internal view { require(isEnabled, "network disabled"); require(kyberProxyContracts[msg.sender], "bad sender"); require(tx.gasprice <= maxGasPriceValue, "gas price"); require(input.srcAmount <= MAX_QTY, "srcAmt > MAX_QTY"); require(input.srcAmount != 0, "0 srcAmt"); require(input.destAddress != address(0), "dest add 0"); require(input.src != input.dest, "src = dest"); if (input.src == ETH_TOKEN_ADDRESS) { require(msg.value == input.srcAmount); // kyberProxy issues message here } else { require(msg.value == 0); // kyberProxy issues message here // funds should have been moved to this contract already. require(input.src.balanceOf(address(this)) >= input.srcAmount, "no tokens"); } } /// @notice Gets the network fee from kyberDao (or use default). View function for getExpectedRate function getNetworkFee() internal view returns (uint256 networkFeeBps) { uint256 expiryTimestamp; (networkFeeBps, expiryTimestamp) = readNetworkFeeData(); if (expiryTimestamp < now && kyberDao != IKyberDao(0)) { (networkFeeBps, expiryTimestamp) = kyberDao.getLatestNetworkFeeData(); } } function readNetworkFeeData() internal view returns (uint256 feeBps, uint256 expiryTimestamp) { feeBps = uint256(networkFeeData.feeBps); expiryTimestamp = uint256(networkFeeData.expiryTimestamp); } /// @dev Checks fee input validity, including correct src amounts /// @param input Trade input structure /// @param networkFeeBps Network fee in bps. function validateFeeInput(TradeInput memory input, uint256 networkFeeBps) internal pure { require(input.platformFeeBps < BPS, "platformFee high"); require(input.platformFeeBps + networkFeeBps + networkFeeBps < BPS, "fees high"); } /// @notice Update reserve data with selected reserves from kyberMatchingEngine function updateReservesList(ReservesData memory reservesData, uint256[] memory selectedIndexes) internal pure { uint256 numReserves = selectedIndexes.length; require(numReserves <= reservesData.addresses.length, "doMatch: too many reserves"); IKyberReserve[] memory reserveAddresses = new IKyberReserve[](numReserves); bytes32[] memory reserveIds = new bytes32[](numReserves); uint256[] memory splitsBps = new uint256[](numReserves); bool[] memory isFeeAccountedFlags = new bool[](numReserves); bool[] memory isEntitledRebateFlags = new bool[](numReserves); uint256[] memory srcAmounts = new uint256[](numReserves); uint256[] memory rates = new uint256[](numReserves); // update participating resevres and all data (rates, srcAmounts, feeAcounted etc.) for (uint256 i = 0; i < numReserves; i++) { reserveAddresses[i] = reservesData.addresses[selectedIndexes[i]]; reserveIds[i] = reservesData.ids[selectedIndexes[i]]; splitsBps[i] = reservesData.splitsBps[selectedIndexes[i]]; isFeeAccountedFlags[i] = reservesData.isFeeAccountedFlags[selectedIndexes[i]]; isEntitledRebateFlags[i] = reservesData.isEntitledRebateFlags[selectedIndexes[i]]; srcAmounts[i] = reservesData.srcAmounts[selectedIndexes[i]]; rates[i] = reservesData.rates[selectedIndexes[i]]; } // update values reservesData.addresses = reserveAddresses; reservesData.ids = reserveIds; reservesData.splitsBps = splitsBps; reservesData.isFeeAccountedFlags = isFeeAccountedFlags; reservesData.isEntitledRebateFlags = isEntitledRebateFlags; reservesData.rates = rates; reservesData.srcAmounts = srcAmounts; } /// @notice Verify split values bps and reserve ids, /// then calculate the destQty from srcAmounts and rates /// @dev Each split bps must be in range (0, BPS] /// @dev Total split bps must be 100% /// @dev Reserve ids must be increasing function validateTradeCalcDestQtyAndFeeData( IERC20 src, ReservesData memory reservesData, TradeData memory tradeData ) internal pure returns (uint256 totalDestAmount) { uint256 totalBps; uint256 srcDecimals = (src == ETH_TOKEN_ADDRESS) ? ETH_DECIMALS : reservesData.decimals; uint256 destDecimals = (src == ETH_TOKEN_ADDRESS) ? reservesData.decimals : ETH_DECIMALS; for (uint256 i = 0; i < reservesData.addresses.length; i++) { if (i > 0 && (uint256(reservesData.ids[i]) <= uint256(reservesData.ids[i - 1]))) { return 0; // ids are not in increasing order } totalBps += reservesData.splitsBps[i]; uint256 destAmount = calcDstQty( reservesData.srcAmounts[i], srcDecimals, destDecimals, reservesData.rates[i] ); if (destAmount == 0) { return 0; } totalDestAmount += destAmount; if (reservesData.isFeeAccountedFlags[i]) { tradeData.feeAccountedBps += reservesData.splitsBps[i]; if (reservesData.isEntitledRebateFlags[i]) { tradeData.numEntitledRebateReserves++; } } } if (totalBps != BPS) { return 0; } } /// @notice Recalculates tradeWei, network and platform fees, and actual source amount needed for the trade /// in the event actualDestAmount > maxDestAmount function calcTradeSrcAmountFromDest(TradeData memory tradeData) internal pure virtual returns (uint256 actualSrcAmount) { uint256 weiAfterDeductingFees; if (tradeData.input.dest != ETH_TOKEN_ADDRESS) { weiAfterDeductingFees = calcTradeSrcAmount( tradeData.tradeWei - tradeData.platformFeeWei - tradeData.networkFeeWei, ETH_DECIMALS, tradeData.ethToToken.decimals, tradeData.input.maxDestAmount, tradeData.ethToToken ); } else { weiAfterDeductingFees = tradeData.input.maxDestAmount; } // reverse calculation, because we are working backwards uint256 newTradeWei = (weiAfterDeductingFees * BPS * BPS) / ((BPS * BPS) - (tradeData.networkFeeBps * tradeData.feeAccountedBps + tradeData.input.platformFeeBps * BPS)); tradeData.tradeWei = minOf(newTradeWei, tradeData.tradeWei); // recalculate network and platform fees based on tradeWei tradeData.networkFeeWei = (((tradeData.tradeWei * tradeData.networkFeeBps) / BPS) * tradeData.feeAccountedBps) / BPS; tradeData.platformFeeWei = (tradeData.tradeWei * tradeData.input.platformFeeBps) / BPS; if (tradeData.input.src != ETH_TOKEN_ADDRESS) { actualSrcAmount = calcTradeSrcAmount( tradeData.input.srcAmount, tradeData.tokenToEth.decimals, ETH_DECIMALS, tradeData.tradeWei, tradeData.tokenToEth ); } else { actualSrcAmount = tradeData.tradeWei; } assert(actualSrcAmount <= tradeData.input.srcAmount); } /// @notice Recalculates srcAmounts and stores into tradingReserves, given the new destAmount. /// Uses the original proportion of srcAmounts and rates to determine new split destAmounts, /// then calculate the respective srcAmounts /// @dev Due to small rounding errors, will fallback to current src amounts if new src amount is greater function calcTradeSrcAmount( uint256 srcAmount, uint256 srcDecimals, uint256 destDecimals, uint256 destAmount, ReservesData memory reservesData ) internal pure returns (uint256 newSrcAmount) { uint256 totalWeightedDestAmount; for (uint256 i = 0; i < reservesData.srcAmounts.length; i++) { totalWeightedDestAmount += reservesData.srcAmounts[i] * reservesData.rates[i]; } uint256[] memory newSrcAmounts = new uint256[](reservesData.srcAmounts.length); uint256 destAmountSoFar; uint256 currentSrcAmount; uint256 destAmountSplit; for (uint256 i = 0; i < reservesData.srcAmounts.length; i++) { currentSrcAmount = reservesData.srcAmounts[i]; require(destAmount * currentSrcAmount * reservesData.rates[i] / destAmount == currentSrcAmount * reservesData.rates[i], "multiplication overflow"); destAmountSplit = i == (reservesData.srcAmounts.length - 1) ? (destAmount - destAmountSoFar) : (destAmount * currentSrcAmount * reservesData.rates[i]) / totalWeightedDestAmount; destAmountSoFar += destAmountSplit; newSrcAmounts[i] = calcSrcQty( destAmountSplit, srcDecimals, destDecimals, reservesData.rates[i] ); if (newSrcAmounts[i] > currentSrcAmount) { // revert back to use current src amounts return srcAmount; } newSrcAmount += newSrcAmounts[i]; } // new src amounts are used only when all of them aren't greater then current srcAmounts reservesData.srcAmounts = newSrcAmounts; } }
File 4 of 9: ABYSS
pragma solidity ^0.4.21; // File: contracts/ownership/MultiOwnable.sol /** * @title MultiOwnable * @dev The MultiOwnable contract has owners addresses and provides basic authorization control * functions, this simplifies the implementation of "users permissions". */ contract MultiOwnable { address public manager; // address used to set owners address[] public owners; mapping(address => bool) public ownerByAddress; event SetOwners(address[] owners); modifier onlyOwner() { require(ownerByAddress[msg.sender] == true); _; } /** * @dev MultiOwnable constructor sets the manager */ function MultiOwnable() public { manager = msg.sender; } /** * @dev Function to set owners addresses */ function setOwners(address[] _owners) public { require(msg.sender == manager); _setOwners(_owners); } function _setOwners(address[] _owners) internal { for(uint256 i = 0; i < owners.length; i++) { ownerByAddress[owners[i]] = false; } for(uint256 j = 0; j < _owners.length; j++) { ownerByAddress[_owners[j]] = true; } owners = _owners; SetOwners(_owners); } function getOwners() public constant returns (address[]) { return owners; } } // File: contracts/math/SafeMath.sol /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ contract SafeMath { /** * @dev constructor */ function SafeMath() public { } function safeMul(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a * b; assert(a == 0 || c / a == b); return c; } function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a / b; return c; } function safeSub(uint256 a, uint256 b) internal pure returns (uint256) { assert(a >= b); return a - b; } function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } // File: contracts/token/IERC20Token.sol /** * @title IERC20Token - ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract IERC20Token { string public name; string public symbol; uint8 public decimals; uint256 public totalSupply; function balanceOf(address _owner) public constant returns (uint256 balance); function transfer(address _to, uint256 _value) public returns (bool success); function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); function approve(address _spender, uint256 _value) public returns (bool success); function allowance(address _owner, address _spender) public constant returns (uint256 remaining); event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); } // File: contracts/token/ERC20Token.sol /** * @title ERC20Token - ERC20 base implementation * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20Token is IERC20Token, SafeMath { mapping (address => uint256) public balances; mapping (address => mapping (address => uint256)) public allowed; function transfer(address _to, uint256 _value) public returns (bool) { require(_to != address(0)); require(balances[msg.sender] >= _value); balances[msg.sender] = safeSub(balances[msg.sender], _value); balances[_to] = safeAdd(balances[_to], _value); Transfer(msg.sender, _to, _value); return true; } function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { require(_to != address(0)); require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value); balances[_to] = safeAdd(balances[_to], _value); balances[_from] = safeSub(balances[_from], _value); allowed[_from][msg.sender] = safeSub(allowed[_from][msg.sender], _value); Transfer(_from, _to, _value); return true; } function balanceOf(address _owner) public constant returns (uint256) { return balances[_owner]; } function approve(address _spender, uint256 _value) public returns (bool) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } function allowance(address _owner, address _spender) public constant returns (uint256) { return allowed[_owner][_spender]; } } // File: contracts/token/ITokenEventListener.sol /** * @title ITokenEventListener * @dev Interface which should be implemented by token listener */ interface ITokenEventListener { /** * @dev Function is called after token transfer/transferFrom * @param _from Sender address * @param _to Receiver address * @param _value Amount of tokens */ function onTokenTransfer(address _from, address _to, uint256 _value) external; } // File: contracts/token/ManagedToken.sol /** * @title ManagedToken * @dev ERC20 compatible token with issue and destroy facilities * @dev All transfers can be monitored by token event listener */ contract ManagedToken is ERC20Token, MultiOwnable { bool public allowTransfers = false; bool public issuanceFinished = false; ITokenEventListener public eventListener; event AllowTransfersChanged(bool _newState); event Issue(address indexed _to, uint256 _value); event Destroy(address indexed _from, uint256 _value); event IssuanceFinished(); modifier transfersAllowed() { require(allowTransfers); _; } modifier canIssue() { require(!issuanceFinished); _; } /** * @dev ManagedToken constructor * @param _listener Token listener(address can be 0x0) * @param _owners Owners list */ function ManagedToken(address _listener, address[] _owners) public { if(_listener != address(0)) { eventListener = ITokenEventListener(_listener); } _setOwners(_owners); } /** * @dev Enable/disable token transfers. Can be called only by owners * @param _allowTransfers True - allow False - disable */ function setAllowTransfers(bool _allowTransfers) external onlyOwner { allowTransfers = _allowTransfers; AllowTransfersChanged(_allowTransfers); } /** * @dev Set/remove token event listener * @param _listener Listener address (Contract must implement ITokenEventListener interface) */ function setListener(address _listener) public onlyOwner { if(_listener != address(0)) { eventListener = ITokenEventListener(_listener); } else { delete eventListener; } } function transfer(address _to, uint256 _value) public transfersAllowed returns (bool) { bool success = super.transfer(_to, _value); if(hasListener() && success) { eventListener.onTokenTransfer(msg.sender, _to, _value); } return success; } function transferFrom(address _from, address _to, uint256 _value) public transfersAllowed returns (bool) { bool success = super.transferFrom(_from, _to, _value); if(hasListener() && success) { eventListener.onTokenTransfer(_from, _to, _value); } return success; } function hasListener() internal view returns(bool) { if(eventListener == address(0)) { return false; } return true; } /** * @dev Issue tokens to specified wallet * @param _to Wallet address * @param _value Amount of tokens */ function issue(address _to, uint256 _value) external onlyOwner canIssue { totalSupply = safeAdd(totalSupply, _value); balances[_to] = safeAdd(balances[_to], _value); Issue(_to, _value); Transfer(address(0), _to, _value); } /** * @dev Destroy tokens on specified address (Called by owner or token holder) * @dev Fund contract address must be in the list of owners to burn token during refund * @param _from Wallet address * @param _value Amount of tokens to destroy */ function destroy(address _from, uint256 _value) external { require(ownerByAddress[msg.sender] || msg.sender == _from); require(balances[_from] >= _value); totalSupply = safeSub(totalSupply, _value); balances[_from] = safeSub(balances[_from], _value); Transfer(_from, address(0), _value); Destroy(_from, _value); } /** * @dev Increase the amount of tokens that an owner allowed to a spender. * * approve should be called when allowed[_spender] == 0. To increment * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From OpenZeppelin StandardToken.sol * @param _spender The address which will spend the funds. * @param _addedValue The amount of tokens to increase the allowance by. */ function increaseApproval(address _spender, uint _addedValue) public returns (bool) { allowed[msg.sender][_spender] = safeAdd(allowed[msg.sender][_spender], _addedValue); Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } /** * @dev Decrease the amount of tokens that an owner allowed to a spender. * * approve should be called when allowed[_spender] == 0. To decrement * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From OpenZeppelin StandardToken.sol * @param _spender The address which will spend the funds. * @param _subtractedValue The amount of tokens to decrease the allowance by. */ function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) { uint oldValue = allowed[msg.sender][_spender]; if (_subtractedValue > oldValue) { allowed[msg.sender][_spender] = 0; } else { allowed[msg.sender][_spender] = safeSub(oldValue, _subtractedValue); } Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } /** * @dev Finish token issuance * @return True if success */ function finishIssuance() public onlyOwner returns (bool) { issuanceFinished = true; IssuanceFinished(); return true; } } // File: contracts/token/TransferLimitedToken.sol /** * @title TransferLimitedToken * @dev Token with ability to limit transfers within wallets included in limitedWallets list for certain period of time */ contract TransferLimitedToken is ManagedToken { uint256 public constant LIMIT_TRANSFERS_PERIOD = 365 days; mapping(address => bool) public limitedWallets; uint256 public limitEndDate; address public limitedWalletsManager; bool public isLimitEnabled; event TransfersEnabled(); modifier onlyManager() { require(msg.sender == limitedWalletsManager); _; } /** * @dev Check if transfer between addresses is available * @param _from From address * @param _to To address */ modifier canTransfer(address _from, address _to) { require(now >= limitEndDate || !isLimitEnabled || (!limitedWallets[_from] && !limitedWallets[_to])); _; } /** * @dev TransferLimitedToken constructor * @param _limitStartDate Limit start date * @param _listener Token listener(address can be 0x0) * @param _owners Owners list * @param _limitedWalletsManager Address used to add/del wallets from limitedWallets */ function TransferLimitedToken( uint256 _limitStartDate, address _listener, address[] _owners, address _limitedWalletsManager ) public ManagedToken(_listener, _owners) { limitEndDate = _limitStartDate + LIMIT_TRANSFERS_PERIOD; isLimitEnabled = true; limitedWalletsManager = _limitedWalletsManager; } /** * @dev Enable token transfers */ function enableTransfers() public { require(msg.sender == limitedWalletsManager); allowTransfers = true; TransfersEnabled(); } /** * @dev Add address to limitedWallets * @dev Can be called only by manager */ function addLimitedWalletAddress(address _wallet) public { require(msg.sender == limitedWalletsManager || ownerByAddress[msg.sender]); limitedWallets[_wallet] = true; } /** * @dev Del address from limitedWallets * @dev Can be called only by manager */ function delLimitedWalletAddress(address _wallet) public onlyManager { limitedWallets[_wallet] = false; } /** * @dev Disable transfer limit manually. Can be called only by manager */ function disableLimit() public onlyManager { isLimitEnabled = false; } function transfer(address _to, uint256 _value) public canTransfer(msg.sender, _to) returns (bool) { return super.transfer(_to, _value); } function transferFrom(address _from, address _to, uint256 _value) public canTransfer(_from, _to) returns (bool) { return super.transferFrom(_from, _to, _value); } function approve(address _spender, uint256 _value) public canTransfer(msg.sender, _spender) returns (bool) { return super.approve(_spender,_value); } } // File: contracts/AbyssToken.sol contract ABYSS is TransferLimitedToken { uint256 public constant SALE_END_TIME = 1526479200; // 16.05.2018 14:00:00 UTC function ABYSS(address _listener, address[] _owners, address manager) public TransferLimitedToken(SALE_END_TIME, _listener, _owners, manager) { name = "ABYSS"; symbol = "ABYSS"; decimals = 18; } }
File 5 of 9: KyberFeeHandler
// File: contracts/sol6/IERC20.sol pragma solidity 0.6.6; interface IERC20 { event Approval(address indexed _owner, address indexed _spender, uint256 _value); function approve(address _spender, uint256 _value) external returns (bool success); function transfer(address _to, uint256 _value) external returns (bool success); function transferFrom( address _from, address _to, uint256 _value ) external returns (bool success); function allowance(address _owner, address _spender) external view returns (uint256 remaining); function balanceOf(address _owner) external view returns (uint256 balance); function decimals() external view returns (uint8 digits); function totalSupply() external view returns (uint256 supply); } // to support backward compatible contract name -- so function signature remains same abstract contract ERC20 is IERC20 { } // File: contracts/sol6/utils/Utils5.sol pragma solidity 0.6.6; /** * @title Kyber utility file * mostly shared constants and rate calculation helpers * inherited by most of kyber contracts. * previous utils implementations are for previous solidity versions. */ contract Utils5 { IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20( 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ); uint256 internal constant PRECISION = (10**18); uint256 internal constant MAX_QTY = (10**28); // 10B tokens uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth uint256 internal constant MAX_DECIMALS = 18; uint256 internal constant ETH_DECIMALS = 18; uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01% uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite mapping(IERC20 => uint256) internal decimals; function getUpdateDecimals(IERC20 token) internal returns (uint256) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint256 tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if (tokenDecimals == 0) { tokenDecimals = token.decimals(); decimals[token] = tokenDecimals; } return tokenDecimals; } function setDecimals(IERC20 token) internal { if (decimals[token] != 0) return; //already set if (token == ETH_TOKEN_ADDRESS) { decimals[token] = ETH_DECIMALS; } else { decimals[token] = token.decimals(); } } /// @dev get the balance of a user. /// @param token The token type /// @return The balance function getBalance(IERC20 token, address user) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) { return user.balance; } else { return token.balanceOf(user); } } function getDecimals(IERC20 token) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint256 tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if (tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDestAmount( IERC20 src, IERC20 dest, uint256 srcAmount, uint256 rate ) internal view returns (uint256) { return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate); } function calcSrcAmount( IERC20 src, IERC20 dest, uint256 destAmount, uint256 rate ) internal view returns (uint256) { return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate); } function calcDstQty( uint256 srcQty, uint256 srcDecimals, uint256 dstDecimals, uint256 rate ) internal pure returns (uint256) { require(srcQty <= MAX_QTY, "srcQty > MAX_QTY"); require(rate <= MAX_RATE, "rate > MAX_RATE"); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty( uint256 dstQty, uint256 srcDecimals, uint256 dstDecimals, uint256 rate ) internal pure returns (uint256) { require(dstQty <= MAX_QTY, "dstQty > MAX_QTY"); require(rate <= MAX_RATE, "rate > MAX_RATE"); //source quantity is rounded up. to avoid dest quantity being too low. uint256 numerator; uint256 denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } function calcRateFromQty( uint256 srcAmount, uint256 destAmount, uint256 srcDecimals, uint256 dstDecimals ) internal pure returns (uint256) { require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY"); require(destAmount <= MAX_QTY, "destAmount > MAX_QTY"); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount)); } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount); } } function minOf(uint256 x, uint256 y) internal pure returns (uint256) { return x > y ? y : x; } } // File: contracts/sol6/utils/zeppelin/ReentrancyGuard.sol pragma solidity 0.6.6; /** * @dev Contract module that helps prevent reentrant calls to a function. * * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier * available, which can be applied to functions to make sure there are no nested * (reentrant) calls to them. * * Note that because there is a single `nonReentrant` guard, functions marked as * `nonReentrant` may not call one another. This can be worked around by making * those functions `private`, and then adding `external` `nonReentrant` entry * points to them. * * TIP: If you would like to learn more about reentrancy and alternative ways * to protect against it, check out our blog post * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. */ contract ReentrancyGuard { bool private _notEntered; constructor () internal { // Storing an initial non-zero value makes deployment a bit more // expensive, but in exchange the refund on every call to nonReentrant // will be lower in amount. Since refunds are capped to a percetange of // the total transaction's gas, it is best to keep them low in cases // like this one, to increase the likelihood of the full refund coming // into effect. _notEntered = true; } /** * @dev Prevents a contract from calling itself, directly or indirectly. * Calling a `nonReentrant` function from another `nonReentrant` * function is not supported. It is possible to prevent this from happening * by making the `nonReentrant` function external, and make it call a * `private` function that does the actual work. */ modifier nonReentrant() { // On the first call to nonReentrant, _notEntered will be true require(_notEntered, "ReentrancyGuard: reentrant call"); // Any calls to nonReentrant after this point will fail _notEntered = false; _; // By storing the original value once again, a refund is triggered (see // https://eips.ethereum.org/EIPS/eip-2200) _notEntered = true; } } // File: contracts/sol6/Dao/IEpochUtils.sol pragma solidity 0.6.6; interface IEpochUtils { function epochPeriodInSeconds() external view returns (uint256); function firstEpochStartTimestamp() external view returns (uint256); function getCurrentEpochNumber() external view returns (uint256); function getEpochNumber(uint256 timestamp) external view returns (uint256); } // File: contracts/sol6/IKyberDao.sol pragma solidity 0.6.6; interface IKyberDao is IEpochUtils { event Voted(address indexed staker, uint indexed epoch, uint indexed campaignID, uint option); function getLatestNetworkFeeDataWithCache() external returns (uint256 feeInBps, uint256 expiryTimestamp); function getLatestBRRDataWithCache() external returns ( uint256 burnInBps, uint256 rewardInBps, uint256 rebateInBps, uint256 epoch, uint256 expiryTimestamp ); function handleWithdrawal(address staker, uint256 penaltyAmount) external; function vote(uint256 campaignID, uint256 option) external; function getLatestNetworkFeeData() external view returns (uint256 feeInBps, uint256 expiryTimestamp); function shouldBurnRewardForEpoch(uint256 epoch) external view returns (bool); /** * @dev return staker's reward percentage in precision for a past epoch only * fee handler should call this function when a staker wants to claim reward * return 0 if staker has no votes or stakes */ function getPastEpochRewardPercentageInPrecision(address staker, uint256 epoch) external view returns (uint256); /** * @dev return staker's reward percentage in precision for the current epoch * reward percentage is not finalized until the current epoch is ended */ function getCurrentEpochRewardPercentageInPrecision(address staker) external view returns (uint256); } // File: contracts/sol6/IKyberFeeHandler.sol pragma solidity 0.6.6; interface IKyberFeeHandler { event RewardPaid(address indexed staker, uint256 indexed epoch, IERC20 indexed token, uint256 amount); event RebatePaid(address indexed rebateWallet, IERC20 indexed token, uint256 amount); event PlatformFeePaid(address indexed platformWallet, IERC20 indexed token, uint256 amount); event KncBurned(uint256 kncTWei, IERC20 indexed token, uint256 amount); function handleFees( IERC20 token, address[] calldata eligibleWallets, uint256[] calldata rebatePercentages, address platformWallet, uint256 platformFee, uint256 networkFee ) external payable; function claimReserveRebate(address rebateWallet) external returns (uint256); function claimPlatformFee(address platformWallet) external returns (uint256); function claimStakerReward( address staker, uint256 epoch ) external returns(uint amount); } // File: contracts/sol6/IKyberNetworkProxy.sol pragma solidity 0.6.6; interface IKyberNetworkProxy { event ExecuteTrade( address indexed trader, IERC20 src, IERC20 dest, address destAddress, uint256 actualSrcAmount, uint256 actualDestAmount, address platformWallet, uint256 platformFeeBps ); /// @notice backward compatible function tradeWithHint( ERC20 src, uint256 srcAmount, ERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable walletId, bytes calldata hint ) external payable returns (uint256); function tradeWithHintAndFee( IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps, bytes calldata hint ) external payable returns (uint256 destAmount); function trade( IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet ) external payable returns (uint256); /// @notice backward compatible /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18 function getExpectedRate( ERC20 src, ERC20 dest, uint256 srcQty ) external view returns (uint256 expectedRate, uint256 worstRate); function getExpectedRateAfterFee( IERC20 src, IERC20 dest, uint256 srcQty, uint256 platformFeeBps, bytes calldata hint ) external view returns (uint256 expectedRate); } // File: contracts/sol6/ISimpleKyberProxy.sol pragma solidity 0.6.6; /* * @title simple Kyber Network proxy interface * add convenient functions to help with kyber proxy API */ interface ISimpleKyberProxy { function swapTokenToEther( IERC20 token, uint256 srcAmount, uint256 minConversionRate ) external returns (uint256 destAmount); function swapEtherToToken(IERC20 token, uint256 minConversionRate) external payable returns (uint256 destAmount); function swapTokenToToken( IERC20 src, uint256 srcAmount, IERC20 dest, uint256 minConversionRate ) external returns (uint256 destAmount); } // File: contracts/sol6/IBurnableToken.sol pragma solidity 0.6.6; interface IBurnableToken { function burn(uint256 _value) external returns (bool); } // File: contracts/sol6/Dao/ISanityRate.sol pragma solidity 0.6.6; /// @title Sanity Rate check to prevent burning knc with too expensive or cheap price /// @dev Using ChainLink as the provider for current knc/eth price interface ISanityRate { // return latest rate of knc/eth function latestAnswer() external view returns (uint256); } // File: contracts/sol6/utils/zeppelin/SafeMath.sol pragma solidity 0.6.6; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } } // File: contracts/sol6/Dao/DaoOperator.sol pragma solidity 0.6.6; contract DaoOperator { address public daoOperator; constructor(address _daoOperator) public { require(_daoOperator != address(0), "daoOperator is 0"); daoOperator = _daoOperator; } modifier onlyDaoOperator() { require(msg.sender == daoOperator, "only daoOperator"); _; } } // File: contracts/sol6/Dao/KyberFeeHandler.sol pragma solidity 0.6.6; /** * @title IKyberProxy * This interface combines two interfaces. * It is needed since we use one function from each of the interfaces. * */ interface IKyberProxy is IKyberNetworkProxy, ISimpleKyberProxy { // empty block } /** * @title kyberFeeHandler * * @dev kyberFeeHandler works tightly with contracts kyberNetwork and kyberDao. * Some events are moved to interface, for easier usage * @dev Terminology: * Epoch - Voting campaign time frame in kyberDao. * kyberDao voting campaigns are in the scope of epochs. * BRR - Burn / Reward / Rebate. kyberNetwork fee is used for 3 purposes: * Burning KNC * Reward an address that staked knc in kyberStaking contract. AKA - stakers * Rebate reserves for supporting trades. * @dev Code flow: * 1. Accumulating && claiming Fees. Per trade on kyberNetwork, it calls handleFees() function which * internally accounts for network & platform fees from the trade. Fee distribution: * rewards: accumulated per epoch. can be claimed by the kyberDao after epoch is concluded. * rebates: accumulated per rebate wallet, can be claimed any time. * Burn: accumulated in the contract. Burned value and interval limited with safe check using sanity rate. * Platfrom fee: accumulated per platform wallet, can be claimed any time. * 2. Network Fee distribution: Per epoch kyberFeeHandler contract reads BRR distribution percentage * from kyberDao. When the data expires, kyberFeeHandler reads updated values. */ contract KyberFeeHandler is IKyberFeeHandler, Utils5, DaoOperator, ReentrancyGuard { using SafeMath for uint256; uint256 internal constant DEFAULT_REWARD_BPS = 3000; uint256 internal constant DEFAULT_REBATE_BPS = 3000; uint256 internal constant SANITY_RATE_DIFF_BPS = 1000; // 10% struct BRRData { uint64 expiryTimestamp; uint32 epoch; uint16 rewardBps; uint16 rebateBps; } struct BRRWei { uint256 rewardWei; uint256 fullRebateWei; uint256 paidRebateWei; uint256 burnWei; } IKyberDao public kyberDao; IKyberProxy public kyberProxy; address public kyberNetwork; IERC20 public immutable knc; uint256 public immutable burnBlockInterval; uint256 public lastBurnBlock; BRRData public brrAndEpochData; address public daoSetter; /// @dev amount of eth to burn for each burn knc call uint256 public weiToBurn = 2 ether; mapping(address => uint256) public feePerPlatformWallet; mapping(address => uint256) public rebatePerWallet; mapping(uint256 => uint256) public rewardsPerEpoch; mapping(uint256 => uint256) public rewardsPaidPerEpoch; // hasClaimedReward[staker][epoch]: true/false if the staker has/hasn't claimed the reward for an epoch mapping(address => mapping (uint256 => bool)) public hasClaimedReward; uint256 public totalPayoutBalance; // total balance in the contract that is for rebate, reward, platform fee /// @dev use to get rate of KNC/ETH to check if rate to burn knc is normal /// @dev index 0 is currently used contract address, indexes > 0 are older versions ISanityRate[] internal sanityRateContract; event FeeDistributed( IERC20 indexed token, address indexed platformWallet, uint256 platformFeeWei, uint256 rewardWei, uint256 rebateWei, address[] rebateWallets, uint256[] rebatePercentBpsPerWallet, uint256 burnAmtWei ); event BRRUpdated( uint256 rewardBps, uint256 rebateBps, uint256 burnBps, uint256 expiryTimestamp, uint256 indexed epoch ); event EthReceived(uint256 amount); event KyberDaoAddressSet(IKyberDao kyberDao); event BurnConfigSet(ISanityRate sanityRate, uint256 weiToBurn); event RewardsRemovedToBurn(uint256 indexed epoch, uint256 rewardsWei); event KyberNetworkUpdated(address kyberNetwork); event KyberProxyUpdated(IKyberProxy kyberProxy); constructor( address _daoSetter, IKyberProxy _kyberProxy, address _kyberNetwork, IERC20 _knc, uint256 _burnBlockInterval, address _daoOperator ) public DaoOperator(_daoOperator) { require(_daoSetter != address(0), "daoSetter 0"); require(_kyberProxy != IKyberProxy(0), "kyberNetworkProxy 0"); require(_kyberNetwork != address(0), "kyberNetwork 0"); require(_knc != IERC20(0), "knc 0"); require(_burnBlockInterval != 0, "_burnBlockInterval 0"); daoSetter = _daoSetter; kyberProxy = _kyberProxy; kyberNetwork = _kyberNetwork; knc = _knc; burnBlockInterval = _burnBlockInterval; //start with epoch 0 updateBRRData(DEFAULT_REWARD_BPS, DEFAULT_REBATE_BPS, now, 0); } modifier onlyKyberDao { require(msg.sender == address(kyberDao), "only kyberDao"); _; } modifier onlyKyberNetwork { require(msg.sender == address(kyberNetwork), "only kyberNetwork"); _; } modifier onlyNonContract { require(tx.origin == msg.sender, "only non-contract"); _; } receive() external payable { emit EthReceived(msg.value); } /// @dev handleFees function is called per trade on kyberNetwork. unless the trade is not involving any fees. /// @param token Token currency of fees /// @param rebateWallets a list of rebate wallets that will get rebate for this trade. /// @param rebateBpsPerWallet percentage of rebate for each wallet, out of total rebate. /// @param platformWallet Wallet address that will receive the platfrom fee. /// @param platformFee Fee amount (in wei) the platfrom wallet is entitled to. /// @param networkFee Fee amount (in wei) to be allocated for BRR function handleFees( IERC20 token, address[] calldata rebateWallets, uint256[] calldata rebateBpsPerWallet, address platformWallet, uint256 platformFee, uint256 networkFee ) external payable override onlyKyberNetwork nonReentrant { require(token == ETH_TOKEN_ADDRESS, "token not eth"); require(msg.value == platformFee.add(networkFee), "msg.value not equal to total fees"); // handle platform fee feePerPlatformWallet[platformWallet] = feePerPlatformWallet[platformWallet].add( platformFee ); if (networkFee == 0) { // only platform fee paid totalPayoutBalance = totalPayoutBalance.add(platformFee); emit FeeDistributed( ETH_TOKEN_ADDRESS, platformWallet, platformFee, 0, 0, rebateWallets, rebateBpsPerWallet, 0 ); return; } BRRWei memory brrAmounts; uint256 epoch; // Decoding BRR data (brrAmounts.rewardWei, brrAmounts.fullRebateWei, epoch) = getRRWeiValues(networkFee); brrAmounts.paidRebateWei = updateRebateValues( brrAmounts.fullRebateWei, rebateWallets, rebateBpsPerWallet ); brrAmounts.rewardWei = brrAmounts.rewardWei.add( brrAmounts.fullRebateWei.sub(brrAmounts.paidRebateWei) ); rewardsPerEpoch[epoch] = rewardsPerEpoch[epoch].add(brrAmounts.rewardWei); // update total balance of rewards, rebates, fee totalPayoutBalance = totalPayoutBalance.add( platformFee).add(brrAmounts.rewardWei).add(brrAmounts.paidRebateWei ); brrAmounts.burnWei = networkFee.sub(brrAmounts.rewardWei).sub(brrAmounts.paidRebateWei); emit FeeDistributed( ETH_TOKEN_ADDRESS, platformWallet, platformFee, brrAmounts.rewardWei, brrAmounts.paidRebateWei, rebateWallets, rebateBpsPerWallet, brrAmounts.burnWei ); } /// @notice WARNING When staker address is a contract, /// it should be able to receive claimed reward in ETH whenever anyone calls this function. /// @dev not revert if already claimed or reward percentage is 0 /// allow writing a wrapper to claim for multiple epochs /// @param staker address. /// @param epoch for which epoch the staker is claiming the reward function claimStakerReward( address staker, uint256 epoch ) external override nonReentrant returns(uint256 amountWei) { if (hasClaimedReward[staker][epoch]) { // staker has already claimed reward for the epoch return 0; } // the relative part of the reward the staker is entitled to for the epoch. // units Precision: 10 ** 18 = 100% // if the epoch is current or in the future, kyberDao will return 0 as result uint256 percentageInPrecision = kyberDao.getPastEpochRewardPercentageInPrecision(staker, epoch); if (percentageInPrecision == 0) { return 0; // not revert, in case a wrapper wants to claim reward for multiple epochs } require(percentageInPrecision <= PRECISION, "percentage too high"); // Amount of reward to be sent to staker amountWei = rewardsPerEpoch[epoch].mul(percentageInPrecision).div(PRECISION); // redundant check, can't happen assert(totalPayoutBalance >= amountWei); assert(rewardsPaidPerEpoch[epoch].add(amountWei) <= rewardsPerEpoch[epoch]); rewardsPaidPerEpoch[epoch] = rewardsPaidPerEpoch[epoch].add(amountWei); totalPayoutBalance = totalPayoutBalance.sub(amountWei); hasClaimedReward[staker][epoch] = true; // send reward to staker (bool success, ) = staker.call{value: amountWei}(""); require(success, "staker rewards transfer failed"); emit RewardPaid(staker, epoch, ETH_TOKEN_ADDRESS, amountWei); } /// @dev claim rebate per reserve wallet. called by any address /// @param rebateWallet the wallet to claim rebates for. Total accumulated rebate sent to this wallet. /// @return amountWei amount of rebate claimed function claimReserveRebate(address rebateWallet) external override nonReentrant returns (uint256 amountWei) { require(rebatePerWallet[rebateWallet] > 1, "no rebate to claim"); // Get total amount of rebate accumulated amountWei = rebatePerWallet[rebateWallet].sub(1); // redundant check, can't happen assert(totalPayoutBalance >= amountWei); totalPayoutBalance = totalPayoutBalance.sub(amountWei); rebatePerWallet[rebateWallet] = 1; // avoid zero to non zero storage cost // send rebate to rebate wallet (bool success, ) = rebateWallet.call{value: amountWei}(""); require(success, "rebate transfer failed"); emit RebatePaid(rebateWallet, ETH_TOKEN_ADDRESS, amountWei); return amountWei; } /// @dev claim accumulated fee per platform wallet. Called by any address /// @param platformWallet the wallet to claim fee for. Total accumulated fee sent to this wallet. /// @return amountWei amount of fee claimed function claimPlatformFee(address platformWallet) external override nonReentrant returns (uint256 amountWei) { require(feePerPlatformWallet[platformWallet] > 1, "no fee to claim"); // Get total amount of fees accumulated amountWei = feePerPlatformWallet[platformWallet].sub(1); // redundant check, can't happen assert(totalPayoutBalance >= amountWei); totalPayoutBalance = totalPayoutBalance.sub(amountWei); feePerPlatformWallet[platformWallet] = 1; // avoid zero to non zero storage cost (bool success, ) = platformWallet.call{value: amountWei}(""); require(success, "platform fee transfer failed"); emit PlatformFeePaid(platformWallet, ETH_TOKEN_ADDRESS, amountWei); return amountWei; } /// @dev set kyberDao contract address once and set setter address to zero. /// @param _kyberDao kyberDao address. function setDaoContract(IKyberDao _kyberDao) external { require(msg.sender == daoSetter, "only daoSetter"); require(_kyberDao != IKyberDao(0)); kyberDao = _kyberDao; emit KyberDaoAddressSet(kyberDao); daoSetter = address(0); } /// @dev set new kyberNetwork address by daoOperator /// @param _kyberNetwork new kyberNetwork contract function setNetworkContract(address _kyberNetwork) external onlyDaoOperator { require(_kyberNetwork != address(0), "kyberNetwork 0"); if (_kyberNetwork != kyberNetwork) { kyberNetwork = _kyberNetwork; emit KyberNetworkUpdated(kyberNetwork); } } /// @dev Allow to set kyberNetworkProxy address by daoOperator /// @param _newProxy new kyberNetworkProxy contract function setKyberProxy(IKyberProxy _newProxy) external onlyDaoOperator { require(_newProxy != IKyberProxy(0), "kyberNetworkProxy 0"); if (_newProxy != kyberProxy) { kyberProxy = _newProxy; emit KyberProxyUpdated(_newProxy); } } /// @dev set knc sanity rate contract and amount wei to burn /// @param _sanityRate new sanity rate contract /// @param _weiToBurn new amount of wei to burn function setBurnConfigParams(ISanityRate _sanityRate, uint256 _weiToBurn) external onlyDaoOperator { require(_weiToBurn > 0, "_weiToBurn is 0"); if (sanityRateContract.length == 0 || (_sanityRate != sanityRateContract[0])) { // it is a new sanity rate contract if (sanityRateContract.length == 0) { sanityRateContract.push(_sanityRate); } else { sanityRateContract.push(sanityRateContract[0]); sanityRateContract[0] = _sanityRate; } } weiToBurn = _weiToBurn; emit BurnConfigSet(_sanityRate, _weiToBurn); } /// @dev Burn knc. The burn amount is limited. Forces block delay between burn calls. /// @dev only none ontract can call this function /// @return kncBurnAmount amount of knc burned function burnKnc() external onlyNonContract returns (uint256 kncBurnAmount) { // check if current block > last burn block number + num block interval require(block.number > lastBurnBlock + burnBlockInterval, "wait more blocks to burn"); // update last burn block number lastBurnBlock = block.number; // Get amount to burn, if greater than weiToBurn, burn only weiToBurn per function call. uint256 balance = address(this).balance; // redundant check, can't happen assert(balance >= totalPayoutBalance); uint256 srcAmount = balance.sub(totalPayoutBalance); srcAmount = srcAmount > weiToBurn ? weiToBurn : srcAmount; // Get rate uint256 kyberEthKncRate = kyberProxy.getExpectedRateAfterFee( ETH_TOKEN_ADDRESS, knc, srcAmount, 0, "" ); validateEthToKncRateToBurn(kyberEthKncRate); // Buy some knc and burn kncBurnAmount = kyberProxy.swapEtherToToken{value: srcAmount}( knc, kyberEthKncRate ); require(IBurnableToken(address(knc)).burn(kncBurnAmount), "knc burn failed"); emit KncBurned(kncBurnAmount, ETH_TOKEN_ADDRESS, srcAmount); return kncBurnAmount; } /// @dev if no one voted for an epoch (like epoch 0), no one gets rewards - should burn it. /// Will move the epoch reward amount to burn amount. So can later be burned. /// calls kyberDao contract to check if there were any votes for this epoch. /// @param epoch epoch number to check. function makeEpochRewardBurnable(uint256 epoch) external { require(kyberDao != IKyberDao(0), "kyberDao not set"); require(kyberDao.shouldBurnRewardForEpoch(epoch), "should not burn reward"); uint256 rewardAmount = rewardsPerEpoch[epoch]; require(rewardAmount > 0, "reward is 0"); // redundant check, can't happen require(totalPayoutBalance >= rewardAmount, "total reward less than epoch reward"); totalPayoutBalance = totalPayoutBalance.sub(rewardAmount); rewardsPerEpoch[epoch] = 0; emit RewardsRemovedToBurn(epoch, rewardAmount); } /// @notice should be called off chain /// @dev returns list of sanity rate contracts /// @dev index 0 is currently used contract address, indexes > 0 are older versions function getSanityRateContracts() external view returns (ISanityRate[] memory sanityRates) { sanityRates = sanityRateContract; } /// @dev return latest knc/eth rate from sanity rate contract function getLatestSanityRate() external view returns (uint256 kncToEthSanityRate) { if (sanityRateContract.length > 0 && sanityRateContract[0] != ISanityRate(0)) { kncToEthSanityRate = sanityRateContract[0].latestAnswer(); } else { kncToEthSanityRate = 0; } } function getBRR() public returns ( uint256 rewardBps, uint256 rebateBps, uint256 epoch ) { uint256 expiryTimestamp; (rewardBps, rebateBps, expiryTimestamp, epoch) = readBRRData(); // Check current timestamp if (now > expiryTimestamp && kyberDao != IKyberDao(0)) { uint256 burnBps; (burnBps, rewardBps, rebateBps, epoch, expiryTimestamp) = kyberDao .getLatestBRRDataWithCache(); require(burnBps.add(rewardBps).add(rebateBps) == BPS, "Bad BRR values"); emit BRRUpdated(rewardBps, rebateBps, burnBps, expiryTimestamp, epoch); // Update brrAndEpochData updateBRRData(rewardBps, rebateBps, expiryTimestamp, epoch); } } function readBRRData() public view returns ( uint256 rewardBps, uint256 rebateBps, uint256 expiryTimestamp, uint256 epoch ) { rewardBps = uint256(brrAndEpochData.rewardBps); rebateBps = uint256(brrAndEpochData.rebateBps); epoch = uint256(brrAndEpochData.epoch); expiryTimestamp = uint256(brrAndEpochData.expiryTimestamp); } function updateBRRData( uint256 reward, uint256 rebate, uint256 expiryTimestamp, uint256 epoch ) internal { // reward and rebate combined values <= BPS. Tested in getBRR. require(expiryTimestamp < 2**64, "expiry timestamp overflow"); require(epoch < 2**32, "epoch overflow"); brrAndEpochData.rewardBps = uint16(reward); brrAndEpochData.rebateBps = uint16(rebate); brrAndEpochData.expiryTimestamp = uint64(expiryTimestamp); brrAndEpochData.epoch = uint32(epoch); } function getRRWeiValues(uint256 RRAmountWei) internal returns ( uint256 rewardWei, uint256 rebateWei, uint256 epoch ) { // Decoding BRR data uint256 rewardInBps; uint256 rebateInBps; (rewardInBps, rebateInBps, epoch) = getBRR(); rebateWei = RRAmountWei.mul(rebateInBps).div(BPS); rewardWei = RRAmountWei.mul(rewardInBps).div(BPS); } function updateRebateValues( uint256 rebateWei, address[] memory rebateWallets, uint256[] memory rebateBpsPerWallet ) internal returns (uint256 totalRebatePaidWei) { uint256 totalRebateBps; uint256 walletRebateWei; for (uint256 i = 0; i < rebateWallets.length; i++) { require(rebateWallets[i] != address(0), "rebate wallet address 0"); walletRebateWei = rebateWei.mul(rebateBpsPerWallet[i]).div(BPS); rebatePerWallet[rebateWallets[i]] = rebatePerWallet[rebateWallets[i]].add( walletRebateWei ); // a few wei could be left out due to rounding down. so count only paid wei totalRebatePaidWei = totalRebatePaidWei.add(walletRebateWei); totalRebateBps = totalRebateBps.add(rebateBpsPerWallet[i]); } require(totalRebateBps <= BPS, "rebates more then 100%"); } function validateEthToKncRateToBurn(uint256 rateEthToKnc) internal view { require(rateEthToKnc <= MAX_RATE, "ethToKnc rate out of bounds"); require(rateEthToKnc > 0, "ethToKnc rate is 0"); require(sanityRateContract.length > 0, "no sanity rate contract"); require(sanityRateContract[0] != ISanityRate(0), "sanity rate is 0x0, burning is blocked"); // get latest knc/eth rate from sanity contract uint256 kncToEthRate = sanityRateContract[0].latestAnswer(); require(kncToEthRate > 0, "sanity rate is 0"); require(kncToEthRate <= MAX_RATE, "sanity rate out of bounds"); uint256 sanityEthToKncRate = PRECISION.mul(PRECISION).div(kncToEthRate); // rate shouldn't be SANITY_RATE_DIFF_BPS lower than sanity rate require( rateEthToKnc.mul(BPS) >= sanityEthToKncRate.mul(BPS.sub(SANITY_RATE_DIFF_BPS)), "kyberNetwork eth to knc rate too low" ); } }
File 6 of 9: KyberMatchingEngine
// File: contracts/sol6/IERC20.sol pragma solidity 0.6.6; interface IERC20 { event Approval(address indexed _owner, address indexed _spender, uint256 _value); function approve(address _spender, uint256 _value) external returns (bool success); function transfer(address _to, uint256 _value) external returns (bool success); function transferFrom( address _from, address _to, uint256 _value ) external returns (bool success); function allowance(address _owner, address _spender) external view returns (uint256 remaining); function balanceOf(address _owner) external view returns (uint256 balance); function decimals() external view returns (uint8 digits); function totalSupply() external view returns (uint256 supply); } // to support backward compatible contract name -- so function signature remains same abstract contract ERC20 is IERC20 { } // File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol pragma solidity 0.6.6; contract PermissionGroupsNoModifiers { address public admin; address public pendingAdmin; mapping(address => bool) internal operators; mapping(address => bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint256 internal constant MAX_GROUP_SIZE = 50; event AdminClaimed(address newAdmin, address previousAdmin); event AlerterAdded(address newAlerter, bool isAdd); event OperatorAdded(address newOperator, bool isAdd); event TransferAdminPending(address pendingAdmin); constructor(address _admin) public { require(_admin != address(0), "admin 0"); admin = _admin; } function getOperators() external view returns (address[] memory) { return operatorsGroup; } function getAlerters() external view returns (address[] memory) { return alertersGroup; } function addAlerter(address newAlerter) public { onlyAdmin(); require(!alerters[newAlerter], "alerter exists"); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters"); emit AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function addOperator(address newOperator) public { onlyAdmin(); require(!operators[newOperator], "operator exists"); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators"); emit OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } /// @dev Allows the pendingAdmin address to finalize the change admin process. function claimAdmin() public { require(pendingAdmin == msg.sender, "not pending"); emit AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } function removeAlerter(address alerter) public { onlyAdmin(); require(alerters[alerter], "not alerter"); delete alerters[alerter]; for (uint256 i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.pop(); emit AlerterAdded(alerter, false); break; } } } function removeOperator(address operator) public { onlyAdmin(); require(operators[operator], "not operator"); delete operators[operator]; for (uint256 i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.pop(); emit OperatorAdded(operator, false); break; } } } /// @dev Allows the current admin to set the pendingAdmin address /// @param newAdmin The address to transfer ownership to function transferAdmin(address newAdmin) public { onlyAdmin(); require(newAdmin != address(0), "new admin 0"); emit TransferAdminPending(newAdmin); pendingAdmin = newAdmin; } /// @dev Allows the current admin to set the admin in one tx. Useful initial deployment. /// @param newAdmin The address to transfer ownership to. function transferAdminQuickly(address newAdmin) public { onlyAdmin(); require(newAdmin != address(0), "admin 0"); emit TransferAdminPending(newAdmin); emit AdminClaimed(newAdmin, admin); admin = newAdmin; } function onlyAdmin() internal view { require(msg.sender == admin, "only admin"); } function onlyAlerter() internal view { require(alerters[msg.sender], "only alerter"); } function onlyOperator() internal view { require(operators[msg.sender], "only operator"); } } // File: contracts/sol6/utils/WithdrawableNoModifiers.sol pragma solidity 0.6.6; contract WithdrawableNoModifiers is PermissionGroupsNoModifiers { constructor(address _admin) public PermissionGroupsNoModifiers(_admin) {} event EtherWithdraw(uint256 amount, address sendTo); event TokenWithdraw(IERC20 token, uint256 amount, address sendTo); /// @dev Withdraw Ethers function withdrawEther(uint256 amount, address payable sendTo) external { onlyAdmin(); (bool success, ) = sendTo.call{value: amount}(""); require(success); emit EtherWithdraw(amount, sendTo); } /// @dev Withdraw all IERC20 compatible tokens /// @param token IERC20 The address of the token contract function withdrawToken( IERC20 token, uint256 amount, address sendTo ) external { onlyAdmin(); token.transfer(sendTo, amount); emit TokenWithdraw(token, amount, sendTo); } } // File: contracts/sol6/IKyberReserve.sol pragma solidity 0.6.6; interface IKyberReserve { function trade( IERC20 srcToken, uint256 srcAmount, IERC20 destToken, address payable destAddress, uint256 conversionRate, bool validate ) external payable returns (bool); function getConversionRate( IERC20 src, IERC20 dest, uint256 srcQty, uint256 blockNumber ) external view returns (uint256); } // File: contracts/sol6/IKyberNetwork.sol pragma solidity 0.6.6; interface IKyberNetwork { event KyberTrade( IERC20 indexed src, IERC20 indexed dest, uint256 ethWeiValue, uint256 networkFeeWei, uint256 customPlatformFeeWei, bytes32[] t2eIds, bytes32[] e2tIds, uint256[] t2eSrcAmounts, uint256[] e2tSrcAmounts, uint256[] t2eRates, uint256[] e2tRates ); function tradeWithHintAndFee( address payable trader, IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps, bytes calldata hint ) external payable returns (uint256 destAmount); function listTokenForReserve( address reserve, IERC20 token, bool add ) external; function enabled() external view returns (bool); function getExpectedRateWithHintAndFee( IERC20 src, IERC20 dest, uint256 srcQty, uint256 platformFeeBps, bytes calldata hint ) external view returns ( uint256 expectedRateAfterNetworkFee, uint256 expectedRateAfterAllFees ); function getNetworkData() external view returns ( uint256 negligibleDiffBps, uint256 networkFeeBps, uint256 expiryTimestamp ); function maxGasPrice() external view returns (uint256); } // File: contracts/sol6/IKyberNetworkProxy.sol pragma solidity 0.6.6; interface IKyberNetworkProxy { event ExecuteTrade( address indexed trader, IERC20 src, IERC20 dest, address destAddress, uint256 actualSrcAmount, uint256 actualDestAmount, address platformWallet, uint256 platformFeeBps ); /// @notice backward compatible function tradeWithHint( ERC20 src, uint256 srcAmount, ERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable walletId, bytes calldata hint ) external payable returns (uint256); function tradeWithHintAndFee( IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps, bytes calldata hint ) external payable returns (uint256 destAmount); function trade( IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet ) external payable returns (uint256); /// @notice backward compatible /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18 function getExpectedRate( ERC20 src, ERC20 dest, uint256 srcQty ) external view returns (uint256 expectedRate, uint256 worstRate); function getExpectedRateAfterFee( IERC20 src, IERC20 dest, uint256 srcQty, uint256 platformFeeBps, bytes calldata hint ) external view returns (uint256 expectedRate); } // File: contracts/sol6/IKyberStorage.sol pragma solidity 0.6.6; interface IKyberStorage { enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST} function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies) external; function removeKyberProxy(address kyberProxy) external; function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external; function setKyberDaoContract(address _kyberDao) external; function getReserveId(address reserve) external view returns (bytes32 reserveId); function getReserveIdsFromAddresses(address[] calldata reserveAddresses) external view returns (bytes32[] memory reserveIds); function getReserveAddressesFromIds(bytes32[] calldata reserveIds) external view returns (address[] memory reserveAddresses); function getReserveIdsPerTokenSrc(IERC20 token) external view returns (bytes32[] memory reserveIds); function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex) external view returns (address[] memory reserveAddresses); function getReserveIdsPerTokenDest(IERC20 token) external view returns (bytes32[] memory reserveIds); function getReserveAddressesByReserveId(bytes32 reserveId) external view returns (address[] memory reserveAddresses); function getRebateWalletsFromIds(bytes32[] calldata reserveIds) external view returns (address[] memory rebateWallets); function getKyberProxies() external view returns (IKyberNetworkProxy[] memory); function getReserveDetailsByAddress(address reserve) external view returns ( bytes32 reserveId, address rebateWallet, ReserveType resType, bool isFeeAccountedFlag, bool isEntitledRebateFlag ); function getReserveDetailsById(bytes32 reserveId) external view returns ( address reserveAddress, address rebateWallet, ReserveType resType, bool isFeeAccountedFlag, bool isEntitledRebateFlag ); function getFeeAccountedData(bytes32[] calldata reserveIds) external view returns (bool[] memory feeAccountedArr); function getEntitledRebateData(bytes32[] calldata reserveIds) external view returns (bool[] memory entitledRebateArr); function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest) external view returns ( bool areAllReservesListed, bool[] memory feeAccountedArr, bool[] memory entitledRebateArr, IKyberReserve[] memory reserveAddresses); function isKyberProxyAdded() external view returns (bool); } // File: contracts/sol6/IKyberMatchingEngine.sol pragma solidity 0.6.6; interface IKyberMatchingEngine { enum ProcessWithRate {NotRequired, Required} function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps) external; function setKyberStorage(IKyberStorage _kyberStorage) external; function getNegligibleRateDiffBps() external view returns (uint256); function getTradingReserves( IERC20 src, IERC20 dest, bool isTokenToToken, bytes calldata hint ) external view returns ( bytes32[] memory reserveIds, uint256[] memory splitValuesBps, ProcessWithRate processWithRate ); function doMatch( IERC20 src, IERC20 dest, uint256[] calldata srcAmounts, uint256[] calldata feesAccountedDestBps, uint256[] calldata rates ) external view returns (uint256[] memory reserveIndexes); } // File: contracts/sol6/utils/Utils5.sol pragma solidity 0.6.6; /** * @title Kyber utility file * mostly shared constants and rate calculation helpers * inherited by most of kyber contracts. * previous utils implementations are for previous solidity versions. */ contract Utils5 { IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20( 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ); uint256 internal constant PRECISION = (10**18); uint256 internal constant MAX_QTY = (10**28); // 10B tokens uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth uint256 internal constant MAX_DECIMALS = 18; uint256 internal constant ETH_DECIMALS = 18; uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01% uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite mapping(IERC20 => uint256) internal decimals; function getUpdateDecimals(IERC20 token) internal returns (uint256) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint256 tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if (tokenDecimals == 0) { tokenDecimals = token.decimals(); decimals[token] = tokenDecimals; } return tokenDecimals; } function setDecimals(IERC20 token) internal { if (decimals[token] != 0) return; //already set if (token == ETH_TOKEN_ADDRESS) { decimals[token] = ETH_DECIMALS; } else { decimals[token] = token.decimals(); } } /// @dev get the balance of a user. /// @param token The token type /// @return The balance function getBalance(IERC20 token, address user) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) { return user.balance; } else { return token.balanceOf(user); } } function getDecimals(IERC20 token) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint256 tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if (tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDestAmount( IERC20 src, IERC20 dest, uint256 srcAmount, uint256 rate ) internal view returns (uint256) { return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate); } function calcSrcAmount( IERC20 src, IERC20 dest, uint256 destAmount, uint256 rate ) internal view returns (uint256) { return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate); } function calcDstQty( uint256 srcQty, uint256 srcDecimals, uint256 dstDecimals, uint256 rate ) internal pure returns (uint256) { require(srcQty <= MAX_QTY, "srcQty > MAX_QTY"); require(rate <= MAX_RATE, "rate > MAX_RATE"); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty( uint256 dstQty, uint256 srcDecimals, uint256 dstDecimals, uint256 rate ) internal pure returns (uint256) { require(dstQty <= MAX_QTY, "dstQty > MAX_QTY"); require(rate <= MAX_RATE, "rate > MAX_RATE"); //source quantity is rounded up. to avoid dest quantity being too low. uint256 numerator; uint256 denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } function calcRateFromQty( uint256 srcAmount, uint256 destAmount, uint256 srcDecimals, uint256 dstDecimals ) internal pure returns (uint256) { require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY"); require(destAmount <= MAX_QTY, "destAmount > MAX_QTY"); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount)); } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount); } } function minOf(uint256 x, uint256 y) internal pure returns (uint256) { return x > y ? y : x; } } // File: contracts/sol6/IKyberHint.sol pragma solidity 0.6.6; interface IKyberHint { enum TradeType {BestOfAll, MaskIn, MaskOut, Split} enum HintErrors { NoError, // Hint is valid NonEmptyDataError, // reserveIDs and splits must be empty for BestOfAll hint ReserveIdDupError, // duplicate reserveID found ReserveIdEmptyError, // reserveIDs array is empty for MaskIn and Split trade type ReserveIdSplitsError, // reserveIDs and splitBpsValues arrays do not have the same length ReserveIdSequenceError, // reserveID sequence in array is not in increasing order ReserveIdNotFound, // reserveID isn't registered or doesn't exist SplitsNotEmptyError, // splitBpsValues is not empty for MaskIn or MaskOut trade type TokenListedError, // reserveID not listed for the token TotalBPSError // total BPS for Split trade type is not 10000 (100%) } function buildTokenToEthHint( IERC20 tokenSrc, TradeType tokenToEthType, bytes32[] calldata tokenToEthReserveIds, uint256[] calldata tokenToEthSplits ) external view returns (bytes memory hint); function buildEthToTokenHint( IERC20 tokenDest, TradeType ethToTokenType, bytes32[] calldata ethToTokenReserveIds, uint256[] calldata ethToTokenSplits ) external view returns (bytes memory hint); function buildTokenToTokenHint( IERC20 tokenSrc, TradeType tokenToEthType, bytes32[] calldata tokenToEthReserveIds, uint256[] calldata tokenToEthSplits, IERC20 tokenDest, TradeType ethToTokenType, bytes32[] calldata ethToTokenReserveIds, uint256[] calldata ethToTokenSplits ) external view returns (bytes memory hint); function parseTokenToEthHint(IERC20 tokenSrc, bytes calldata hint) external view returns ( TradeType tokenToEthType, bytes32[] memory tokenToEthReserveIds, IKyberReserve[] memory tokenToEthAddresses, uint256[] memory tokenToEthSplits ); function parseEthToTokenHint(IERC20 tokenDest, bytes calldata hint) external view returns ( TradeType ethToTokenType, bytes32[] memory ethToTokenReserveIds, IKyberReserve[] memory ethToTokenAddresses, uint256[] memory ethToTokenSplits ); function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes calldata hint) external view returns ( TradeType tokenToEthType, bytes32[] memory tokenToEthReserveIds, IKyberReserve[] memory tokenToEthAddresses, uint256[] memory tokenToEthSplits, TradeType ethToTokenType, bytes32[] memory ethToTokenReserveIds, IKyberReserve[] memory ethToTokenAddresses, uint256[] memory ethToTokenSplits ); } // File: contracts/sol6/KyberHintHandler.sol pragma solidity 0.6.6; /** * @title kyberHintHandler contract * The contract provides the following functionality: * - building hints * - parsing hints * * All external functions, build*Hint() and parse*Hint: * - Will revert with error message if an error is found * - parse*Hint() returns both reserveIds and reserveAddresses * Internal functions unpackT2THint() and parseHint(): * - Are part of get rate && trade flow * - Don't revert if an error is found * - If an error is found, return no data such that the trade flow * returns 0 rate for bad hint values */ abstract contract KyberHintHandler is IKyberHint, Utils5 { /// @notice Parses the hint for a token -> eth trade /// @param tokenSrc source token to trade /// @param hint The ABI encoded hint, built using the build*Hint functions /// @return tokenToEthType Decoded hint type /// @return tokenToEthReserveIds Decoded reserve IDs /// @return tokenToEthAddresses Reserve addresses corresponding to reserve IDs /// @return tokenToEthSplits Decoded splits function parseTokenToEthHint(IERC20 tokenSrc, bytes memory hint) public view override returns ( TradeType tokenToEthType, bytes32[] memory tokenToEthReserveIds, IKyberReserve[] memory tokenToEthAddresses, uint256[] memory tokenToEthSplits ) { HintErrors error; (tokenToEthType, tokenToEthReserveIds, tokenToEthSplits, error) = parseHint(hint); if (error != HintErrors.NoError) throwHintError(error); if (tokenToEthType == TradeType.MaskIn || tokenToEthType == TradeType.Split) { checkTokenListedForReserve(tokenSrc, tokenToEthReserveIds, true); } tokenToEthAddresses = new IKyberReserve[](tokenToEthReserveIds.length); for (uint256 i = 0; i < tokenToEthReserveIds.length; i++) { checkReserveIdsExists(tokenToEthReserveIds[i]); checkDuplicateReserveIds(tokenToEthReserveIds, i); if (i > 0 && tokenToEthType == TradeType.Split) { checkSplitReserveIdSeq(tokenToEthReserveIds[i], tokenToEthReserveIds[i - 1]); } tokenToEthAddresses[i] = IKyberReserve( getReserveAddress(tokenToEthReserveIds[i]) ); } } /// @notice Parses the hint for a eth -> token trade /// @param tokenDest destination token to trade /// @param hint The ABI encoded hint, built using the build*Hint functions /// @return ethToTokenType Decoded hint type /// @return ethToTokenReserveIds Decoded reserve IDs /// @return ethToTokenAddresses Reserve addresses corresponding to reserve IDs /// @return ethToTokenSplits Decoded splits function parseEthToTokenHint(IERC20 tokenDest, bytes memory hint) public view override returns ( TradeType ethToTokenType, bytes32[] memory ethToTokenReserveIds, IKyberReserve[] memory ethToTokenAddresses, uint256[] memory ethToTokenSplits ) { HintErrors error; (ethToTokenType, ethToTokenReserveIds, ethToTokenSplits, error) = parseHint(hint); if (error != HintErrors.NoError) throwHintError(error); if (ethToTokenType == TradeType.MaskIn || ethToTokenType == TradeType.Split) { checkTokenListedForReserve(tokenDest, ethToTokenReserveIds, false); } ethToTokenAddresses = new IKyberReserve[](ethToTokenReserveIds.length); for (uint256 i = 0; i < ethToTokenReserveIds.length; i++) { checkReserveIdsExists(ethToTokenReserveIds[i]); checkDuplicateReserveIds(ethToTokenReserveIds, i); if (i > 0 && ethToTokenType == TradeType.Split) { checkSplitReserveIdSeq(ethToTokenReserveIds[i], ethToTokenReserveIds[i - 1]); } ethToTokenAddresses[i] = IKyberReserve( getReserveAddress(ethToTokenReserveIds[i]) ); } } /// @notice Parses the hint for a token to token trade /// @param tokenSrc source token to trade /// @param tokenDest destination token to trade /// @param hint The ABI encoded hint, built using the build*Hint functions /// @return tokenToEthType Decoded hint type /// @return tokenToEthReserveIds Decoded reserve IDs /// @return tokenToEthAddresses Reserve addresses corresponding to reserve IDs /// @return tokenToEthSplits Decoded splits /// @return ethToTokenType Decoded hint type /// @return ethToTokenReserveIds Decoded reserve IDs /// @return ethToTokenAddresses Reserve addresses corresponding to reserve IDs /// @return ethToTokenSplits Decoded splits function parseTokenToTokenHint(IERC20 tokenSrc, IERC20 tokenDest, bytes memory hint) public view override returns ( TradeType tokenToEthType, bytes32[] memory tokenToEthReserveIds, IKyberReserve[] memory tokenToEthAddresses, uint256[] memory tokenToEthSplits, TradeType ethToTokenType, bytes32[] memory ethToTokenReserveIds, IKyberReserve[] memory ethToTokenAddresses, uint256[] memory ethToTokenSplits ) { bytes memory t2eHint; bytes memory e2tHint; (t2eHint, e2tHint) = unpackT2THint(hint); ( tokenToEthType, tokenToEthReserveIds, tokenToEthAddresses, tokenToEthSplits ) = parseTokenToEthHint(tokenSrc, t2eHint); ( ethToTokenType, ethToTokenReserveIds, ethToTokenAddresses, ethToTokenSplits ) = parseEthToTokenHint(tokenDest, e2tHint); } /// @notice Builds the hint for a token -> eth trade /// @param tokenSrc source token to trade /// @param tokenToEthType token -> eth trade hint type /// @param tokenToEthReserveIds token -> eth reserve IDs /// @param tokenToEthSplits token -> eth reserve splits /// @return hint The ABI encoded hint function buildTokenToEthHint( IERC20 tokenSrc, TradeType tokenToEthType, bytes32[] memory tokenToEthReserveIds, uint256[] memory tokenToEthSplits ) public view override returns (bytes memory hint) { for (uint256 i = 0; i < tokenToEthReserveIds.length; i++) { checkReserveIdsExists(tokenToEthReserveIds[i]); } HintErrors valid = verifyData( tokenToEthType, tokenToEthReserveIds, tokenToEthSplits ); if (valid != HintErrors.NoError) throwHintError(valid); if (tokenToEthType == TradeType.MaskIn || tokenToEthType == TradeType.Split) { checkTokenListedForReserve(tokenSrc, tokenToEthReserveIds, true); } if (tokenToEthType == TradeType.Split) { bytes32[] memory seqT2EReserveIds; uint256[] memory seqT2ESplits; (seqT2EReserveIds, seqT2ESplits) = ensureSplitSeq( tokenToEthReserveIds, tokenToEthSplits ); hint = abi.encode(tokenToEthType, seqT2EReserveIds, seqT2ESplits); } else { hint = abi.encode(tokenToEthType, tokenToEthReserveIds, tokenToEthSplits); } } /// @notice Builds the hint for a eth -> token trade /// @param tokenDest destination token to trade /// @param ethToTokenType eth -> token trade hint type /// @param ethToTokenReserveIds eth -> token reserve IDs /// @param ethToTokenSplits eth -> token reserve splits /// @return hint The ABI encoded hint function buildEthToTokenHint( IERC20 tokenDest, TradeType ethToTokenType, bytes32[] memory ethToTokenReserveIds, uint256[] memory ethToTokenSplits ) public view override returns (bytes memory hint) { for (uint256 i = 0; i < ethToTokenReserveIds.length; i++) { checkReserveIdsExists(ethToTokenReserveIds[i]); } HintErrors valid = verifyData( ethToTokenType, ethToTokenReserveIds, ethToTokenSplits ); if (valid != HintErrors.NoError) throwHintError(valid); if (ethToTokenType == TradeType.MaskIn || ethToTokenType == TradeType.Split) { checkTokenListedForReserve(tokenDest, ethToTokenReserveIds, false); } if (ethToTokenType == TradeType.Split) { bytes32[] memory seqE2TReserveIds; uint256[] memory seqE2TSplits; (seqE2TReserveIds, seqE2TSplits) = ensureSplitSeq( ethToTokenReserveIds, ethToTokenSplits ); hint = abi.encode(ethToTokenType, seqE2TReserveIds, seqE2TSplits); } else { hint = abi.encode(ethToTokenType, ethToTokenReserveIds, ethToTokenSplits); } } /// @notice Builds the hint for a token to token trade /// @param tokenSrc source token to trade /// @param tokenToEthType token -> eth trade hint type /// @param tokenToEthReserveIds token -> eth reserve IDs /// @param tokenToEthSplits token -> eth reserve splits /// @param tokenDest destination token to trade /// @param ethToTokenType eth -> token trade hint type /// @param ethToTokenReserveIds eth -> token reserve IDs /// @param ethToTokenSplits eth -> token reserve splits /// @return hint The ABI encoded hint function buildTokenToTokenHint( IERC20 tokenSrc, TradeType tokenToEthType, bytes32[] memory tokenToEthReserveIds, uint256[] memory tokenToEthSplits, IERC20 tokenDest, TradeType ethToTokenType, bytes32[] memory ethToTokenReserveIds, uint256[] memory ethToTokenSplits ) public view override returns (bytes memory hint) { bytes memory t2eHint = buildTokenToEthHint( tokenSrc, tokenToEthType, tokenToEthReserveIds, tokenToEthSplits ); bytes memory e2tHint = buildEthToTokenHint( tokenDest, ethToTokenType, ethToTokenReserveIds, ethToTokenSplits ); hint = abi.encode(t2eHint, e2tHint); } /// @notice Parses or decodes the token -> eth or eth -> token bytes hint /// @param hint token -> eth or eth -> token trade hint /// @return tradeType Decoded hint type /// @return reserveIds Decoded reserve IDs /// @return splits Reserve addresses corresponding to reserve IDs /// @return valid Whether the decoded is valid function parseHint(bytes memory hint) internal pure returns ( TradeType tradeType, bytes32[] memory reserveIds, uint256[] memory splits, HintErrors valid ) { (tradeType, reserveIds, splits) = abi.decode(hint, (TradeType, bytes32[], uint256[])); // solhint-disable valid = verifyData(tradeType, reserveIds, splits); if (valid != HintErrors.NoError) { reserveIds = new bytes32[](0); splits = new uint256[](0); } } /// @notice Unpacks the token to token hint to token -> eth and eth -> token hints /// @param hint token to token trade hint /// @return t2eHint The ABI encoded token -> eth hint /// @return e2tHint The ABI encoded eth -> token hint function unpackT2THint(bytes memory hint) internal pure returns (bytes memory t2eHint, bytes memory e2tHint) { (t2eHint, e2tHint) = abi.decode(hint, (bytes, bytes)); } /// @notice Checks if the reserveId exists /// @param reserveId Reserve ID to check function checkReserveIdsExists(bytes32 reserveId) internal view { if (getReserveAddress(reserveId) == address(0)) throwHintError(HintErrors.ReserveIdNotFound); } /// @notice Checks that the token is listed for the reserves /// @param token ERC20 token /// @param reserveIds Reserve IDs /// @param isTokenToEth Flag to indicate token -> eth or eth -> token function checkTokenListedForReserve( IERC20 token, bytes32[] memory reserveIds, bool isTokenToEth ) internal view { IERC20 src = (isTokenToEth) ? token : ETH_TOKEN_ADDRESS; IERC20 dest = (isTokenToEth) ? ETH_TOKEN_ADDRESS : token; if (!areAllReservesListed(reserveIds, src, dest)) throwHintError(HintErrors.TokenListedError); } /// @notice Ensures that the reserveIds in the hint to be parsed has no duplicates /// and applies to all trade types /// @param reserveIds Array of reserve IDs /// @param i Starting index from outer loop function checkDuplicateReserveIds(bytes32[] memory reserveIds, uint256 i) internal pure { for (uint256 j = i + 1; j < reserveIds.length; j++) { if (uint256(reserveIds[i]) == uint256(reserveIds[j])) { throwHintError(HintErrors.ReserveIdDupError); } } } /// @notice Ensures that the reserveIds in the hint to be parsed is in /// sequence for and applies to only Split trade type /// @param reserveId Current index Reserve ID in array /// @param prevReserveId Previous index Reserve ID in array function checkSplitReserveIdSeq(bytes32 reserveId, bytes32 prevReserveId) internal pure { if (uint256(reserveId) <= uint256(prevReserveId)) { throwHintError(HintErrors.ReserveIdSequenceError); } } /// @notice Ensures that the reserveIds and splits passed when building Split hints are in increasing sequence /// @param reserveIds Reserve IDs /// @param splits Reserve splits /// @return Returns a bytes32[] with reserveIds in increasing sequence and respective arranged splits function ensureSplitSeq( bytes32[] memory reserveIds, uint256[] memory splits ) internal pure returns (bytes32[] memory, uint256[] memory) { for (uint256 i = 0; i < reserveIds.length; i++) { for (uint256 j = i + 1; j < reserveIds.length; j++) { if (uint256(reserveIds[i]) > (uint256(reserveIds[j]))) { bytes32 tempId = reserveIds[i]; uint256 tempSplit = splits[i]; reserveIds[i] = reserveIds[j]; reserveIds[j] = tempId; splits[i] = splits[j]; splits[j] = tempSplit; } else if (reserveIds[i] == reserveIds[j]) { throwHintError(HintErrors.ReserveIdDupError); } } } return (reserveIds, splits); } /// @notice Ensures that the data passed when building/parsing hints is valid /// @param tradeType Trade hint type /// @param reserveIds Reserve IDs /// @param splits Reserve splits /// @return Returns a HintError enum to indicate valid or invalid hint data function verifyData( TradeType tradeType, bytes32[] memory reserveIds, uint256[] memory splits ) internal pure returns (HintErrors) { if (tradeType == TradeType.BestOfAll) { if (reserveIds.length != 0 || splits.length != 0) return HintErrors.NonEmptyDataError; } if ( (tradeType == TradeType.MaskIn || tradeType == TradeType.Split) && reserveIds.length == 0 ) return HintErrors.ReserveIdEmptyError; if (tradeType == TradeType.Split) { if (reserveIds.length != splits.length) return HintErrors.ReserveIdSplitsError; uint256 bpsSoFar; for (uint256 i = 0; i < splits.length; i++) { bpsSoFar += splits[i]; } if (bpsSoFar != BPS) return HintErrors.TotalBPSError; } else { if (splits.length != 0) return HintErrors.SplitsNotEmptyError; } return HintErrors.NoError; } /// @notice Throws error message to user to indicate error on hint /// @param error Error type from HintErrors enum function throwHintError(HintErrors error) internal pure { if (error == HintErrors.NonEmptyDataError) revert("reserveIds and splits must be empty"); if (error == HintErrors.ReserveIdDupError) revert("duplicate reserveId"); if (error == HintErrors.ReserveIdEmptyError) revert("reserveIds cannot be empty"); if (error == HintErrors.ReserveIdSplitsError) revert("reserveIds.length != splits.length"); if (error == HintErrors.ReserveIdSequenceError) revert("reserveIds not in increasing order"); if (error == HintErrors.ReserveIdNotFound) revert("reserveId not found"); if (error == HintErrors.SplitsNotEmptyError) revert("splits must be empty"); if (error == HintErrors.TokenListedError) revert("token is not listed for reserveId"); if (error == HintErrors.TotalBPSError) revert("total BPS != 10000"); } function getReserveAddress(bytes32 reserveId) internal view virtual returns (address); function areAllReservesListed( bytes32[] memory reserveIds, IERC20 src, IERC20 dest ) internal virtual view returns (bool); } // File: contracts/sol6/KyberMatchingEngine.sol pragma solidity 0.6.6; /** * @title kyberMatchingEngine contract * During getExpectedRate flow and trade flow this contract is called for: * - parsing hint and returning reserve list (function getTradingReserves) * - matching best reserves to trade with (function doMatch) */ contract KyberMatchingEngine is KyberHintHandler, IKyberMatchingEngine, WithdrawableNoModifiers { struct BestReserveInfo { uint256 index; uint256 destAmount; uint256 numRelevantReserves; } IKyberNetwork public kyberNetwork; IKyberStorage public kyberStorage; uint256 negligibleRateDiffBps = 5; // 1 bps is 0.01% event KyberStorageUpdated(IKyberStorage newKyberStorage); event KyberNetworkUpdated(IKyberNetwork newKyberNetwork); constructor(address _admin) public WithdrawableNoModifiers(_admin) { /* empty body */ } function setKyberStorage(IKyberStorage _kyberStorage) external virtual override { onlyAdmin(); emit KyberStorageUpdated(_kyberStorage); kyberStorage = _kyberStorage; } function setNegligibleRateDiffBps(uint256 _negligibleRateDiffBps) external virtual override { onlyNetwork(); require(_negligibleRateDiffBps <= BPS, "rateDiffBps exceed BPS"); // at most 100% negligibleRateDiffBps = _negligibleRateDiffBps; } function setNetworkContract(IKyberNetwork _kyberNetwork) external { onlyAdmin(); require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0"); emit KyberNetworkUpdated(_kyberNetwork); kyberNetwork = _kyberNetwork; } /// @dev Returns trading reserves info for a trade /// @param src Source token /// @param dest Destination token /// @param isTokenToToken Whether the trade is token -> token /// @param hint Advanced instructions for running the trade /// @return reserveIds Array of reserve IDs for the trade, each being 32 bytes /// @return splitValuesBps Array of split values (in basis points) for the trade /// @return processWithRate Enum ProcessWithRate, whether extra processing is required or not function getTradingReserves( IERC20 src, IERC20 dest, bool isTokenToToken, bytes calldata hint ) external view override returns ( bytes32[] memory reserveIds, uint256[] memory splitValuesBps, ProcessWithRate processWithRate ) { HintErrors error; if (hint.length == 0 || hint.length == 4) { reserveIds = (dest == ETH_TOKEN_ADDRESS) ? kyberStorage.getReserveIdsPerTokenSrc(src) : kyberStorage.getReserveIdsPerTokenDest(dest); splitValuesBps = populateSplitValuesBps(reserveIds.length); processWithRate = ProcessWithRate.Required; return (reserveIds, splitValuesBps, processWithRate); } TradeType tradeType; if (isTokenToToken) { bytes memory unpackedHint; if (src == ETH_TOKEN_ADDRESS) { (, unpackedHint) = unpackT2THint(hint); (tradeType, reserveIds, splitValuesBps, error) = parseHint(unpackedHint); } if (dest == ETH_TOKEN_ADDRESS) { (unpackedHint, ) = unpackT2THint(hint); (tradeType, reserveIds, splitValuesBps, error) = parseHint(unpackedHint); } } else { (tradeType, reserveIds, splitValuesBps, error) = parseHint(hint); } if (error != HintErrors.NoError) return (new bytes32[](0), new uint256[](0), ProcessWithRate.NotRequired); if (tradeType == TradeType.MaskIn) { splitValuesBps = populateSplitValuesBps(reserveIds.length); } else if (tradeType == TradeType.BestOfAll || tradeType == TradeType.MaskOut) { bytes32[] memory allReserves = (dest == ETH_TOKEN_ADDRESS) ? kyberStorage.getReserveIdsPerTokenSrc(src) : kyberStorage.getReserveIdsPerTokenDest(dest); // if bestOfAll, reserveIds = allReserves // if mask out, apply masking out logic reserveIds = (tradeType == TradeType.BestOfAll) ? allReserves : maskOutReserves(allReserves, reserveIds); splitValuesBps = populateSplitValuesBps(reserveIds.length); } // for split no need to search for best rate. User defines full trade details in advance. processWithRate = (tradeType == TradeType.Split) ? ProcessWithRate.NotRequired : ProcessWithRate.Required; } function getNegligibleRateDiffBps() external view override returns (uint256) { return negligibleRateDiffBps; } /// @dev Returns the indexes of the best rate from the rates array /// for token -> eth or eth -> token side of trade /// @param src Source token (not needed in this kyberMatchingEngine version) /// @param dest Destination token (not needed in this kyberMatchingEngine version) /// @param srcAmounts Array of srcAmounts after deducting fees. /// @param feesAccountedDestBps Fees charged in BPS, to be deducted from calculated destAmount /// @param rates Rates queried from reserves /// @return reserveIndexes An array of the indexes most suited for the trade function doMatch( IERC20 src, IERC20 dest, uint256[] calldata srcAmounts, uint256[] calldata feesAccountedDestBps, // 0 for no fee, networkFeeBps when has fee uint256[] calldata rates ) external view override returns (uint256[] memory reserveIndexes) { src; dest; reserveIndexes = new uint256[](1); // use destAmounts for comparison, but return the best rate BestReserveInfo memory bestReserve; bestReserve.numRelevantReserves = 1; // assume always best reserve will be relevant // return empty array for unlisted tokens if (rates.length == 0) { reserveIndexes = new uint256[](0); return reserveIndexes; } uint256[] memory reserveCandidates = new uint256[](rates.length); uint256[] memory destAmounts = new uint256[](rates.length); uint256 destAmount; for (uint256 i = 0; i < rates.length; i++) { // if fee is accounted on dest amount of this reserve, should deduct it destAmount = (srcAmounts[i] * rates[i] * (BPS - feesAccountedDestBps[i])) / BPS; if (destAmount > bestReserve.destAmount) { // best rate is highest rate bestReserve.destAmount = destAmount; bestReserve.index = i; } destAmounts[i] = destAmount; } if (bestReserve.destAmount == 0) { reserveIndexes[0] = bestReserve.index; return reserveIndexes; } reserveCandidates[0] = bestReserve.index; // update best reserve destAmount to be its destAmount after deducting negligible diff. // if any reserve has better or equal dest amount it can be considred to be chosen as best bestReserve.destAmount = (bestReserve.destAmount * BPS) / (BPS + negligibleRateDiffBps); for (uint256 i = 0; i < rates.length; i++) { if (i == bestReserve.index) continue; if (destAmounts[i] > bestReserve.destAmount) { reserveCandidates[bestReserve.numRelevantReserves++] = i; } } if (bestReserve.numRelevantReserves > 1) { // when encountering small rate diff from bestRate. draw from relevant reserves bestReserve.index = reserveCandidates[uint256(blockhash(block.number - 1)) % bestReserve.numRelevantReserves]; } else { bestReserve.index = reserveCandidates[0]; } reserveIndexes[0] = bestReserve.index; } function getReserveAddress(bytes32 reserveId) internal view override returns (address reserveAddress) { (reserveAddress, , , ,) = kyberStorage.getReserveDetailsById(reserveId); } function areAllReservesListed( bytes32[] memory reserveIds, IERC20 src, IERC20 dest ) internal override view returns (bool allReservesListed) { (allReservesListed, , ,) = kyberStorage.getReservesData(reserveIds, src, dest); } /// @notice Logic for masking out reserves /// @param allReservesPerToken Array of reserveIds that support /// the token -> eth or eth -> token side of the trade /// @param maskedOutReserves Array of reserveIds to be excluded from allReservesPerToken /// @return filteredReserves An array of reserveIds that can be used for the trade function maskOutReserves( bytes32[] memory allReservesPerToken, bytes32[] memory maskedOutReserves ) internal pure returns (bytes32[] memory filteredReserves) { require( allReservesPerToken.length >= maskedOutReserves.length, "mask out exceeds available reserves" ); filteredReserves = new bytes32[](allReservesPerToken.length - maskedOutReserves.length); uint256 currentResultIndex = 0; for (uint256 i = 0; i < allReservesPerToken.length; i++) { bytes32 reserveId = allReservesPerToken[i]; bool notMaskedOut = true; for (uint256 j = 0; j < maskedOutReserves.length; j++) { bytes32 maskedOutReserveId = maskedOutReserves[j]; if (reserveId == maskedOutReserveId) { notMaskedOut = false; break; } } if (notMaskedOut) filteredReserves[currentResultIndex++] = reserveId; } } function onlyNetwork() internal view { require(msg.sender == address(kyberNetwork), "only kyberNetwork"); } function populateSplitValuesBps(uint256 length) internal pure returns (uint256[] memory splitValuesBps) { splitValuesBps = new uint256[](length); for (uint256 i = 0; i < length; i++) { splitValuesBps[i] = BPS; } } }
File 7 of 9: KyberStorage
// File: contracts/sol6/IKyberHistory.sol pragma solidity 0.6.6; interface IKyberHistory { function saveContract(address _contract) external; function getContracts() external view returns (address[] memory); } // File: contracts/sol6/IERC20.sol pragma solidity 0.6.6; interface IERC20 { event Approval(address indexed _owner, address indexed _spender, uint256 _value); function approve(address _spender, uint256 _value) external returns (bool success); function transfer(address _to, uint256 _value) external returns (bool success); function transferFrom( address _from, address _to, uint256 _value ) external returns (bool success); function allowance(address _owner, address _spender) external view returns (uint256 remaining); function balanceOf(address _owner) external view returns (uint256 balance); function decimals() external view returns (uint8 digits); function totalSupply() external view returns (uint256 supply); } // to support backward compatible contract name -- so function signature remains same abstract contract ERC20 is IERC20 { } // File: contracts/sol6/IKyberNetworkProxy.sol pragma solidity 0.6.6; interface IKyberNetworkProxy { event ExecuteTrade( address indexed trader, IERC20 src, IERC20 dest, address destAddress, uint256 actualSrcAmount, uint256 actualDestAmount, address platformWallet, uint256 platformFeeBps ); /// @notice backward compatible function tradeWithHint( ERC20 src, uint256 srcAmount, ERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable walletId, bytes calldata hint ) external payable returns (uint256); function tradeWithHintAndFee( IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps, bytes calldata hint ) external payable returns (uint256 destAmount); function trade( IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet ) external payable returns (uint256); /// @notice backward compatible /// @notice Rate units (10 ** 18) => destQty (twei) / srcQty (twei) * 10 ** 18 function getExpectedRate( ERC20 src, ERC20 dest, uint256 srcQty ) external view returns (uint256 expectedRate, uint256 worstRate); function getExpectedRateAfterFee( IERC20 src, IERC20 dest, uint256 srcQty, uint256 platformFeeBps, bytes calldata hint ) external view returns (uint256 expectedRate); } // File: contracts/sol6/IKyberReserve.sol pragma solidity 0.6.6; interface IKyberReserve { function trade( IERC20 srcToken, uint256 srcAmount, IERC20 destToken, address payable destAddress, uint256 conversionRate, bool validate ) external payable returns (bool); function getConversionRate( IERC20 src, IERC20 dest, uint256 srcQty, uint256 blockNumber ) external view returns (uint256); } // File: contracts/sol6/IKyberStorage.sol pragma solidity 0.6.6; interface IKyberStorage { enum ReserveType {NONE, FPR, APR, BRIDGE, UTILITY, CUSTOM, ORDERBOOK, LAST} function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies) external; function removeKyberProxy(address kyberProxy) external; function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external; function setKyberDaoContract(address _kyberDao) external; function getReserveId(address reserve) external view returns (bytes32 reserveId); function getReserveIdsFromAddresses(address[] calldata reserveAddresses) external view returns (bytes32[] memory reserveIds); function getReserveAddressesFromIds(bytes32[] calldata reserveIds) external view returns (address[] memory reserveAddresses); function getReserveIdsPerTokenSrc(IERC20 token) external view returns (bytes32[] memory reserveIds); function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex) external view returns (address[] memory reserveAddresses); function getReserveIdsPerTokenDest(IERC20 token) external view returns (bytes32[] memory reserveIds); function getReserveAddressesByReserveId(bytes32 reserveId) external view returns (address[] memory reserveAddresses); function getRebateWalletsFromIds(bytes32[] calldata reserveIds) external view returns (address[] memory rebateWallets); function getKyberProxies() external view returns (IKyberNetworkProxy[] memory); function getReserveDetailsByAddress(address reserve) external view returns ( bytes32 reserveId, address rebateWallet, ReserveType resType, bool isFeeAccountedFlag, bool isEntitledRebateFlag ); function getReserveDetailsById(bytes32 reserveId) external view returns ( address reserveAddress, address rebateWallet, ReserveType resType, bool isFeeAccountedFlag, bool isEntitledRebateFlag ); function getFeeAccountedData(bytes32[] calldata reserveIds) external view returns (bool[] memory feeAccountedArr); function getEntitledRebateData(bytes32[] calldata reserveIds) external view returns (bool[] memory entitledRebateArr); function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest) external view returns ( bool areAllReservesListed, bool[] memory feeAccountedArr, bool[] memory entitledRebateArr, IKyberReserve[] memory reserveAddresses); function isKyberProxyAdded() external view returns (bool); } // File: contracts/sol6/IKyberNetwork.sol pragma solidity 0.6.6; interface IKyberNetwork { event KyberTrade( IERC20 indexed src, IERC20 indexed dest, uint256 ethWeiValue, uint256 networkFeeWei, uint256 customPlatformFeeWei, bytes32[] t2eIds, bytes32[] e2tIds, uint256[] t2eSrcAmounts, uint256[] e2tSrcAmounts, uint256[] t2eRates, uint256[] e2tRates ); function tradeWithHintAndFee( address payable trader, IERC20 src, uint256 srcAmount, IERC20 dest, address payable destAddress, uint256 maxDestAmount, uint256 minConversionRate, address payable platformWallet, uint256 platformFeeBps, bytes calldata hint ) external payable returns (uint256 destAmount); function listTokenForReserve( address reserve, IERC20 token, bool add ) external; function enabled() external view returns (bool); function getExpectedRateWithHintAndFee( IERC20 src, IERC20 dest, uint256 srcQty, uint256 platformFeeBps, bytes calldata hint ) external view returns ( uint256 expectedRateAfterNetworkFee, uint256 expectedRateAfterAllFees ); function getNetworkData() external view returns ( uint256 negligibleDiffBps, uint256 networkFeeBps, uint256 expiryTimestamp ); function maxGasPrice() external view returns (uint256); } // File: contracts/sol6/utils/PermissionGroupsNoModifiers.sol pragma solidity 0.6.6; contract PermissionGroupsNoModifiers { address public admin; address public pendingAdmin; mapping(address => bool) internal operators; mapping(address => bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint256 internal constant MAX_GROUP_SIZE = 50; event AdminClaimed(address newAdmin, address previousAdmin); event AlerterAdded(address newAlerter, bool isAdd); event OperatorAdded(address newOperator, bool isAdd); event TransferAdminPending(address pendingAdmin); constructor(address _admin) public { require(_admin != address(0), "admin 0"); admin = _admin; } function getOperators() external view returns (address[] memory) { return operatorsGroup; } function getAlerters() external view returns (address[] memory) { return alertersGroup; } function addAlerter(address newAlerter) public { onlyAdmin(); require(!alerters[newAlerter], "alerter exists"); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE, "max alerters"); emit AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function addOperator(address newOperator) public { onlyAdmin(); require(!operators[newOperator], "operator exists"); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE, "max operators"); emit OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } /// @dev Allows the pendingAdmin address to finalize the change admin process. function claimAdmin() public { require(pendingAdmin == msg.sender, "not pending"); emit AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } function removeAlerter(address alerter) public { onlyAdmin(); require(alerters[alerter], "not alerter"); delete alerters[alerter]; for (uint256 i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.pop(); emit AlerterAdded(alerter, false); break; } } } function removeOperator(address operator) public { onlyAdmin(); require(operators[operator], "not operator"); delete operators[operator]; for (uint256 i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.pop(); emit OperatorAdded(operator, false); break; } } } /// @dev Allows the current admin to set the pendingAdmin address /// @param newAdmin The address to transfer ownership to function transferAdmin(address newAdmin) public { onlyAdmin(); require(newAdmin != address(0), "new admin 0"); emit TransferAdminPending(newAdmin); pendingAdmin = newAdmin; } /// @dev Allows the current admin to set the admin in one tx. Useful initial deployment. /// @param newAdmin The address to transfer ownership to. function transferAdminQuickly(address newAdmin) public { onlyAdmin(); require(newAdmin != address(0), "admin 0"); emit TransferAdminPending(newAdmin); emit AdminClaimed(newAdmin, admin); admin = newAdmin; } function onlyAdmin() internal view { require(msg.sender == admin, "only admin"); } function onlyAlerter() internal view { require(alerters[msg.sender], "only alerter"); } function onlyOperator() internal view { require(operators[msg.sender], "only operator"); } } // File: contracts/sol6/utils/Utils5.sol pragma solidity 0.6.6; /** * @title Kyber utility file * mostly shared constants and rate calculation helpers * inherited by most of kyber contracts. * previous utils implementations are for previous solidity versions. */ contract Utils5 { IERC20 internal constant ETH_TOKEN_ADDRESS = IERC20( 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE ); uint256 internal constant PRECISION = (10**18); uint256 internal constant MAX_QTY = (10**28); // 10B tokens uint256 internal constant MAX_RATE = (PRECISION * 10**7); // up to 10M tokens per eth uint256 internal constant MAX_DECIMALS = 18; uint256 internal constant ETH_DECIMALS = 18; uint256 constant BPS = 10000; // Basic Price Steps. 1 step = 0.01% uint256 internal constant MAX_ALLOWANCE = uint256(-1); // token.approve inifinite mapping(IERC20 => uint256) internal decimals; function getUpdateDecimals(IERC20 token) internal returns (uint256) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint256 tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if (tokenDecimals == 0) { tokenDecimals = token.decimals(); decimals[token] = tokenDecimals; } return tokenDecimals; } function setDecimals(IERC20 token) internal { if (decimals[token] != 0) return; //already set if (token == ETH_TOKEN_ADDRESS) { decimals[token] = ETH_DECIMALS; } else { decimals[token] = token.decimals(); } } /// @dev get the balance of a user. /// @param token The token type /// @return The balance function getBalance(IERC20 token, address user) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) { return user.balance; } else { return token.balanceOf(user); } } function getDecimals(IERC20 token) internal view returns (uint256) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint256 tokenDecimals = decimals[token]; // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if (tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDestAmount( IERC20 src, IERC20 dest, uint256 srcAmount, uint256 rate ) internal view returns (uint256) { return calcDstQty(srcAmount, getDecimals(src), getDecimals(dest), rate); } function calcSrcAmount( IERC20 src, IERC20 dest, uint256 destAmount, uint256 rate ) internal view returns (uint256) { return calcSrcQty(destAmount, getDecimals(src), getDecimals(dest), rate); } function calcDstQty( uint256 srcQty, uint256 srcDecimals, uint256 dstDecimals, uint256 rate ) internal pure returns (uint256) { require(srcQty <= MAX_QTY, "srcQty > MAX_QTY"); require(rate <= MAX_RATE, "rate > MAX_RATE"); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty( uint256 dstQty, uint256 srcDecimals, uint256 dstDecimals, uint256 rate ) internal pure returns (uint256) { require(dstQty <= MAX_QTY, "dstQty > MAX_QTY"); require(rate <= MAX_RATE, "rate > MAX_RATE"); //source quantity is rounded up. to avoid dest quantity being too low. uint256 numerator; uint256 denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } function calcRateFromQty( uint256 srcAmount, uint256 destAmount, uint256 srcDecimals, uint256 dstDecimals ) internal pure returns (uint256) { require(srcAmount <= MAX_QTY, "srcAmount > MAX_QTY"); require(destAmount <= MAX_QTY, "destAmount > MAX_QTY"); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS, "dst - src > MAX_DECIMALS"); return ((destAmount * PRECISION) / ((10**(dstDecimals - srcDecimals)) * srcAmount)); } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS, "src - dst > MAX_DECIMALS"); return ((destAmount * PRECISION * (10**(srcDecimals - dstDecimals))) / srcAmount); } } function minOf(uint256 x, uint256 y) internal pure returns (uint256) { return x > y ? y : x; } } // File: contracts/sol6/KyberStorage.sol pragma solidity 0.6.6; /** * @title kyberStorage contract * The contract provides the following functions for kyberNetwork contract: * - Stores reserve and token listing information by the kyberNetwork * - Stores feeAccounted data for reserve types * - Record contract changes for reserves and kyberProxies * - Points to historical contracts that record contract changes for kyberNetwork, * kyberFeeHandler, kyberDao and kyberMatchingEngine */ contract KyberStorage is IKyberStorage, PermissionGroupsNoModifiers, Utils5 { // store current and previous contracts IKyberHistory public kyberNetworkHistory; IKyberHistory public kyberFeeHandlerHistory; IKyberHistory public kyberDaoHistory; IKyberHistory public kyberMatchingEngineHistory; IKyberReserve[] internal reserves; IKyberNetworkProxy[] internal kyberProxyArray; mapping(bytes32 => address[]) internal reserveIdToAddresses; mapping(bytes32 => address) internal reserveRebateWallet; mapping(address => bytes32) internal reserveAddressToId; mapping(IERC20 => bytes32[]) internal reservesPerTokenSrc; // reserves supporting token to eth mapping(IERC20 => bytes32[]) internal reservesPerTokenDest; // reserves support eth to token mapping(bytes32 => IERC20[]) internal srcTokensPerReserve; mapping(bytes32 => IERC20[]) internal destTokensPerReserve; mapping(IERC20 => mapping(bytes32 => bool)) internal isListedReserveWithTokenSrc; mapping(IERC20 => mapping(bytes32 => bool)) internal isListedReserveWithTokenDest; uint256 internal feeAccountedPerType = 0xffffffff; uint256 internal entitledRebatePerType = 0xffffffff; mapping(bytes32 => uint256) internal reserveType; // type from enum ReserveType mapping(ReserveType => bytes32[]) internal reservesPerType; IKyberNetwork public kyberNetwork; constructor( address _admin, IKyberHistory _kyberNetworkHistory, IKyberHistory _kyberFeeHandlerHistory, IKyberHistory _kyberDaoHistory, IKyberHistory _kyberMatchingEngineHistory ) public PermissionGroupsNoModifiers(_admin) { require(_kyberNetworkHistory != IKyberHistory(0), "kyberNetworkHistory 0"); require(_kyberFeeHandlerHistory != IKyberHistory(0), "kyberFeeHandlerHistory 0"); require(_kyberDaoHistory != IKyberHistory(0), "kyberDaoHistory 0"); require(_kyberMatchingEngineHistory != IKyberHistory(0), "kyberMatchingEngineHistory 0"); kyberNetworkHistory = _kyberNetworkHistory; kyberFeeHandlerHistory = _kyberFeeHandlerHistory; kyberDaoHistory = _kyberDaoHistory; kyberMatchingEngineHistory = _kyberMatchingEngineHistory; } event KyberNetworkUpdated(IKyberNetwork newKyberNetwork); event RemoveReserveFromStorage(address indexed reserve, bytes32 indexed reserveId); event AddReserveToStorage( address indexed reserve, bytes32 indexed reserveId, IKyberStorage.ReserveType reserveType, address indexed rebateWallet ); event ReserveRebateWalletSet( bytes32 indexed reserveId, address indexed rebateWallet ); event ListReservePairs( bytes32 indexed reserveId, address reserve, IERC20 indexed src, IERC20 indexed dest, bool add ); function setNetworkContract(IKyberNetwork _kyberNetwork) external { onlyAdmin(); require(_kyberNetwork != IKyberNetwork(0), "kyberNetwork 0"); emit KyberNetworkUpdated(_kyberNetwork); kyberNetworkHistory.saveContract(address(_kyberNetwork)); kyberNetwork = _kyberNetwork; } function setRebateWallet(bytes32 reserveId, address rebateWallet) external { onlyOperator(); require(rebateWallet != address(0), "rebate wallet is 0"); require(reserveId != bytes32(0), "reserveId = 0"); require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found"); require(reserveIdToAddresses[reserveId][0] != address(0), "no reserve associated"); reserveRebateWallet[reserveId] = rebateWallet; emit ReserveRebateWalletSet(reserveId, rebateWallet); } function setContracts(address _kyberFeeHandler, address _kyberMatchingEngine) external override { onlyNetwork(); require(_kyberFeeHandler != address(0), "kyberFeeHandler 0"); require(_kyberMatchingEngine != address(0), "kyberMatchingEngine 0"); kyberFeeHandlerHistory.saveContract(_kyberFeeHandler); kyberMatchingEngineHistory.saveContract(_kyberMatchingEngine); } function setKyberDaoContract(address _kyberDao) external override { onlyNetwork(); kyberDaoHistory.saveContract(_kyberDao); } /// @notice Can be called only by operator /// @dev Adds a reserve to the storage /// @param reserve The reserve address /// @param reserveId The reserve ID in 32 bytes. /// @param resType Type of the reserve out of enum ReserveType /// @param rebateWallet Rebate wallet address for this reserve function addReserve( address reserve, bytes32 reserveId, ReserveType resType, address payable rebateWallet ) external { onlyOperator(); require(reserveAddressToId[reserve] == bytes32(0), "reserve has id"); require(reserveId != bytes32(0), "reserveId = 0"); require( (resType != ReserveType.NONE) && (uint256(resType) < uint256(ReserveType.LAST)), "bad reserve type" ); require(feeAccountedPerType != 0xffffffff, "fee accounted data not set"); require(entitledRebatePerType != 0xffffffff, "entitled rebate data not set"); require(rebateWallet != address(0), "rebate wallet is 0"); reserveRebateWallet[reserveId] = rebateWallet; if (reserveIdToAddresses[reserveId].length == 0) { reserveIdToAddresses[reserveId].push(reserve); } else { require(reserveIdToAddresses[reserveId][0] == address(0), "reserveId taken"); reserveIdToAddresses[reserveId][0] = reserve; } reserves.push(IKyberReserve(reserve)); reservesPerType[resType].push(reserveId); reserveAddressToId[reserve] = reserveId; reserveType[reserveId] = uint256(resType); emit AddReserveToStorage(reserve, reserveId, resType, rebateWallet); emit ReserveRebateWalletSet(reserveId, rebateWallet); } /// @notice Can be called only by operator /// @dev Removes a reserve from the storage /// @param reserveId The reserve id /// @param startIndex Index to start searching from in reserve array function removeReserve(bytes32 reserveId, uint256 startIndex) external { onlyOperator(); require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found"); address reserve = reserveIdToAddresses[reserveId][0]; // delist all token pairs for reserve delistTokensOfReserve(reserveId); uint256 reserveIndex = 2**255; for (uint256 i = startIndex; i < reserves.length; i++) { if (reserves[i] == IKyberReserve(reserve)) { reserveIndex = i; break; } } require(reserveIndex != 2**255, "reserve not found"); reserves[reserveIndex] = reserves[reserves.length - 1]; reserves.pop(); // remove reserve from mapping to address require(reserveAddressToId[reserve] != bytes32(0), "reserve's existing reserveId is 0"); reserveId = reserveAddressToId[reserve]; // update reserve mappings reserveIdToAddresses[reserveId].push(reserveIdToAddresses[reserveId][0]); reserveIdToAddresses[reserveId][0] = address(0); // remove reserveId from reservesPerType bytes32[] storage reservesOfType = reservesPerType[ReserveType(reserveType[reserveId])]; for (uint256 i = 0; i < reservesOfType.length; i++) { if (reserveId == reservesOfType[i]) { reservesOfType[i] = reservesOfType[reservesOfType.length - 1]; reservesOfType.pop(); break; } } delete reserveAddressToId[reserve]; delete reserveType[reserveId]; delete reserveRebateWallet[reserveId]; emit RemoveReserveFromStorage(reserve, reserveId); } /// @notice Can be called only by operator /// @dev Allow or prevent a specific reserve to trade a pair of tokens /// @param reserveId The reserve id /// @param token Token address /// @param ethToToken Will it support ether to token trade /// @param tokenToEth Will it support token to ether trade /// @param add If true then list this pair, otherwise unlist it function listPairForReserve( bytes32 reserveId, IERC20 token, bool ethToToken, bool tokenToEth, bool add ) public { onlyOperator(); require(reserveIdToAddresses[reserveId].length > 0, "reserveId not found"); address reserve = reserveIdToAddresses[reserveId][0]; require(reserve != address(0), "reserve = 0"); if (ethToToken) { listPairs(reserveId, token, false, add); emit ListReservePairs(reserveId, reserve, ETH_TOKEN_ADDRESS, token, add); } if (tokenToEth) { kyberNetwork.listTokenForReserve(reserve, token, add); listPairs(reserveId, token, true, add); emit ListReservePairs(reserveId, reserve, token, ETH_TOKEN_ADDRESS, add); } } /// @dev No. of kyberProxies are capped function addKyberProxy(address kyberProxy, uint256 maxApprovedProxies) external override { onlyNetwork(); require(kyberProxy != address(0), "kyberProxy 0"); require(kyberProxyArray.length < maxApprovedProxies, "max kyberProxies limit reached"); kyberProxyArray.push(IKyberNetworkProxy(kyberProxy)); } function removeKyberProxy(address kyberProxy) external override { onlyNetwork(); uint256 proxyIndex = 2**255; for (uint256 i = 0; i < kyberProxyArray.length; i++) { if (kyberProxyArray[i] == IKyberNetworkProxy(kyberProxy)) { proxyIndex = i; break; } } require(proxyIndex != 2**255, "kyberProxy not found"); kyberProxyArray[proxyIndex] = kyberProxyArray[kyberProxyArray.length - 1]; kyberProxyArray.pop(); } function setFeeAccountedPerReserveType( bool fpr, bool apr, bool bridge, bool utility, bool custom, bool orderbook ) external { onlyAdmin(); uint256 feeAccountedData; if (fpr) feeAccountedData |= 1 << uint256(ReserveType.FPR); if (apr) feeAccountedData |= 1 << uint256(ReserveType.APR); if (bridge) feeAccountedData |= 1 << uint256(ReserveType.BRIDGE); if (utility) feeAccountedData |= 1 << uint256(ReserveType.UTILITY); if (custom) feeAccountedData |= 1 << uint256(ReserveType.CUSTOM); if (orderbook) feeAccountedData |= 1 << uint256(ReserveType.ORDERBOOK); feeAccountedPerType = feeAccountedData; } function setEntitledRebatePerReserveType( bool fpr, bool apr, bool bridge, bool utility, bool custom, bool orderbook ) external { onlyAdmin(); require(feeAccountedPerType != 0xffffffff, "fee accounted data not set"); uint256 entitledRebateData; if (fpr) { require(feeAccountedPerType & (1 << uint256(ReserveType.FPR)) > 0, "fpr not fee accounted"); entitledRebateData |= 1 << uint256(ReserveType.FPR); } if (apr) { require(feeAccountedPerType & (1 << uint256(ReserveType.APR)) > 0, "apr not fee accounted"); entitledRebateData |= 1 << uint256(ReserveType.APR); } if (bridge) { require(feeAccountedPerType & (1 << uint256(ReserveType.BRIDGE)) > 0, "bridge not fee accounted"); entitledRebateData |= 1 << uint256(ReserveType.BRIDGE); } if (utility) { require(feeAccountedPerType & (1 << uint256(ReserveType.UTILITY)) > 0, "utility not fee accounted"); entitledRebateData |= 1 << uint256(ReserveType.UTILITY); } if (custom) { require(feeAccountedPerType & (1 << uint256(ReserveType.CUSTOM)) > 0, "custom not fee accounted"); entitledRebateData |= 1 << uint256(ReserveType.CUSTOM); } if (orderbook) { require(feeAccountedPerType & (1 << uint256(ReserveType.ORDERBOOK)) > 0, "orderbook not fee accounted"); entitledRebateData |= 1 << uint256(ReserveType.ORDERBOOK); } entitledRebatePerType = entitledRebateData; } /// @notice Should be called off chain /// @return An array of all reserves function getReserves() external view returns (IKyberReserve[] memory) { return reserves; } function getReservesPerType(ReserveType resType) external view returns (bytes32[] memory) { return reservesPerType[resType]; } function getReserveId(address reserve) external view override returns (bytes32) { return reserveAddressToId[reserve]; } function getReserveIdsFromAddresses(address[] calldata reserveAddresses) external override view returns (bytes32[] memory reserveIds) { reserveIds = new bytes32[](reserveAddresses.length); for (uint256 i = 0; i < reserveAddresses.length; i++) { reserveIds[i] = reserveAddressToId[reserveAddresses[i]]; } } function getReserveAddressesFromIds(bytes32[] calldata reserveIds) external view override returns (address[] memory reserveAddresses) { reserveAddresses = new address[](reserveIds.length); for (uint256 i = 0; i < reserveIds.length; i++) { reserveAddresses[i] = reserveIdToAddresses[reserveIds[i]][0]; } } function getRebateWalletsFromIds(bytes32[] calldata reserveIds) external view override returns (address[] memory rebateWallets) { rebateWallets = new address[](reserveIds.length); for (uint256 i = 0; i < rebateWallets.length; i++) { rebateWallets[i] = reserveRebateWallet[reserveIds[i]]; } } function getReserveIdsPerTokenSrc(IERC20 token) external view override returns (bytes32[] memory reserveIds) { reserveIds = reservesPerTokenSrc[token]; } /// @dev kyberNetwork is calling this function to approve (allowance) for list of reserves for a token /// in case we have a long list of reserves, approving all of them could run out of gas /// using startIndex and endIndex to prevent above scenario /// also enable us to approve reserve one by one function getReserveAddressesPerTokenSrc(IERC20 token, uint256 startIndex, uint256 endIndex) external view override returns (address[] memory reserveAddresses) { bytes32[] memory reserveIds = reservesPerTokenSrc[token]; if (reserveIds.length == 0) { return reserveAddresses; } uint256 endId = (endIndex >= reserveIds.length) ? (reserveIds.length - 1) : endIndex; if (endId < startIndex) { return reserveAddresses; } reserveAddresses = new address[](endId - startIndex + 1); for(uint256 i = startIndex; i <= endId; i++) { reserveAddresses[i - startIndex] = reserveIdToAddresses[reserveIds[i]][0]; } } function getReserveIdsPerTokenDest(IERC20 token) external view override returns (bytes32[] memory reserveIds) { reserveIds = reservesPerTokenDest[token]; } function getReserveAddressesByReserveId(bytes32 reserveId) external view override returns (address[] memory reserveAddresses) { reserveAddresses = reserveIdToAddresses[reserveId]; } /// @notice Should be called off chain /// @dev Returns list of kyberDao, kyberFeeHandler, kyberMatchingEngine and kyberNetwork contracts /// @dev Index 0 is currently used contract address, indexes > 0 are older versions function getContracts() external view returns ( address[] memory kyberDaoAddresses, address[] memory kyberFeeHandlerAddresses, address[] memory kyberMatchingEngineAddresses, address[] memory kyberNetworkAddresses ) { kyberDaoAddresses = kyberDaoHistory.getContracts(); kyberFeeHandlerAddresses = kyberFeeHandlerHistory.getContracts(); kyberMatchingEngineAddresses = kyberMatchingEngineHistory.getContracts(); kyberNetworkAddresses = kyberNetworkHistory.getContracts(); } /// @notice Should be called off chain /// @return An array of KyberNetworkProxies function getKyberProxies() external view override returns (IKyberNetworkProxy[] memory) { return kyberProxyArray; } function isKyberProxyAdded() external view override returns (bool) { return (kyberProxyArray.length > 0); } /// @notice Returns information about a reserve given its reserve ID /// @return reserveAddress Address of the reserve /// @return rebateWallet address of rebate wallet of this reserve /// @return resType Reserve type from enum ReserveType /// @return isFeeAccountedFlag Whether fees are to be charged for the trade for this reserve /// @return isEntitledRebateFlag Whether reserve is entitled rebate from the trade fees function getReserveDetailsById(bytes32 reserveId) external view override returns ( address reserveAddress, address rebateWallet, ReserveType resType, bool isFeeAccountedFlag, bool isEntitledRebateFlag ) { address[] memory reserveAddresses = reserveIdToAddresses[reserveId]; if (reserveAddresses.length != 0) { reserveAddress = reserveIdToAddresses[reserveId][0]; rebateWallet = reserveRebateWallet[reserveId]; uint256 resTypeUint = reserveType[reserveId]; resType = ReserveType(resTypeUint); isFeeAccountedFlag = (feeAccountedPerType & (1 << resTypeUint)) > 0; isEntitledRebateFlag = (entitledRebatePerType & (1 << resTypeUint)) > 0; } } /// @notice Returns information about a reserve given its reserve ID /// @return reserveId The reserve ID in 32 bytes. /// @return rebateWallet address of rebate wallet of this reserve /// @return resType Reserve type from enum ReserveType /// @return isFeeAccountedFlag Whether fees are to be charged for the trade for this reserve /// @return isEntitledRebateFlag Whether reserve is entitled rebate from the trade fees function getReserveDetailsByAddress(address reserve) external view override returns ( bytes32 reserveId, address rebateWallet, ReserveType resType, bool isFeeAccountedFlag, bool isEntitledRebateFlag ) { reserveId = reserveAddressToId[reserve]; rebateWallet = reserveRebateWallet[reserveId]; uint256 resTypeUint = reserveType[reserveId]; resType = ReserveType(resTypeUint); isFeeAccountedFlag = (feeAccountedPerType & (1 << resTypeUint)) > 0; isEntitledRebateFlag = (entitledRebatePerType & (1 << resTypeUint)) > 0; } function getListedTokensByReserveId(bytes32 reserveId) external view returns ( IERC20[] memory srcTokens, IERC20[] memory destTokens ) { srcTokens = srcTokensPerReserve[reserveId]; destTokens = destTokensPerReserve[reserveId]; } function getFeeAccountedData(bytes32[] calldata reserveIds) external view override returns (bool[] memory feeAccountedArr) { feeAccountedArr = new bool[](reserveIds.length); uint256 feeAccountedData = feeAccountedPerType; for (uint256 i = 0; i < reserveIds.length; i++) { feeAccountedArr[i] = (feeAccountedData & (1 << reserveType[reserveIds[i]]) > 0); } } function getEntitledRebateData(bytes32[] calldata reserveIds) external view override returns (bool[] memory entitledRebateArr) { entitledRebateArr = new bool[](reserveIds.length); uint256 entitledRebateData = entitledRebatePerType; for (uint256 i = 0; i < reserveIds.length; i++) { entitledRebateArr[i] = (entitledRebateData & (1 << reserveType[reserveIds[i]]) > 0); } } /// @dev Returns information about reserves given their reserve IDs /// Also check if these reserve IDs are listed for token /// Network calls this function to retrive information about fee, address and rebate information function getReservesData(bytes32[] calldata reserveIds, IERC20 src, IERC20 dest) external view override returns ( bool areAllReservesListed, bool[] memory feeAccountedArr, bool[] memory entitledRebateArr, IKyberReserve[] memory reserveAddresses) { feeAccountedArr = new bool[](reserveIds.length); entitledRebateArr = new bool[](reserveIds.length); reserveAddresses = new IKyberReserve[](reserveIds.length); areAllReservesListed = true; uint256 entitledRebateData = entitledRebatePerType; uint256 feeAccountedData = feeAccountedPerType; mapping(bytes32 => bool) storage isListedReserveWithToken = (dest == ETH_TOKEN_ADDRESS) ? isListedReserveWithTokenSrc[src]: isListedReserveWithTokenDest[dest]; for (uint256 i = 0; i < reserveIds.length; i++) { uint256 resType = reserveType[reserveIds[i]]; entitledRebateArr[i] = (entitledRebateData & (1 << resType) > 0); feeAccountedArr[i] = (feeAccountedData & (1 << resType) > 0); reserveAddresses[i] = IKyberReserve(reserveIdToAddresses[reserveIds[i]][0]); if (!isListedReserveWithToken[reserveIds[i]]){ areAllReservesListed = false; break; } } } function delistTokensOfReserve(bytes32 reserveId) internal { // token to ether // memory declaration instead of storage because we are modifying the storage array IERC20[] memory tokensArr = srcTokensPerReserve[reserveId]; for (uint256 i = 0; i < tokensArr.length; i++) { listPairForReserve(reserveId, tokensArr[i], false, true, false); } // ether to token tokensArr = destTokensPerReserve[reserveId]; for (uint256 i = 0; i < tokensArr.length; i++) { listPairForReserve(reserveId, tokensArr[i], true, false, false); } } function listPairs( bytes32 reserveId, IERC20 token, bool isTokenToEth, bool add ) internal { uint256 i; bytes32[] storage reserveArr = reservesPerTokenDest[token]; IERC20[] storage tokensArr = destTokensPerReserve[reserveId]; mapping(bytes32 => bool) storage isListedReserveWithToken = isListedReserveWithTokenDest[token]; if (isTokenToEth) { reserveArr = reservesPerTokenSrc[token]; tokensArr = srcTokensPerReserve[reserveId]; isListedReserveWithToken = isListedReserveWithTokenSrc[token]; } for (i = 0; i < reserveArr.length; i++) { if (reserveId == reserveArr[i]) { if (add) { return; // reserve already added, no further action needed } else { // remove reserve from reserveArr reserveArr[i] = reserveArr[reserveArr.length - 1]; reserveArr.pop(); break; } } } if (add) { // add reserve and token to reserveArr and tokensArr respectively reserveArr.push(reserveId); tokensArr.push(token); isListedReserveWithToken[reserveId] = true; } else { // remove token from tokenArr for (i = 0; i < tokensArr.length; i++) { if (token == tokensArr[i]) { tokensArr[i] = tokensArr[tokensArr.length - 1]; tokensArr.pop(); break; } } delete isListedReserveWithToken[reserveId]; } } function onlyNetwork() internal view { require(msg.sender == address(kyberNetwork), "only kyberNetwork"); } }
File 8 of 9: LiquidityConversionRates
pragma solidity 0.4.18; // File: contracts/ERC20Interface.sol // https://github.com/ethereum/EIPs/issues/20 interface ERC20 { function totalSupply() public view returns (uint supply); function balanceOf(address _owner) public view returns (uint balance); function transfer(address _to, uint _value) public returns (bool success); function transferFrom(address _from, address _to, uint _value) public returns (bool success); function approve(address _spender, uint _value) public returns (bool success); function allowance(address _owner, address _spender) public view returns (uint remaining); function decimals() public view returns(uint digits); event Approval(address indexed _owner, address indexed _spender, uint _value); } // File: contracts/ConversionRatesInterface.sol interface ConversionRatesInterface { function recordImbalance( ERC20 token, int buyAmount, uint rateUpdateBlock, uint currentBlock ) public; function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint); } // File: contracts/LiquidityFormula.sol contract UtilMath { uint public constant BIG_NUMBER = (uint(1)<<uint(200)); function checkMultOverflow(uint x, uint y) public pure returns(bool) { if (y == 0) return false; return (((x*y) / y) != x); } function compactFraction(uint p, uint q, uint precision) public pure returns (uint, uint) { if (q < precision * precision) return (p, q); return compactFraction(p/precision, q/precision, precision); } /* solhint-disable code-complexity */ function exp(uint p, uint q, uint precision) public pure returns (uint) { uint n = 0; uint nFact = 1; uint currentP = 1; uint currentQ = 1; uint sum = 0; uint prevSum = 0; while (true) { if (checkMultOverflow(currentP, precision)) return sum; if (checkMultOverflow(currentQ, nFact)) return sum; sum += (currentP * precision) / (currentQ * nFact); if (sum == prevSum) return sum; prevSum = sum; n++; if (checkMultOverflow(currentP, p)) return sum; if (checkMultOverflow(currentQ, q)) return sum; if (checkMultOverflow(nFact, n)) return sum; currentP *= p; currentQ *= q; nFact *= n; (currentP, currentQ) = compactFraction(currentP, currentQ, precision); } } /* solhint-enable code-complexity */ function countLeadingZeros(uint p, uint q) public pure returns (uint) { uint denomator = (uint(1)<<255); for (int i = 255; i >= 0; i--) { if ((q*denomator)/denomator != q) { // overflow denomator = denomator/2; continue; } if (p/(q*denomator) > 0) return uint(i); denomator = denomator/2; } return uint(-1); } // log2 for a number that it in [1,2) function log2ForSmallNumber(uint x, uint numPrecisionBits) public pure returns (uint) { uint res = 0; uint one = (uint(1)<<numPrecisionBits); uint two = 2 * one; uint addition = one; require((x >= one) && (x <= two)); require(numPrecisionBits < 125); for (uint i = numPrecisionBits; i > 0; i--) { x = (x*x) / one; addition = addition/2; if (x >= two) { x = x/2; res += addition; } } return res; } function logBase2 (uint p, uint q, uint numPrecisionBits) public pure returns (uint) { uint n = 0; uint precision = (uint(1)<<numPrecisionBits); if (p > q) { n = countLeadingZeros(p, q); } require(!checkMultOverflow(p, precision)); require(!checkMultOverflow(n, precision)); require(!checkMultOverflow(uint(1)<<n, q)); uint y = p * precision / (q * (uint(1)<<n)); uint log2Small = log2ForSmallNumber(y, numPrecisionBits); require(n*precision <= BIG_NUMBER); require(log2Small <= BIG_NUMBER); return n * precision + log2Small; } function ln(uint p, uint q, uint numPrecisionBits) public pure returns (uint) { uint ln2Numerator = 6931471805599453094172; uint ln2Denomerator = 10000000000000000000000; uint log2x = logBase2(p, q, numPrecisionBits); require(!checkMultOverflow(ln2Numerator, log2x)); return ln2Numerator * log2x / ln2Denomerator; } } contract LiquidityFormula is UtilMath { function pE(uint r, uint pMIn, uint e, uint precision) public pure returns (uint) { uint expRE = exp(r*e, precision*precision, precision); require(!checkMultOverflow(expRE, pMIn)); return pMIn*expRE / precision; } function deltaTFunc(uint r, uint pMIn, uint e, uint deltaE, uint precision) public pure returns (uint) { uint pe = pE(r, pMIn, e, precision); uint rpe = r * pe; uint erdeltaE = exp(r*deltaE, precision*precision, precision); require(erdeltaE >= precision); require(!checkMultOverflow(erdeltaE - precision, precision)); require(!checkMultOverflow((erdeltaE - precision)*precision, precision)); require(!checkMultOverflow((erdeltaE - precision)*precision*precision, precision)); require(!checkMultOverflow(rpe, erdeltaE)); require(!checkMultOverflow(r, pe)); return (erdeltaE - precision) * precision * precision * precision / (rpe*erdeltaE); } function deltaEFunc(uint r, uint pMIn, uint e, uint deltaT, uint precision, uint numPrecisionBits) public pure returns (uint) { uint pe = pE(r, pMIn, e, precision); uint rpe = r * pe; uint lnPart = ln(precision*precision + rpe*deltaT/precision, precision*precision, numPrecisionBits); require(!checkMultOverflow(r, pe)); require(!checkMultOverflow(precision, precision)); require(!checkMultOverflow(rpe, deltaT)); require(!checkMultOverflow(lnPart, precision)); return lnPart * precision / r; } } // File: contracts/Utils.sol /// @title Kyber constants contract contract Utils { ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee); uint constant internal PRECISION = (10**18); uint constant internal MAX_QTY = (10**28); // 10B tokens uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH uint constant internal MAX_DECIMALS = 18; uint constant internal ETH_DECIMALS = 18; mapping(address=>uint) internal decimals; function setDecimals(ERC20 token) internal { if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS; else decimals[token] = token.decimals(); } function getDecimals(ERC20 token) internal view returns(uint) { if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access uint tokenDecimals = decimals[token]; // technically, there might be token with decimals 0 // moreover, very possible that old tokens have decimals 0 // these tokens will just have higher gas fees. if(tokenDecimals == 0) return token.decimals(); return tokenDecimals; } function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(srcQty <= MAX_QTY); require(rate <= MAX_RATE); if (dstDecimals >= srcDecimals) { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION; } else { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals))); } } function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) { require(dstQty <= MAX_QTY); require(rate <= MAX_RATE); //source quantity is rounded up. to avoid dest quantity being too low. uint numerator; uint denominator; if (srcDecimals >= dstDecimals) { require((srcDecimals - dstDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals))); denominator = rate; } else { require((dstDecimals - srcDecimals) <= MAX_DECIMALS); numerator = (PRECISION * dstQty); denominator = (rate * (10**(dstDecimals - srcDecimals))); } return (numerator + denominator - 1) / denominator; //avoid rounding down errors } } // File: contracts/PermissionGroups.sol contract PermissionGroups { address public admin; address public pendingAdmin; mapping(address=>bool) internal operators; mapping(address=>bool) internal alerters; address[] internal operatorsGroup; address[] internal alertersGroup; uint constant internal MAX_GROUP_SIZE = 50; function PermissionGroups() public { admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin); _; } modifier onlyOperator() { require(operators[msg.sender]); _; } modifier onlyAlerter() { require(alerters[msg.sender]); _; } function getOperators () external view returns(address[]) { return operatorsGroup; } function getAlerters () external view returns(address[]) { return alertersGroup; } event TransferAdminPending(address pendingAdmin); /** * @dev Allows the current admin to set the pendingAdmin address. * @param newAdmin The address to transfer ownership to. */ function transferAdmin(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(pendingAdmin); pendingAdmin = newAdmin; } /** * @dev Allows the current admin to set the admin in one tx. Useful initial deployment. * @param newAdmin The address to transfer ownership to. */ function transferAdminQuickly(address newAdmin) public onlyAdmin { require(newAdmin != address(0)); TransferAdminPending(newAdmin); AdminClaimed(newAdmin, admin); admin = newAdmin; } event AdminClaimed( address newAdmin, address previousAdmin); /** * @dev Allows the pendingAdmin address to finalize the change admin process. */ function claimAdmin() public { require(pendingAdmin == msg.sender); AdminClaimed(pendingAdmin, admin); admin = pendingAdmin; pendingAdmin = address(0); } event AlerterAdded (address newAlerter, bool isAdd); function addAlerter(address newAlerter) public onlyAdmin { require(!alerters[newAlerter]); // prevent duplicates. require(alertersGroup.length < MAX_GROUP_SIZE); AlerterAdded(newAlerter, true); alerters[newAlerter] = true; alertersGroup.push(newAlerter); } function removeAlerter (address alerter) public onlyAdmin { require(alerters[alerter]); alerters[alerter] = false; for (uint i = 0; i < alertersGroup.length; ++i) { if (alertersGroup[i] == alerter) { alertersGroup[i] = alertersGroup[alertersGroup.length - 1]; alertersGroup.length--; AlerterAdded(alerter, false); break; } } } event OperatorAdded(address newOperator, bool isAdd); function addOperator(address newOperator) public onlyAdmin { require(!operators[newOperator]); // prevent duplicates. require(operatorsGroup.length < MAX_GROUP_SIZE); OperatorAdded(newOperator, true); operators[newOperator] = true; operatorsGroup.push(newOperator); } function removeOperator (address operator) public onlyAdmin { require(operators[operator]); operators[operator] = false; for (uint i = 0; i < operatorsGroup.length; ++i) { if (operatorsGroup[i] == operator) { operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1]; operatorsGroup.length -= 1; OperatorAdded(operator, false); break; } } } } // File: contracts/Withdrawable.sol /** * @title Contracts that should be able to recover tokens or ethers * @author Ilan Doron * @dev This allows to recover any tokens or Ethers received in a contract. * This will prevent any accidental loss of tokens. */ contract Withdrawable is PermissionGroups { event TokenWithdraw(ERC20 token, uint amount, address sendTo); /** * @dev Withdraw all ERC20 compatible tokens * @param token ERC20 The address of the token contract */ function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin { require(token.transfer(sendTo, amount)); TokenWithdraw(token, amount, sendTo); } event EtherWithdraw(uint amount, address sendTo); /** * @dev Withdraw Ethers */ function withdrawEther(uint amount, address sendTo) external onlyAdmin { sendTo.transfer(amount); EtherWithdraw(amount, sendTo); } } // File: contracts/LiquidityConversionRates.sol contract LiquidityConversionRates is ConversionRatesInterface, LiquidityFormula, Withdrawable, Utils { ERC20 public token; address public reserveContract; uint public numFpBits; uint public formulaPrecision; uint public rInFp; uint public pMinInFp; uint public maxEthCapBuyInFp; uint public maxEthCapSellInFp; uint public maxQtyInFp; uint public feeInBps; uint public collectedFeesInTwei = 0; uint public maxBuyRateInPrecision; uint public minBuyRateInPrecision; uint public maxSellRateInPrecision; uint public minSellRateInPrecision; function LiquidityConversionRates(address _admin, ERC20 _token) public { transferAdminQuickly(_admin); token = _token; setDecimals(token); require(getDecimals(token) <= MAX_DECIMALS); } event ReserveAddressSet(address reserve); function setReserveAddress(address reserve) public onlyAdmin { reserveContract = reserve; ReserveAddressSet(reserve); } event LiquidityParamsSet( uint rInFp, uint pMinInFp, uint numFpBits, uint maxCapBuyInFp, uint maxEthCapSellInFp, uint feeInBps, uint formulaPrecision, uint maxQtyInFp, uint maxBuyRateInPrecision, uint minBuyRateInPrecision, uint maxSellRateInPrecision, uint minSellRateInPrecision ); function setLiquidityParams( uint _rInFp, uint _pMinInFp, uint _numFpBits, uint _maxCapBuyInWei, uint _maxCapSellInWei, uint _feeInBps, uint _maxTokenToEthRateInPrecision, uint _minTokenToEthRateInPrecision ) public onlyAdmin { require(_numFpBits < 256); require(formulaPrecision <= MAX_QTY); require(_feeInBps < 10000); require(_minTokenToEthRateInPrecision < _maxTokenToEthRateInPrecision); rInFp = _rInFp; pMinInFp = _pMinInFp; formulaPrecision = uint(1)<<_numFpBits; maxQtyInFp = fromWeiToFp(MAX_QTY); numFpBits = _numFpBits; maxEthCapBuyInFp = fromWeiToFp(_maxCapBuyInWei); maxEthCapSellInFp = fromWeiToFp(_maxCapSellInWei); feeInBps = _feeInBps; maxBuyRateInPrecision = PRECISION * PRECISION / _minTokenToEthRateInPrecision; minBuyRateInPrecision = PRECISION * PRECISION / _maxTokenToEthRateInPrecision; maxSellRateInPrecision = _maxTokenToEthRateInPrecision; minSellRateInPrecision = _minTokenToEthRateInPrecision; LiquidityParamsSet( rInFp, pMinInFp, numFpBits, maxEthCapBuyInFp, maxEthCapSellInFp, feeInBps, formulaPrecision, maxQtyInFp, maxBuyRateInPrecision, minBuyRateInPrecision, maxSellRateInPrecision, minSellRateInPrecision ); } function recordImbalance( ERC20 conversionToken, int buyAmountInTwei, uint rateUpdateBlock, uint currentBlock ) public { conversionToken; rateUpdateBlock; currentBlock; require(msg.sender == reserveContract); if (buyAmountInTwei > 0) { // Buy case collectedFeesInTwei += calcCollectedFee(abs(buyAmountInTwei)); } else { // Sell case collectedFeesInTwei += abs(buyAmountInTwei) * feeInBps / 10000; } } event CollectedFeesReset(uint resetFeesInTwei); function resetCollectedFees() public onlyAdmin { uint resetFeesInTwei = collectedFeesInTwei; collectedFeesInTwei = 0; CollectedFeesReset(resetFeesInTwei); } function getRate( ERC20 conversionToken, uint currentBlockNumber, bool buy, uint qtyInSrcWei ) public view returns(uint) { currentBlockNumber; require(qtyInSrcWei <= MAX_QTY); uint eInFp = fromWeiToFp(reserveContract.balance); uint rateInPrecision = getRateWithE(conversionToken, buy, qtyInSrcWei, eInFp); require(rateInPrecision <= MAX_RATE); return rateInPrecision; } function getRateWithE(ERC20 conversionToken, bool buy, uint qtyInSrcWei, uint eInFp) public view returns(uint) { uint deltaEInFp; uint sellInputTokenQtyInFp; uint deltaTInFp; uint rateInPrecision; require(qtyInSrcWei <= MAX_QTY); require(eInFp <= maxQtyInFp); if (conversionToken != token) return 0; if (buy) { // ETH goes in, token goes out deltaEInFp = fromWeiToFp(qtyInSrcWei); if (deltaEInFp > maxEthCapBuyInFp) return 0; if (deltaEInFp == 0) { rateInPrecision = buyRateZeroQuantity(eInFp); } else { rateInPrecision = buyRate(eInFp, deltaEInFp); } } else { sellInputTokenQtyInFp = fromTweiToFp(qtyInSrcWei); deltaTInFp = valueAfterReducingFee(sellInputTokenQtyInFp); if (deltaTInFp == 0) { rateInPrecision = sellRateZeroQuantity(eInFp); deltaEInFp = 0; } else { (rateInPrecision, deltaEInFp) = sellRate(eInFp, sellInputTokenQtyInFp, deltaTInFp); } if (deltaEInFp > maxEthCapSellInFp) return 0; } rateInPrecision = rateAfterValidation(rateInPrecision, buy); return rateInPrecision; } function rateAfterValidation(uint rateInPrecision, bool buy) public view returns(uint) { uint minAllowRateInPrecision; uint maxAllowedRateInPrecision; if (buy) { minAllowRateInPrecision = minBuyRateInPrecision; maxAllowedRateInPrecision = maxBuyRateInPrecision; } else { minAllowRateInPrecision = minSellRateInPrecision; maxAllowedRateInPrecision = maxSellRateInPrecision; } if ((rateInPrecision > maxAllowedRateInPrecision) || (rateInPrecision < minAllowRateInPrecision)) { return 0; } else if (rateInPrecision > MAX_RATE) { return 0; } else { return rateInPrecision; } } function buyRate(uint eInFp, uint deltaEInFp) public view returns(uint) { uint deltaTInFp = deltaTFunc(rInFp, pMinInFp, eInFp, deltaEInFp, formulaPrecision); require(deltaTInFp <= maxQtyInFp); deltaTInFp = valueAfterReducingFee(deltaTInFp); return deltaTInFp * PRECISION / deltaEInFp; } function buyRateZeroQuantity(uint eInFp) public view returns(uint) { uint ratePreReductionInPrecision = formulaPrecision * PRECISION / pE(rInFp, pMinInFp, eInFp, formulaPrecision); return valueAfterReducingFee(ratePreReductionInPrecision); } function sellRate( uint eInFp, uint sellInputTokenQtyInFp, uint deltaTInFp ) public view returns(uint rateInPrecision, uint deltaEInFp) { deltaEInFp = deltaEFunc(rInFp, pMinInFp, eInFp, deltaTInFp, formulaPrecision, numFpBits); require(deltaEInFp <= maxQtyInFp); rateInPrecision = deltaEInFp * PRECISION / sellInputTokenQtyInFp; } function sellRateZeroQuantity(uint eInFp) public view returns(uint) { uint ratePreReductionInPrecision = pE(rInFp, pMinInFp, eInFp, formulaPrecision) * PRECISION / formulaPrecision; return valueAfterReducingFee(ratePreReductionInPrecision); } function fromTweiToFp(uint qtyInTwei) public view returns(uint) { require(qtyInTwei <= MAX_QTY); return qtyInTwei * formulaPrecision / (10 ** getDecimals(token)); } function fromWeiToFp(uint qtyInwei) public view returns(uint) { require(qtyInwei <= MAX_QTY); return qtyInwei * formulaPrecision / (10 ** ETH_DECIMALS); } function valueAfterReducingFee(uint val) public view returns(uint) { require(val <= BIG_NUMBER); return ((10000 - feeInBps) * val) / 10000; } function calcCollectedFee(uint val) public view returns(uint) { require(val <= MAX_QTY); return val * feeInBps / (10000 - feeInBps); } function abs(int val) public pure returns(uint) { if (val < 0) { return uint(val * (-1)); } else { return uint(val); } } }
File 9 of 9: PollManagedFund
pragma solidity ^0.4.21; // File: contracts/DateTime.sol contract DateTime { /* * Date and Time utilities for ethereum contracts * */ struct _DateTime { uint16 year; uint8 month; uint8 day; uint8 hour; uint8 minute; uint8 second; uint8 weekday; } uint constant DAY_IN_SECONDS = 86400; uint constant YEAR_IN_SECONDS = 31536000; uint constant LEAP_YEAR_IN_SECONDS = 31622400; uint constant HOUR_IN_SECONDS = 3600; uint constant MINUTE_IN_SECONDS = 60; uint16 constant ORIGIN_YEAR = 1970; function isLeapYear(uint16 year) public pure returns (bool) { if (year % 4 != 0) { return false; } if (year % 100 != 0) { return true; } if (year % 400 != 0) { return false; } return true; } function leapYearsBefore(uint year) public pure returns (uint) { year -= 1; return year / 4 - year / 100 + year / 400; } function getDaysInMonth(uint8 month, uint16 year) public pure returns (uint8) { if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { return 31; } else if (month == 4 || month == 6 || month == 9 || month == 11) { return 30; } else if (isLeapYear(year)) { return 29; } else { return 28; } } function parseTimestamp(uint timestamp) internal pure returns (_DateTime dt) { uint secondsAccountedFor = 0; uint buf; uint8 i; // Year dt.year = getYear(timestamp); buf = leapYearsBefore(dt.year) - leapYearsBefore(ORIGIN_YEAR); secondsAccountedFor += LEAP_YEAR_IN_SECONDS * buf; secondsAccountedFor += YEAR_IN_SECONDS * (dt.year - ORIGIN_YEAR - buf); // Month uint secondsInMonth; for (i = 1; i <= 12; i++) { secondsInMonth = DAY_IN_SECONDS * getDaysInMonth(i, dt.year); if (secondsInMonth + secondsAccountedFor > timestamp) { dt.month = i; break; } secondsAccountedFor += secondsInMonth; } // Day for (i = 1; i <= getDaysInMonth(dt.month, dt.year); i++) { if (DAY_IN_SECONDS + secondsAccountedFor > timestamp) { dt.day = i; break; } secondsAccountedFor += DAY_IN_SECONDS; } // Hour dt.hour = getHour(timestamp); // Minute dt.minute = getMinute(timestamp); // Second dt.second = getSecond(timestamp); // Day of week. dt.weekday = getWeekday(timestamp); } function getYear(uint timestamp) public pure returns (uint16) { uint secondsAccountedFor = 0; uint16 year; uint numLeapYears; // Year year = uint16(ORIGIN_YEAR + timestamp / YEAR_IN_SECONDS); numLeapYears = leapYearsBefore(year) - leapYearsBefore(ORIGIN_YEAR); secondsAccountedFor += LEAP_YEAR_IN_SECONDS * numLeapYears; secondsAccountedFor += YEAR_IN_SECONDS * (year - ORIGIN_YEAR - numLeapYears); while (secondsAccountedFor > timestamp) { if (isLeapYear(uint16(year - 1))) { secondsAccountedFor -= LEAP_YEAR_IN_SECONDS; } else { secondsAccountedFor -= YEAR_IN_SECONDS; } year -= 1; } return year; } function getMonth(uint timestamp) public pure returns (uint8) { return parseTimestamp(timestamp).month; } function getDay(uint timestamp) public pure returns (uint8) { return parseTimestamp(timestamp).day; } function getHour(uint timestamp) public pure returns (uint8) { return uint8((timestamp / 60 / 60) % 24); } function getMinute(uint timestamp) public pure returns (uint8) { return uint8((timestamp / 60) % 60); } function getSecond(uint timestamp) public pure returns (uint8) { return uint8(timestamp % 60); } function getWeekday(uint timestamp) public pure returns (uint8) { return uint8((timestamp / DAY_IN_SECONDS + 4) % 7); } function toTimestamp(uint16 year, uint8 month, uint8 day) public pure returns (uint timestamp) { return toTimestamp(year, month, day, 0, 0, 0); } function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour) public pure returns (uint timestamp) { return toTimestamp(year, month, day, hour, 0, 0); } function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute) public pure returns (uint timestamp) { return toTimestamp(year, month, day, hour, minute, 0); } function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute, uint8 second) public pure returns (uint timestamp) { uint16 i; // Year for (i = ORIGIN_YEAR; i < year; i++) { if (isLeapYear(i)) { timestamp += LEAP_YEAR_IN_SECONDS; } else { timestamp += YEAR_IN_SECONDS; } } // Month uint8[12] memory monthDayCounts; monthDayCounts[0] = 31; if (isLeapYear(year)) { monthDayCounts[1] = 29; } else { monthDayCounts[1] = 28; } monthDayCounts[2] = 31; monthDayCounts[3] = 30; monthDayCounts[4] = 31; monthDayCounts[5] = 30; monthDayCounts[6] = 31; monthDayCounts[7] = 31; monthDayCounts[8] = 30; monthDayCounts[9] = 31; monthDayCounts[10] = 30; monthDayCounts[11] = 31; for (i = 1; i < month; i++) { timestamp += DAY_IN_SECONDS * monthDayCounts[i - 1]; } // Day timestamp += DAY_IN_SECONDS * (day - 1); // Hour timestamp += HOUR_IN_SECONDS * (hour); // Minute timestamp += MINUTE_IN_SECONDS * (minute); // Second timestamp += second; return timestamp; } } // File: contracts/ISimpleCrowdsale.sol interface ISimpleCrowdsale { function getSoftCap() external view returns(uint256); function isContributorInLists(address contributorAddress) external view returns(bool); function processReservationFundContribution( address contributor, uint256 tokenAmount, uint256 tokenBonusAmount ) external payable; } // File: contracts/fund/ICrowdsaleFund.sol /** * @title ICrowdsaleFund * @dev Fund methods used by crowdsale contract */ interface ICrowdsaleFund { /** * @dev Function accepts user`s contributed ether and logs contribution * @param contributor Contributor wallet address. */ function processContribution(address contributor) external payable; /** * @dev Function is called on the end of successful crowdsale */ function onCrowdsaleEnd() external; /** * @dev Function is called if crowdsale failed to reach soft cap */ function enableCrowdsaleRefund() external; } // File: contracts/math/SafeMath.sol /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ contract SafeMath { /** * @dev constructor */ function SafeMath() public { } function safeMul(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a * b; assert(a == 0 || c / a == b); return c; } function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a / b; return c; } function safeSub(uint256 a, uint256 b) internal pure returns (uint256) { assert(a >= b); return a - b; } function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } } // File: contracts/ownership/MultiOwnable.sol /** * @title MultiOwnable * @dev The MultiOwnable contract has owners addresses and provides basic authorization control * functions, this simplifies the implementation of "users permissions". */ contract MultiOwnable { address public manager; // address used to set owners address[] public owners; mapping(address => bool) public ownerByAddress; event SetOwners(address[] owners); modifier onlyOwner() { require(ownerByAddress[msg.sender] == true); _; } /** * @dev MultiOwnable constructor sets the manager */ function MultiOwnable() public { manager = msg.sender; } /** * @dev Function to set owners addresses */ function setOwners(address[] _owners) public { require(msg.sender == manager); _setOwners(_owners); } function _setOwners(address[] _owners) internal { for(uint256 i = 0; i < owners.length; i++) { ownerByAddress[owners[i]] = false; } for(uint256 j = 0; j < _owners.length; j++) { ownerByAddress[_owners[j]] = true; } owners = _owners; SetOwners(_owners); } function getOwners() public constant returns (address[]) { return owners; } } // File: contracts/token/IERC20Token.sol /** * @title IERC20Token - ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract IERC20Token { string public name; string public symbol; uint8 public decimals; uint256 public totalSupply; function balanceOf(address _owner) public constant returns (uint256 balance); function transfer(address _to, uint256 _value) public returns (bool success); function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); function approve(address _spender, uint256 _value) public returns (bool success); function allowance(address _owner, address _spender) public constant returns (uint256 remaining); event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); } // File: contracts/token/ERC20Token.sol /** * @title ERC20Token - ERC20 base implementation * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20Token is IERC20Token, SafeMath { mapping (address => uint256) public balances; mapping (address => mapping (address => uint256)) public allowed; function transfer(address _to, uint256 _value) public returns (bool) { require(_to != address(0)); require(balances[msg.sender] >= _value); balances[msg.sender] = safeSub(balances[msg.sender], _value); balances[_to] = safeAdd(balances[_to], _value); Transfer(msg.sender, _to, _value); return true; } function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { require(_to != address(0)); require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value); balances[_to] = safeAdd(balances[_to], _value); balances[_from] = safeSub(balances[_from], _value); allowed[_from][msg.sender] = safeSub(allowed[_from][msg.sender], _value); Transfer(_from, _to, _value); return true; } function balanceOf(address _owner) public constant returns (uint256) { return balances[_owner]; } function approve(address _spender, uint256 _value) public returns (bool) { allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } function allowance(address _owner, address _spender) public constant returns (uint256) { return allowed[_owner][_spender]; } } // File: contracts/token/ITokenEventListener.sol /** * @title ITokenEventListener * @dev Interface which should be implemented by token listener */ interface ITokenEventListener { /** * @dev Function is called after token transfer/transferFrom * @param _from Sender address * @param _to Receiver address * @param _value Amount of tokens */ function onTokenTransfer(address _from, address _to, uint256 _value) external; } // File: contracts/token/ManagedToken.sol /** * @title ManagedToken * @dev ERC20 compatible token with issue and destroy facilities * @dev All transfers can be monitored by token event listener */ contract ManagedToken is ERC20Token, MultiOwnable { bool public allowTransfers = false; bool public issuanceFinished = false; ITokenEventListener public eventListener; event AllowTransfersChanged(bool _newState); event Issue(address indexed _to, uint256 _value); event Destroy(address indexed _from, uint256 _value); event IssuanceFinished(); modifier transfersAllowed() { require(allowTransfers); _; } modifier canIssue() { require(!issuanceFinished); _; } /** * @dev ManagedToken constructor * @param _listener Token listener(address can be 0x0) * @param _owners Owners list */ function ManagedToken(address _listener, address[] _owners) public { if(_listener != address(0)) { eventListener = ITokenEventListener(_listener); } _setOwners(_owners); } /** * @dev Enable/disable token transfers. Can be called only by owners * @param _allowTransfers True - allow False - disable */ function setAllowTransfers(bool _allowTransfers) external onlyOwner { allowTransfers = _allowTransfers; AllowTransfersChanged(_allowTransfers); } /** * @dev Set/remove token event listener * @param _listener Listener address (Contract must implement ITokenEventListener interface) */ function setListener(address _listener) public onlyOwner { if(_listener != address(0)) { eventListener = ITokenEventListener(_listener); } else { delete eventListener; } } function transfer(address _to, uint256 _value) public transfersAllowed returns (bool) { bool success = super.transfer(_to, _value); if(hasListener() && success) { eventListener.onTokenTransfer(msg.sender, _to, _value); } return success; } function transferFrom(address _from, address _to, uint256 _value) public transfersAllowed returns (bool) { bool success = super.transferFrom(_from, _to, _value); if(hasListener() && success) { eventListener.onTokenTransfer(_from, _to, _value); } return success; } function hasListener() internal view returns(bool) { if(eventListener == address(0)) { return false; } return true; } /** * @dev Issue tokens to specified wallet * @param _to Wallet address * @param _value Amount of tokens */ function issue(address _to, uint256 _value) external onlyOwner canIssue { totalSupply = safeAdd(totalSupply, _value); balances[_to] = safeAdd(balances[_to], _value); Issue(_to, _value); Transfer(address(0), _to, _value); } /** * @dev Destroy tokens on specified address (Called by owner or token holder) * @dev Fund contract address must be in the list of owners to burn token during refund * @param _from Wallet address * @param _value Amount of tokens to destroy */ function destroy(address _from, uint256 _value) external { require(ownerByAddress[msg.sender] || msg.sender == _from); require(balances[_from] >= _value); totalSupply = safeSub(totalSupply, _value); balances[_from] = safeSub(balances[_from], _value); Transfer(_from, address(0), _value); Destroy(_from, _value); } /** * @dev Increase the amount of tokens that an owner allowed to a spender. * * approve should be called when allowed[_spender] == 0. To increment * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From OpenZeppelin StandardToken.sol * @param _spender The address which will spend the funds. * @param _addedValue The amount of tokens to increase the allowance by. */ function increaseApproval(address _spender, uint _addedValue) public returns (bool) { allowed[msg.sender][_spender] = safeAdd(allowed[msg.sender][_spender], _addedValue); Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } /** * @dev Decrease the amount of tokens that an owner allowed to a spender. * * approve should be called when allowed[_spender] == 0. To decrement * allowed value is better to use this function to avoid 2 calls (and wait until * the first transaction is mined) * From OpenZeppelin StandardToken.sol * @param _spender The address which will spend the funds. * @param _subtractedValue The amount of tokens to decrease the allowance by. */ function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) { uint oldValue = allowed[msg.sender][_spender]; if (_subtractedValue > oldValue) { allowed[msg.sender][_spender] = 0; } else { allowed[msg.sender][_spender] = safeSub(oldValue, _subtractedValue); } Approval(msg.sender, _spender, allowed[msg.sender][_spender]); return true; } /** * @dev Finish token issuance * @return True if success */ function finishIssuance() public onlyOwner returns (bool) { issuanceFinished = true; IssuanceFinished(); return true; } } // File: contracts/Fund.sol contract Fund is ICrowdsaleFund, SafeMath, MultiOwnable { enum FundState { Crowdsale, CrowdsaleRefund, TeamWithdraw, Refund } FundState public state = FundState.Crowdsale; ManagedToken public token; uint256 public constant INITIAL_TAP = 192901234567901; // (wei/sec) == 500 ether/month address public teamWallet; uint256 public crowdsaleEndDate; address public referralTokenWallet; address public foundationTokenWallet; address public reserveTokenWallet; address public bountyTokenWallet; address public companyTokenWallet; address public advisorTokenWallet; address public lockedTokenAddress; address public refundManager; uint256 public tap; uint256 public lastWithdrawTime = 0; uint256 public firstWithdrawAmount = 0; address public crowdsaleAddress; mapping(address => uint256) public contributions; event RefundContributor(address tokenHolder, uint256 amountWei, uint256 timestamp); event RefundHolder(address tokenHolder, uint256 amountWei, uint256 tokenAmount, uint256 timestamp); event Withdraw(uint256 amountWei, uint256 timestamp); event RefundEnabled(address initiatorAddress); /** * @dev Fund constructor * @param _teamWallet Withdraw functions transfers ether to this address * @param _referralTokenWallet Referral wallet address * @param _companyTokenWallet Company wallet address * @param _reserveTokenWallet Reserve wallet address * @param _bountyTokenWallet Bounty wallet address * @param _advisorTokenWallet Advisor wallet address * @param _owners Contract owners */ function Fund( address _teamWallet, address _referralTokenWallet, address _foundationTokenWallet, address _companyTokenWallet, address _reserveTokenWallet, address _bountyTokenWallet, address _advisorTokenWallet, address _refundManager, address[] _owners ) public { teamWallet = _teamWallet; referralTokenWallet = _referralTokenWallet; foundationTokenWallet = _foundationTokenWallet; companyTokenWallet = _companyTokenWallet; reserveTokenWallet = _reserveTokenWallet; bountyTokenWallet = _bountyTokenWallet; advisorTokenWallet = _advisorTokenWallet; refundManager = _refundManager; _setOwners(_owners); } modifier withdrawEnabled() { require(canWithdraw()); _; } modifier onlyCrowdsale() { require(msg.sender == crowdsaleAddress); _; } function canWithdraw() public returns(bool); function setCrowdsaleAddress(address _crowdsaleAddress) public onlyOwner { require(crowdsaleAddress == address(0)); crowdsaleAddress = _crowdsaleAddress; } function setTokenAddress(address _tokenAddress) public onlyOwner { require(address(token) == address(0)); token = ManagedToken(_tokenAddress); } function setLockedTokenAddress(address _lockedTokenAddress) public onlyOwner { require(address(lockedTokenAddress) == address(0)); lockedTokenAddress = _lockedTokenAddress; } /** * @dev Process crowdsale contribution */ function processContribution(address contributor) external payable onlyCrowdsale { require(state == FundState.Crowdsale); uint256 totalContribution = safeAdd(contributions[contributor], msg.value); contributions[contributor] = totalContribution; } /** * @dev Callback is called after crowdsale finalization if soft cap is reached */ function onCrowdsaleEnd() external onlyCrowdsale { state = FundState.TeamWithdraw; ISimpleCrowdsale crowdsale = ISimpleCrowdsale(crowdsaleAddress); firstWithdrawAmount = safeDiv(crowdsale.getSoftCap(), 2); lastWithdrawTime = now; tap = INITIAL_TAP; crowdsaleEndDate = now; } /** * @dev Callback is called after crowdsale finalization if soft cap is not reached */ function enableCrowdsaleRefund() external onlyCrowdsale { require(state == FundState.Crowdsale); state = FundState.CrowdsaleRefund; } /** * @dev Function is called by contributor to refund payments if crowdsale failed to reach soft cap */ function refundCrowdsaleContributor() external { require(state == FundState.CrowdsaleRefund); require(contributions[msg.sender] > 0); uint256 refundAmount = contributions[msg.sender]; contributions[msg.sender] = 0; token.destroy(msg.sender, token.balanceOf(msg.sender)); msg.sender.transfer(refundAmount); RefundContributor(msg.sender, refundAmount, now); } /** * @dev Function is called by owner to refund payments if crowdsale failed to reach soft cap */ function autoRefundCrowdsaleContributor(address contributorAddress) external { require(ownerByAddress[msg.sender] == true || msg.sender == refundManager); require(state == FundState.CrowdsaleRefund); require(contributions[contributorAddress] > 0); uint256 refundAmount = contributions[contributorAddress]; contributions[contributorAddress] = 0; token.destroy(contributorAddress, token.balanceOf(contributorAddress)); contributorAddress.transfer(refundAmount); RefundContributor(contributorAddress, refundAmount, now); } /** * @dev Decrease tap amount * @param _tap New tap value */ function decTap(uint256 _tap) external onlyOwner { require(state == FundState.TeamWithdraw); require(_tap < tap); tap = _tap; } function getCurrentTapAmount() public constant returns(uint256) { if(state != FundState.TeamWithdraw) { return 0; } return calcTapAmount(); } function calcTapAmount() internal view returns(uint256) { uint256 amount = safeMul(safeSub(now, lastWithdrawTime), tap); if(address(this).balance < amount) { amount = address(this).balance; } return amount; } function firstWithdraw() public onlyOwner withdrawEnabled { require(firstWithdrawAmount > 0); uint256 amount = firstWithdrawAmount; firstWithdrawAmount = 0; teamWallet.transfer(amount); Withdraw(amount, now); } /** * @dev Withdraw tap amount */ function withdraw() public onlyOwner withdrawEnabled { require(state == FundState.TeamWithdraw); uint256 amount = calcTapAmount(); lastWithdrawTime = now; teamWallet.transfer(amount); Withdraw(amount, now); } // Refund /** * @dev Called to start refunding */ function enableRefund() internal { require(state == FundState.TeamWithdraw); state = FundState.Refund; token.destroy(lockedTokenAddress, token.balanceOf(lockedTokenAddress)); token.destroy(companyTokenWallet, token.balanceOf(companyTokenWallet)); token.destroy(reserveTokenWallet, token.balanceOf(reserveTokenWallet)); token.destroy(foundationTokenWallet, token.balanceOf(foundationTokenWallet)); token.destroy(bountyTokenWallet, token.balanceOf(bountyTokenWallet)); token.destroy(referralTokenWallet, token.balanceOf(referralTokenWallet)); token.destroy(advisorTokenWallet, token.balanceOf(advisorTokenWallet)); RefundEnabled(msg.sender); } /** * @dev Function is called by contributor to refund * Buy user tokens for refundTokenPrice and destroy them */ function refundTokenHolder() public { require(state == FundState.Refund); uint256 tokenBalance = token.balanceOf(msg.sender); require(tokenBalance > 0); uint256 refundAmount = safeDiv(safeMul(tokenBalance, address(this).balance), token.totalSupply()); require(refundAmount > 0); token.destroy(msg.sender, tokenBalance); msg.sender.transfer(refundAmount); RefundHolder(msg.sender, refundAmount, tokenBalance, now); } } // File: contracts/fund/IPollManagedFund.sol /** * @title IPollManagedFund * @dev Fund callbacks used by polling contracts */ interface IPollManagedFund { /** * @dev TapPoll callback * @param agree True if new tap value is accepted by majority of contributors * @param _tap New tap value */ function onTapPollFinish(bool agree, uint256 _tap) external; /** * @dev RefundPoll callback * @param agree True if contributors decided to allow refunding */ function onRefundPollFinish(bool agree) external; } // File: contracts/poll/BasePoll.sol /** * @title BasePoll * @dev Abstract base class for polling contracts */ contract BasePoll is SafeMath { struct Vote { uint256 time; uint256 weight; bool agree; } uint256 public constant MAX_TOKENS_WEIGHT_DENOM = 1000; IERC20Token public token; address public fundAddress; uint256 public startTime; uint256 public endTime; bool checkTransfersAfterEnd; uint256 public yesCounter = 0; uint256 public noCounter = 0; uint256 public totalVoted = 0; bool public finalized; mapping(address => Vote) public votesByAddress; modifier checkTime() { require(now >= startTime && now <= endTime); _; } modifier notFinalized() { require(!finalized); _; } /** * @dev BasePoll constructor * @param _tokenAddress ERC20 compatible token contract address * @param _fundAddress Fund contract address * @param _startTime Poll start time * @param _endTime Poll end time */ function BasePoll(address _tokenAddress, address _fundAddress, uint256 _startTime, uint256 _endTime, bool _checkTransfersAfterEnd) public { require(_tokenAddress != address(0)); require(_startTime >= now && _endTime > _startTime); token = IERC20Token(_tokenAddress); fundAddress = _fundAddress; startTime = _startTime; endTime = _endTime; finalized = false; checkTransfersAfterEnd = _checkTransfersAfterEnd; } /** * @dev Process user`s vote * @param agree True if user endorses the proposal else False */ function vote(bool agree) public checkTime { require(votesByAddress[msg.sender].time == 0); uint256 voiceWeight = token.balanceOf(msg.sender); uint256 maxVoiceWeight = safeDiv(token.totalSupply(), MAX_TOKENS_WEIGHT_DENOM); voiceWeight = voiceWeight <= maxVoiceWeight ? voiceWeight : maxVoiceWeight; if(agree) { yesCounter = safeAdd(yesCounter, voiceWeight); } else { noCounter = safeAdd(noCounter, voiceWeight); } votesByAddress[msg.sender].time = now; votesByAddress[msg.sender].weight = voiceWeight; votesByAddress[msg.sender].agree = agree; totalVoted = safeAdd(totalVoted, 1); } /** * @dev Revoke user`s vote */ function revokeVote() public checkTime { require(votesByAddress[msg.sender].time > 0); uint256 voiceWeight = votesByAddress[msg.sender].weight; bool agree = votesByAddress[msg.sender].agree; votesByAddress[msg.sender].time = 0; votesByAddress[msg.sender].weight = 0; votesByAddress[msg.sender].agree = false; totalVoted = safeSub(totalVoted, 1); if(agree) { yesCounter = safeSub(yesCounter, voiceWeight); } else { noCounter = safeSub(noCounter, voiceWeight); } } /** * @dev Function is called after token transfer from user`s wallet to check and correct user`s vote * */ function onTokenTransfer(address tokenHolder, uint256 amount) public { require(msg.sender == fundAddress); if(votesByAddress[tokenHolder].time == 0) { return; } if(!checkTransfersAfterEnd) { if(finalized || (now < startTime || now > endTime)) { return; } } if(token.balanceOf(tokenHolder) >= votesByAddress[tokenHolder].weight) { return; } uint256 voiceWeight = amount; if(amount > votesByAddress[tokenHolder].weight) { voiceWeight = votesByAddress[tokenHolder].weight; } if(votesByAddress[tokenHolder].agree) { yesCounter = safeSub(yesCounter, voiceWeight); } else { noCounter = safeSub(noCounter, voiceWeight); } votesByAddress[tokenHolder].weight = safeSub(votesByAddress[tokenHolder].weight, voiceWeight); } /** * Finalize poll and call onPollFinish callback with result */ function tryToFinalize() public notFinalized returns(bool) { if(now < endTime) { return false; } finalized = true; onPollFinish(isSubjectApproved()); return true; } function isNowApproved() public view returns(bool) { return isSubjectApproved(); } function isSubjectApproved() internal view returns(bool) { return yesCounter > noCounter; } /** * @dev callback called after poll finalization */ function onPollFinish(bool agree) internal; } // File: contracts/RefundPoll.sol /** * @title RefundPoll * @dev Enables fund refund mode */ contract RefundPoll is BasePoll { uint256 public holdEndTime = 0; /** * RefundPoll constructor * @param _tokenAddress ERC20 compatible token contract address * @param _fundAddress Fund contract address * @param _startTime Poll start time * @param _endTime Poll end time */ function RefundPoll( address _tokenAddress, address _fundAddress, uint256 _startTime, uint256 _endTime, uint256 _holdEndTime, bool _checkTransfersAfterEnd ) public BasePoll(_tokenAddress, _fundAddress, _startTime, _endTime, _checkTransfersAfterEnd) { holdEndTime = _holdEndTime; } function tryToFinalize() public returns(bool) { if(holdEndTime > 0 && holdEndTime > endTime) { require(now >= holdEndTime); } else { require(now >= endTime); } finalized = true; onPollFinish(isSubjectApproved()); return true; } function isSubjectApproved() internal view returns(bool) { return yesCounter > noCounter && yesCounter >= safeDiv(token.totalSupply(), 3); } function onPollFinish(bool agree) internal { IPollManagedFund fund = IPollManagedFund(fundAddress); fund.onRefundPollFinish(agree); } } // File: contracts/TapPoll.sol /** * @title TapPoll * @dev Poll to increase tap amount */ contract TapPoll is BasePoll { uint256 public tap; uint256 public minTokensPerc = 0; /** * TapPoll constructor * @param _tap New tap value * @param _tokenAddress ERC20 compatible token contract address * @param _fundAddress Fund contract address * @param _startTime Poll start time * @param _endTime Poll end time * @param _minTokensPerc - Min percent of tokens from totalSupply where poll is considered to be fulfilled */ function TapPoll( uint256 _tap, address _tokenAddress, address _fundAddress, uint256 _startTime, uint256 _endTime, uint256 _minTokensPerc ) public BasePoll(_tokenAddress, _fundAddress, _startTime, _endTime, false) { tap = _tap; minTokensPerc = _minTokensPerc; } function onPollFinish(bool agree) internal { IPollManagedFund fund = IPollManagedFund(fundAddress); fund.onTapPollFinish(agree, tap); } function getVotedTokensPerc() public view returns(uint256) { return safeDiv(safeMul(safeAdd(yesCounter, noCounter), 100), token.totalSupply()); } function isSubjectApproved() internal view returns(bool) { return yesCounter > noCounter && getVotedTokensPerc() >= minTokensPerc; } } // File: contracts/PollManagedFund.sol /** * @title PollManagedFund * @dev Fund controlled by users */ contract PollManagedFund is Fund, DateTime, ITokenEventListener { uint256 public constant TAP_POLL_DURATION = 3 days; uint256 public constant REFUND_POLL_DURATION = 7 days; uint256 public constant MAX_VOTED_TOKEN_PERC = 10; TapPoll public tapPoll; RefundPoll public refundPoll; uint256 public minVotedTokensPerc = 0; uint256 public secondRefundPollDate = 0; bool public isWithdrawEnabled = true; uint256[] public refundPollDates = [ 1530403200, // 01.07.2018 1538352000, // 01.10.2018 1546300800, // 01.01.2019 1554076800, // 01.04.2019 1561939200, // 01.07.2019 1569888000, // 01.10.2019 1577836800, // 01.01.2020 1585699200 // 01.04.2020 ]; modifier onlyTokenHolder() { require(token.balanceOf(msg.sender) > 0); _; } event TapPollCreated(); event TapPollFinished(bool approved, uint256 _tap); event RefundPollCreated(); event RefundPollFinished(bool approved); /** * @dev PollManagedFund constructor * params - see Fund constructor */ function PollManagedFund( address _teamWallet, address _referralTokenWallet, address _foundationTokenWallet, address _companyTokenWallet, address _reserveTokenWallet, address _bountyTokenWallet, address _advisorTokenWallet, address _refundManager, address[] _owners ) public Fund(_teamWallet, _referralTokenWallet, _foundationTokenWallet, _companyTokenWallet, _reserveTokenWallet, _bountyTokenWallet, _advisorTokenWallet, _refundManager, _owners) { } function canWithdraw() public returns(bool) { if( address(refundPoll) != address(0) && !refundPoll.finalized() && refundPoll.holdEndTime() > 0 && now >= refundPoll.holdEndTime() && refundPoll.isNowApproved() ) { return false; } return isWithdrawEnabled; } /** * @dev ITokenEventListener implementation. Notify active poll contracts about token transfers */ function onTokenTransfer(address _from, address /*_to*/, uint256 _value) external { require(msg.sender == address(token)); if(address(tapPoll) != address(0) && !tapPoll.finalized()) { tapPoll.onTokenTransfer(_from, _value); } if(address(refundPoll) != address(0) && !refundPoll.finalized()) { refundPoll.onTokenTransfer(_from, _value); } } /** * @dev Update minVotedTokensPerc value after tap poll. * Set new value == 50% from current voted tokens amount */ function updateMinVotedTokens(uint256 _minVotedTokensPerc) internal { uint256 newPerc = safeDiv(_minVotedTokensPerc, 2); if(newPerc > MAX_VOTED_TOKEN_PERC) { minVotedTokensPerc = MAX_VOTED_TOKEN_PERC; return; } minVotedTokensPerc = newPerc; } // Tap poll function createTapPoll(uint8 tapIncPerc) public onlyOwner { require(state == FundState.TeamWithdraw); require(tapPoll == address(0)); require(getDay(now) == 10); require(tapIncPerc <= 50); uint256 _tap = safeAdd(tap, safeDiv(safeMul(tap, tapIncPerc), 100)); uint256 startTime = now; uint256 endTime = startTime + TAP_POLL_DURATION; tapPoll = new TapPoll(_tap, token, this, startTime, endTime, minVotedTokensPerc); TapPollCreated(); } function onTapPollFinish(bool agree, uint256 _tap) external { require(msg.sender == address(tapPoll) && tapPoll.finalized()); if(agree) { tap = _tap; } updateMinVotedTokens(tapPoll.getVotedTokensPerc()); TapPollFinished(agree, _tap); delete tapPoll; } // Refund poll function checkRefundPollDate() internal view returns(bool) { if(secondRefundPollDate > 0 && now >= secondRefundPollDate && now <= safeAdd(secondRefundPollDate, 1 days)) { return true; } for(uint i; i < refundPollDates.length; i++) { if(now >= refundPollDates[i] && now <= safeAdd(refundPollDates[i], 1 days)) { return true; } } return false; } function createRefundPoll() public onlyTokenHolder { require(state == FundState.TeamWithdraw); require(address(refundPoll) == address(0)); require(checkRefundPollDate()); if(secondRefundPollDate > 0 && now > safeAdd(secondRefundPollDate, 1 days)) { secondRefundPollDate = 0; } uint256 startTime = now; uint256 endTime = startTime + REFUND_POLL_DURATION; bool isFirstRefund = secondRefundPollDate == 0; uint256 holdEndTime = 0; if(isFirstRefund) { holdEndTime = toTimestamp( getYear(startTime), getMonth(startTime) + 1, 1 ); } refundPoll = new RefundPoll(token, this, startTime, endTime, holdEndTime, isFirstRefund); RefundPollCreated(); } function onRefundPollFinish(bool agree) external { require(msg.sender == address(refundPoll) && refundPoll.finalized()); if(agree) { if(secondRefundPollDate > 0) { enableRefund(); } else { uint256 startTime = refundPoll.startTime(); secondRefundPollDate = toTimestamp( getYear(startTime), getMonth(startTime) + 2, 1 ); isWithdrawEnabled = false; } } else { secondRefundPollDate = 0; isWithdrawEnabled = true; } RefundPollFinished(agree); delete refundPoll; } function forceRefund() public { require(msg.sender == refundManager); enableRefund(); } }