Transaction Hash:
Block:
10670250 at Aug-16-2020 09:16:04 AM +UTC
Transaction Fee:
0.052288416 ETH
$102.11
Gas Used:
544,671 Gas / 96 Gwei
Emitted Events:
| 136 |
SmartToken.Transfer( _from=0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, _to=KyberNetwork, _value=3278546914203000613861 )
|
| 137 |
KyberReserve.TradeExecute( origin=KyberNetwork, src=0xEeeeeEee...eeeeeEEeE, srcAmount=16492078666679263871, destToken=SmartToken, destAmount=3278546914203000613861, destAddress=KyberNetwork )
|
| 138 |
SmartToken.Transfer( _from=KyberNetwork, _to=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, _value=3278546914203000613861 )
|
| 139 |
KyberFeeHandler.FeeDistributed( token=0xEeeeeEee...eeeeeEEeE, platformWallet=0x00000000...000000000, platformFeeWei=0, rewardWei=21581818375433986, rebateWei=10146429159660388, rebateWallets=[0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9], rebatePercentBpsPerWallet=[10000], burnAmtWei=1322010313962267 )
|
| 140 |
KyberNetwork.KyberTrade( 0x30bbea603a7b36858fe5e3ec6ba5ff59dde039d02120d758eacfaed01520577d, 0x000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee, 0x0000000000000000000000001f573d6fb3f13d689ff844b4ce37794d79a7ff1c, 000000000000000000000000000000000000000000000000e5550d33f60b2000, 00000000000000000000000000000000000000000000000000756b08ced3fd81, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000120, 0000000000000000000000000000000000000000000000000000000000000140, 0000000000000000000000000000000000000000000000000000000000000180, 00000000000000000000000000000000000000000000000000000000000001a0, 00000000000000000000000000000000000000000000000000000000000001e0, 0000000000000000000000000000000000000000000000000000000000000200, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, ffabcd0000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000000000000000000000000000e4dfa22b2737227f, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000001, 00000000000000000000000000000000000000000000000ac6d69b4f4aac655a )
|
| 141 |
KyberNetworkProxy.ExecuteTrade( trader=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, src=0xEeeeeEee...eeeeeEEeE, dest=SmartToken, destAddress=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, actualSrcAmount=16525128924528320512, actualDestAmount=3278546914203000613861, platformWallet=0x00000000...000000000, platformFeeBps=0 )
|
| 142 |
SmartToken.Transfer( _from=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, _to=LiquidityPoolV1Converter, _value=3278546914203000635392 )
|
| 143 |
LiquidityPoolV1Converter.Conversion( _fromToken=SmartToken, _toToken=0xEeeeeEee...eeeeeEEeE, _trader=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e, _amount=3278546914203000635392, _return=16792965095137177722, _conversionFee=16809774870007184 )
|
| 144 |
LiquidityPoolV1Converter.TokenRateUpdate( _token1=SmartToken, _token2=0xEeeeeEee...eeeeeEEeE, _rateN=10070154208758833758178000000, _rateD=1965701594705949851679102000000 )
|
| 145 |
LiquidityPoolV1Converter.TokenRateUpdate( _token1=SmartToken, _token2=SmartToken, _rateN=3931403189411899703358204000000, _rateD=5885696366553071107328661500000 )
|
| 146 |
LiquidityPoolV1Converter.TokenRateUpdate( _token1=SmartToken, _token2=0xEeeeeEee...eeeeeEEeE, _rateN=20140308417517667516356000000, _rateD=5885696366553071107328661500000 )
|
| 147 |
LiquidityPoolV1Converter.PriceDataUpdate( _connectorToken=SmartToken, _tokenSupply=11771392733106142214657323, _connectorBalance=3931403189411899703358204, _connectorWeight=500000 )
|
| 148 |
LiquidityPoolV1Converter.PriceDataUpdate( _connectorToken=0xEeeeeEee...eeeeeEEeE, _tokenSupply=11771392733106142214657323, _connectorBalance=20140308417517667516356, _connectorWeight=500000 )
|
| 149 |
BancorNetwork.Conversion( _smartToken=SmartToken, _fromToken=SmartToken, _toToken=0xEeeeeEee...eeeeeEEeE, _fromAmount=3278546914203000635392, _toAmount=16792965095137177722, _trader=[Receiver] 0x693c188e40f760ecf00d2946ef45260b84fbc43e )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x1F573D6F...d79a7FF1C | |||||
|
0x5A0b54D5...D3E029c4c
Miner
| (Spark Pool) | 29.095750208286904277 Eth | 29.148038624286904277 Eth | 0.052288416 | |
| 0x693c188E...b84FBc43e | 921.858568728911925491 Eth | 922.126404899520782701 Eth | 0.26783617060885721 | ||
| 0x7a337007...5552d4F7D | (Kyber: Reserve 2) | 1,121.185346500057580789 Eth | 1,137.67742516673684466 Eth | 16.492078666679263871 | |
| 0xd3d2b564...a941114fe | (Kyber: Fee Handler) | 1,420.582838747283624275 Eth | 1,420.615889005132680916 Eth | 0.033050257849056641 | |
| 0xE870D001...E22be4ABd | (Bancor: Converter 216) | 20,157.101382612804694078 Eth | 20,140.308417517667516356 Eth | 16.792965095137177722 | |
| 0xFB80Bfa1...deeb10483 | |||||
| 0xfF1b9745...895FB701A |
89.610512558364816037 Eth
Nonce: 185343
|
89.558224142364816037 Eth
Nonce: 185344
| 0.052288416 |
Execution Trace
0x693c188e40f760ecf00d2946ef45260b84fbc43e.00040503( )
0x398c39714e3c6f9f6f967bb81556e529db5078a9.1ca4695f( )KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, srcQty=16525128924528320512, blockNumber=10670250 ) => ( 198795250766478861658 )-
ConversionRates.getRate( token=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, currentBlockNumber=10670250, buy=True, qty=16525128924528320512 ) => ( 198795250766478861658 ) -
SmartToken.CALL( )
-
SmartToken.balanceOf( 0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9 ) => ( 3968236531845659051467 )
-
SmartToken.allowance( 0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, 0x7a3370075a54B187d7bD5DceBf0ff2B5552d4F7D ) => ( 115792089237316195423570985008687907853269984665640563632633342951265004345438 )
-
-
0xaaf0e98629e639703dbf6133836bed7cdfc556cd.78789120( ) 0x398c39714e3c6f9f6f967bb81556e529db5078a9.51d88471( )ETH 16.525128924528320512
KyberNetworkProxy.tradeWithHint( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=16525128924528320512, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, destAddress=0x693c188E40F760ecF00d2946ef45260b84FBc43e, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=0, walletId=0x0000000000000000000000000000000000000000, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FFABCD00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) => ( 3278546914203000613861 )-
SmartToken.balanceOf( 0x693c188E40F760ecF00d2946ef45260b84FBc43e ) => ( 392719474705303 )
ETH 16.525128924528320512
KyberNetwork.tradeWithHintAndFee( trader=0x693c188E40F760ecF00d2946ef45260b84FBc43e, src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=16525128924528320512, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, destAddress=0x693c188E40F760ecF00d2946ef45260b84FBc43e, maxDestAmount=57896044618658097711785492504343953926634992332820282019728792003956564819968, minConversionRate=0, platformWallet=0x0000000000000000000000000000000000000000, platformFeeBps=0, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FFABCD00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) => ( destAmount=3278546914203000613861 )-
KyberMatchingEngine.getTradingReserves( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, isTokenToToken=False, hint=0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000A00000000000000000000000000000000000000000000000000000000000000001FFABCD00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) -
KyberStorage.getReservesData( ) => ( areAllReservesListed=True, feeAccountedArr=[true], entitledRebateArr=[true], reserveAddresses=[0x7a3370075a54B187d7bD5DceBf0ff2B5552d4F7D] ) KyberReserve.getConversionRate( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, srcQty=16492078666679263871, blockNumber=10670250 ) => ( 198795250766478861658 )-
ConversionRates.getRate( token=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, currentBlockNumber=10670250, buy=True, qty=16492078666679263871 ) => ( 198795250766478861658 ) -
SmartToken.CALL( )
-
SmartToken.balanceOf( 0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9 ) => ( 3968236531845659051467 )
-
SmartToken.allowance( 0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, 0x7a3370075a54B187d7bD5DceBf0ff2B5552d4F7D ) => ( 115792089237316195423570985008687907853269984665640563632633342951265004345438 )
-
-
KyberMatchingEngine.doMatch( src=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, dest=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, srcAmounts=[16492078666679263871], feesAccountedDestBps=[0], rates=[198795250766478861658] ) => ( reserveIndexes=[0] ) -
SmartToken.balanceOf( 0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 1966 )
ETH 16.492078666679263871
KyberReserve.trade( srcToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, srcAmount=16492078666679263871, destToken=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, destAddress=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, conversionRate=198795250766478861658, validate=True ) => ( True )-
SmartToken.CALL( )
-
ConversionRates.recordImbalance( token=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, buyAmount=3278546914203000613861, rateUpdateBlock=0, currentBlock=10670250 ) -
SmartToken.transferFrom( _from=0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9, _to=0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC, _value=3278546914203000613861 ) => ( success=True )
-
-
SmartToken.balanceOf( 0x7C66550C9c730B6fdd4C03bc2e73c5462c5F7ACC ) => ( 3278546914203000615827 )
-
SmartToken.transfer( _to=0x693c188E40F760ecF00d2946ef45260b84FBc43e, _value=3278546914203000613861 ) => ( success=True )
-
KyberStorage.getRebateWalletsFromIds( ) => ( rebateWallets=[0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9] ) - ETH 0.033050257849056641
KyberFeeHandler.handleFees( token=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, rebateWallets=[0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9], rebateBpsPerWallet=[10000], platformWallet=0x0000000000000000000000000000000000000000, platformFee=0, networkFee=33050257849056641 )
-
-
SmartToken.balanceOf( 0x693c188E40F760ecF00d2946ef45260b84FBc43e ) => ( 3278547306922475319164 )
-
0xaaf0e98629e639703dbf6133836bed7cdfc556cd.401687f4( )-
SmartToken.balanceOf( 0x693c188E40F760ecF00d2946ef45260b84FBc43e ) => ( 3278547306922475319164 )
BancorNetwork.convert( _path=[0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, 0xb1CD6e4153B2a390Cf00A6556b0fC1458C4A5533, 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE], _amount=3278546914203000635392, _minReturn=1 ) => ( 16792965095137177722 )-
SmartToken.CALL( )
-
LiquidityPoolV1Converter.STATICCALL( ) -
SmartToken.transferFrom( _from=0x693c188E40F760ecF00d2946ef45260b84FBc43e, _to=0xE870D00176b2C71AFD4c43ceA550228E22be4ABd, _value=3278546914203000635392 ) => ( success=True )
-
ContractRegistry.addressOf( _contractName=424E54546F6B656E000000000000000000000000000000000000000000000000 ) => ( 0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C ) -
SmartToken.CALL( )
-
LiquidityPoolV1Converter.STATICCALL( ) LiquidityPoolV1Converter.convert( _sourceToken=0x1F573D6Fb3F13d689FF844B4cE37794d79a7FF1C, _targetToken=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, _amount=3278546914203000635392, _trader=0x693c188E40F760ecF00d2946ef45260b84FBc43e, _beneficiary=0x693c188E40F760ecF00d2946ef45260b84FBc43e ) => ( 16792965095137177722 )-
ContractRegistry.addressOf( _contractName=42616E636F724E6574776F726B00000000000000000000000000000000000000 ) => ( 0x2F9EC37d6CcFFf1caB21733BdaDEdE11c823cCB0 ) -
SmartToken.CALL( )
-
ContractRegistry.addressOf( _contractName=42616E636F72466F726D756C6100000000000000000000000000000000000000 ) => ( 0xA049894d5dcaD406b7C827D6dc6A0B58CA4AE73a ) -
BancorFormula.crossReserveTargetAmount( _sourceReserveBalance=3928124642497696702722812, _sourceReserveWeight=500000, _targetReserveBalance=20157101382612804694078, _targetReserveWeight=500000, _amount=3278546914203000635392 ) => ( 16809774870007184906 ) -
SmartToken.balanceOf( 0xE870D00176b2C71AFD4c43ceA550228E22be4ABd ) => ( 3931403189411899703358204 )
-
SmartToken.balanceOf( 0xE870D00176b2C71AFD4c43ceA550228E22be4ABd ) => ( 3931403189411899703358204 )
- ETH 16.792965095137177722
0x693c188e40f760ecf00d2946ef45260b84fbc43e.CALL( ) -
SmartToken.CALL( )
-
-
-
File 1 of 13: KyberNetwork
File 2 of 13: SmartToken
File 3 of 13: KyberReserve
File 4 of 13: KyberFeeHandler
File 5 of 13: KyberNetworkProxy
File 6 of 13: LiquidityPoolV1Converter
File 7 of 13: BancorNetwork
File 8 of 13: ConversionRates
File 9 of 13: KyberMatchingEngine
File 10 of 13: KyberStorage
File 11 of 13: SmartToken
File 12 of 13: ContractRegistry
File 13 of 13: BancorFormula
// 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 2 of 13: SmartToken
pragma solidity ^0.4.11;
/*
Overflow protected math functions
*/
contract SafeMath {
/**
constructor
*/
function SafeMath() {
}
/**
@dev returns the sum of _x and _y, asserts if the calculation overflows
@param _x value 1
@param _y value 2
@return sum
*/
function safeAdd(uint256 _x, uint256 _y) internal returns (uint256) {
uint256 z = _x + _y;
assert(z >= _x);
return z;
}
/**
@dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
@param _x minuend
@param _y subtrahend
@return difference
*/
function safeSub(uint256 _x, uint256 _y) internal returns (uint256) {
assert(_x >= _y);
return _x - _y;
}
/**
@dev returns the product of multiplying _x by _y, asserts if the calculation overflows
@param _x factor 1
@param _y factor 2
@return product
*/
function safeMul(uint256 _x, uint256 _y) internal returns (uint256) {
uint256 z = _x * _y;
assert(_x == 0 || z / _x == _y);
return z;
}
}
/*
Owned contract interface
*/
contract IOwned {
// this function isn't abstract since the compiler emits automatically generated getter functions as external
function owner() public constant returns (address owner) { owner; }
function transferOwnership(address _newOwner) public;
function acceptOwnership() public;
}
/*
Provides support and utilities for contract ownership
*/
contract Owned is IOwned {
address public owner;
address public newOwner;
event OwnerUpdate(address _prevOwner, address _newOwner);
/**
@dev constructor
*/
function Owned() {
owner = msg.sender;
}
// allows execution by the owner only
modifier ownerOnly {
assert(msg.sender == owner);
_;
}
/**
@dev allows transferring the contract ownership
the new owner still need to accept the transfer
can only be called by the contract owner
@param _newOwner new contract owner
*/
function transferOwnership(address _newOwner) public ownerOnly {
require(_newOwner != owner);
newOwner = _newOwner;
}
/**
@dev used by a new owner to accept an ownership transfer
*/
function acceptOwnership() public {
require(msg.sender == newOwner);
OwnerUpdate(owner, newOwner);
owner = newOwner;
newOwner = 0x0;
}
}
/*
Token Holder interface
*/
contract ITokenHolder is IOwned {
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
}
/*
We consider every contract to be a 'token holder' since it's currently not possible
for a contract to deny receiving tokens.
The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
the owner to send tokens that were sent to the contract by mistake back to their sender.
*/
contract TokenHolder is ITokenHolder, Owned {
/**
@dev constructor
*/
function TokenHolder() {
}
// validates an address - currently only checks that it isn't null
modifier validAddress(address _address) {
require(_address != 0x0);
_;
}
// verifies that the address is different than this contract address
modifier notThis(address _address) {
require(_address != address(this));
_;
}
/**
@dev withdraws tokens held by the contract and sends them to an account
can only be called by the owner
@param _token ERC20 token contract address
@param _to account to receive the new amount
@param _amount amount to withdraw
*/
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
public
ownerOnly
validAddress(_token)
validAddress(_to)
notThis(_to)
{
assert(_token.transfer(_to, _amount));
}
}
/*
ERC20 Standard Token interface
*/
contract IERC20Token {
// these functions aren't abstract since the compiler emits automatically generated getter functions as external
function name() public constant returns (string name) { name; }
function symbol() public constant returns (string symbol) { symbol; }
function decimals() public constant returns (uint8 decimals) { decimals; }
function totalSupply() public constant returns (uint256 totalSupply) { totalSupply; }
function balanceOf(address _owner) public constant returns (uint256 balance) { _owner; balance; }
function allowance(address _owner, address _spender) public constant returns (uint256 remaining) { _owner; _spender; remaining; }
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);
}
/**
ERC20 Standard Token implementation
*/
contract ERC20Token is IERC20Token, SafeMath {
string public standard = 'Token 0.1';
string public name = '';
string public symbol = '';
uint8 public decimals = 0;
uint256 public totalSupply = 0;
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
/**
@dev constructor
@param _name token name
@param _symbol token symbol
@param _decimals decimal points, for display purposes
*/
function ERC20Token(string _name, string _symbol, uint8 _decimals) {
require(bytes(_name).length > 0 && bytes(_symbol).length > 0); // validate input
name = _name;
symbol = _symbol;
decimals = _decimals;
}
// validates an address - currently only checks that it isn't null
modifier validAddress(address _address) {
require(_address != 0x0);
_;
}
/**
@dev send coins
throws on any error rather then return a false flag to minimize user errors
@param _to target address
@param _value transfer amount
@return true if the transfer was successful, false if it wasn't
*/
function transfer(address _to, uint256 _value)
public
validAddress(_to)
returns (bool success)
{
balanceOf[msg.sender] = safeSub(balanceOf[msg.sender], _value);
balanceOf[_to] = safeAdd(balanceOf[_to], _value);
Transfer(msg.sender, _to, _value);
return true;
}
/**
@dev an account/contract attempts to get the coins
throws on any error rather then return a false flag to minimize user errors
@param _from source address
@param _to target address
@param _value transfer amount
@return true if the transfer was successful, false if it wasn't
*/
function transferFrom(address _from, address _to, uint256 _value)
public
validAddress(_from)
validAddress(_to)
returns (bool success)
{
allowance[_from][msg.sender] = safeSub(allowance[_from][msg.sender], _value);
balanceOf[_from] = safeSub(balanceOf[_from], _value);
balanceOf[_to] = safeAdd(balanceOf[_to], _value);
Transfer(_from, _to, _value);
return true;
}
/**
@dev allow another account/contract to spend some tokens on your behalf
throws on any error rather then return a false flag to minimize user errors
also, to minimize the risk of the approve/transferFrom attack vector
(see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), approve has to be called twice
in 2 separate transactions - once to change the allowance to 0 and secondly to change it to the new allowance value
@param _spender approved address
@param _value allowance amount
@return true if the approval was successful, false if it wasn't
*/
function approve(address _spender, uint256 _value)
public
validAddress(_spender)
returns (bool success)
{
// if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal
require(_value == 0 || allowance[msg.sender][_spender] == 0);
allowance[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
}
/*
Smart Token interface
*/
contract ISmartToken is ITokenHolder, IERC20Token {
function disableTransfers(bool _disable) public;
function issue(address _to, uint256 _amount) public;
function destroy(address _from, uint256 _amount) public;
}
/*
Smart Token v0.2
'Owned' is specified here for readability reasons
*/
contract SmartToken is ISmartToken, ERC20Token, Owned, TokenHolder {
string public version = '0.2';
bool public transfersEnabled = true; // true if transfer/transferFrom are enabled, false if not
// triggered when a smart token is deployed - the _token address is defined for forward compatibility, in case we want to trigger the event from a factory
event NewSmartToken(address _token);
// triggered when the total supply is increased
event Issuance(uint256 _amount);
// triggered when the total supply is decreased
event Destruction(uint256 _amount);
/**
@dev constructor
@param _name token name
@param _symbol token short symbol, 1-6 characters
@param _decimals for display purposes only
*/
function SmartToken(string _name, string _symbol, uint8 _decimals)
ERC20Token(_name, _symbol, _decimals)
{
require(bytes(_symbol).length <= 6); // validate input
NewSmartToken(address(this));
}
// allows execution only when transfers aren't disabled
modifier transfersAllowed {
assert(transfersEnabled);
_;
}
/**
@dev disables/enables transfers
can only be called by the contract owner
@param _disable true to disable transfers, false to enable them
*/
function disableTransfers(bool _disable) public ownerOnly {
transfersEnabled = !_disable;
}
/**
@dev increases the token supply and sends the new tokens to an account
can only be called by the contract owner
@param _to account to receive the new amount
@param _amount amount to increase the supply by
*/
function issue(address _to, uint256 _amount)
public
ownerOnly
validAddress(_to)
notThis(_to)
{
totalSupply = safeAdd(totalSupply, _amount);
balanceOf[_to] = safeAdd(balanceOf[_to], _amount);
Issuance(_amount);
Transfer(this, _to, _amount);
}
/**
@dev removes tokens from an account and decreases the token supply
can only be called by the contract owner
@param _from account to remove the amount from
@param _amount amount to decrease the supply by
*/
function destroy(address _from, uint256 _amount)
public
ownerOnly
{
balanceOf[_from] = safeSub(balanceOf[_from], _amount);
totalSupply = safeSub(totalSupply, _amount);
Transfer(_from, this, _amount);
Destruction(_amount);
}
// ERC20 standard method overrides with some extra functionality
/**
@dev send coins
throws on any error rather then return a false flag to minimize user errors
note that when transferring to the smart token's address, the coins are actually destroyed
@param _to target address
@param _value transfer amount
@return true if the transfer was successful, false if it wasn't
*/
function transfer(address _to, uint256 _value) public transfersAllowed returns (bool success) {
assert(super.transfer(_to, _value));
// transferring to the contract address destroys tokens
if (_to == address(this)) {
balanceOf[_to] -= _value;
totalSupply -= _value;
Destruction(_value);
}
return true;
}
/**
@dev an account/contract attempts to get the coins
throws on any error rather then return a false flag to minimize user errors
note that when transferring to the smart token's address, the coins are actually destroyed
@param _from source address
@param _to target address
@param _value transfer amount
@return true if the transfer was successful, false if it wasn't
*/
function transferFrom(address _from, address _to, uint256 _value) public transfersAllowed returns (bool success) {
assert(super.transferFrom(_from, _to, _value));
// transferring to the contract address destroys tokens
if (_to == address(this)) {
balanceOf[_to] -= _value;
totalSupply -= _value;
Destruction(_value);
}
return true;
}
}File 3 of 13: 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 4 of 13: 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 5 of 13: KyberNetworkProxy
// 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 6 of 13: LiquidityPoolV1Converter
// File: contracts/utility/interfaces/IOwned.sol
pragma solidity 0.4.26;
/*
Owned contract interface
*/
contract IOwned {
// this function isn't abstract since the compiler emits automatically generated getter functions as external
function owner() public view returns (address) {this;}
function transferOwnership(address _newOwner) public;
function acceptOwnership() public;
}
// File: contracts/token/interfaces/IERC20Token.sol
pragma solidity 0.4.26;
/*
ERC20 Standard Token interface
*/
contract IERC20Token {
// these functions aren't abstract since the compiler emits automatically generated getter functions as external
function name() public view returns (string) {this;}
function symbol() public view returns (string) {this;}
function decimals() public view returns (uint8) {this;}
function totalSupply() public view returns (uint256) {this;}
function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
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);
}
// File: contracts/utility/interfaces/ITokenHolder.sol
pragma solidity 0.4.26;
/*
Token Holder interface
*/
contract ITokenHolder is IOwned {
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
}
// File: contracts/converter/interfaces/IConverterAnchor.sol
pragma solidity 0.4.26;
/*
Converter Anchor interface
*/
contract IConverterAnchor is IOwned, ITokenHolder {
}
// File: contracts/utility/interfaces/IWhitelist.sol
pragma solidity 0.4.26;
/*
Whitelist interface
*/
contract IWhitelist {
function isWhitelisted(address _address) public view returns (bool);
}
// File: contracts/converter/interfaces/IConverter.sol
pragma solidity 0.4.26;
/*
Converter interface
*/
contract IConverter is IOwned {
function converterType() public pure returns (uint16);
function anchor() public view returns (IConverterAnchor) {this;}
function isActive() public view returns (bool);
function targetAmountAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount) public view returns (uint256, uint256);
function convert(IERC20Token _sourceToken,
IERC20Token _targetToken,
uint256 _amount,
address _trader,
address _beneficiary) public payable returns (uint256);
function conversionWhitelist() public view returns (IWhitelist) {this;}
function conversionFee() public view returns (uint32) {this;}
function maxConversionFee() public view returns (uint32) {this;}
function reserveBalance(IERC20Token _reserveToken) public view returns (uint256);
function() external payable;
function transferAnchorOwnership(address _newOwner) public;
function acceptAnchorOwnership() public;
function setConversionFee(uint32 _conversionFee) public;
function setConversionWhitelist(IWhitelist _whitelist) public;
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
function withdrawETH(address _to) public;
function addReserve(IERC20Token _token, uint32 _ratio) public;
// deprecated, backward compatibility
function token() public view returns (IConverterAnchor);
function transferTokenOwnership(address _newOwner) public;
function acceptTokenOwnership() public;
function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool);
function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256);
function connectorTokens(uint256 _index) public view returns (IERC20Token);
function connectorTokenCount() public view returns (uint16);
}
// File: contracts/converter/interfaces/IConverterUpgrader.sol
pragma solidity 0.4.26;
/*
Converter Upgrader interface
*/
contract IConverterUpgrader {
function upgrade(bytes32 _version) public;
function upgrade(uint16 _version) public;
}
// File: contracts/converter/interfaces/IBancorFormula.sol
pragma solidity 0.4.26;
/*
Bancor Formula interface
*/
contract IBancorFormula {
function purchaseTargetAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256);
function saleTargetAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256);
function crossReserveTargetAmount(uint256 _sourceReserveBalance,
uint32 _sourceReserveWeight,
uint256 _targetReserveBalance,
uint32 _targetReserveWeight,
uint256 _amount)
public view returns (uint256);
function fundCost(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function fundSupplyAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function liquidateReserveAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
}
// File: contracts/IBancorNetwork.sol
pragma solidity 0.4.26;
/*
Bancor Network interface
*/
contract IBancorNetwork {
function convert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
) public payable returns (uint256);
function claimAndConvert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
) public returns (uint256);
function convertFor2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for,
address _affiliateAccount,
uint256 _affiliateFee
) public payable returns (uint256);
function claimAndConvertFor2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for,
address _affiliateAccount,
uint256 _affiliateFee
) public returns (uint256);
// deprecated, backward compatibility
function convert(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn
) public payable returns (uint256);
// deprecated, backward compatibility
function claimAndConvert(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn
) public returns (uint256);
// deprecated, backward compatibility
function convertFor(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for
) public payable returns (uint256);
// deprecated, backward compatibility
function claimAndConvertFor(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for
) public returns (uint256);
}
// File: contracts/utility/Owned.sol
pragma solidity 0.4.26;
/**
* @dev Provides support and utilities for contract ownership
*/
contract Owned is IOwned {
address public owner;
address public newOwner;
/**
* @dev triggered when the owner is updated
*
* @param _prevOwner previous owner
* @param _newOwner new owner
*/
event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
/**
* @dev initializes a new Owned instance
*/
constructor() public {
owner = msg.sender;
}
// allows execution by the owner only
modifier ownerOnly {
_ownerOnly();
_;
}
// error message binary size optimization
function _ownerOnly() internal view {
require(msg.sender == owner, "ERR_ACCESS_DENIED");
}
/**
* @dev allows transferring the contract ownership
* the new owner still needs to accept the transfer
* can only be called by the contract owner
*
* @param _newOwner new contract owner
*/
function transferOwnership(address _newOwner) public ownerOnly {
require(_newOwner != owner, "ERR_SAME_OWNER");
newOwner = _newOwner;
}
/**
* @dev used by a new owner to accept an ownership transfer
*/
function acceptOwnership() public {
require(msg.sender == newOwner, "ERR_ACCESS_DENIED");
emit OwnerUpdate(owner, newOwner);
owner = newOwner;
newOwner = address(0);
}
}
// File: contracts/utility/Utils.sol
pragma solidity 0.4.26;
/**
* @dev Utilities & Common Modifiers
*/
contract Utils {
// verifies that a value is greater than zero
modifier greaterThanZero(uint256 _value) {
_greaterThanZero(_value);
_;
}
// error message binary size optimization
function _greaterThanZero(uint256 _value) internal pure {
require(_value > 0, "ERR_ZERO_VALUE");
}
// validates an address - currently only checks that it isn't null
modifier validAddress(address _address) {
_validAddress(_address);
_;
}
// error message binary size optimization
function _validAddress(address _address) internal pure {
require(_address != address(0), "ERR_INVALID_ADDRESS");
}
// verifies that the address is different than this contract address
modifier notThis(address _address) {
_notThis(_address);
_;
}
// error message binary size optimization
function _notThis(address _address) internal view {
require(_address != address(this), "ERR_ADDRESS_IS_SELF");
}
}
// File: contracts/utility/interfaces/IContractRegistry.sol
pragma solidity 0.4.26;
/*
Contract Registry interface
*/
contract IContractRegistry {
function addressOf(bytes32 _contractName) public view returns (address);
// deprecated, backward compatibility
function getAddress(bytes32 _contractName) public view returns (address);
}
// File: contracts/utility/ContractRegistryClient.sol
pragma solidity 0.4.26;
/**
* @dev Base contract for ContractRegistry clients
*/
contract ContractRegistryClient is Owned, Utils {
bytes32 internal constant CONTRACT_REGISTRY = "ContractRegistry";
bytes32 internal constant BANCOR_NETWORK = "BancorNetwork";
bytes32 internal constant BANCOR_FORMULA = "BancorFormula";
bytes32 internal constant CONVERTER_FACTORY = "ConverterFactory";
bytes32 internal constant CONVERSION_PATH_FINDER = "ConversionPathFinder";
bytes32 internal constant CONVERTER_UPGRADER = "BancorConverterUpgrader";
bytes32 internal constant CONVERTER_REGISTRY = "BancorConverterRegistry";
bytes32 internal constant CONVERTER_REGISTRY_DATA = "BancorConverterRegistryData";
bytes32 internal constant BNT_TOKEN = "BNTToken";
bytes32 internal constant BANCOR_X = "BancorX";
bytes32 internal constant BANCOR_X_UPGRADER = "BancorXUpgrader";
IContractRegistry public registry; // address of the current contract-registry
IContractRegistry public prevRegistry; // address of the previous contract-registry
bool public onlyOwnerCanUpdateRegistry; // only an owner can update the contract-registry
/**
* @dev verifies that the caller is mapped to the given contract name
*
* @param _contractName contract name
*/
modifier only(bytes32 _contractName) {
_only(_contractName);
_;
}
// error message binary size optimization
function _only(bytes32 _contractName) internal view {
require(msg.sender == addressOf(_contractName), "ERR_ACCESS_DENIED");
}
/**
* @dev initializes a new ContractRegistryClient instance
*
* @param _registry address of a contract-registry contract
*/
constructor(IContractRegistry _registry) internal validAddress(_registry) {
registry = IContractRegistry(_registry);
prevRegistry = IContractRegistry(_registry);
}
/**
* @dev updates to the new contract-registry
*/
function updateRegistry() public {
// verify that this function is permitted
require(msg.sender == owner || !onlyOwnerCanUpdateRegistry, "ERR_ACCESS_DENIED");
// get the new contract-registry
IContractRegistry newRegistry = IContractRegistry(addressOf(CONTRACT_REGISTRY));
// verify that the new contract-registry is different and not zero
require(newRegistry != address(registry) && newRegistry != address(0), "ERR_INVALID_REGISTRY");
// verify that the new contract-registry is pointing to a non-zero contract-registry
require(newRegistry.addressOf(CONTRACT_REGISTRY) != address(0), "ERR_INVALID_REGISTRY");
// save a backup of the current contract-registry before replacing it
prevRegistry = registry;
// replace the current contract-registry with the new contract-registry
registry = newRegistry;
}
/**
* @dev restores the previous contract-registry
*/
function restoreRegistry() public ownerOnly {
// restore the previous contract-registry
registry = prevRegistry;
}
/**
* @dev restricts the permission to update the contract-registry
*
* @param _onlyOwnerCanUpdateRegistry indicates whether or not permission is restricted to owner only
*/
function restrictRegistryUpdate(bool _onlyOwnerCanUpdateRegistry) public ownerOnly {
// change the permission to update the contract-registry
onlyOwnerCanUpdateRegistry = _onlyOwnerCanUpdateRegistry;
}
/**
* @dev returns the address associated with the given contract name
*
* @param _contractName contract name
*
* @return contract address
*/
function addressOf(bytes32 _contractName) internal view returns (address) {
return registry.addressOf(_contractName);
}
}
// File: contracts/utility/ReentrancyGuard.sol
pragma solidity 0.4.26;
/**
* @dev ReentrancyGuard
*
* The contract provides protection against re-entrancy - calling a function (directly or
* indirectly) from within itself.
*/
contract ReentrancyGuard {
// true while protected code is being executed, false otherwise
bool private locked = false;
/**
* @dev ensures instantiation only by sub-contracts
*/
constructor() internal {}
// protects a function against reentrancy attacks
modifier protected() {
_protected();
locked = true;
_;
locked = false;
}
// error message binary size optimization
function _protected() internal view {
require(!locked, "ERR_REENTRANCY");
}
}
// File: contracts/utility/SafeMath.sol
pragma solidity 0.4.26;
/**
* @dev Library for basic math operations with overflow/underflow protection
*/
library SafeMath {
/**
* @dev returns the sum of _x and _y, reverts if the calculation overflows
*
* @param _x value 1
* @param _y value 2
*
* @return sum
*/
function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x + _y;
require(z >= _x, "ERR_OVERFLOW");
return z;
}
/**
* @dev returns the difference of _x minus _y, reverts if the calculation underflows
*
* @param _x minuend
* @param _y subtrahend
*
* @return difference
*/
function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_x >= _y, "ERR_UNDERFLOW");
return _x - _y;
}
/**
* @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
*
* @param _x factor 1
* @param _y factor 2
*
* @return product
*/
function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
// gas optimization
if (_x == 0)
return 0;
uint256 z = _x * _y;
require(z / _x == _y, "ERR_OVERFLOW");
return z;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*
* @param _x dividend
* @param _y divisor
*
* @return quotient
*/
function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_y > 0, "ERR_DIVIDE_BY_ZERO");
uint256 c = _x / _y;
return c;
}
}
// File: contracts/utility/TokenHandler.sol
pragma solidity 0.4.26;
contract TokenHandler {
bytes4 private constant APPROVE_FUNC_SELECTOR = bytes4(keccak256("approve(address,uint256)"));
bytes4 private constant TRANSFER_FUNC_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
bytes4 private constant TRANSFER_FROM_FUNC_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)"));
/**
* @dev executes the ERC20 token's `approve` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _spender approved address
* @param _value allowance amount
*/
function safeApprove(IERC20Token _token, address _spender, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(APPROVE_FUNC_SELECTOR, _spender, _value));
}
/**
* @dev executes the ERC20 token's `transfer` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _to target address
* @param _value transfer amount
*/
function safeTransfer(IERC20Token _token, address _to, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(TRANSFER_FUNC_SELECTOR, _to, _value));
}
/**
* @dev executes the ERC20 token's `transferFrom` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _from source address
* @param _to target address
* @param _value transfer amount
*/
function safeTransferFrom(IERC20Token _token, address _from, address _to, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(TRANSFER_FROM_FUNC_SELECTOR, _from, _to, _value));
}
/**
* @dev executes a function on the ERC20 token and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _data data to pass in to the token's contract for execution
*/
function execute(IERC20Token _token, bytes memory _data) private {
uint256[1] memory ret = [uint256(1)];
assembly {
let success := call(
gas, // gas remaining
_token, // destination address
0, // no ether
add(_data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
mload(_data), // input length (loaded from the first 32 bytes in the `data` array)
ret, // output buffer
32 // output length
)
if iszero(success) {
revert(0, 0)
}
}
require(ret[0] != 0, "ERR_TRANSFER_FAILED");
}
}
// File: contracts/utility/TokenHolder.sol
pragma solidity 0.4.26;
/**
* @dev We consider every contract to be a 'token holder' since it's currently not possible
* for a contract to deny receiving tokens.
*
* The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
* the owner to send tokens that were sent to the contract by mistake back to their sender.
*
* Note that we use the non standard ERC-20 interface which has no return value for transfer
* in order to support both non standard as well as standard token contracts.
* see https://github.com/ethereum/solidity/issues/4116
*/
contract TokenHolder is ITokenHolder, TokenHandler, Owned, Utils {
/**
* @dev withdraws tokens held by the contract and sends them to an account
* can only be called by the owner
*
* @param _token ERC20 token contract address
* @param _to account to receive the new amount
* @param _amount amount to withdraw
*/
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
public
ownerOnly
validAddress(_token)
validAddress(_to)
notThis(_to)
{
safeTransfer(_token, _to, _amount);
}
}
// File: contracts/token/interfaces/IEtherToken.sol
pragma solidity 0.4.26;
/*
Ether Token interface
*/
contract IEtherToken is IERC20Token {
function deposit() public payable;
function withdraw(uint256 _amount) public;
function depositTo(address _to) public payable;
function withdrawTo(address _to, uint256 _amount) public;
}
// File: contracts/bancorx/interfaces/IBancorX.sol
pragma solidity 0.4.26;
contract IBancorX {
function token() public view returns (IERC20Token) {this;}
function xTransfer(bytes32 _toBlockchain, bytes32 _to, uint256 _amount, uint256 _id) public;
function getXTransferAmount(uint256 _xTransferId, address _for) public view returns (uint256);
}
// File: contracts/converter/ConverterBase.sol
pragma solidity 0.4.26;
/**
* @dev ConverterBase
*
* The converter contains the main logic for conversions between different ERC20 tokens.
*
* It is also the upgradable part of the mechanism (note that upgrades are opt-in).
*
* The anchor must be set on construction and cannot be changed afterwards.
* Wrappers are provided for some of the anchor's functions, for easier access.
*
* Once the converter accepts ownership of the anchor, it becomes the anchor's sole controller
* and can execute any of its functions.
*
* To upgrade the converter, anchor ownership must be transferred to a new converter, along with
* any relevant data.
*
* Note that the converter can transfer anchor ownership to a new converter that
* doesn't allow upgrades anymore, for finalizing the relationship between the converter
* and the anchor.
*
* Converter types (defined as uint16 type) -
* 0 = liquid token converter
* 1 = liquidity pool v1 converter
* 2 = liquidity pool v2 converter
*
* Note that converters don't currently support tokens with transfer fees.
*/
contract ConverterBase is IConverter, TokenHandler, TokenHolder, ContractRegistryClient, ReentrancyGuard {
using SafeMath for uint256;
uint32 internal constant WEIGHT_RESOLUTION = 1000000;
uint64 internal constant CONVERSION_FEE_RESOLUTION = 1000000;
address internal constant ETH_RESERVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
struct Reserve {
uint256 balance; // reserve balance
uint32 weight; // reserve weight, represented in ppm, 1-1000000
bool deprecated1; // deprecated
bool deprecated2; // deprecated
bool isSet; // true if the reserve is valid, false otherwise
}
/**
* @dev version number
*/
uint16 public constant version = 30;
IConverterAnchor public anchor; // converter anchor contract
IWhitelist public conversionWhitelist; // whitelist contract with list of addresses that are allowed to use the converter
IERC20Token[] public reserveTokens; // ERC20 standard token addresses (prior version 17, use 'connectorTokens' instead)
mapping (address => Reserve) public reserves; // reserve token addresses -> reserve data (prior version 17, use 'connectors' instead)
uint32 public reserveRatio = 0; // ratio between the reserves and the market cap, equal to the total reserve weights
uint32 public maxConversionFee = 0; // maximum conversion fee for the lifetime of the contract,
// represented in ppm, 0...1000000 (0 = no fee, 100 = 0.01%, 1000000 = 100%)
uint32 public conversionFee = 0; // current conversion fee, represented in ppm, 0...maxConversionFee
bool public constant conversionsEnabled = true; // deprecated, backward compatibility
/**
* @dev triggered when the converter is activated
*
* @param _anchor converter anchor
* @param _activated true if the converter was activated, false if it was deactivated
*/
event Activation(IConverterAnchor _anchor, bool _activated);
/**
* @dev triggered when a conversion between two tokens occurs
*
* @param _fromToken source ERC20 token
* @param _toToken target ERC20 token
* @param _trader wallet that initiated the trade
* @param _amount amount converted, in the source token
* @param _return amount returned, minus conversion fee
* @param _conversionFee conversion fee
*/
event Conversion(
address indexed _fromToken,
address indexed _toToken,
address indexed _trader,
uint256 _amount,
uint256 _return,
int256 _conversionFee
);
/**
* @dev triggered when the rate between two tokens in the converter changes
* note that the event might be dispatched for rate updates between any two tokens in the converter
* note that prior to version 28, you should use the 'PriceDataUpdate' event instead
*
* @param _token1 address of the first token
* @param _token2 address of the second token
* @param _rateN rate of 1 unit of `_token1` in `_token2` (numerator)
* @param _rateD rate of 1 unit of `_token1` in `_token2` (denominator)
*/
event TokenRateUpdate(
address indexed _token1,
address indexed _token2,
uint256 _rateN,
uint256 _rateD
);
/**
* @dev triggered when the conversion fee is updated
*
* @param _prevFee previous fee percentage, represented in ppm
* @param _newFee new fee percentage, represented in ppm
*/
event ConversionFeeUpdate(uint32 _prevFee, uint32 _newFee);
/**
* @dev used by sub-contracts to initialize a new converter
*
* @param _anchor anchor governed by the converter
* @param _registry address of a contract registry contract
* @param _maxConversionFee maximum conversion fee, represented in ppm
*/
constructor(
IConverterAnchor _anchor,
IContractRegistry _registry,
uint32 _maxConversionFee
)
validAddress(_anchor)
ContractRegistryClient(_registry)
internal
validConversionFee(_maxConversionFee)
{
anchor = _anchor;
maxConversionFee = _maxConversionFee;
}
// ensures that the converter is active
modifier active() {
_active();
_;
}
// error message binary size optimization
function _active() internal view {
require(isActive(), "ERR_INACTIVE");
}
// ensures that the converter is not active
modifier inactive() {
_inactive();
_;
}
// error message binary size optimization
function _inactive() internal view {
require(!isActive(), "ERR_ACTIVE");
}
// validates a reserve token address - verifies that the address belongs to one of the reserve tokens
modifier validReserve(IERC20Token _address) {
_validReserve(_address);
_;
}
// error message binary size optimization
function _validReserve(IERC20Token _address) internal view {
require(reserves[_address].isSet, "ERR_INVALID_RESERVE");
}
// validates conversion fee
modifier validConversionFee(uint32 _conversionFee) {
_validConversionFee(_conversionFee);
_;
}
// error message binary size optimization
function _validConversionFee(uint32 _conversionFee) internal pure {
require(_conversionFee <= CONVERSION_FEE_RESOLUTION, "ERR_INVALID_CONVERSION_FEE");
}
// validates reserve weight
modifier validReserveWeight(uint32 _weight) {
_validReserveWeight(_weight);
_;
}
// error message binary size optimization
function _validReserveWeight(uint32 _weight) internal pure {
require(_weight > 0 && _weight <= WEIGHT_RESOLUTION, "ERR_INVALID_RESERVE_WEIGHT");
}
/**
* @dev deposits ether
* can only be called if the converter has an ETH reserve
*/
function() external payable {
require(reserves[ETH_RESERVE_ADDRESS].isSet, "ERR_INVALID_RESERVE"); // require(hasETHReserve(), "ERR_INVALID_RESERVE");
// a workaround for a problem when running solidity-coverage
// see https://github.com/sc-forks/solidity-coverage/issues/487
}
/**
* @dev withdraws ether
* can only be called by the owner if the converter is inactive or by upgrader contract
* can only be called after the upgrader contract has accepted the ownership of this contract
* can only be called if the converter has an ETH reserve
*
* @param _to address to send the ETH to
*/
function withdrawETH(address _to)
public
protected
ownerOnly
validReserve(IERC20Token(ETH_RESERVE_ADDRESS))
{
address converterUpgrader = addressOf(CONVERTER_UPGRADER);
// verify that the converter is inactive or that the owner is the upgrader contract
require(!isActive() || owner == converterUpgrader, "ERR_ACCESS_DENIED");
_to.transfer(address(this).balance);
// sync the ETH reserve balance
syncReserveBalance(IERC20Token(ETH_RESERVE_ADDRESS));
}
/**
* @dev checks whether or not the converter version is 28 or higher
*
* @return true, since the converter version is 28 or higher
*/
function isV28OrHigher() public pure returns (bool) {
return true;
}
/**
* @dev allows the owner to update & enable the conversion whitelist contract address
* when set, only addresses that are whitelisted are actually allowed to use the converter
* note that the whitelist check is actually done by the BancorNetwork contract
*
* @param _whitelist address of a whitelist contract
*/
function setConversionWhitelist(IWhitelist _whitelist)
public
ownerOnly
notThis(_whitelist)
{
conversionWhitelist = _whitelist;
}
/**
* @dev returns true if the converter is active, false otherwise
*/
function isActive() public view returns (bool) {
return anchor.owner() == address(this);
}
/**
* @dev transfers the anchor ownership
* the new owner needs to accept the transfer
* can only be called by the converter upgrder while the upgrader is the owner
* note that prior to version 28, you should use 'transferAnchorOwnership' instead
*
* @param _newOwner new token owner
*/
function transferAnchorOwnership(address _newOwner)
public
ownerOnly
only(CONVERTER_UPGRADER)
{
anchor.transferOwnership(_newOwner);
}
/**
* @dev accepts ownership of the anchor after an ownership transfer
* most converters are also activated as soon as they accept the anchor ownership
* can only be called by the contract owner
* note that prior to version 28, you should use 'acceptTokenOwnership' instead
*/
function acceptAnchorOwnership() public ownerOnly {
// verify the the converter has at least one reserve
require(reserveTokenCount() > 0, "ERR_INVALID_RESERVE_COUNT");
anchor.acceptOwnership();
syncReserveBalances();
}
/**
* @dev withdraws tokens held by the anchor and sends them to an account
* can only be called by the owner
*
* @param _token ERC20 token contract address
* @param _to account to receive the new amount
* @param _amount amount to withdraw
*/
function withdrawFromAnchor(IERC20Token _token, address _to, uint256 _amount) public ownerOnly {
anchor.withdrawTokens(_token, _to, _amount);
}
/**
* @dev updates the current conversion fee
* can only be called by the contract owner
*
* @param _conversionFee new conversion fee, represented in ppm
*/
function setConversionFee(uint32 _conversionFee) public ownerOnly {
require(_conversionFee <= maxConversionFee, "ERR_INVALID_CONVERSION_FEE");
emit ConversionFeeUpdate(conversionFee, _conversionFee);
conversionFee = _conversionFee;
}
/**
* @dev withdraws tokens held by the converter and sends them to an account
* can only be called by the owner
* note that reserve tokens can only be withdrawn by the owner while the converter is inactive
* unless the owner is the converter upgrader contract
*
* @param _token ERC20 token contract address
* @param _to account to receive the new amount
* @param _amount amount to withdraw
*/
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public protected ownerOnly {
address converterUpgrader = addressOf(CONVERTER_UPGRADER);
// if the token is not a reserve token, allow withdrawal
// otherwise verify that the converter is inactive or that the owner is the upgrader contract
require(!reserves[_token].isSet || !isActive() || owner == converterUpgrader, "ERR_ACCESS_DENIED");
super.withdrawTokens(_token, _to, _amount);
// if the token is a reserve token, sync the reserve balance
if (reserves[_token].isSet)
syncReserveBalance(_token);
}
/**
* @dev upgrades the converter to the latest version
* can only be called by the owner
* note that the owner needs to call acceptOwnership on the new converter after the upgrade
*/
function upgrade() public ownerOnly {
IConverterUpgrader converterUpgrader = IConverterUpgrader(addressOf(CONVERTER_UPGRADER));
transferOwnership(converterUpgrader);
converterUpgrader.upgrade(version);
acceptOwnership();
}
/**
* @dev returns the number of reserve tokens defined
* note that prior to version 17, you should use 'connectorTokenCount' instead
*
* @return number of reserve tokens
*/
function reserveTokenCount() public view returns (uint16) {
return uint16(reserveTokens.length);
}
/**
* @dev defines a new reserve token for the converter
* can only be called by the owner while the converter is inactive
*
* @param _token address of the reserve token
* @param _weight reserve weight, represented in ppm, 1-1000000
*/
function addReserve(IERC20Token _token, uint32 _weight)
public
ownerOnly
inactive
validAddress(_token)
notThis(_token)
validReserveWeight(_weight)
{
// validate input
require(_token != address(anchor) && !reserves[_token].isSet, "ERR_INVALID_RESERVE");
require(_weight <= WEIGHT_RESOLUTION - reserveRatio, "ERR_INVALID_RESERVE_WEIGHT");
require(reserveTokenCount() < uint16(-1), "ERR_INVALID_RESERVE_COUNT");
Reserve storage newReserve = reserves[_token];
newReserve.balance = 0;
newReserve.weight = _weight;
newReserve.isSet = true;
reserveTokens.push(_token);
reserveRatio += _weight;
}
/**
* @dev returns the reserve's weight
* added in version 28
*
* @param _reserveToken reserve token contract address
*
* @return reserve weight
*/
function reserveWeight(IERC20Token _reserveToken)
public
view
validReserve(_reserveToken)
returns (uint32)
{
return reserves[_reserveToken].weight;
}
/**
* @dev returns the reserve's balance
* note that prior to version 17, you should use 'getConnectorBalance' instead
*
* @param _reserveToken reserve token contract address
*
* @return reserve balance
*/
function reserveBalance(IERC20Token _reserveToken)
public
view
validReserve(_reserveToken)
returns (uint256)
{
return reserves[_reserveToken].balance;
}
/**
* @dev checks whether or not the converter has an ETH reserve
*
* @return true if the converter has an ETH reserve, false otherwise
*/
function hasETHReserve() public view returns (bool) {
return reserves[ETH_RESERVE_ADDRESS].isSet;
}
/**
* @dev converts a specific amount of source tokens to target tokens
* can only be called by the bancor network contract
*
* @param _sourceToken source ERC20 token
* @param _targetToken target ERC20 token
* @param _amount amount of tokens to convert (in units of the source token)
* @param _trader address of the caller who executed the conversion
* @param _beneficiary wallet to receive the conversion result
*
* @return amount of tokens received (in units of the target token)
*/
function convert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary)
public
payable
protected
only(BANCOR_NETWORK)
returns (uint256)
{
// validate input
require(_sourceToken != _targetToken, "ERR_SAME_SOURCE_TARGET");
// if a whitelist is set, verify that both and trader and the beneficiary are whitelisted
require(conversionWhitelist == address(0) ||
(conversionWhitelist.isWhitelisted(_trader) && conversionWhitelist.isWhitelisted(_beneficiary)),
"ERR_NOT_WHITELISTED");
return doConvert(_sourceToken, _targetToken, _amount, _trader, _beneficiary);
}
/**
* @dev converts a specific amount of source tokens to target tokens
* called by ConverterBase and allows the inherited contracts to implement custom conversion logic
*
* @param _sourceToken source ERC20 token
* @param _targetToken target ERC20 token
* @param _amount amount of tokens to convert (in units of the source token)
* @param _trader address of the caller who executed the conversion
* @param _beneficiary wallet to receive the conversion result
*
* @return amount of tokens received (in units of the target token)
*/
function doConvert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary) internal returns (uint256);
/**
* @dev returns the conversion fee for a given return amount
*
* @param _amount return amount
*
* @return conversion fee
*/
function calculateFee(uint256 _amount) internal view returns (uint256) {
return _amount.mul(conversionFee).div(CONVERSION_FEE_RESOLUTION);
}
/**
* @dev syncs the stored reserve balance for a given reserve with the real reserve balance
*
* @param _reserveToken address of the reserve token
*/
function syncReserveBalance(IERC20Token _reserveToken) internal validReserve(_reserveToken) {
if (_reserveToken == ETH_RESERVE_ADDRESS)
reserves[_reserveToken].balance = address(this).balance;
else
reserves[_reserveToken].balance = _reserveToken.balanceOf(this);
}
/**
* @dev syncs all stored reserve balances
*/
function syncReserveBalances() internal {
uint256 reserveCount = reserveTokens.length;
for (uint256 i = 0; i < reserveCount; i++)
syncReserveBalance(reserveTokens[i]);
}
/**
* @dev helper, dispatches the Conversion event
*
* @param _sourceToken source ERC20 token
* @param _targetToken target ERC20 token
* @param _trader address of the caller who executed the conversion
* @param _amount amount purchased/sold (in the source token)
* @param _returnAmount amount returned (in the target token)
*/
function dispatchConversionEvent(
IERC20Token _sourceToken,
IERC20Token _targetToken,
address _trader,
uint256 _amount,
uint256 _returnAmount,
uint256 _feeAmount)
internal
{
// fee amount is converted to 255 bits -
// negative amount means the fee is taken from the source token, positive amount means its taken from the target token
// currently the fee is always taken from the target token
// since we convert it to a signed number, we first ensure that it's capped at 255 bits to prevent overflow
assert(_feeAmount < 2 ** 255);
emit Conversion(_sourceToken, _targetToken, _trader, _amount, _returnAmount, int256(_feeAmount));
}
/**
* @dev deprecated since version 28, backward compatibility - use only for earlier versions
*/
function token() public view returns (IConverterAnchor) {
return anchor;
}
/**
* @dev deprecated, backward compatibility
*/
function transferTokenOwnership(address _newOwner) public ownerOnly {
transferAnchorOwnership(_newOwner);
}
/**
* @dev deprecated, backward compatibility
*/
function acceptTokenOwnership() public ownerOnly {
acceptAnchorOwnership();
}
/**
* @dev deprecated, backward compatibility
*/
function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool) {
Reserve memory reserve = reserves[_address];
return(reserve.balance, reserve.weight, false, false, reserve.isSet);
}
/**
* @dev deprecated, backward compatibility
*/
function connectorTokens(uint256 _index) public view returns (IERC20Token) {
return ConverterBase.reserveTokens[_index];
}
/**
* @dev deprecated, backward compatibility
*/
function connectorTokenCount() public view returns (uint16) {
return reserveTokenCount();
}
/**
* @dev deprecated, backward compatibility
*/
function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256) {
return reserveBalance(_connectorToken);
}
/**
* @dev deprecated, backward compatibility
*/
function getReturn(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount) public view returns (uint256, uint256) {
return targetAmountAndFee(_sourceToken, _targetToken, _amount);
}
}
// File: contracts/converter/LiquidityPoolConverter.sol
pragma solidity 0.4.26;
/**
* @dev Liquidity Pool Converter
*
* The liquidity pool converter is the base contract for specific types of converters that
* manage liquidity pools.
*
* Liquidity pools have 2 reserves or more and they allow converting between them.
*
* Note that TokenRateUpdate events are dispatched for pool tokens as well.
* The pool token is the first token in the event in that case.
*/
contract LiquidityPoolConverter is ConverterBase {
/**
* @dev triggered after liquidity is added
*
* @param _provider liquidity provider
* @param _reserveToken reserve token address
* @param _amount reserve token amount
* @param _newBalance reserve token new balance
* @param _newSupply pool token new supply
*/
event LiquidityAdded(
address indexed _provider,
address indexed _reserveToken,
uint256 _amount,
uint256 _newBalance,
uint256 _newSupply
);
/**
* @dev triggered after liquidity is removed
*
* @param _provider liquidity provider
* @param _reserveToken reserve token address
* @param _amount reserve token amount
* @param _newBalance reserve token new balance
* @param _newSupply pool token new supply
*/
event LiquidityRemoved(
address indexed _provider,
address indexed _reserveToken,
uint256 _amount,
uint256 _newBalance,
uint256 _newSupply
);
/**
* @dev initializes a new LiquidityPoolConverter instance
*
* @param _anchor anchor governed by the converter
* @param _registry address of a contract registry contract
* @param _maxConversionFee maximum conversion fee, represented in ppm
*/
constructor(
IConverterAnchor _anchor,
IContractRegistry _registry,
uint32 _maxConversionFee
)
ConverterBase(_anchor, _registry, _maxConversionFee)
internal
{
}
/**
* @dev accepts ownership of the anchor after an ownership transfer
* also activates the converter
* can only be called by the contract owner
* note that prior to version 28, you should use 'acceptTokenOwnership' instead
*/
function acceptTokenOwnership() public {
// verify that the converter has at least 2 reserves
require(reserveTokenCount() > 1, "ERR_INVALID_RESERVE_COUNT");
super.acceptTokenOwnership();
}
}
// File: contracts/converter/interfaces/ITypedConverterFactory.sol
pragma solidity 0.4.26;
/*
Typed Converter Factory interface
*/
contract ITypedConverterFactory {
function converterType() public pure returns (uint16);
function createConverter(IConverterAnchor _anchor, IContractRegistry _registry, uint32 _maxConversionFee) public returns (IConverter);
}
// File: contracts/token/interfaces/ISmartToken.sol
pragma solidity 0.4.26;
/*
Smart Token interface
*/
contract ISmartToken is IConverterAnchor, IERC20Token {
function disableTransfers(bool _disable) public;
function issue(address _to, uint256 _amount) public;
function destroy(address _from, uint256 _amount) public;
}
// File: contracts/converter/LiquidityPoolV1Converter.sol
pragma solidity 0.4.26;
/*
LiquidityPoolV1Converter Factory
*/
contract LiquidityPoolV1ConverterFactory is ITypedConverterFactory {
/**
* @dev returns the converter type the factory is associated with
*
* @return converter type
*/
function converterType() public pure returns (uint16) {
return 1;
}
/**
* @dev creates a new converter with the given arguments and transfers
* the ownership to the caller
*
* @param _anchor anchor governed by the converter
* @param _registry address of a contract registry contract
* @param _maxConversionFee maximum conversion fee, represented in ppm
*
* @return a new converter
*/
function createConverter(IConverterAnchor _anchor, IContractRegistry _registry, uint32 _maxConversionFee) public returns (IConverter) {
ConverterBase converter = new LiquidityPoolV1Converter(ISmartToken(_anchor), _registry, _maxConversionFee);
converter.transferOwnership(msg.sender);
return converter;
}
}
/**
* @dev Liquidity Pool v1 Converter
*
* The liquidity pool v1 converter is a specialized version of a converter that manages
* a classic bancor liquidity pool.
*
* Even though classic pools can have many reserves, the most common configuration of
* the pool has 2 reserves with 50%/50% weights.
*/
contract LiquidityPoolV1Converter is LiquidityPoolConverter {
IEtherToken internal etherToken = IEtherToken(0xc0829421C1d260BD3cB3E0F06cfE2D52db2cE315);
/**
* @dev triggered after a conversion with new price data
* deprecated, use `TokenRateUpdate` from version 28 and up
*
* @param _connectorToken reserve token
* @param _tokenSupply smart token supply
* @param _connectorBalance reserve balance
* @param _connectorWeight reserve weight
*/
event PriceDataUpdate(
address indexed _connectorToken,
uint256 _tokenSupply,
uint256 _connectorBalance,
uint32 _connectorWeight
);
/**
* @dev initializes a new LiquidityPoolV1Converter instance
*
* @param _token pool token governed by the converter
* @param _registry address of a contract registry contract
* @param _maxConversionFee maximum conversion fee, represented in ppm
*/
constructor(
ISmartToken _token,
IContractRegistry _registry,
uint32 _maxConversionFee
)
LiquidityPoolConverter(_token, _registry, _maxConversionFee)
public
{
}
/**
* @dev returns the converter type
*
* @return see the converter types in the the main contract doc
*/
function converterType() public pure returns (uint16) {
return 1;
}
/**
* @dev accepts ownership of the anchor after an ownership transfer
* also activates the converter
* can only be called by the contract owner
* note that prior to version 28, you should use 'acceptTokenOwnership' instead
*/
function acceptAnchorOwnership() public ownerOnly {
super.acceptAnchorOwnership();
emit Activation(anchor, true);
}
/**
* @dev returns the expected target amount of converting one reserve to another along with the fee
*
* @param _sourceToken contract address of the source reserve token
* @param _targetToken contract address of the target reserve token
* @param _amount amount of tokens received from the user
*
* @return expected target amount
* @return expected fee
*/
function targetAmountAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount)
public
view
active
validReserve(_sourceToken)
validReserve(_targetToken)
returns (uint256, uint256)
{
// validate input
require(_sourceToken != _targetToken, "ERR_SAME_SOURCE_TARGET");
uint256 amount = IBancorFormula(addressOf(BANCOR_FORMULA)).crossReserveTargetAmount(
reserveBalance(_sourceToken),
reserves[_sourceToken].weight,
reserveBalance(_targetToken),
reserves[_targetToken].weight,
_amount
);
// return the amount minus the conversion fee and the conversion fee
uint256 fee = calculateFee(amount);
return (amount - fee, fee);
}
/**
* @dev converts a specific amount of source tokens to target tokens
* can only be called by the bancor network contract
*
* @param _sourceToken source ERC20 token
* @param _targetToken target ERC20 token
* @param _amount amount of tokens to convert (in units of the source token)
* @param _trader address of the caller who executed the conversion
* @param _beneficiary wallet to receive the conversion result
*
* @return amount of tokens received (in units of the target token)
*/
function doConvert(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, address _trader, address _beneficiary)
internal
returns (uint256)
{
// get expected target amount and fee
(uint256 amount, uint256 fee) = targetAmountAndFee(_sourceToken, _targetToken, _amount);
// ensure that the trade gives something in return
require(amount != 0, "ERR_ZERO_TARGET_AMOUNT");
// ensure that the trade won't deplete the reserve balance
uint256 targetReserveBalance = reserveBalance(_targetToken);
assert(amount < targetReserveBalance);
// ensure that the input amount was already deposited
if (_sourceToken == ETH_RESERVE_ADDRESS)
require(msg.value == _amount, "ERR_ETH_AMOUNT_MISMATCH");
else
require(msg.value == 0 && _sourceToken.balanceOf(this).sub(reserveBalance(_sourceToken)) >= _amount, "ERR_INVALID_AMOUNT");
// sync the reserve balances
syncReserveBalance(_sourceToken);
reserves[_targetToken].balance = reserves[_targetToken].balance.sub(amount);
// transfer funds to the beneficiary in the to reserve token
if (_targetToken == ETH_RESERVE_ADDRESS)
_beneficiary.transfer(amount);
else
safeTransfer(_targetToken, _beneficiary, amount);
// dispatch the conversion event
dispatchConversionEvent(_sourceToken, _targetToken, _trader, _amount, amount, fee);
// dispatch rate updates
dispatchRateEvents(_sourceToken, _targetToken);
return amount;
}
/**
* @dev increases the pool's liquidity and mints new shares in the pool to the caller
* note that prior to version 28, you should use 'fund' instead
*
* @param _reserveTokens address of each reserve token
* @param _reserveAmounts amount of each reserve token
* @param _minReturn token minimum return-amount
*/
function addLiquidity(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _minReturn)
public
payable
protected
active
{
// verify the user input
verifyLiquidityInput(_reserveTokens, _reserveAmounts, _minReturn);
// if one of the reserves is ETH, then verify that the input amount of ETH is equal to the input value of ETH
for (uint256 i = 0; i < _reserveTokens.length; i++)
if (_reserveTokens[i] == ETH_RESERVE_ADDRESS)
require(_reserveAmounts[i] == msg.value, "ERR_ETH_AMOUNT_MISMATCH");
// if the input value of ETH is larger than zero, then verify that one of the reserves is ETH
if (msg.value > 0)
require(reserves[ETH_RESERVE_ADDRESS].isSet, "ERR_NO_ETH_RESERVE");
// get the total supply
uint256 totalSupply = ISmartToken(anchor).totalSupply();
// transfer from the user an equally-worth amount of each one of the reserve tokens
uint256 amount = addLiquidityToPool(_reserveTokens, _reserveAmounts, totalSupply);
// verify that the equivalent amount of tokens is equal to or larger than the user's expectation
require(amount >= _minReturn, "ERR_RETURN_TOO_LOW");
// issue the tokens to the user
ISmartToken(anchor).issue(msg.sender, amount);
}
/**
* @dev decreases the pool's liquidity and burns the caller's shares in the pool
* note that prior to version 28, you should use 'liquidate' instead
*
* @param _amount token amount
* @param _reserveTokens address of each reserve token
* @param _reserveMinReturnAmounts minimum return-amount of each reserve token
*/
function removeLiquidity(uint256 _amount, IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts)
public
protected
active
{
// verify the user input
verifyLiquidityInput(_reserveTokens, _reserveMinReturnAmounts, _amount);
// get the total supply BEFORE destroying the user tokens
uint256 totalSupply = ISmartToken(anchor).totalSupply();
// destroy the user tokens
ISmartToken(anchor).destroy(msg.sender, _amount);
// transfer to the user an equivalent amount of each one of the reserve tokens
removeLiquidityFromPool(_reserveTokens, _reserveMinReturnAmounts, totalSupply, _amount);
}
/**
* @dev increases the pool's liquidity and mints new shares in the pool to the caller
* for example, if the caller increases the supply by 10%,
* then it will cost an amount equal to 10% of each reserve token balance
* note that starting from version 28, you should use 'addLiquidity' instead
*
* @param _amount amount to increase the supply by (in the pool token)
*/
function fund(uint256 _amount) public payable protected {
syncReserveBalances();
reserves[ETH_RESERVE_ADDRESS].balance = reserves[ETH_RESERVE_ADDRESS].balance.sub(msg.value);
uint256 supply = ISmartToken(anchor).totalSupply();
IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
// iterate through the reserve tokens and transfer a percentage equal to the weight between
// _amount and the total supply in each reserve from the caller to the converter
uint256 reserveCount = reserveTokens.length;
for (uint256 i = 0; i < reserveCount; i++) {
IERC20Token reserveToken = reserveTokens[i];
uint256 rsvBalance = reserves[reserveToken].balance;
uint256 reserveAmount = formula.fundCost(supply, rsvBalance, reserveRatio, _amount);
// transfer funds from the caller in the reserve token
if (reserveToken == ETH_RESERVE_ADDRESS) {
if (msg.value > reserveAmount) {
msg.sender.transfer(msg.value - reserveAmount);
}
else if (msg.value < reserveAmount) {
require(msg.value == 0, "ERR_INVALID_ETH_VALUE");
safeTransferFrom(etherToken, msg.sender, this, reserveAmount);
etherToken.withdraw(reserveAmount);
}
}
else {
safeTransferFrom(reserveToken, msg.sender, this, reserveAmount);
}
// sync the reserve balance
uint256 newReserveBalance = rsvBalance.add(reserveAmount);
reserves[reserveToken].balance = newReserveBalance;
uint256 newPoolTokenSupply = supply.add(_amount);
// dispatch liquidity update for the pool token/reserve
emit LiquidityAdded(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
// dispatch the `TokenRateUpdate` event for the pool token
uint32 reserveWeight = reserves[reserveToken].weight;
dispatchPoolTokenRateEvent(newPoolTokenSupply, reserveToken, newReserveBalance, reserveWeight);
}
// issue new funds to the caller in the pool token
ISmartToken(anchor).issue(msg.sender, _amount);
}
/**
* @dev decreases the pool's liquidity and burns the caller's shares in the pool
* for example, if the holder sells 10% of the supply,
* then they will receive 10% of each reserve token balance in return
* note that starting from version 28, you should use 'removeLiquidity' instead
*
* @param _amount amount to liquidate (in the pool token)
*/
function liquidate(uint256 _amount) public protected {
require(_amount > 0, "ERR_ZERO_AMOUNT");
uint256 totalSupply = ISmartToken(anchor).totalSupply();
ISmartToken(anchor).destroy(msg.sender, _amount);
uint256[] memory reserveMinReturnAmounts = new uint256[](reserveTokens.length);
for (uint256 i = 0; i < reserveMinReturnAmounts.length; i++)
reserveMinReturnAmounts[i] = 1;
removeLiquidityFromPool(reserveTokens, reserveMinReturnAmounts, totalSupply, _amount);
}
/**
* @dev verifies that a given array of tokens is identical to the converter's array of reserve tokens
* we take this input in order to allow specifying the corresponding reserve amounts in any order
*
* @param _reserveTokens array of reserve tokens
* @param _reserveAmounts array of reserve amounts
* @param _amount token amount
*/
function verifyLiquidityInput(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _amount) private view {
uint256 i;
uint256 j;
uint256 length = reserveTokens.length;
require(length == _reserveTokens.length, "ERR_INVALID_RESERVE");
require(length == _reserveAmounts.length, "ERR_INVALID_AMOUNT");
for (i = 0; i < length; i++) {
// verify that every input reserve token is included in the reserve tokens
require(reserves[_reserveTokens[i]].isSet, "ERR_INVALID_RESERVE");
for (j = 0; j < length; j++) {
if (reserveTokens[i] == _reserveTokens[j])
break;
}
// verify that every reserve token is included in the input reserve tokens
require(j < length, "ERR_INVALID_RESERVE");
// verify that every input reserve token amount is larger than zero
require(_reserveAmounts[i] > 0, "ERR_INVALID_AMOUNT");
}
// verify that the input token amount is larger than zero
require(_amount > 0, "ERR_ZERO_AMOUNT");
}
/**
* @dev adds liquidity (reserve) to the pool
*
* @param _reserveTokens address of each reserve token
* @param _reserveAmounts amount of each reserve token
* @param _totalSupply token total supply
*/
function addLiquidityToPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _totalSupply)
private
returns (uint256)
{
if (_totalSupply == 0)
return addLiquidityToEmptyPool(_reserveTokens, _reserveAmounts);
return addLiquidityToNonEmptyPool(_reserveTokens, _reserveAmounts, _totalSupply);
}
/**
* @dev adds liquidity (reserve) to the pool when it's empty
*
* @param _reserveTokens address of each reserve token
* @param _reserveAmounts amount of each reserve token
*/
function addLiquidityToEmptyPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts)
private
returns (uint256)
{
// calculate the geometric-mean of the reserve amounts approved by the user
uint256 amount = geometricMean(_reserveAmounts);
// transfer each one of the reserve amounts from the user to the pool
for (uint256 i = 0; i < _reserveTokens.length; i++) {
if (_reserveTokens[i] != ETH_RESERVE_ADDRESS) // ETH has already been transferred as part of the transaction
safeTransferFrom(_reserveTokens[i], msg.sender, this, _reserveAmounts[i]);
reserves[_reserveTokens[i]].balance = _reserveAmounts[i];
emit LiquidityAdded(msg.sender, _reserveTokens[i], _reserveAmounts[i], _reserveAmounts[i], amount);
// dispatch the `TokenRateUpdate` event for the pool token
uint32 reserveWeight = reserves[_reserveTokens[i]].weight;
dispatchPoolTokenRateEvent(amount, _reserveTokens[i], _reserveAmounts[i], reserveWeight);
}
return amount;
}
/**
* @dev adds liquidity (reserve) to the pool when it's not empty
*
* @param _reserveTokens address of each reserve token
* @param _reserveAmounts amount of each reserve token
* @param _totalSupply token total supply
*/
function addLiquidityToNonEmptyPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts, uint256 _totalSupply)
private
returns (uint256)
{
syncReserveBalances();
reserves[ETH_RESERVE_ADDRESS].balance = reserves[ETH_RESERVE_ADDRESS].balance.sub(msg.value);
IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
uint256 amount = getMinShare(formula, _totalSupply, _reserveTokens, _reserveAmounts);
uint256 newPoolTokenSupply = _totalSupply.add(amount);
for (uint256 i = 0; i < _reserveTokens.length; i++) {
IERC20Token reserveToken = _reserveTokens[i];
uint256 rsvBalance = reserves[reserveToken].balance;
uint256 reserveAmount = formula.fundCost(_totalSupply, rsvBalance, reserveRatio, amount);
require(reserveAmount > 0, "ERR_ZERO_TARGET_AMOUNT");
assert(reserveAmount <= _reserveAmounts[i]);
// transfer each one of the reserve amounts from the user to the pool
if (reserveToken != ETH_RESERVE_ADDRESS) // ETH has already been transferred as part of the transaction
safeTransferFrom(reserveToken, msg.sender, this, reserveAmount);
else if (_reserveAmounts[i] > reserveAmount) // transfer the extra amount of ETH back to the user
msg.sender.transfer(_reserveAmounts[i] - reserveAmount);
uint256 newReserveBalance = rsvBalance.add(reserveAmount);
reserves[reserveToken].balance = newReserveBalance;
emit LiquidityAdded(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
// dispatch the `TokenRateUpdate` event for the pool token
uint32 reserveWeight = reserves[_reserveTokens[i]].weight;
dispatchPoolTokenRateEvent(newPoolTokenSupply, _reserveTokens[i], newReserveBalance, reserveWeight);
}
return amount;
}
/**
* @dev removes liquidity (reserve) from the pool
*
* @param _reserveTokens address of each reserve token
* @param _reserveMinReturnAmounts minimum return-amount of each reserve token
* @param _totalSupply token total supply
* @param _amount token amount
*/
function removeLiquidityFromPool(IERC20Token[] memory _reserveTokens, uint256[] memory _reserveMinReturnAmounts, uint256 _totalSupply, uint256 _amount)
private
{
syncReserveBalances();
IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
uint256 newPoolTokenSupply = _totalSupply.sub(_amount);
for (uint256 i = 0; i < _reserveTokens.length; i++) {
IERC20Token reserveToken = _reserveTokens[i];
uint256 rsvBalance = reserves[reserveToken].balance;
uint256 reserveAmount = formula.liquidateReserveAmount(_totalSupply, rsvBalance, reserveRatio, _amount);
require(reserveAmount >= _reserveMinReturnAmounts[i], "ERR_ZERO_TARGET_AMOUNT");
uint256 newReserveBalance = rsvBalance.sub(reserveAmount);
reserves[reserveToken].balance = newReserveBalance;
// transfer each one of the reserve amounts from the pool to the user
if (reserveToken == ETH_RESERVE_ADDRESS)
msg.sender.transfer(reserveAmount);
else
safeTransfer(reserveToken, msg.sender, reserveAmount);
emit LiquidityRemoved(msg.sender, reserveToken, reserveAmount, newReserveBalance, newPoolTokenSupply);
// dispatch the `TokenRateUpdate` event for the pool token
uint32 reserveWeight = reserves[reserveToken].weight;
dispatchPoolTokenRateEvent(newPoolTokenSupply, reserveToken, newReserveBalance, reserveWeight);
}
}
function getMinShare(IBancorFormula formula, uint256 _totalSupply, IERC20Token[] memory _reserveTokens, uint256[] memory _reserveAmounts) private view returns (uint256) {
uint256 minIndex = 0;
for (uint256 i = 1; i < _reserveTokens.length; i++) {
if (_reserveAmounts[i].mul(reserves[_reserveTokens[minIndex]].balance) < _reserveAmounts[minIndex].mul(reserves[_reserveTokens[i]].balance))
minIndex = i;
}
return formula.fundSupplyAmount(_totalSupply, reserves[_reserveTokens[minIndex]].balance, reserveRatio, _reserveAmounts[minIndex]);
}
/**
* @dev calculates the number of decimal digits in a given value
*
* @param _x value (assumed positive)
* @return the number of decimal digits in the given value
*/
function decimalLength(uint256 _x) public pure returns (uint256) {
uint256 y = 0;
for (uint256 x = _x; x > 0; x /= 10)
y++;
return y;
}
/**
* @dev calculates the nearest integer to a given quotient
*
* @param _n quotient numerator
* @param _d quotient denominator
* @return the nearest integer to the given quotient
*/
function roundDiv(uint256 _n, uint256 _d) public pure returns (uint256) {
return (_n + _d / 2) / _d;
}
/**
* @dev calculates the average number of decimal digits in a given list of values
*
* @param _values list of values (each of which assumed positive)
* @return the average number of decimal digits in the given list of values
*/
function geometricMean(uint256[] memory _values) public pure returns (uint256) {
uint256 numOfDigits = 0;
uint256 length = _values.length;
for (uint256 i = 0; i < length; i++)
numOfDigits += decimalLength(_values[i]);
return uint256(10) ** (roundDiv(numOfDigits, length) - 1);
}
/**
* @dev dispatches rate events for both reserves / pool tokens
* only used to circumvent the `stack too deep` compiler error
*
* @param _sourceToken address of the source reserve token
* @param _targetToken address of the target reserve token
*/
function dispatchRateEvents(IERC20Token _sourceToken, IERC20Token _targetToken) private {
uint256 poolTokenSupply = ISmartToken(anchor).totalSupply();
uint256 sourceReserveBalance = reserveBalance(_sourceToken);
uint256 targetReserveBalance = reserveBalance(_targetToken);
uint32 sourceReserveWeight = reserves[_sourceToken].weight;
uint32 targetReserveWeight = reserves[_targetToken].weight;
// dispatch token rate update event
uint256 rateN = targetReserveBalance.mul(sourceReserveWeight);
uint256 rateD = sourceReserveBalance.mul(targetReserveWeight);
emit TokenRateUpdate(_sourceToken, _targetToken, rateN, rateD);
// dispatch the `TokenRateUpdate` event for the pool token
dispatchPoolTokenRateEvent(poolTokenSupply, _sourceToken, sourceReserveBalance, sourceReserveWeight);
dispatchPoolTokenRateEvent(poolTokenSupply, _targetToken, targetReserveBalance, targetReserveWeight);
// dispatch price data update events (deprecated events)
emit PriceDataUpdate(_sourceToken, poolTokenSupply, sourceReserveBalance, sourceReserveWeight);
emit PriceDataUpdate(_targetToken, poolTokenSupply, targetReserveBalance, targetReserveWeight);
}
/**
* @dev dispatches the `TokenRateUpdate` for the pool token
* only used to circumvent the `stack too deep` compiler error
*
* @param _poolTokenSupply total pool token supply
* @param _reserveToken address of the reserve token
* @param _reserveBalance reserve balance
* @param _reserveWeight reserve weight
*/
function dispatchPoolTokenRateEvent(uint256 _poolTokenSupply, IERC20Token _reserveToken, uint256 _reserveBalance, uint32 _reserveWeight) private {
emit TokenRateUpdate(anchor, _reserveToken, _reserveBalance.mul(WEIGHT_RESOLUTION), _poolTokenSupply.mul(_reserveWeight));
}
}File 7 of 13: BancorNetwork
// File: contracts/token/interfaces/IERC20Token.sol
pragma solidity 0.4.26;
/*
ERC20 Standard Token interface
*/
contract IERC20Token {
// these functions aren't abstract since the compiler emits automatically generated getter functions as external
function name() public view returns (string) {this;}
function symbol() public view returns (string) {this;}
function decimals() public view returns (uint8) {this;}
function totalSupply() public view returns (uint256) {this;}
function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
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);
}
// File: contracts/IBancorNetwork.sol
pragma solidity 0.4.26;
/*
Bancor Network interface
*/
contract IBancorNetwork {
function convert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
) public payable returns (uint256);
function claimAndConvert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
) public returns (uint256);
function convertFor2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for,
address _affiliateAccount,
uint256 _affiliateFee
) public payable returns (uint256);
function claimAndConvertFor2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for,
address _affiliateAccount,
uint256 _affiliateFee
) public returns (uint256);
// deprecated, backward compatibility
function convert(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn
) public payable returns (uint256);
// deprecated, backward compatibility
function claimAndConvert(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn
) public returns (uint256);
// deprecated, backward compatibility
function convertFor(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for
) public payable returns (uint256);
// deprecated, backward compatibility
function claimAndConvertFor(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _for
) public returns (uint256);
}
// File: contracts/IConversionPathFinder.sol
pragma solidity 0.4.26;
/*
Conversion Path Finder interface
*/
contract IConversionPathFinder {
function findPath(address _sourceToken, address _targetToken) public view returns (address[] memory);
}
// File: contracts/utility/interfaces/IOwned.sol
pragma solidity 0.4.26;
/*
Owned contract interface
*/
contract IOwned {
// this function isn't abstract since the compiler emits automatically generated getter functions as external
function owner() public view returns (address) {this;}
function transferOwnership(address _newOwner) public;
function acceptOwnership() public;
}
// File: contracts/utility/interfaces/ITokenHolder.sol
pragma solidity 0.4.26;
/*
Token Holder interface
*/
contract ITokenHolder is IOwned {
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
}
// File: contracts/converter/interfaces/IConverterAnchor.sol
pragma solidity 0.4.26;
/*
Converter Anchor interface
*/
contract IConverterAnchor is IOwned, ITokenHolder {
}
// File: contracts/utility/interfaces/IWhitelist.sol
pragma solidity 0.4.26;
/*
Whitelist interface
*/
contract IWhitelist {
function isWhitelisted(address _address) public view returns (bool);
}
// File: contracts/converter/interfaces/IConverter.sol
pragma solidity 0.4.26;
/*
Converter interface
*/
contract IConverter is IOwned {
function converterType() public pure returns (uint16);
function anchor() public view returns (IConverterAnchor) {this;}
function isActive() public view returns (bool);
function rateAndFee(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount) public view returns (uint256, uint256);
function convert(IERC20Token _sourceToken,
IERC20Token _targetToken,
uint256 _amount,
address _trader,
address _beneficiary) public payable returns (uint256);
function conversionWhitelist() public view returns (IWhitelist) {this;}
function conversionFee() public view returns (uint32) {this;}
function maxConversionFee() public view returns (uint32) {this;}
function reserveBalance(IERC20Token _reserveToken) public view returns (uint256);
function() external payable;
function transferAnchorOwnership(address _newOwner) public;
function acceptAnchorOwnership() public;
function setConversionFee(uint32 _conversionFee) public;
function setConversionWhitelist(IWhitelist _whitelist) public;
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
function withdrawETH(address _to) public;
function addReserve(IERC20Token _token, uint32 _ratio) public;
// deprecated, backward compatibility
function token() public view returns (IConverterAnchor);
function transferTokenOwnership(address _newOwner) public;
function acceptTokenOwnership() public;
function connectors(address _address) public view returns (uint256, uint32, bool, bool, bool);
function getConnectorBalance(IERC20Token _connectorToken) public view returns (uint256);
function connectorTokens(uint256 _index) public view returns (IERC20Token);
function connectorTokenCount() public view returns (uint16);
}
// File: contracts/converter/interfaces/IBancorFormula.sol
pragma solidity 0.4.26;
/*
Bancor Formula interface
*/
contract IBancorFormula {
function purchaseRate(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256);
function saleRate(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256);
function crossReserveRate(uint256 _sourceReserveBalance,
uint32 _sourceReserveWeight,
uint256 _targetReserveBalance,
uint32 _targetReserveWeight,
uint256 _amount)
public view returns (uint256);
function fundCost(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function liquidateRate(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
}
// File: contracts/utility/Owned.sol
pragma solidity 0.4.26;
/**
* @dev Provides support and utilities for contract ownership
*/
contract Owned is IOwned {
address public owner;
address public newOwner;
/**
* @dev triggered when the owner is updated
*
* @param _prevOwner previous owner
* @param _newOwner new owner
*/
event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
/**
* @dev initializes a new Owned instance
*/
constructor() public {
owner = msg.sender;
}
// allows execution by the owner only
modifier ownerOnly {
_ownerOnly();
_;
}
// error message binary size optimization
function _ownerOnly() internal view {
require(msg.sender == owner, "ERR_ACCESS_DENIED");
}
/**
* @dev allows transferring the contract ownership
* the new owner still needs to accept the transfer
* can only be called by the contract owner
*
* @param _newOwner new contract owner
*/
function transferOwnership(address _newOwner) public ownerOnly {
require(_newOwner != owner, "ERR_SAME_OWNER");
newOwner = _newOwner;
}
/**
* @dev used by a new owner to accept an ownership transfer
*/
function acceptOwnership() public {
require(msg.sender == newOwner, "ERR_ACCESS_DENIED");
emit OwnerUpdate(owner, newOwner);
owner = newOwner;
newOwner = address(0);
}
}
// File: contracts/utility/Utils.sol
pragma solidity 0.4.26;
/**
* @dev Utilities & Common Modifiers
*/
contract Utils {
// verifies that a value is greater than zero
modifier greaterThanZero(uint256 _value) {
_greaterThanZero(_value);
_;
}
// error message binary size optimization
function _greaterThanZero(uint256 _value) internal pure {
require(_value > 0, "ERR_ZERO_VALUE");
}
// validates an address - currently only checks that it isn't null
modifier validAddress(address _address) {
_validAddress(_address);
_;
}
// error message binary size optimization
function _validAddress(address _address) internal pure {
require(_address != address(0), "ERR_INVALID_ADDRESS");
}
// verifies that the address is different than this contract address
modifier notThis(address _address) {
_notThis(_address);
_;
}
// error message binary size optimization
function _notThis(address _address) internal view {
require(_address != address(this), "ERR_ADDRESS_IS_SELF");
}
}
// File: contracts/utility/interfaces/IContractRegistry.sol
pragma solidity 0.4.26;
/*
Contract Registry interface
*/
contract IContractRegistry {
function addressOf(bytes32 _contractName) public view returns (address);
// deprecated, backward compatibility
function getAddress(bytes32 _contractName) public view returns (address);
}
// File: contracts/utility/ContractRegistryClient.sol
pragma solidity 0.4.26;
/**
* @dev Base contract for ContractRegistry clients
*/
contract ContractRegistryClient is Owned, Utils {
bytes32 internal constant CONTRACT_REGISTRY = "ContractRegistry";
bytes32 internal constant BANCOR_NETWORK = "BancorNetwork";
bytes32 internal constant BANCOR_FORMULA = "BancorFormula";
bytes32 internal constant CONVERTER_FACTORY = "ConverterFactory";
bytes32 internal constant CONVERSION_PATH_FINDER = "ConversionPathFinder";
bytes32 internal constant CONVERTER_UPGRADER = "BancorConverterUpgrader";
bytes32 internal constant CONVERTER_REGISTRY = "BancorConverterRegistry";
bytes32 internal constant CONVERTER_REGISTRY_DATA = "BancorConverterRegistryData";
bytes32 internal constant BNT_TOKEN = "BNTToken";
bytes32 internal constant BANCOR_X = "BancorX";
bytes32 internal constant BANCOR_X_UPGRADER = "BancorXUpgrader";
IContractRegistry public registry; // address of the current contract-registry
IContractRegistry public prevRegistry; // address of the previous contract-registry
bool public onlyOwnerCanUpdateRegistry; // only an owner can update the contract-registry
/**
* @dev verifies that the caller is mapped to the given contract name
*
* @param _contractName contract name
*/
modifier only(bytes32 _contractName) {
_only(_contractName);
_;
}
// error message binary size optimization
function _only(bytes32 _contractName) internal view {
require(msg.sender == addressOf(_contractName), "ERR_ACCESS_DENIED");
}
/**
* @dev initializes a new ContractRegistryClient instance
*
* @param _registry address of a contract-registry contract
*/
constructor(IContractRegistry _registry) internal validAddress(_registry) {
registry = IContractRegistry(_registry);
prevRegistry = IContractRegistry(_registry);
}
/**
* @dev updates to the new contract-registry
*/
function updateRegistry() public {
// verify that this function is permitted
require(msg.sender == owner || !onlyOwnerCanUpdateRegistry, "ERR_ACCESS_DENIED");
// get the new contract-registry
IContractRegistry newRegistry = IContractRegistry(addressOf(CONTRACT_REGISTRY));
// verify that the new contract-registry is different and not zero
require(newRegistry != address(registry) && newRegistry != address(0), "ERR_INVALID_REGISTRY");
// verify that the new contract-registry is pointing to a non-zero contract-registry
require(newRegistry.addressOf(CONTRACT_REGISTRY) != address(0), "ERR_INVALID_REGISTRY");
// save a backup of the current contract-registry before replacing it
prevRegistry = registry;
// replace the current contract-registry with the new contract-registry
registry = newRegistry;
}
/**
* @dev restores the previous contract-registry
*/
function restoreRegistry() public ownerOnly {
// restore the previous contract-registry
registry = prevRegistry;
}
/**
* @dev restricts the permission to update the contract-registry
*
* @param _onlyOwnerCanUpdateRegistry indicates whether or not permission is restricted to owner only
*/
function restrictRegistryUpdate(bool _onlyOwnerCanUpdateRegistry) public ownerOnly {
// change the permission to update the contract-registry
onlyOwnerCanUpdateRegistry = _onlyOwnerCanUpdateRegistry;
}
/**
* @dev returns the address associated with the given contract name
*
* @param _contractName contract name
*
* @return contract address
*/
function addressOf(bytes32 _contractName) internal view returns (address) {
return registry.addressOf(_contractName);
}
}
// File: contracts/utility/ReentrancyGuard.sol
pragma solidity 0.4.26;
/**
* @dev ReentrancyGuard
*
* The contract provides protection against re-entrancy - calling a function (directly or
* indirectly) from within itself.
*/
contract ReentrancyGuard {
// true while protected code is being executed, false otherwise
bool private locked = false;
/**
* @dev ensures instantiation only by sub-contracts
*/
constructor() internal {}
// protects a function against reentrancy attacks
modifier protected() {
_protected();
locked = true;
_;
locked = false;
}
// error message binary size optimization
function _protected() internal view {
require(!locked, "ERR_REENTRANCY");
}
}
// File: contracts/utility/TokenHandler.sol
pragma solidity 0.4.26;
contract TokenHandler {
bytes4 private constant APPROVE_FUNC_SELECTOR = bytes4(keccak256("approve(address,uint256)"));
bytes4 private constant TRANSFER_FUNC_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
bytes4 private constant TRANSFER_FROM_FUNC_SELECTOR = bytes4(keccak256("transferFrom(address,address,uint256)"));
/**
* @dev executes the ERC20 token's `approve` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _spender approved address
* @param _value allowance amount
*/
function safeApprove(IERC20Token _token, address _spender, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(APPROVE_FUNC_SELECTOR, _spender, _value));
}
/**
* @dev executes the ERC20 token's `transfer` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _to target address
* @param _value transfer amount
*/
function safeTransfer(IERC20Token _token, address _to, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(TRANSFER_FUNC_SELECTOR, _to, _value));
}
/**
* @dev executes the ERC20 token's `transferFrom` function and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _from source address
* @param _to target address
* @param _value transfer amount
*/
function safeTransferFrom(IERC20Token _token, address _from, address _to, uint256 _value) internal {
execute(_token, abi.encodeWithSelector(TRANSFER_FROM_FUNC_SELECTOR, _from, _to, _value));
}
/**
* @dev executes a function on the ERC20 token and reverts upon failure
* the main purpose of this function is to prevent a non standard ERC20 token
* from failing silently
*
* @param _token ERC20 token address
* @param _data data to pass in to the token's contract for execution
*/
function execute(IERC20Token _token, bytes memory _data) private {
uint256[1] memory ret = [uint256(1)];
assembly {
let success := call(
gas, // gas remaining
_token, // destination address
0, // no ether
add(_data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
mload(_data), // input length (loaded from the first 32 bytes in the `data` array)
ret, // output buffer
32 // output length
)
if iszero(success) {
revert(0, 0)
}
}
require(ret[0] != 0, "ERR_TRANSFER_FAILED");
}
}
// File: contracts/utility/TokenHolder.sol
pragma solidity 0.4.26;
/**
* @dev We consider every contract to be a 'token holder' since it's currently not possible
* for a contract to deny receiving tokens.
*
* The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
* the owner to send tokens that were sent to the contract by mistake back to their sender.
*
* Note that we use the non standard ERC-20 interface which has no return value for transfer
* in order to support both non standard as well as standard token contracts.
* see https://github.com/ethereum/solidity/issues/4116
*/
contract TokenHolder is ITokenHolder, TokenHandler, Owned, Utils {
/**
* @dev withdraws tokens held by the contract and sends them to an account
* can only be called by the owner
*
* @param _token ERC20 token contract address
* @param _to account to receive the new amount
* @param _amount amount to withdraw
*/
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
public
ownerOnly
validAddress(_token)
validAddress(_to)
notThis(_to)
{
safeTransfer(_token, _to, _amount);
}
}
// File: contracts/utility/SafeMath.sol
pragma solidity 0.4.26;
/**
* @dev Library for basic math operations with overflow/underflow protection
*/
library SafeMath {
/**
* @dev returns the sum of _x and _y, reverts if the calculation overflows
*
* @param _x value 1
* @param _y value 2
*
* @return sum
*/
function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x + _y;
require(z >= _x, "ERR_OVERFLOW");
return z;
}
/**
* @dev returns the difference of _x minus _y, reverts if the calculation underflows
*
* @param _x minuend
* @param _y subtrahend
*
* @return difference
*/
function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_x >= _y, "ERR_UNDERFLOW");
return _x - _y;
}
/**
* @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
*
* @param _x factor 1
* @param _y factor 2
*
* @return product
*/
function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
// gas optimization
if (_x == 0)
return 0;
uint256 z = _x * _y;
require(z / _x == _y, "ERR_OVERFLOW");
return z;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*
* @param _x dividend
* @param _y divisor
*
* @return quotient
*/
function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_y > 0, "ERR_DIVIDE_BY_ZERO");
uint256 c = _x / _y;
return c;
}
}
// File: contracts/token/interfaces/IEtherToken.sol
pragma solidity 0.4.26;
/*
Ether Token interface
*/
contract IEtherToken is IERC20Token {
function deposit() public payable;
function withdraw(uint256 _amount) public;
function depositTo(address _to) public payable;
function withdrawTo(address _to, uint256 _amount) public;
}
// File: contracts/token/interfaces/ISmartToken.sol
pragma solidity 0.4.26;
/*
Smart Token interface
*/
contract ISmartToken is IConverterAnchor, IERC20Token {
function disableTransfers(bool _disable) public;
function issue(address _to, uint256 _amount) public;
function destroy(address _from, uint256 _amount) public;
}
// File: contracts/bancorx/interfaces/IBancorX.sol
pragma solidity 0.4.26;
contract IBancorX {
function token() public view returns (IERC20Token) {this;}
function xTransfer(bytes32 _toBlockchain, bytes32 _to, uint256 _amount, uint256 _id) public;
function getXTransferAmount(uint256 _xTransferId, address _for) public view returns (uint256);
}
// File: contracts/BancorNetwork.sol
pragma solidity 0.4.26;
// interface of older converters for backward compatibility
contract ILegacyConverter {
function change(IERC20Token _sourceToken, IERC20Token _targetToken, uint256 _amount, uint256 _minReturn) public returns (uint256);
}
/**
* @dev The BancorNetwork contract is the main entry point for Bancor token conversions.
* It also allows for the conversion of any token in the Bancor Network to any other token in a single
* transaction by providing a conversion path.
*
* A note on Conversion Path: Conversion path is a data structure that is used when converting a token
* to another token in the Bancor Network, when the conversion cannot necessarily be done by a single
* converter and might require multiple 'hops'.
* The path defines which converters should be used and what kind of conversion should be done in each step.
*
* The path format doesn't include complex structure; instead, it is represented by a single array
* in which each 'hop' is represented by a 2-tuple - converter anchor & target token.
* In addition, the first element is always the source token.
* The converter anchor is only used as a pointer to a converter (since converter addresses are more
* likely to change as opposed to anchor addresses).
*
* Format:
* [source token, converter anchor, target token, converter anchor, target token...]
*/
contract BancorNetwork is IBancorNetwork, TokenHolder, ContractRegistryClient, ReentrancyGuard {
using SafeMath for uint256;
uint256 private constant CONVERSION_FEE_RESOLUTION = 1000000;
uint256 private constant AFFILIATE_FEE_RESOLUTION = 1000000;
address private constant ETH_RESERVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
struct ConversionStep {
IConverter converter;
IConverterAnchor anchor;
IERC20Token sourceToken;
IERC20Token targetToken;
address beneficiary;
bool isV28OrHigherConverter;
bool processAffiliateFee;
}
uint256 public maxAffiliateFee = 30000; // maximum affiliate-fee
mapping (address => bool) public etherTokens; // list of all supported ether tokens
/**
* @dev triggered when a conversion between two tokens occurs
*
* @param _smartToken anchor governed by the converter
* @param _fromToken source ERC20 token
* @param _toToken target ERC20 token
* @param _fromAmount amount converted, in the source token
* @param _toAmount amount returned, minus conversion fee
* @param _trader wallet that initiated the trade
*/
event Conversion(
address indexed _smartToken,
address indexed _fromToken,
address indexed _toToken,
uint256 _fromAmount,
uint256 _toAmount,
address _trader
);
/**
* @dev initializes a new BancorNetwork instance
*
* @param _registry address of a contract registry contract
*/
constructor(IContractRegistry _registry) ContractRegistryClient(_registry) public {
etherTokens[ETH_RESERVE_ADDRESS] = true;
}
/**
* @dev allows the owner to update the maximum affiliate-fee
*
* @param _maxAffiliateFee maximum affiliate-fee
*/
function setMaxAffiliateFee(uint256 _maxAffiliateFee)
public
ownerOnly
{
require(_maxAffiliateFee <= AFFILIATE_FEE_RESOLUTION, "ERR_INVALID_AFFILIATE_FEE");
maxAffiliateFee = _maxAffiliateFee;
}
/**
* @dev allows the owner to register/unregister ether tokens
*
* @param _token ether token contract address
* @param _register true to register, false to unregister
*/
function registerEtherToken(IEtherToken _token, bool _register)
public
ownerOnly
validAddress(_token)
notThis(_token)
{
etherTokens[_token] = _register;
}
/**
* @dev returns the conversion path between two tokens in the network
* note that this method is quite expensive in terms of gas and should generally be called off-chain
*
* @param _sourceToken source token address
* @param _targetToken target token address
*
* @return conversion path between the two tokens
*/
function conversionPath(IERC20Token _sourceToken, IERC20Token _targetToken) public view returns (address[]) {
IConversionPathFinder pathFinder = IConversionPathFinder(addressOf(CONVERSION_PATH_FINDER));
return pathFinder.findPath(_sourceToken, _targetToken);
}
/**
* @dev returns the expected rate of converting a given amount on a given path
* note that there is no support for circular paths
*
* @param _path conversion path (see conversion path format above)
* @param _amount amount of _path[0] tokens received from the sender
*
* @return expected rate
*/
function rateByPath(IERC20Token[] _path, uint256 _amount) public view returns (uint256) {
uint256 amount;
uint256 fee;
uint256 supply;
uint256 balance;
uint32 weight;
IConverter converter;
IBancorFormula formula = IBancorFormula(addressOf(BANCOR_FORMULA));
amount = _amount;
// verify that the number of elements is larger than 2 and odd
require(_path.length > 2 && _path.length % 2 == 1, "ERR_INVALID_PATH");
// iterate over the conversion path
for (uint256 i = 2; i < _path.length; i += 2) {
IERC20Token sourceToken = _path[i - 2];
IERC20Token anchor = _path[i - 1];
IERC20Token targetToken = _path[i];
converter = IConverter(IConverterAnchor(anchor).owner());
// backward compatibility
sourceToken = getConverterTokenAddress(converter, sourceToken);
targetToken = getConverterTokenAddress(converter, targetToken);
if (targetToken == anchor) { // buy the smart token
// check if the current smart token has changed
if (i < 3 || anchor != _path[i - 3])
supply = ISmartToken(anchor).totalSupply();
// get the amount & the conversion fee
balance = converter.getConnectorBalance(sourceToken);
(, weight, , , ) = converter.connectors(sourceToken);
amount = formula.purchaseRate(supply, balance, weight, amount);
fee = amount.mul(converter.conversionFee()).div(CONVERSION_FEE_RESOLUTION);
amount -= fee;
// update the smart token supply for the next iteration
supply = supply.add(amount);
}
else if (sourceToken == anchor) { // sell the smart token
// check if the current smart token has changed
if (i < 3 || anchor != _path[i - 3])
supply = ISmartToken(anchor).totalSupply();
// get the amount & the conversion fee
balance = converter.getConnectorBalance(targetToken);
(, weight, , , ) = converter.connectors(targetToken);
amount = formula.saleRate(supply, balance, weight, amount);
fee = amount.mul(converter.conversionFee()).div(CONVERSION_FEE_RESOLUTION);
amount -= fee;
// update the smart token supply for the next iteration
supply = supply.sub(amount);
}
else { // cross reserve conversion
(amount, fee) = getReturn(converter, sourceToken, targetToken, amount);
}
}
return amount;
}
/**
* @dev converts the token to any other token in the bancor network by following
* a predefined conversion path and transfers the result tokens to a target account
* affiliate account/fee can also be passed in to receive a conversion fee (on top of the liquidity provider fees)
* note that the network should already have been given allowance of the source token (if not ETH)
*
* @param _path conversion path, see conversion path format above
* @param _amount amount to convert from, in the source token
* @param _minReturn if the conversion results in an amount smaller than the minimum return - it is cancelled, must be greater than zero
* @param _beneficiary account that will receive the conversion result or 0x0 to send the result to the sender account
* @param _affiliateAccount wallet address to receive the affiliate fee or 0x0 to disable affiliate fee
* @param _affiliateFee affiliate fee in PPM or 0 to disable affiliate fee
*
* @return amount of tokens received from the conversion
*/
function convertByPath(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, address _beneficiary, address _affiliateAccount, uint256 _affiliateFee)
public
payable
protected
greaterThanZero(_minReturn)
returns (uint256)
{
// verify that the path contrains at least a single 'hop' and that the number of elements is odd
require(_path.length > 2 && _path.length % 2 == 1, "ERR_INVALID_PATH");
// validate msg.value and prepare the source token for the conversion
handleSourceToken(_path[0], IConverterAnchor(_path[1]), _amount);
// check if affiliate fee is enabled
bool affiliateFeeEnabled = false;
if (address(_affiliateAccount) == 0) {
require(_affiliateFee == 0, "ERR_INVALID_AFFILIATE_FEE");
}
else {
require(0 < _affiliateFee && _affiliateFee <= maxAffiliateFee, "ERR_INVALID_AFFILIATE_FEE");
affiliateFeeEnabled = true;
}
// check if beneficiary is set
address beneficiary = msg.sender;
if (_beneficiary != address(0))
beneficiary = _beneficiary;
// convert and get the resulting amount
ConversionStep[] memory data = createConversionData(_path, beneficiary, affiliateFeeEnabled);
uint256 amount = doConversion(data, _amount, _minReturn, _affiliateAccount, _affiliateFee);
// handle the conversion target tokens
handleTargetToken(data, amount, beneficiary);
return amount;
}
/**
* @dev converts any other token to BNT in the bancor network by following
a predefined conversion path and transfers the result to an account on a different blockchain
* note that the network should already have been given allowance of the source token (if not ETH)
*
* @param _path conversion path, see conversion path format above
* @param _amount amount to convert from, in the source token
* @param _minReturn if the conversion results in an amount smaller than the minimum return - it is cancelled, must be greater than zero
* @param _targetBlockchain blockchain BNT will be issued on
* @param _targetAccount address/account on the target blockchain to send the BNT to
* @param _conversionId pre-determined unique (if non zero) id which refers to this transaction
*
* @return the amount of BNT received from this conversion
*/
function xConvert(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
bytes32 _targetBlockchain,
bytes32 _targetAccount,
uint256 _conversionId
)
public
payable
returns (uint256)
{
return xConvert2(_path, _amount, _minReturn, _targetBlockchain, _targetAccount, _conversionId, address(0), 0);
}
/**
* @dev converts any other token to BNT in the bancor network by following
a predefined conversion path and transfers the result to an account on a different blockchain
* note that the network should already have been given allowance of the source token (if not ETH)
*
* @param _path conversion path, see conversion path format above
* @param _amount amount to convert from, in the source token
* @param _minReturn if the conversion results in an amount smaller than the minimum return - it is cancelled, must be greater than zero
* @param _targetBlockchain blockchain BNT will be issued on
* @param _targetAccount address/account on the target blockchain to send the BNT to
* @param _conversionId pre-determined unique (if non zero) id which refers to this transaction
* @param _affiliateAccount affiliate account
* @param _affiliateFee affiliate fee in PPM
*
* @return the amount of BNT received from this conversion
*/
function xConvert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
bytes32 _targetBlockchain,
bytes32 _targetAccount,
uint256 _conversionId,
address _affiliateAccount,
uint256 _affiliateFee
)
public
payable
greaterThanZero(_minReturn)
returns (uint256)
{
IERC20Token targetToken = _path[_path.length - 1];
IBancorX bancorX = IBancorX(addressOf(BANCOR_X));
// verify that the destination token is BNT
require(targetToken == addressOf(BNT_TOKEN), "ERR_INVALID_TARGET_TOKEN");
// convert and get the resulting amount
uint256 amount = convertByPath(_path, _amount, _minReturn, this, _affiliateAccount, _affiliateFee);
// grant BancorX allowance
ensureAllowance(targetToken, bancorX, amount);
// transfer the resulting amount to BancorX
bancorX.xTransfer(_targetBlockchain, _targetAccount, amount, _conversionId);
return amount;
}
/**
* @dev allows a user to convert a token that was sent from another blockchain into any other
* token on the BancorNetwork
* ideally this transaction is created before the previous conversion is even complete, so
* so the input amount isn't known at that point - the amount is actually take from the
* BancorX contract directly by specifying the conversion id
*
* @param _path conversion path
* @param _bancorX address of the BancorX contract for the source token
* @param _conversionId pre-determined unique (if non zero) id which refers to this conversion
* @param _minReturn if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero
* @param _beneficiary wallet to receive the conversion result
*
* @return amount of tokens received from the conversion
*/
function completeXConversion(IERC20Token[] _path, IBancorX _bancorX, uint256 _conversionId, uint256 _minReturn, address _beneficiary)
public returns (uint256)
{
// verify that the source token is the BancorX token
require(_path[0] == _bancorX.token(), "ERR_INVALID_SOURCE_TOKEN");
// get conversion amount from BancorX contract
uint256 amount = _bancorX.getXTransferAmount(_conversionId, msg.sender);
// perform the conversion
return convertByPath(_path, amount, _minReturn, _beneficiary, address(0), 0);
}
/**
* @dev executes the actual conversion by following the conversion path
*
* @param _data conversion data, see ConversionStep struct above
* @param _amount amount to convert from, in the source token
* @param _minReturn if the conversion results in an amount smaller than the minimum return - it is cancelled, must be greater than zero
* @param _affiliateAccount affiliate account
* @param _affiliateFee affiliate fee in PPM
*
* @return amount of tokens received from the conversion
*/
function doConversion(
ConversionStep[] _data,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
) private returns (uint256) {
uint256 toAmount;
uint256 fromAmount = _amount;
// iterate over the conversion data
for (uint256 i = 0; i < _data.length; i++) {
ConversionStep memory stepData = _data[i];
// newer converter
if (stepData.isV28OrHigherConverter) {
// transfer the tokens to the converter only if the network contract currently holds the tokens
// not needed with ETH or if it's the first conversion step
if (i != 0 && _data[i - 1].beneficiary == address(this) && !etherTokens[stepData.sourceToken])
safeTransfer(stepData.sourceToken, stepData.converter, fromAmount);
}
// older converter
// if the source token is the smart token, no need to do any transfers as the converter controls it
else if (stepData.sourceToken != ISmartToken(stepData.anchor)) {
// grant allowance for it to transfer the tokens from the network contract
ensureAllowance(stepData.sourceToken, stepData.converter, fromAmount);
}
// do the conversion
if (!stepData.isV28OrHigherConverter)
toAmount = ILegacyConverter(stepData.converter).change(stepData.sourceToken, stepData.targetToken, fromAmount, 1);
else if (etherTokens[stepData.sourceToken])
toAmount = stepData.converter.convert.value(msg.value)(stepData.sourceToken, stepData.targetToken, fromAmount, msg.sender, stepData.beneficiary);
else
toAmount = stepData.converter.convert(stepData.sourceToken, stepData.targetToken, fromAmount, msg.sender, stepData.beneficiary);
// pay affiliate-fee if needed
if (stepData.processAffiliateFee) {
uint256 affiliateAmount = toAmount.mul(_affiliateFee).div(AFFILIATE_FEE_RESOLUTION);
require(stepData.targetToken.transfer(_affiliateAccount, affiliateAmount), "ERR_FEE_TRANSFER_FAILED");
toAmount -= affiliateAmount;
}
emit Conversion(stepData.anchor, stepData.sourceToken, stepData.targetToken, fromAmount, toAmount, msg.sender);
fromAmount = toAmount;
}
// ensure the trade meets the minimum requested amount
require(toAmount >= _minReturn, "ERR_RETURN_TOO_LOW");
return toAmount;
}
/**
* @dev validates msg.value and prepares the conversion source token for the conversion
*
* @param _sourceToken source token of the first conversion step
* @param _anchor converter anchor of the first conversion step
* @param _amount amount to convert from, in the source token
*/
function handleSourceToken(IERC20Token _sourceToken, IConverterAnchor _anchor, uint256 _amount) private {
IConverter firstConverter = IConverter(_anchor.owner());
bool isNewerConverter = isV28OrHigherConverter(firstConverter);
// ETH
if (msg.value > 0) {
// validate msg.value
require(msg.value == _amount, "ERR_ETH_AMOUNT_MISMATCH");
// EtherToken converter - deposit the ETH into the EtherToken
// note that it can still be a non ETH converter if the path is wrong
// but such conversion will simply revert
if (!isNewerConverter)
IEtherToken(getConverterEtherTokenAddress(firstConverter)).deposit.value(msg.value)();
}
// EtherToken
else if (etherTokens[_sourceToken]) {
// claim the tokens - if the source token is ETH reserve, this call will fail
// since in that case the transaction must be sent with msg.value
safeTransferFrom(_sourceToken, msg.sender, this, _amount);
// ETH converter - withdraw the ETH
if (isNewerConverter)
IEtherToken(_sourceToken).withdraw(_amount);
}
// other ERC20 token
else {
// newer converter - transfer the tokens from the sender directly to the converter
// otherwise claim the tokens
if (isNewerConverter)
safeTransferFrom(_sourceToken, msg.sender, firstConverter, _amount);
else
safeTransferFrom(_sourceToken, msg.sender, this, _amount);
}
}
/**
* @dev handles the conversion target token if the network still holds it at the end of the conversion
*
* @param _data conversion data, see ConversionStep struct above
* @param _amount conversion return amount, in the target token
* @param _beneficiary wallet to receive the conversion result
*/
function handleTargetToken(ConversionStep[] _data, uint256 _amount, address _beneficiary) private {
ConversionStep memory stepData = _data[_data.length - 1];
// network contract doesn't hold the tokens, do nothing
if (stepData.beneficiary != address(this))
return;
IERC20Token targetToken = stepData.targetToken;
// ETH / EtherToken
if (etherTokens[targetToken]) {
// newer converter should send ETH directly to the beneficiary
assert(!stepData.isV28OrHigherConverter);
// EtherToken converter - withdraw the ETH and transfer to the beneficiary
IEtherToken(targetToken).withdrawTo(_beneficiary, _amount);
}
// other ERC20 token
else {
safeTransfer(targetToken, _beneficiary, _amount);
}
}
/**
* @dev creates a memory cache of all conversion steps data to minimize logic and external calls during conversions
*
* @param _conversionPath conversion path, see conversion path format above
* @param _beneficiary wallet to receive the conversion result
* @param _affiliateFeeEnabled true if affiliate fee was requested by the sender, false if not
*
* @return cached conversion data to be ingested later on by the conversion flow
*/
function createConversionData(IERC20Token[] _conversionPath, address _beneficiary, bool _affiliateFeeEnabled) private view returns (ConversionStep[]) {
ConversionStep[] memory data = new ConversionStep[](_conversionPath.length / 2);
bool affiliateFeeProcessed = false;
address bntToken = addressOf(BNT_TOKEN);
// iterate the conversion path and create the conversion data for each step
uint256 i;
for (i = 0; i < _conversionPath.length - 1; i += 2) {
IConverterAnchor anchor = IConverterAnchor(_conversionPath[i + 1]);
IConverter converter = IConverter(anchor.owner());
IERC20Token targetToken = _conversionPath[i + 2];
// check if the affiliate fee should be processed in this step
bool processAffiliateFee = _affiliateFeeEnabled && !affiliateFeeProcessed && targetToken == bntToken;
if (processAffiliateFee)
affiliateFeeProcessed = true;
data[i / 2] = ConversionStep({
// set the converter anchor
anchor: anchor,
// set the converter
converter: converter,
// set the source/target tokens
sourceToken: _conversionPath[i],
targetToken: targetToken,
// requires knowledge about the next step, so initialize in the next phase
beneficiary: address(0),
// set flags
isV28OrHigherConverter: isV28OrHigherConverter(converter),
processAffiliateFee: processAffiliateFee
});
}
// ETH support
// source is ETH
ConversionStep memory stepData = data[0];
if (etherTokens[stepData.sourceToken]) {
// newer converter - replace the source token address with ETH reserve address
if (stepData.isV28OrHigherConverter)
stepData.sourceToken = IERC20Token(ETH_RESERVE_ADDRESS);
// older converter - replace the source token with the EtherToken address used by the converter
else
stepData.sourceToken = IERC20Token(getConverterEtherTokenAddress(stepData.converter));
}
// target is ETH
stepData = data[data.length - 1];
if (etherTokens[stepData.targetToken]) {
// newer converter - replace the target token address with ETH reserve address
if (stepData.isV28OrHigherConverter)
stepData.targetToken = IERC20Token(ETH_RESERVE_ADDRESS);
// older converter - replace the target token with the EtherToken address used by the converter
else
stepData.targetToken = IERC20Token(getConverterEtherTokenAddress(stepData.converter));
}
// set the beneficiary for each step
for (i = 0; i < data.length; i++) {
stepData = data[i];
// first check if the converter in this step is newer as older converters don't even support the beneficiary argument
if (stepData.isV28OrHigherConverter) {
// if affiliate fee is processed in this step, beneficiary is the network contract
if (stepData.processAffiliateFee)
stepData.beneficiary = this;
// if it's the last step, beneficiary is the final beneficiary
else if (i == data.length - 1)
stepData.beneficiary = _beneficiary;
// if the converter in the next step is newer, beneficiary is the next converter
else if (data[i + 1].isV28OrHigherConverter)
stepData.beneficiary = data[i + 1].converter;
// the converter in the next step is older, beneficiary is the network contract
else
stepData.beneficiary = this;
}
else {
// converter in this step is older, beneficiary is the network contract
stepData.beneficiary = this;
}
}
return data;
}
/**
* @dev utility, checks whether allowance for the given spender exists and approves one if it doesn't.
* Note that we use the non standard erc-20 interface in which `approve` has no return value so that
* this function will work for both standard and non standard tokens
*
* @param _token token to check the allowance in
* @param _spender approved address
* @param _value allowance amount
*/
function ensureAllowance(IERC20Token _token, address _spender, uint256 _value) private {
uint256 allowance = _token.allowance(this, _spender);
if (allowance < _value) {
if (allowance > 0)
safeApprove(_token, _spender, 0);
safeApprove(_token, _spender, _value);
}
}
// legacy - returns the address of an EtherToken used by the converter
function getConverterEtherTokenAddress(IConverter _converter) private view returns (address) {
uint256 reserveCount = _converter.connectorTokenCount();
for (uint256 i = 0; i < reserveCount; i++) {
address reserveTokenAddress = _converter.connectorTokens(i);
if (etherTokens[reserveTokenAddress])
return reserveTokenAddress;
}
return ETH_RESERVE_ADDRESS;
}
// legacy - if the token is an ether token, returns the ETH reserve address
// used by the converter, otherwise returns the input token address
function getConverterTokenAddress(IConverter _converter, IERC20Token _token) private view returns (IERC20Token) {
if (!etherTokens[_token])
return _token;
if (isV28OrHigherConverter(_converter))
return IERC20Token(ETH_RESERVE_ADDRESS);
return IERC20Token(getConverterEtherTokenAddress(_converter));
}
bytes4 private constant GET_RETURN_FUNC_SELECTOR = bytes4(keccak256("getReturn(address,address,uint256)"));
// using assembly code since older converter versions have different return values
function getReturn(address _dest, address _sourceToken, address _targetToken, uint256 _amount) internal view returns (uint256, uint256) {
uint256[2] memory ret;
bytes memory data = abi.encodeWithSelector(GET_RETURN_FUNC_SELECTOR, _sourceToken, _targetToken, _amount);
assembly {
let success := staticcall(
gas, // gas remaining
_dest, // destination address
add(data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
mload(data), // input length (loaded from the first 32 bytes in the `data` array)
ret, // output buffer
64 // output length
)
if iszero(success) {
revert(0, 0)
}
}
return (ret[0], ret[1]);
}
bytes4 private constant IS_V28_OR_HIGHER_FUNC_SELECTOR = bytes4(keccak256("isV28OrHigher()"));
// using assembly code to identify converter version
// can't rely on the version number since the function had a different signature in older converters
function isV28OrHigherConverter(IConverter _converter) internal view returns (bool) {
bool success;
uint256[1] memory ret;
bytes memory data = abi.encodeWithSelector(IS_V28_OR_HIGHER_FUNC_SELECTOR);
assembly {
success := staticcall(
5000, // isV28OrHigher consumes 190 gas, but just for extra safety
_converter, // destination address
add(data, 32), // input buffer (starts after the first 32 bytes in the `data` array)
mload(data), // input length (loaded from the first 32 bytes in the `data` array)
ret, // output buffer
32 // output length
)
}
return success && ret[0] != 0;
}
/**
* @dev deprecated, backward compatibility
*/
function getReturnByPath(IERC20Token[] _path, uint256 _amount) public view returns (uint256, uint256) {
return (rateByPath(_path, _amount), 0);
}
/**
* @dev deprecated, backward compatibility
*/
function convert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn) public payable returns (uint256) {
return convertByPath(_path, _amount, _minReturn, address(0), address(0), 0);
}
/**
* @dev deprecated, backward compatibility
*/
function convert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
)
public
payable
returns (uint256)
{
return convertByPath(_path, _amount, _minReturn, address(0), _affiliateAccount, _affiliateFee);
}
/**
* @dev deprecated, backward compatibility
*/
function convertFor(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, address _beneficiary) public payable returns (uint256) {
return convertByPath(_path, _amount, _minReturn, _beneficiary, address(0), 0);
}
/**
* @dev deprecated, backward compatibility
*/
function convertFor2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _beneficiary,
address _affiliateAccount,
uint256 _affiliateFee
)
public
payable
greaterThanZero(_minReturn)
returns (uint256)
{
return convertByPath(_path, _amount, _minReturn, _beneficiary, _affiliateAccount, _affiliateFee);
}
/**
* @dev deprecated, backward compatibility
*/
function claimAndConvert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn) public returns (uint256) {
return convertByPath(_path, _amount, _minReturn, address(0), address(0), 0);
}
/**
* @dev deprecated, backward compatibility
*/
function claimAndConvert2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _affiliateAccount,
uint256 _affiliateFee
)
public
returns (uint256)
{
return convertByPath(_path, _amount, _minReturn, address(0), _affiliateAccount, _affiliateFee);
}
/**
* @dev deprecated, backward compatibility
*/
function claimAndConvertFor(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, address _beneficiary) public returns (uint256) {
return convertByPath(_path, _amount, _minReturn, _beneficiary, address(0), 0);
}
/**
* @dev deprecated, backward compatibility
*/
function claimAndConvertFor2(
IERC20Token[] _path,
uint256 _amount,
uint256 _minReturn,
address _beneficiary,
address _affiliateAccount,
uint256 _affiliateFee
)
public
returns (uint256)
{
return convertByPath(_path, _amount, _minReturn, _beneficiary, _affiliateAccount, _affiliateFee);
}
}File 8 of 13: ConversionRates
pragma solidity 0.4.18;
interface ConversionRatesInterface {
function recordImbalance(
ERC20 token,
int buyAmount,
uint rateUpdateBlock,
uint currentBlock
)
public;
function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint);
}
interface ERC20 {
function totalSupply() public view returns (uint supply);
function balanceOf(address _owner) public view returns (uint balance);
function transfer(address _to, uint _value) public returns (bool success);
function transferFrom(address _from, address _to, uint _value) public returns (bool success);
function approve(address _spender, uint _value) public returns (bool success);
function allowance(address _owner, address _spender) public view returns (uint remaining);
function decimals() public view returns(uint digits);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
contract PermissionGroups {
address public admin;
address public pendingAdmin;
mapping(address=>bool) internal operators;
mapping(address=>bool) internal alerters;
address[] internal operatorsGroup;
address[] internal alertersGroup;
uint constant internal MAX_GROUP_SIZE = 50;
function PermissionGroups() public {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin);
_;
}
modifier onlyOperator() {
require(operators[msg.sender]);
_;
}
modifier onlyAlerter() {
require(alerters[msg.sender]);
_;
}
function getOperators () external view returns(address[]) {
return operatorsGroup;
}
function getAlerters () external view returns(address[]) {
return alertersGroup;
}
event TransferAdminPending(address pendingAdmin);
/**
* @dev Allows the current admin to set the pendingAdmin address.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdmin(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(pendingAdmin);
pendingAdmin = newAdmin;
}
/**
* @dev Allows the current admin to set the admin in one tx. Useful initial deployment.
* @param newAdmin The address to transfer ownership to.
*/
function transferAdminQuickly(address newAdmin) public onlyAdmin {
require(newAdmin != address(0));
TransferAdminPending(newAdmin);
AdminClaimed(newAdmin, admin);
admin = newAdmin;
}
event AdminClaimed( address newAdmin, address previousAdmin);
/**
* @dev Allows the pendingAdmin address to finalize the change admin process.
*/
function claimAdmin() public {
require(pendingAdmin == msg.sender);
AdminClaimed(pendingAdmin, admin);
admin = pendingAdmin;
pendingAdmin = address(0);
}
event AlerterAdded (address newAlerter, bool isAdd);
function addAlerter(address newAlerter) public onlyAdmin {
require(!alerters[newAlerter]); // prevent duplicates.
require(alertersGroup.length < MAX_GROUP_SIZE);
AlerterAdded(newAlerter, true);
alerters[newAlerter] = true;
alertersGroup.push(newAlerter);
}
function removeAlerter (address alerter) public onlyAdmin {
require(alerters[alerter]);
alerters[alerter] = false;
for (uint i = 0; i < alertersGroup.length; ++i) {
if (alertersGroup[i] == alerter) {
alertersGroup[i] = alertersGroup[alertersGroup.length - 1];
alertersGroup.length--;
AlerterAdded(alerter, false);
break;
}
}
}
event OperatorAdded(address newOperator, bool isAdd);
function addOperator(address newOperator) public onlyAdmin {
require(!operators[newOperator]); // prevent duplicates.
require(operatorsGroup.length < MAX_GROUP_SIZE);
OperatorAdded(newOperator, true);
operators[newOperator] = true;
operatorsGroup.push(newOperator);
}
function removeOperator (address operator) public onlyAdmin {
require(operators[operator]);
operators[operator] = false;
for (uint i = 0; i < operatorsGroup.length; ++i) {
if (operatorsGroup[i] == operator) {
operatorsGroup[i] = operatorsGroup[operatorsGroup.length - 1];
operatorsGroup.length -= 1;
OperatorAdded(operator, false);
break;
}
}
}
}
contract Withdrawable is PermissionGroups {
event TokenWithdraw(ERC20 token, uint amount, address sendTo);
/**
* @dev Withdraw all ERC20 compatible tokens
* @param token ERC20 The address of the token contract
*/
function withdrawToken(ERC20 token, uint amount, address sendTo) external onlyAdmin {
require(token.transfer(sendTo, amount));
TokenWithdraw(token, amount, sendTo);
}
event EtherWithdraw(uint amount, address sendTo);
/**
* @dev Withdraw Ethers
*/
function withdrawEther(uint amount, address sendTo) external onlyAdmin {
sendTo.transfer(amount);
EtherWithdraw(amount, sendTo);
}
}
contract VolumeImbalanceRecorder is Withdrawable {
uint constant internal SLIDING_WINDOW_SIZE = 5;
uint constant internal POW_2_64 = 2 ** 64;
struct TokenControlInfo {
uint minimalRecordResolution; // can be roughly 1 cent
uint maxPerBlockImbalance; // in twei resolution
uint maxTotalImbalance; // max total imbalance (between rate updates)
// before halting trade
}
mapping(address => TokenControlInfo) internal tokenControlInfo;
struct TokenImbalanceData {
int lastBlockBuyUnitsImbalance;
uint lastBlock;
int totalBuyUnitsImbalance;
uint lastRateUpdateBlock;
}
mapping(address => mapping(uint=>uint)) public tokenImbalanceData;
function VolumeImbalanceRecorder(address _admin) public {
require(_admin != address(0));
admin = _admin;
}
function setTokenControlInfo(
ERC20 token,
uint minimalRecordResolution,
uint maxPerBlockImbalance,
uint maxTotalImbalance
)
public
onlyAdmin
{
tokenControlInfo[token] =
TokenControlInfo(
minimalRecordResolution,
maxPerBlockImbalance,
maxTotalImbalance
);
}
function getTokenControlInfo(ERC20 token) public view returns(uint, uint, uint) {
return (tokenControlInfo[token].minimalRecordResolution,
tokenControlInfo[token].maxPerBlockImbalance,
tokenControlInfo[token].maxTotalImbalance);
}
function addImbalance(
ERC20 token,
int buyAmount,
uint rateUpdateBlock,
uint currentBlock
)
internal
{
uint currentBlockIndex = currentBlock % SLIDING_WINDOW_SIZE;
int recordedBuyAmount = int(buyAmount / int(tokenControlInfo[token].minimalRecordResolution));
int prevImbalance = 0;
TokenImbalanceData memory currentBlockData =
decodeTokenImbalanceData(tokenImbalanceData[token][currentBlockIndex]);
// first scenario - this is not the first tx in the current block
if (currentBlockData.lastBlock == currentBlock) {
if (uint(currentBlockData.lastRateUpdateBlock) == rateUpdateBlock) {
// just increase imbalance
currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;
currentBlockData.totalBuyUnitsImbalance += recordedBuyAmount;
} else {
// imbalance was changed in the middle of the block
prevImbalance = getImbalanceInRange(token, rateUpdateBlock, currentBlock);
currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;
currentBlockData.lastBlockBuyUnitsImbalance += recordedBuyAmount;
currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);
}
} else {
// first tx in the current block
int currentBlockImbalance;
(prevImbalance, currentBlockImbalance) = getImbalanceSinceRateUpdate(token, rateUpdateBlock, currentBlock);
currentBlockData.lastBlockBuyUnitsImbalance = recordedBuyAmount;
currentBlockData.lastBlock = uint(currentBlock);
currentBlockData.lastRateUpdateBlock = uint(rateUpdateBlock);
currentBlockData.totalBuyUnitsImbalance = int(prevImbalance) + recordedBuyAmount;
}
tokenImbalanceData[token][currentBlockIndex] = encodeTokenImbalanceData(currentBlockData);
}
function setGarbageToVolumeRecorder(ERC20 token) internal {
for (uint i = 0; i < SLIDING_WINDOW_SIZE; i++) {
tokenImbalanceData[token][i] = 0x1;
}
}
function getImbalanceInRange(ERC20 token, uint startBlock, uint endBlock) internal view returns(int buyImbalance) {
// check the imbalance in the sliding window
require(startBlock <= endBlock);
buyImbalance = 0;
for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) {
TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);
if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) {
buyImbalance += int(perBlockData.lastBlockBuyUnitsImbalance);
}
}
}
function getImbalanceSinceRateUpdate(ERC20 token, uint rateUpdateBlock, uint currentBlock)
internal view
returns(int buyImbalance, int currentBlockImbalance)
{
buyImbalance = 0;
currentBlockImbalance = 0;
uint latestBlock = 0;
int imbalanceInRange = 0;
uint startBlock = rateUpdateBlock;
uint endBlock = currentBlock;
for (uint windowInd = 0; windowInd < SLIDING_WINDOW_SIZE; windowInd++) {
TokenImbalanceData memory perBlockData = decodeTokenImbalanceData(tokenImbalanceData[token][windowInd]);
if (perBlockData.lastBlock <= endBlock && perBlockData.lastBlock >= startBlock) {
imbalanceInRange += perBlockData.lastBlockBuyUnitsImbalance;
}
if (perBlockData.lastRateUpdateBlock != rateUpdateBlock) continue;
if (perBlockData.lastBlock < latestBlock) continue;
latestBlock = perBlockData.lastBlock;
buyImbalance = perBlockData.totalBuyUnitsImbalance;
if (uint(perBlockData.lastBlock) == currentBlock) {
currentBlockImbalance = perBlockData.lastBlockBuyUnitsImbalance;
}
}
if (buyImbalance == 0) {
buyImbalance = imbalanceInRange;
}
}
function getImbalance(ERC20 token, uint rateUpdateBlock, uint currentBlock)
internal view
returns(int totalImbalance, int currentBlockImbalance)
{
int resolution = int(tokenControlInfo[token].minimalRecordResolution);
(totalImbalance, currentBlockImbalance) =
getImbalanceSinceRateUpdate(
token,
rateUpdateBlock,
currentBlock);
totalImbalance *= resolution;
currentBlockImbalance *= resolution;
}
function getMaxPerBlockImbalance(ERC20 token) internal view returns(uint) {
return tokenControlInfo[token].maxPerBlockImbalance;
}
function getMaxTotalImbalance(ERC20 token) internal view returns(uint) {
return tokenControlInfo[token].maxTotalImbalance;
}
function encodeTokenImbalanceData(TokenImbalanceData data) internal pure returns(uint) {
// check for overflows
require(data.lastBlockBuyUnitsImbalance < int(POW_2_64 / 2));
require(data.lastBlockBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2));
require(data.lastBlock < POW_2_64);
require(data.totalBuyUnitsImbalance < int(POW_2_64 / 2));
require(data.totalBuyUnitsImbalance > int(-1 * int(POW_2_64) / 2));
require(data.lastRateUpdateBlock < POW_2_64);
// do encoding
uint result = uint(data.lastBlockBuyUnitsImbalance) & (POW_2_64 - 1);
result |= data.lastBlock * POW_2_64;
result |= (uint(data.totalBuyUnitsImbalance) & (POW_2_64 - 1)) * POW_2_64 * POW_2_64;
result |= data.lastRateUpdateBlock * POW_2_64 * POW_2_64 * POW_2_64;
return result;
}
function decodeTokenImbalanceData(uint input) internal pure returns(TokenImbalanceData) {
TokenImbalanceData memory data;
data.lastBlockBuyUnitsImbalance = int(int64(input & (POW_2_64 - 1)));
data.lastBlock = uint(uint64((input / POW_2_64) & (POW_2_64 - 1)));
data.totalBuyUnitsImbalance = int(int64((input / (POW_2_64 * POW_2_64)) & (POW_2_64 - 1)));
data.lastRateUpdateBlock = uint(uint64((input / (POW_2_64 * POW_2_64 * POW_2_64))));
return data;
}
}
contract Utils {
ERC20 constant internal ETH_TOKEN_ADDRESS = ERC20(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee);
uint constant internal PRECISION = (10**18);
uint constant internal MAX_QTY = (10**28); // 10B tokens
uint constant internal MAX_RATE = (PRECISION * 10**6); // up to 1M tokens per ETH
uint constant internal MAX_DECIMALS = 18;
uint constant internal ETH_DECIMALS = 18;
mapping(address=>uint) internal decimals;
function setDecimals(ERC20 token) internal {
if (token == ETH_TOKEN_ADDRESS) decimals[token] = ETH_DECIMALS;
else decimals[token] = token.decimals();
}
function getDecimals(ERC20 token) internal view returns(uint) {
if (token == ETH_TOKEN_ADDRESS) return ETH_DECIMALS; // save storage access
uint tokenDecimals = decimals[token];
// technically, there might be token with decimals 0
// moreover, very possible that old tokens have decimals 0
// these tokens will just have higher gas fees.
if(tokenDecimals == 0) return token.decimals();
return tokenDecimals;
}
function calcDstQty(uint srcQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(srcQty <= MAX_QTY);
require(rate <= MAX_RATE);
if (dstDecimals >= srcDecimals) {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
return (srcQty * rate * (10**(dstDecimals - srcDecimals))) / PRECISION;
} else {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
return (srcQty * rate) / (PRECISION * (10**(srcDecimals - dstDecimals)));
}
}
function calcSrcQty(uint dstQty, uint srcDecimals, uint dstDecimals, uint rate) internal pure returns(uint) {
require(dstQty <= MAX_QTY);
require(rate <= MAX_RATE);
//source quantity is rounded up. to avoid dest quantity being too low.
uint numerator;
uint denominator;
if (srcDecimals >= dstDecimals) {
require((srcDecimals - dstDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty * (10**(srcDecimals - dstDecimals)));
denominator = rate;
} else {
require((dstDecimals - srcDecimals) <= MAX_DECIMALS);
numerator = (PRECISION * dstQty);
denominator = (rate * (10**(dstDecimals - srcDecimals)));
}
return (numerator + denominator - 1) / denominator; //avoid rounding down errors
}
}
contract ConversionRates is ConversionRatesInterface, VolumeImbalanceRecorder, Utils {
// bps - basic rate steps. one step is 1 / 10000 of the rate.
struct StepFunction {
int[] x; // quantity for each step. Quantity of each step includes previous steps.
int[] y; // rate change per quantity step in bps.
}
struct TokenData {
bool listed; // was added to reserve
bool enabled; // whether trade is enabled
// position in the compact data
uint compactDataArrayIndex;
uint compactDataFieldIndex;
// rate data. base and changes according to quantity and reserve balance.
// generally speaking. Sell rate is 1 / buy rate i.e. the buy in the other direction.
uint baseBuyRate; // in PRECISION units. see KyberConstants
uint baseSellRate; // PRECISION units. without (sell / buy) spread it is 1 / baseBuyRate
StepFunction buyRateQtyStepFunction; // in bps. higher quantity - bigger the rate.
StepFunction sellRateQtyStepFunction;// in bps. higher the qua
StepFunction buyRateImbalanceStepFunction; // in BPS. higher reserve imbalance - bigger the rate.
StepFunction sellRateImbalanceStepFunction;
}
/*
this is the data for tokenRatesCompactData
but solidity compiler optimizer is sub-optimal, and cannot write this structure in a single storage write
so we represent it as bytes32 and do the byte tricks ourselves.
struct TokenRatesCompactData {
bytes14 buy; // change buy rate of token from baseBuyRate in 10 bps
bytes14 sell; // change sell rate of token from baseSellRate in 10 bps
uint32 blockNumber;
} */
uint public validRateDurationInBlocks = 10; // rates are valid for this amount of blocks
ERC20[] internal listedTokens;
mapping(address=>TokenData) internal tokenData;
bytes32[] internal tokenRatesCompactData;
uint public numTokensInCurrentCompactData = 0;
address public reserveContract;
uint constant internal NUM_TOKENS_IN_COMPACT_DATA = 14;
uint constant internal BYTES_14_OFFSET = (2 ** (8 * NUM_TOKENS_IN_COMPACT_DATA));
uint constant internal MAX_STEPS_IN_FUNCTION = 10;
int constant internal MAX_BPS_ADJUSTMENT = 10 ** 11; // 1B %
int constant internal MIN_BPS_ADJUSTMENT = -100 * 100; // cannot go down by more than 100%
function ConversionRates(address _admin) public VolumeImbalanceRecorder(_admin)
{ } // solhint-disable-line no-empty-blocks
function addToken(ERC20 token) public onlyAdmin {
require(!tokenData[token].listed);
tokenData[token].listed = true;
listedTokens.push(token);
if (numTokensInCurrentCompactData == 0) {
tokenRatesCompactData.length++; // add new structure
}
tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1;
tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData;
numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA;
setGarbageToVolumeRecorder(token);
setDecimals(token);
}
function setCompactData(bytes14[] buy, bytes14[] sell, uint blockNumber, uint[] indices) public onlyOperator {
require(buy.length == sell.length);
require(indices.length == buy.length);
require(blockNumber <= 0xFFFFFFFF);
uint bytes14Offset = BYTES_14_OFFSET;
for (uint i = 0; i < indices.length; i++) {
require(indices[i] < tokenRatesCompactData.length);
uint data = uint(buy[i]) | uint(sell[i]) * bytes14Offset | (blockNumber * (bytes14Offset * bytes14Offset));
tokenRatesCompactData[indices[i]] = bytes32(data);
}
}
function setBaseRate(
ERC20[] tokens,
uint[] baseBuy,
uint[] baseSell,
bytes14[] buy,
bytes14[] sell,
uint blockNumber,
uint[] indices
)
public
onlyOperator
{
require(tokens.length == baseBuy.length);
require(tokens.length == baseSell.length);
require(sell.length == buy.length);
require(sell.length == indices.length);
for (uint ind = 0; ind < tokens.length; ind++) {
require(tokenData[tokens[ind]].listed);
tokenData[tokens[ind]].baseBuyRate = baseBuy[ind];
tokenData[tokens[ind]].baseSellRate = baseSell[ind];
}
setCompactData(buy, sell, blockNumber, indices);
}
function setQtyStepFunction(
ERC20 token,
int[] xBuy,
int[] yBuy,
int[] xSell,
int[] ySell
)
public
onlyOperator
{
require(xBuy.length == yBuy.length);
require(xSell.length == ySell.length);
require(xBuy.length <= MAX_STEPS_IN_FUNCTION);
require(xSell.length <= MAX_STEPS_IN_FUNCTION);
require(tokenData[token].listed);
tokenData[token].buyRateQtyStepFunction = StepFunction(xBuy, yBuy);
tokenData[token].sellRateQtyStepFunction = StepFunction(xSell, ySell);
}
function setImbalanceStepFunction(
ERC20 token,
int[] xBuy,
int[] yBuy,
int[] xSell,
int[] ySell
)
public
onlyOperator
{
require(xBuy.length == yBuy.length);
require(xSell.length == ySell.length);
require(xBuy.length <= MAX_STEPS_IN_FUNCTION);
require(xSell.length <= MAX_STEPS_IN_FUNCTION);
require(tokenData[token].listed);
tokenData[token].buyRateImbalanceStepFunction = StepFunction(xBuy, yBuy);
tokenData[token].sellRateImbalanceStepFunction = StepFunction(xSell, ySell);
}
function setValidRateDurationInBlocks(uint duration) public onlyAdmin {
validRateDurationInBlocks = duration;
}
function enableTokenTrade(ERC20 token) public onlyAdmin {
require(tokenData[token].listed);
require(tokenControlInfo[token].minimalRecordResolution != 0);
tokenData[token].enabled = true;
}
function disableTokenTrade(ERC20 token) public onlyAlerter {
require(tokenData[token].listed);
tokenData[token].enabled = false;
}
function setReserveAddress(address reserve) public onlyAdmin {
reserveContract = reserve;
}
function recordImbalance(
ERC20 token,
int buyAmount,
uint rateUpdateBlock,
uint currentBlock
)
public
{
require(msg.sender == reserveContract);
if (rateUpdateBlock == 0) rateUpdateBlock = getRateUpdateBlock(token);
return addImbalance(token, buyAmount, rateUpdateBlock, currentBlock);
}
/* solhint-disable function-max-lines */
function getRate(ERC20 token, uint currentBlockNumber, bool buy, uint qty) public view returns(uint) {
// check if trade is enabled
if (!tokenData[token].enabled) return 0;
if (tokenControlInfo[token].minimalRecordResolution == 0) return 0; // token control info not set
// get rate update block
bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
uint updateRateBlock = getLast4Bytes(compactData);
if (currentBlockNumber >= updateRateBlock + validRateDurationInBlocks) return 0; // rate is expired
// check imbalance
int totalImbalance;
int blockImbalance;
(totalImbalance, blockImbalance) = getImbalance(token, updateRateBlock, currentBlockNumber);
// calculate actual rate
int imbalanceQty;
int extraBps;
int8 rateUpdate;
uint rate;
if (buy) {
// start with base rate
rate = tokenData[token].baseBuyRate;
// add rate update
rateUpdate = getRateByteFromCompactData(compactData, token, true);
extraBps = int(rateUpdate) * 10;
rate = addBps(rate, extraBps);
// compute token qty
qty = getTokenQty(token, rate, qty);
imbalanceQty = int(qty);
totalImbalance += imbalanceQty;
// add qty overhead
extraBps = executeStepFunction(tokenData[token].buyRateQtyStepFunction, int(qty));
rate = addBps(rate, extraBps);
// add imbalance overhead
extraBps = executeStepFunction(tokenData[token].buyRateImbalanceStepFunction, totalImbalance);
rate = addBps(rate, extraBps);
} else {
// start with base rate
rate = tokenData[token].baseSellRate;
// add rate update
rateUpdate = getRateByteFromCompactData(compactData, token, false);
extraBps = int(rateUpdate) * 10;
rate = addBps(rate, extraBps);
// compute token qty
imbalanceQty = -1 * int(qty);
totalImbalance += imbalanceQty;
// add qty overhead
extraBps = executeStepFunction(tokenData[token].sellRateQtyStepFunction, int(qty));
rate = addBps(rate, extraBps);
// add imbalance overhead
extraBps = executeStepFunction(tokenData[token].sellRateImbalanceStepFunction, totalImbalance);
rate = addBps(rate, extraBps);
}
if (abs(totalImbalance) >= getMaxTotalImbalance(token)) return 0;
if (abs(blockImbalance + imbalanceQty) >= getMaxPerBlockImbalance(token)) return 0;
return rate;
}
/* solhint-enable function-max-lines */
function getBasicRate(ERC20 token, bool buy) public view returns(uint) {
if (buy)
return tokenData[token].baseBuyRate;
else
return tokenData[token].baseSellRate;
}
function getCompactData(ERC20 token) public view returns(uint, uint, byte, byte) {
require(tokenData[token].listed);
uint arrayIndex = tokenData[token].compactDataArrayIndex;
uint fieldOffset = tokenData[token].compactDataFieldIndex;
return (
arrayIndex,
fieldOffset,
byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, true)),
byte(getRateByteFromCompactData(tokenRatesCompactData[arrayIndex], token, false))
);
}
function getTokenBasicData(ERC20 token) public view returns(bool, bool) {
return (tokenData[token].listed, tokenData[token].enabled);
}
/* solhint-disable code-complexity */
function getStepFunctionData(ERC20 token, uint command, uint param) public view returns(int) {
if (command == 0) return int(tokenData[token].buyRateQtyStepFunction.x.length);
if (command == 1) return tokenData[token].buyRateQtyStepFunction.x[param];
if (command == 2) return int(tokenData[token].buyRateQtyStepFunction.y.length);
if (command == 3) return tokenData[token].buyRateQtyStepFunction.y[param];
if (command == 4) return int(tokenData[token].sellRateQtyStepFunction.x.length);
if (command == 5) return tokenData[token].sellRateQtyStepFunction.x[param];
if (command == 6) return int(tokenData[token].sellRateQtyStepFunction.y.length);
if (command == 7) return tokenData[token].sellRateQtyStepFunction.y[param];
if (command == 8) return int(tokenData[token].buyRateImbalanceStepFunction.x.length);
if (command == 9) return tokenData[token].buyRateImbalanceStepFunction.x[param];
if (command == 10) return int(tokenData[token].buyRateImbalanceStepFunction.y.length);
if (command == 11) return tokenData[token].buyRateImbalanceStepFunction.y[param];
if (command == 12) return int(tokenData[token].sellRateImbalanceStepFunction.x.length);
if (command == 13) return tokenData[token].sellRateImbalanceStepFunction.x[param];
if (command == 14) return int(tokenData[token].sellRateImbalanceStepFunction.y.length);
if (command == 15) return tokenData[token].sellRateImbalanceStepFunction.y[param];
revert();
}
/* solhint-enable code-complexity */
function getRateUpdateBlock(ERC20 token) public view returns(uint) {
bytes32 compactData = tokenRatesCompactData[tokenData[token].compactDataArrayIndex];
return getLast4Bytes(compactData);
}
function getListedTokens() public view returns(ERC20[]) {
return listedTokens;
}
function getTokenQty(ERC20 token, uint ethQty, uint rate) internal view returns(uint) {
uint dstDecimals = getDecimals(token);
uint srcDecimals = ETH_DECIMALS;
return calcDstQty(ethQty, srcDecimals, dstDecimals, rate);
}
function getLast4Bytes(bytes32 b) internal pure returns(uint) {
// cannot trust compiler with not turning bit operations into EXP opcode
return uint(b) / (BYTES_14_OFFSET * BYTES_14_OFFSET);
}
function getRateByteFromCompactData(bytes32 data, ERC20 token, bool buy) internal view returns(int8) {
uint fieldOffset = tokenData[token].compactDataFieldIndex;
uint byteOffset;
if (buy)
byteOffset = 32 - NUM_TOKENS_IN_COMPACT_DATA + fieldOffset;
else
byteOffset = 4 + fieldOffset;
return int8(data[byteOffset]);
}
function executeStepFunction(StepFunction f, int x) internal pure returns(int) {
uint len = f.y.length;
for (uint ind = 0; ind < len; ind++) {
if (x <= f.x[ind]) return f.y[ind];
}
return f.y[len-1];
}
function addBps(uint rate, int bps) internal pure returns(uint) {
require(rate <= MAX_RATE);
require(bps >= MIN_BPS_ADJUSTMENT);
require(bps <= MAX_BPS_ADJUSTMENT);
uint maxBps = 100 * 100;
return (rate * uint(int(maxBps) + bps)) / maxBps;
}
function abs(int x) internal pure returns(uint) {
if (x < 0)
return uint(-1 * x);
else
return uint(x);
}
}File 9 of 13: 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 10 of 13: 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 11 of 13: SmartToken
pragma solidity 0.4.26;
// File: contracts/token/interfaces/IERC20Token.sol
/*
ERC20 Standard Token interface
*/
contract IERC20Token {
// these functions aren't abstract since the compiler emits automatically generated getter functions as external
function name() public view returns (string) {this;}
function symbol() public view returns (string) {this;}
function decimals() public view returns (uint8) {this;}
function totalSupply() public view returns (uint256) {this;}
function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
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);
}
// File: contracts/utility/Utils.sol
/**
* @dev Utilities & Common Modifiers
*/
contract Utils {
/**
* constructor
*/
constructor() public {
}
// verifies that an amount is greater than zero
modifier greaterThanZero(uint256 _amount) {
require(_amount > 0);
_;
}
// validates an address - currently only checks that it isn't null
modifier validAddress(address _address) {
require(_address != address(0));
_;
}
// verifies that the address is different than this contract address
modifier notThis(address _address) {
require(_address != address(this));
_;
}
}
// File: contracts/utility/SafeMath.sol
/**
* @dev Library for basic math operations with overflow/underflow protection
*/
library SafeMath {
/**
* @dev returns the sum of _x and _y, reverts if the calculation overflows
*
* @param _x value 1
* @param _y value 2
*
* @return sum
*/
function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x + _y;
require(z >= _x);
return z;
}
/**
* @dev returns the difference of _x minus _y, reverts if the calculation underflows
*
* @param _x minuend
* @param _y subtrahend
*
* @return difference
*/
function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_x >= _y);
return _x - _y;
}
/**
* @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
*
* @param _x factor 1
* @param _y factor 2
*
* @return product
*/
function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
// gas optimization
if (_x == 0)
return 0;
uint256 z = _x * _y;
require(z / _x == _y);
return z;
}
/**
* ev Integer division of two numbers truncating the quotient, reverts on division by zero.
*
* aram _x dividend
* aram _y divisor
*
* eturn quotient
*/
function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_y > 0);
uint256 c = _x / _y;
return c;
}
}
// File: contracts/token/ERC20Token.sol
/**
* @dev ERC20 Standard Token implementation
*/
contract ERC20Token is IERC20Token, Utils {
using SafeMath for uint256;
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
/**
* @dev triggered when tokens are transferred between wallets
*
* @param _from source address
* @param _to target address
* @param _value transfer amount
*/
event Transfer(address indexed _from, address indexed _to, uint256 _value);
/**
* @dev triggered when a wallet allows another wallet to transfer tokens from on its behalf
*
* @param _owner wallet that approves the allowance
* @param _spender wallet that receives the allowance
* @param _value allowance amount
*/
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
/**
* @dev initializes a new ERC20Token instance
*
* @param _name token name
* @param _symbol token symbol
* @param _decimals decimal points, for display purposes
* @param _totalSupply total supply of token units
*/
constructor(string _name, string _symbol, uint8 _decimals, uint256 _totalSupply) public {
require(bytes(_name).length > 0 && bytes(_symbol).length > 0); // validate input
name = _name;
symbol = _symbol;
decimals = _decimals;
totalSupply = _totalSupply;
balanceOf[msg.sender] = _totalSupply;
}
/**
* @dev send coins
* throws on any error rather then return a false flag to minimize user errors
*
* @param _to target address
* @param _value transfer amount
*
* @return true if the transfer was successful, false if it wasn't
*/
function transfer(address _to, uint256 _value)
public
validAddress(_to)
returns (bool success)
{
balanceOf[msg.sender] = balanceOf[msg.sender].sub(_value);
balanceOf[_to] = balanceOf[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}
/**
* @dev an account/contract attempts to get the coins
* throws on any error rather then return a false flag to minimize user errors
*
* @param _from source address
* @param _to target address
* @param _value transfer amount
*
* @return true if the transfer was successful, false if it wasn't
*/
function transferFrom(address _from, address _to, uint256 _value)
public
validAddress(_from)
validAddress(_to)
returns (bool success)
{
allowance[_from][msg.sender] = allowance[_from][msg.sender].sub(_value);
balanceOf[_from] = balanceOf[_from].sub(_value);
balanceOf[_to] = balanceOf[_to].add(_value);
emit Transfer(_from, _to, _value);
return true;
}
/**
* @dev allow another account/contract to spend some tokens on your behalf
* throws on any error rather then return a false flag to minimize user errors
*
* also, to minimize the risk of the approve/transferFrom attack vector
* (see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), approve has to be called twice
* in 2 separate transactions - once to change the allowance to 0 and secondly to change it to the new allowance value
*
* @param _spender approved address
* @param _value allowance amount
*
* @return true if the approval was successful, false if it wasn't
*/
function approve(address _spender, uint256 _value)
public
validAddress(_spender)
returns (bool success)
{
// if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal
require(_value == 0 || allowance[msg.sender][_spender] == 0);
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
}
// File: contracts/utility/interfaces/IOwned.sol
/*
Owned contract interface
*/
contract IOwned {
// this function isn't abstract since the compiler emits automatically generated getter functions as external
function owner() public view returns (address) {this;}
function transferOwnership(address _newOwner) public;
function acceptOwnership() public;
}
// File: contracts/token/interfaces/ISmartToken.sol
/*
Smart Token interface
*/
contract ISmartToken is IOwned, IERC20Token {
function disableTransfers(bool _disable) public;
function issue(address _to, uint256 _amount) public;
function destroy(address _from, uint256 _amount) public;
}
// File: contracts/utility/Owned.sol
/**
* @dev Provides support and utilities for contract ownership
*/
contract Owned is IOwned {
address public owner;
address public newOwner;
/**
* @dev triggered when the owner is updated
*
* @param _prevOwner previous owner
* @param _newOwner new owner
*/
event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
/**
* @dev initializes a new Owned instance
*/
constructor() public {
owner = msg.sender;
}
// allows execution by the owner only
modifier ownerOnly {
require(msg.sender == owner);
_;
}
/**
* @dev allows transferring the contract ownership
* the new owner still needs to accept the transfer
* can only be called by the contract owner
*
* @param _newOwner new contract owner
*/
function transferOwnership(address _newOwner) public ownerOnly {
require(_newOwner != owner);
newOwner = _newOwner;
}
/**
* @dev used by a new owner to accept an ownership transfer
*/
function acceptOwnership() public {
require(msg.sender == newOwner);
emit OwnerUpdate(owner, newOwner);
owner = newOwner;
newOwner = address(0);
}
}
// File: contracts/utility/interfaces/ITokenHolder.sol
/*
Token Holder interface
*/
contract ITokenHolder is IOwned {
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount) public;
}
// File: contracts/token/interfaces/INonStandardERC20.sol
/*
ERC20 Standard Token interface which doesn't return true/false for transfer, transferFrom and approve
*/
contract INonStandardERC20 {
// these functions aren't abstract since the compiler emits automatically generated getter functions as external
function name() public view returns (string) {this;}
function symbol() public view returns (string) {this;}
function decimals() public view returns (uint8) {this;}
function totalSupply() public view returns (uint256) {this;}
function balanceOf(address _owner) public view returns (uint256) {_owner; this;}
function allowance(address _owner, address _spender) public view returns (uint256) {_owner; _spender; this;}
function transfer(address _to, uint256 _value) public;
function transferFrom(address _from, address _to, uint256 _value) public;
function approve(address _spender, uint256 _value) public;
}
// File: contracts/utility/TokenHolder.sol
/**
* @dev We consider every contract to be a 'token holder' since it's currently not possible
* for a contract to deny receiving tokens.
*
* The TokenHolder's contract sole purpose is to provide a safety mechanism that allows
* the owner to send tokens that were sent to the contract by mistake back to their sender.
*
* Note that we use the non standard ERC-20 interface which has no return value for transfer
* in order to support both non standard as well as standard token contracts.
* see https://github.com/ethereum/solidity/issues/4116
*/
contract TokenHolder is ITokenHolder, Owned, Utils {
/**
* @dev initializes a new TokenHolder instance
*/
constructor() public {
}
/**
* @dev withdraws tokens held by the contract and sends them to an account
* can only be called by the owner
*
* @param _token ERC20 token contract address
* @param _to account to receive the new amount
* @param _amount amount to withdraw
*/
function withdrawTokens(IERC20Token _token, address _to, uint256 _amount)
public
ownerOnly
validAddress(_token)
validAddress(_to)
notThis(_to)
{
INonStandardERC20(_token).transfer(_to, _amount);
}
}
// File: contracts/token/SmartToken.sol
/**
* @dev Smart Token
*
* 'Owned' is specified here for readability reasons
*/
contract SmartToken is ISmartToken, Owned, ERC20Token, TokenHolder {
using SafeMath for uint256;
string public version = '0.3';
bool public transfersEnabled = true; // true if transfer/transferFrom are enabled, false if not
/**
* @dev triggered when a smart token is deployed
* the _token address is defined for forward compatibility, in case the event is trigger by a factory
*
* @param _token new smart token address
*/
event NewSmartToken(address _token);
/**
* @dev triggered when the total supply is increased
*
* @param _amount amount that gets added to the supply
*/
event Issuance(uint256 _amount);
/**
* @dev triggered when the total supply is decreased
*
* @param _amount amount that gets removed from the supply
*/
event Destruction(uint256 _amount);
/**
* @dev initializes a new SmartToken instance
*
* @param _name token name
* @param _symbol token short symbol, minimum 1 character
* @param _decimals for display purposes only
*/
constructor(string _name, string _symbol, uint8 _decimals)
public
ERC20Token(_name, _symbol, _decimals, 0)
{
emit NewSmartToken(address(this));
}
// allows execution only when transfers aren't disabled
modifier transfersAllowed {
assert(transfersEnabled);
_;
}
/**
* @dev disables/enables transfers
* can only be called by the contract owner
*
* @param _disable true to disable transfers, false to enable them
*/
function disableTransfers(bool _disable) public ownerOnly {
transfersEnabled = !_disable;
}
/**
* @dev increases the token supply and sends the new tokens to an account
* can only be called by the contract owner
*
* @param _to account to receive the new amount
* @param _amount amount to increase the supply by
*/
function issue(address _to, uint256 _amount)
public
ownerOnly
validAddress(_to)
notThis(_to)
{
totalSupply = totalSupply.add(_amount);
balanceOf[_to] = balanceOf[_to].add(_amount);
emit Issuance(_amount);
emit Transfer(this, _to, _amount);
}
/**
* @dev removes tokens from an account and decreases the token supply
* can be called by the contract owner to destroy tokens from any account or by any holder to destroy tokens from his/her own account
*
* @param _from account to remove the amount from
* @param _amount amount to decrease the supply by
*/
function destroy(address _from, uint256 _amount) public {
require(msg.sender == _from || msg.sender == owner); // validate input
balanceOf[_from] = balanceOf[_from].sub(_amount);
totalSupply = totalSupply.sub(_amount);
emit Transfer(_from, this, _amount);
emit Destruction(_amount);
}
// ERC20 standard method overrides with some extra functionality
/**
* @dev send coins
* throws on any error rather then return a false flag to minimize user errors
* in addition to the standard checks, the function throws if transfers are disabled
*
* @param _to target address
* @param _value transfer amount
*
* @return true if the transfer was successful, false if it wasn't
*/
function transfer(address _to, uint256 _value) public transfersAllowed returns (bool success) {
assert(super.transfer(_to, _value));
return true;
}
/**
* @dev an account/contract attempts to get the coins
* throws on any error rather then return a false flag to minimize user errors
* in addition to the standard checks, the function throws if transfers are disabled
*
* @param _from source address
* @param _to target address
* @param _value transfer amount
*
* @return true if the transfer was successful, false if it wasn't
*/
function transferFrom(address _from, address _to, uint256 _value) public transfersAllowed returns (bool success) {
assert(super.transferFrom(_from, _to, _value));
return true;
}
}
File 12 of 13: ContractRegistry
pragma solidity ^0.4.24;
// File: contracts/utility/interfaces/IOwned.sol
/*
Owned contract interface
*/
contract IOwned {
// this function isn't abstract since the compiler emits automatically generated getter functions as external
function owner() public view returns (address) {}
function transferOwnership(address _newOwner) public;
function acceptOwnership() public;
}
// File: contracts/utility/Owned.sol
/*
Provides support and utilities for contract ownership
*/
contract Owned is IOwned {
address public owner;
address public newOwner;
event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner);
/**
@dev constructor
*/
constructor() public {
owner = msg.sender;
}
// allows execution by the owner only
modifier ownerOnly {
require(msg.sender == owner);
_;
}
/**
@dev allows transferring the contract ownership
the new owner still needs to accept the transfer
can only be called by the contract owner
@param _newOwner new contract owner
*/
function transferOwnership(address _newOwner) public ownerOnly {
require(_newOwner != owner);
newOwner = _newOwner;
}
/**
@dev used by a new owner to accept an ownership transfer
*/
function acceptOwnership() public {
require(msg.sender == newOwner);
emit OwnerUpdate(owner, newOwner);
owner = newOwner;
newOwner = address(0);
}
}
// File: contracts/utility/Utils.sol
/*
Utilities & Common Modifiers
*/
contract Utils {
/**
constructor
*/
constructor() public {
}
// verifies that an amount is greater than zero
modifier greaterThanZero(uint256 _amount) {
require(_amount > 0);
_;
}
// validates an address - currently only checks that it isn't null
modifier validAddress(address _address) {
require(_address != address(0));
_;
}
// verifies that the address is different than this contract address
modifier notThis(address _address) {
require(_address != address(this));
_;
}
// Overflow protected math functions
/**
@dev returns the sum of _x and _y, asserts if the calculation overflows
@param _x value 1
@param _y value 2
@return sum
*/
function safeAdd(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x + _y;
assert(z >= _x);
return z;
}
/**
@dev returns the difference of _x minus _y, asserts if the subtraction results in a negative number
@param _x minuend
@param _y subtrahend
@return difference
*/
function safeSub(uint256 _x, uint256 _y) internal pure returns (uint256) {
assert(_x >= _y);
return _x - _y;
}
/**
@dev returns the product of multiplying _x by _y, asserts if the calculation overflows
@param _x factor 1
@param _y factor 2
@return product
*/
function safeMul(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x * _y;
assert(_x == 0 || z / _x == _y);
return z;
}
}
// File: contracts/utility/interfaces/IContractRegistry.sol
/*
Contract Registry interface
*/
contract IContractRegistry {
function addressOf(bytes32 _contractName) public view returns (address);
// deprecated, backward compatibility
function getAddress(bytes32 _contractName) public view returns (address);
}
// File: contracts/ContractIds.sol
/**
Id definitions for bancor contracts
Can be used in conjunction with the contract registry to get contract addresses
*/
contract ContractIds {
// generic
bytes32 public constant CONTRACT_FEATURES = "ContractFeatures";
bytes32 public constant CONTRACT_REGISTRY = "ContractRegistry";
// bancor logic
bytes32 public constant BANCOR_NETWORK = "BancorNetwork";
bytes32 public constant BANCOR_FORMULA = "BancorFormula";
bytes32 public constant BANCOR_GAS_PRICE_LIMIT = "BancorGasPriceLimit";
bytes32 public constant BANCOR_CONVERTER_UPGRADER = "BancorConverterUpgrader";
bytes32 public constant BANCOR_CONVERTER_FACTORY = "BancorConverterFactory";
// Ids of BNT converter and BNT token
bytes32 public constant BNT_TOKEN = "BNTToken";
bytes32 public constant BNT_CONVERTER = "BNTConverter";
// Id of BancorX contract
bytes32 public constant BANCOR_X = "BancorX";
}
// File: contracts/utility/ContractRegistry.sol
/**
Contract Registry
The contract registry keeps contract addresses by name.
The owner can update contract addresses so that a contract name always points to the latest version
of the given contract.
Other contracts can query the registry to get updated addresses instead of depending on specific
addresses.
Note that contract names are limited to 32 bytes UTF8 encoded ASCII strings to optimize gas costs
*/
contract ContractRegistry is IContractRegistry, Owned, Utils, ContractIds {
struct RegistryItem {
address contractAddress; // contract address
uint256 nameIndex; // index of the item in the list of contract names
bool isSet; // used to tell if the mapping element is defined
}
mapping (bytes32 => RegistryItem) private items; // name -> RegistryItem mapping
string[] public contractNames; // list of all registered contract names
// triggered when an address pointed to by a contract name is modified
event AddressUpdate(bytes32 indexed _contractName, address _contractAddress);
/**
@dev constructor
*/
constructor() public {
registerAddress(ContractIds.CONTRACT_REGISTRY, address(this));
}
/**
@dev returns the number of items in the registry
@return number of items
*/
function itemCount() public view returns (uint256) {
return contractNames.length;
}
/**
@dev returns the address associated with the given contract name
@param _contractName contract name
@return contract address
*/
function addressOf(bytes32 _contractName) public view returns (address) {
return items[_contractName].contractAddress;
}
/**
@dev registers a new address for the contract name in the registry
@param _contractName contract name
@param _contractAddress contract address
*/
function registerAddress(bytes32 _contractName, address _contractAddress)
public
ownerOnly
validAddress(_contractAddress)
{
require(_contractName.length > 0); // validate input
// update the address in the registry
items[_contractName].contractAddress = _contractAddress;
if (!items[_contractName].isSet) {
// mark the item as set
items[_contractName].isSet = true;
// add the contract name to the name list
uint256 i = contractNames.push(bytes32ToString(_contractName));
// update the item's index in the list
items[_contractName].nameIndex = i - 1;
}
// dispatch the address update event
emit AddressUpdate(_contractName, _contractAddress);
}
/**
@dev removes an existing contract address from the registry
@param _contractName contract name
*/
function unregisterAddress(bytes32 _contractName) public ownerOnly {
require(_contractName.length > 0); // validate input
// remove the address from the registry
items[_contractName].contractAddress = address(0);
// if there are multiple items in the registry, move the last element to the deleted element's position
// and modify last element's registryItem.nameIndex in the items collection to point to the right position in contractNames
if (contractNames.length > 1) {
string memory lastContractNameString = contractNames[contractNames.length - 1];
uint256 unregisterIndex = items[_contractName].nameIndex;
contractNames[unregisterIndex] = lastContractNameString;
bytes32 lastContractName = stringToBytes32(lastContractNameString);
RegistryItem storage registryItem = items[lastContractName];
registryItem.nameIndex = unregisterIndex;
}
// remove the last element from the name list
contractNames.length--;
// zero the deleted element's index
items[_contractName].nameIndex = 0;
// dispatch the address update event
emit AddressUpdate(_contractName, address(0));
}
/**
@dev utility, converts bytes32 to a string
note that the bytes32 argument is assumed to be UTF8 encoded ASCII string
@return string representation of the given bytes32 argument
*/
function bytes32ToString(bytes32 _bytes) private pure returns (string) {
bytes memory byteArray = new bytes(32);
for (uint256 i; i < 32; i++) {
byteArray[i] = _bytes[i];
}
return string(byteArray);
}
// @dev utility, converts string to bytes32
function stringToBytes32(string memory _string) private pure returns (bytes32) {
bytes32 result;
assembly {
result := mload(add(_string,32))
}
return result;
}
// deprecated, backward compatibility
function getAddress(bytes32 _contractName) public view returns (address) {
return addressOf(_contractName);
}
}File 13 of 13: BancorFormula
// File: solidity/contracts/converter/interfaces/IBancorFormula.sol
pragma solidity 0.4.26;
/*
Bancor Formula interface
*/
contract IBancorFormula {
function purchaseTargetAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256);
function saleTargetAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256);
function crossReserveTargetAmount(uint256 _sourceReserveBalance,
uint32 _sourceReserveWeight,
uint256 _targetReserveBalance,
uint32 _targetReserveWeight,
uint256 _amount)
public view returns (uint256);
function fundCost(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function fundSupplyAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function liquidateReserveAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256);
function balancedWeights(uint256 _primaryReserveStakedBalance,
uint256 _primaryReserveBalance,
uint256 _secondaryReserveBalance,
uint256 _reserveRateNumerator,
uint256 _reserveRateDenominator)
public view returns (uint32, uint32);
}
// File: solidity/contracts/utility/SafeMath.sol
pragma solidity 0.4.26;
/**
* @dev Library for basic math operations with overflow/underflow protection
*/
library SafeMath {
/**
* @dev returns the sum of _x and _y, reverts if the calculation overflows
*
* @param _x value 1
* @param _y value 2
*
* @return sum
*/
function add(uint256 _x, uint256 _y) internal pure returns (uint256) {
uint256 z = _x + _y;
require(z >= _x, "ERR_OVERFLOW");
return z;
}
/**
* @dev returns the difference of _x minus _y, reverts if the calculation underflows
*
* @param _x minuend
* @param _y subtrahend
*
* @return difference
*/
function sub(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_x >= _y, "ERR_UNDERFLOW");
return _x - _y;
}
/**
* @dev returns the product of multiplying _x by _y, reverts if the calculation overflows
*
* @param _x factor 1
* @param _y factor 2
*
* @return product
*/
function mul(uint256 _x, uint256 _y) internal pure returns (uint256) {
// gas optimization
if (_x == 0)
return 0;
uint256 z = _x * _y;
require(z / _x == _y, "ERR_OVERFLOW");
return z;
}
/**
* @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
*
* @param _x dividend
* @param _y divisor
*
* @return quotient
*/
function div(uint256 _x, uint256 _y) internal pure returns (uint256) {
require(_y > 0, "ERR_DIVIDE_BY_ZERO");
uint256 c = _x / _y;
return c;
}
}
// File: solidity/contracts/converter/BancorFormula.sol
pragma solidity 0.4.26;
contract BancorFormula is IBancorFormula {
using SafeMath for uint256;
uint16 public constant version = 8;
uint256 private constant ONE = 1;
uint32 private constant MAX_WEIGHT = 1000000;
uint8 private constant MIN_PRECISION = 32;
uint8 private constant MAX_PRECISION = 127;
/**
* Auto-generated via 'PrintIntScalingFactors.py'
*/
uint256 private constant FIXED_1 = 0x080000000000000000000000000000000;
uint256 private constant FIXED_2 = 0x100000000000000000000000000000000;
uint256 private constant MAX_NUM = 0x200000000000000000000000000000000;
/**
* Auto-generated via 'PrintLn2ScalingFactors.py'
*/
uint256 private constant LN2_NUMERATOR = 0x3f80fe03f80fe03f80fe03f80fe03f8;
uint256 private constant LN2_DENOMINATOR = 0x5b9de1d10bf4103d647b0955897ba80;
/**
* Auto-generated via 'PrintFunctionOptimalLog.py' and 'PrintFunctionOptimalExp.py'
*/
uint256 private constant OPT_LOG_MAX_VAL = 0x15bf0a8b1457695355fb8ac404e7a79e3;
uint256 private constant OPT_EXP_MAX_VAL = 0x800000000000000000000000000000000;
/**
* Auto-generated via 'PrintLambertFactors.py'
*/
uint256 private constant LAMBERT_CONV_RADIUS = 0x002f16ac6c59de6f8d5d6f63c1482a7c86;
uint256 private constant LAMBERT_POS2_SAMPLE = 0x0003060c183060c183060c183060c18306;
uint256 private constant LAMBERT_POS2_MAXVAL = 0x01af16ac6c59de6f8d5d6f63c1482a7c80;
uint256 private constant LAMBERT_POS3_MAXVAL = 0x6b22d43e72c326539cceeef8bb48f255ff;
/**
* Auto-generated via 'PrintWeightFactors.py'
*/
uint256 private constant MAX_UNF_WEIGHT = 0x10c6f7a0b5ed8d36b4c7f34938583621fafc8b0079a2834d26fa3fcc9ea9;
/**
* Auto-generated via 'PrintMaxExpArray.py'
*/
uint256[128] private maxExpArray;
function initMaxExpArray() private {
// maxExpArray[ 0] = 0x6bffffffffffffffffffffffffffffffff;
// maxExpArray[ 1] = 0x67ffffffffffffffffffffffffffffffff;
// maxExpArray[ 2] = 0x637fffffffffffffffffffffffffffffff;
// maxExpArray[ 3] = 0x5f6fffffffffffffffffffffffffffffff;
// maxExpArray[ 4] = 0x5b77ffffffffffffffffffffffffffffff;
// maxExpArray[ 5] = 0x57b3ffffffffffffffffffffffffffffff;
// maxExpArray[ 6] = 0x5419ffffffffffffffffffffffffffffff;
// maxExpArray[ 7] = 0x50a2ffffffffffffffffffffffffffffff;
// maxExpArray[ 8] = 0x4d517fffffffffffffffffffffffffffff;
// maxExpArray[ 9] = 0x4a233fffffffffffffffffffffffffffff;
// maxExpArray[ 10] = 0x47165fffffffffffffffffffffffffffff;
// maxExpArray[ 11] = 0x4429afffffffffffffffffffffffffffff;
// maxExpArray[ 12] = 0x415bc7ffffffffffffffffffffffffffff;
// maxExpArray[ 13] = 0x3eab73ffffffffffffffffffffffffffff;
// maxExpArray[ 14] = 0x3c1771ffffffffffffffffffffffffffff;
// maxExpArray[ 15] = 0x399e96ffffffffffffffffffffffffffff;
// maxExpArray[ 16] = 0x373fc47fffffffffffffffffffffffffff;
// maxExpArray[ 17] = 0x34f9e8ffffffffffffffffffffffffffff;
// maxExpArray[ 18] = 0x32cbfd5fffffffffffffffffffffffffff;
// maxExpArray[ 19] = 0x30b5057fffffffffffffffffffffffffff;
// maxExpArray[ 20] = 0x2eb40f9fffffffffffffffffffffffffff;
// maxExpArray[ 21] = 0x2cc8340fffffffffffffffffffffffffff;
// maxExpArray[ 22] = 0x2af09481ffffffffffffffffffffffffff;
// maxExpArray[ 23] = 0x292c5bddffffffffffffffffffffffffff;
// maxExpArray[ 24] = 0x277abdcdffffffffffffffffffffffffff;
// maxExpArray[ 25] = 0x25daf6657fffffffffffffffffffffffff;
// maxExpArray[ 26] = 0x244c49c65fffffffffffffffffffffffff;
// maxExpArray[ 27] = 0x22ce03cd5fffffffffffffffffffffffff;
// maxExpArray[ 28] = 0x215f77c047ffffffffffffffffffffffff;
// maxExpArray[ 29] = 0x1fffffffffffffffffffffffffffffffff;
// maxExpArray[ 30] = 0x1eaefdbdabffffffffffffffffffffffff;
// maxExpArray[ 31] = 0x1d6bd8b2ebffffffffffffffffffffffff;
maxExpArray[ 32] = 0x1c35fedd14ffffffffffffffffffffffff;
maxExpArray[ 33] = 0x1b0ce43b323fffffffffffffffffffffff;
maxExpArray[ 34] = 0x19f0028ec1ffffffffffffffffffffffff;
maxExpArray[ 35] = 0x18ded91f0e7fffffffffffffffffffffff;
maxExpArray[ 36] = 0x17d8ec7f0417ffffffffffffffffffffff;
maxExpArray[ 37] = 0x16ddc6556cdbffffffffffffffffffffff;
maxExpArray[ 38] = 0x15ecf52776a1ffffffffffffffffffffff;
maxExpArray[ 39] = 0x15060c256cb2ffffffffffffffffffffff;
maxExpArray[ 40] = 0x1428a2f98d72ffffffffffffffffffffff;
maxExpArray[ 41] = 0x13545598e5c23fffffffffffffffffffff;
maxExpArray[ 42] = 0x1288c4161ce1dfffffffffffffffffffff;
maxExpArray[ 43] = 0x11c592761c666fffffffffffffffffffff;
maxExpArray[ 44] = 0x110a688680a757ffffffffffffffffffff;
maxExpArray[ 45] = 0x1056f1b5bedf77ffffffffffffffffffff;
maxExpArray[ 46] = 0x0faadceceeff8bffffffffffffffffffff;
maxExpArray[ 47] = 0x0f05dc6b27edadffffffffffffffffffff;
maxExpArray[ 48] = 0x0e67a5a25da4107fffffffffffffffffff;
maxExpArray[ 49] = 0x0dcff115b14eedffffffffffffffffffff;
maxExpArray[ 50] = 0x0d3e7a392431239fffffffffffffffffff;
maxExpArray[ 51] = 0x0cb2ff529eb71e4fffffffffffffffffff;
maxExpArray[ 52] = 0x0c2d415c3db974afffffffffffffffffff;
maxExpArray[ 53] = 0x0bad03e7d883f69bffffffffffffffffff;
maxExpArray[ 54] = 0x0b320d03b2c343d5ffffffffffffffffff;
maxExpArray[ 55] = 0x0abc25204e02828dffffffffffffffffff;
maxExpArray[ 56] = 0x0a4b16f74ee4bb207fffffffffffffffff;
maxExpArray[ 57] = 0x09deaf736ac1f569ffffffffffffffffff;
maxExpArray[ 58] = 0x0976bd9952c7aa957fffffffffffffffff;
maxExpArray[ 59] = 0x09131271922eaa606fffffffffffffffff;
maxExpArray[ 60] = 0x08b380f3558668c46fffffffffffffffff;
maxExpArray[ 61] = 0x0857ddf0117efa215bffffffffffffffff;
maxExpArray[ 62] = 0x07ffffffffffffffffffffffffffffffff;
maxExpArray[ 63] = 0x07abbf6f6abb9d087fffffffffffffffff;
maxExpArray[ 64] = 0x075af62cbac95f7dfa7fffffffffffffff;
maxExpArray[ 65] = 0x070d7fb7452e187ac13fffffffffffffff;
maxExpArray[ 66] = 0x06c3390ecc8af379295fffffffffffffff;
maxExpArray[ 67] = 0x067c00a3b07ffc01fd6fffffffffffffff;
maxExpArray[ 68] = 0x0637b647c39cbb9d3d27ffffffffffffff;
maxExpArray[ 69] = 0x05f63b1fc104dbd39587ffffffffffffff;
maxExpArray[ 70] = 0x05b771955b36e12f7235ffffffffffffff;
maxExpArray[ 71] = 0x057b3d49dda84556d6f6ffffffffffffff;
maxExpArray[ 72] = 0x054183095b2c8ececf30ffffffffffffff;
maxExpArray[ 73] = 0x050a28be635ca2b888f77fffffffffffff;
maxExpArray[ 74] = 0x04d5156639708c9db33c3fffffffffffff;
maxExpArray[ 75] = 0x04a23105873875bd52dfdfffffffffffff;
maxExpArray[ 76] = 0x0471649d87199aa990756fffffffffffff;
maxExpArray[ 77] = 0x04429a21a029d4c1457cfbffffffffffff;
maxExpArray[ 78] = 0x0415bc6d6fb7dd71af2cb3ffffffffffff;
maxExpArray[ 79] = 0x03eab73b3bbfe282243ce1ffffffffffff;
maxExpArray[ 80] = 0x03c1771ac9fb6b4c18e229ffffffffffff;
maxExpArray[ 81] = 0x0399e96897690418f785257fffffffffff;
maxExpArray[ 82] = 0x0373fc456c53bb779bf0ea9fffffffffff;
maxExpArray[ 83] = 0x034f9e8e490c48e67e6ab8bfffffffffff;
maxExpArray[ 84] = 0x032cbfd4a7adc790560b3337ffffffffff;
maxExpArray[ 85] = 0x030b50570f6e5d2acca94613ffffffffff;
maxExpArray[ 86] = 0x02eb40f9f620fda6b56c2861ffffffffff;
maxExpArray[ 87] = 0x02cc8340ecb0d0f520a6af58ffffffffff;
maxExpArray[ 88] = 0x02af09481380a0a35cf1ba02ffffffffff;
maxExpArray[ 89] = 0x0292c5bdd3b92ec810287b1b3fffffffff;
maxExpArray[ 90] = 0x0277abdcdab07d5a77ac6d6b9fffffffff;
maxExpArray[ 91] = 0x025daf6654b1eaa55fd64df5efffffffff;
maxExpArray[ 92] = 0x0244c49c648baa98192dce88b7ffffffff;
maxExpArray[ 93] = 0x022ce03cd5619a311b2471268bffffffff;
maxExpArray[ 94] = 0x0215f77c045fbe885654a44a0fffffffff;
maxExpArray[ 95] = 0x01ffffffffffffffffffffffffffffffff;
maxExpArray[ 96] = 0x01eaefdbdaaee7421fc4d3ede5ffffffff;
maxExpArray[ 97] = 0x01d6bd8b2eb257df7e8ca57b09bfffffff;
maxExpArray[ 98] = 0x01c35fedd14b861eb0443f7f133fffffff;
maxExpArray[ 99] = 0x01b0ce43b322bcde4a56e8ada5afffffff;
maxExpArray[100] = 0x019f0028ec1fff007f5a195a39dfffffff;
maxExpArray[101] = 0x018ded91f0e72ee74f49b15ba527ffffff;
maxExpArray[102] = 0x017d8ec7f04136f4e5615fd41a63ffffff;
maxExpArray[103] = 0x016ddc6556cdb84bdc8d12d22e6fffffff;
maxExpArray[104] = 0x015ecf52776a1155b5bd8395814f7fffff;
maxExpArray[105] = 0x015060c256cb23b3b3cc3754cf40ffffff;
maxExpArray[106] = 0x01428a2f98d728ae223ddab715be3fffff;
maxExpArray[107] = 0x013545598e5c23276ccf0ede68034fffff;
maxExpArray[108] = 0x01288c4161ce1d6f54b7f61081194fffff;
maxExpArray[109] = 0x011c592761c666aa641d5a01a40f17ffff;
maxExpArray[110] = 0x0110a688680a7530515f3e6e6cfdcdffff;
maxExpArray[111] = 0x01056f1b5bedf75c6bcb2ce8aed428ffff;
maxExpArray[112] = 0x00faadceceeff8a0890f3875f008277fff;
maxExpArray[113] = 0x00f05dc6b27edad306388a600f6ba0bfff;
maxExpArray[114] = 0x00e67a5a25da41063de1495d5b18cdbfff;
maxExpArray[115] = 0x00dcff115b14eedde6fc3aa5353f2e4fff;
maxExpArray[116] = 0x00d3e7a3924312399f9aae2e0f868f8fff;
maxExpArray[117] = 0x00cb2ff529eb71e41582cccd5a1ee26fff;
maxExpArray[118] = 0x00c2d415c3db974ab32a51840c0b67edff;
maxExpArray[119] = 0x00bad03e7d883f69ad5b0a186184e06bff;
maxExpArray[120] = 0x00b320d03b2c343d4829abd6075f0cc5ff;
maxExpArray[121] = 0x00abc25204e02828d73c6e80bcdb1a95bf;
maxExpArray[122] = 0x00a4b16f74ee4bb2040a1ec6c15fbbf2df;
maxExpArray[123] = 0x009deaf736ac1f569deb1b5ae3f36c130f;
maxExpArray[124] = 0x00976bd9952c7aa957f5937d790ef65037;
maxExpArray[125] = 0x009131271922eaa6064b73a22d0bd4f2bf;
maxExpArray[126] = 0x008b380f3558668c46c91c49a2f8e967b9;
maxExpArray[127] = 0x00857ddf0117efa215952912839f6473e6;
}
/**
* Auto-generated via 'PrintLambertArray.py'
*/
uint256[128] private lambertArray;
function initLambertArray() private {
lambertArray[ 0] = 0x60e393c68d20b1bd09deaabc0373b9c5;
lambertArray[ 1] = 0x5f8f46e4854120989ed94719fb4c2011;
lambertArray[ 2] = 0x5e479ebb9129fb1b7e72a648f992b606;
lambertArray[ 3] = 0x5d0bd23fe42dfedde2e9586be12b85fe;
lambertArray[ 4] = 0x5bdb29ddee979308ddfca81aeeb8095a;
lambertArray[ 5] = 0x5ab4fd8a260d2c7e2c0d2afcf0009dad;
lambertArray[ 6] = 0x5998b31359a55d48724c65cf09001221;
lambertArray[ 7] = 0x5885bcad2b322dfc43e8860f9c018cf5;
lambertArray[ 8] = 0x577b97aa1fe222bb452fdf111b1f0be2;
lambertArray[ 9] = 0x5679cb5e3575632e5baa27e2b949f704;
lambertArray[ 10] = 0x557fe8241b3a31c83c732f1cdff4a1c5;
lambertArray[ 11] = 0x548d868026504875d6e59bbe95fc2a6b;
lambertArray[ 12] = 0x53a2465ce347cf34d05a867c17dd3088;
lambertArray[ 13] = 0x52bdce5dcd4faed59c7f5511cf8f8acc;
lambertArray[ 14] = 0x51dfcb453c07f8da817606e7885f7c3e;
lambertArray[ 15] = 0x5107ef6b0a5a2be8f8ff15590daa3cce;
lambertArray[ 16] = 0x5035f241d6eae0cd7bacba119993de7b;
lambertArray[ 17] = 0x4f698fe90d5b53d532171e1210164c66;
lambertArray[ 18] = 0x4ea288ca297a0e6a09a0eee240e16c85;
lambertArray[ 19] = 0x4de0a13fdcf5d4213fc398ba6e3becde;
lambertArray[ 20] = 0x4d23a145eef91fec06b06140804c4808;
lambertArray[ 21] = 0x4c6b5430d4c1ee5526473db4ae0f11de;
lambertArray[ 22] = 0x4bb7886c240562eba11f4963a53b4240;
lambertArray[ 23] = 0x4b080f3f1cb491d2d521e0ea4583521e;
lambertArray[ 24] = 0x4a5cbc96a05589cb4d86be1db3168364;
lambertArray[ 25] = 0x49b566d40243517658d78c33162d6ece;
lambertArray[ 26] = 0x4911e6a02e5507a30f947383fd9a3276;
lambertArray[ 27] = 0x487216c2b31be4adc41db8a8d5cc0c88;
lambertArray[ 28] = 0x47d5d3fc4a7a1b188cd3d788b5c5e9fc;
lambertArray[ 29] = 0x473cfce4871a2c40bc4f9e1c32b955d0;
lambertArray[ 30] = 0x46a771ca578ab878485810e285e31c67;
lambertArray[ 31] = 0x4615149718aed4c258c373dc676aa72d;
lambertArray[ 32] = 0x4585c8b3f8fe489c6e1833ca47871384;
lambertArray[ 33] = 0x44f972f174e41e5efb7e9d63c29ce735;
lambertArray[ 34] = 0x446ff970ba86d8b00beb05ecebf3c4dc;
lambertArray[ 35] = 0x43e9438ec88971812d6f198b5ccaad96;
lambertArray[ 36] = 0x436539d11ff7bea657aeddb394e809ef;
lambertArray[ 37] = 0x42e3c5d3e5a913401d86f66db5d81c2c;
lambertArray[ 38] = 0x4264d2395303070ea726cbe98df62174;
lambertArray[ 39] = 0x41e84a9a593bb7194c3a6349ecae4eea;
lambertArray[ 40] = 0x416e1b785d13eba07a08f3f18876a5ab;
lambertArray[ 41] = 0x40f6322ff389d423ba9dd7e7e7b7e809;
lambertArray[ 42] = 0x40807cec8a466880ecf4184545d240a4;
lambertArray[ 43] = 0x400cea9ce88a8d3ae668e8ea0d9bf07f;
lambertArray[ 44] = 0x3f9b6ae8772d4c55091e0ed7dfea0ac1;
lambertArray[ 45] = 0x3f2bee253fd84594f54bcaafac383a13;
lambertArray[ 46] = 0x3ebe654e95208bb9210c575c081c5958;
lambertArray[ 47] = 0x3e52c1fc5665635b78ce1f05ad53c086;
lambertArray[ 48] = 0x3de8f65ac388101ddf718a6f5c1eff65;
lambertArray[ 49] = 0x3d80f522d59bd0b328ca012df4cd2d49;
lambertArray[ 50] = 0x3d1ab193129ea72b23648a161163a85a;
lambertArray[ 51] = 0x3cb61f68d32576c135b95cfb53f76d75;
lambertArray[ 52] = 0x3c5332d9f1aae851a3619e77e4cc8473;
lambertArray[ 53] = 0x3bf1e08edbe2aa109e1525f65759ef73;
lambertArray[ 54] = 0x3b921d9cff13fa2c197746a3dfc4918f;
lambertArray[ 55] = 0x3b33df818910bfc1a5aefb8f63ae2ac4;
lambertArray[ 56] = 0x3ad71c1c77e34fa32a9f184967eccbf6;
lambertArray[ 57] = 0x3a7bc9abf2c5bb53e2f7384a8a16521a;
lambertArray[ 58] = 0x3a21dec7e76369783a68a0c6385a1c57;
lambertArray[ 59] = 0x39c9525de6c9cdf7c1c157ca4a7a6ee3;
lambertArray[ 60] = 0x39721bad3dc85d1240ff0190e0adaac3;
lambertArray[ 61] = 0x391c324344d3248f0469eb28dd3d77e0;
lambertArray[ 62] = 0x38c78df7e3c796279fb4ff84394ab3da;
lambertArray[ 63] = 0x387426ea4638ae9aae08049d3554c20a;
lambertArray[ 64] = 0x3821f57dbd2763256c1a99bbd2051378;
lambertArray[ 65] = 0x37d0f256cb46a8c92ff62fbbef289698;
lambertArray[ 66] = 0x37811658591ffc7abdd1feaf3cef9b73;
lambertArray[ 67] = 0x37325aa10e9e82f7df0f380f7997154b;
lambertArray[ 68] = 0x36e4b888cfb408d873b9a80d439311c6;
lambertArray[ 69] = 0x3698299e59f4bb9de645fc9b08c64cca;
lambertArray[ 70] = 0x364ca7a5012cb603023b57dd3ebfd50d;
lambertArray[ 71] = 0x36022c928915b778ab1b06aaee7e61d4;
lambertArray[ 72] = 0x35b8b28d1a73dc27500ffe35559cc028;
lambertArray[ 73] = 0x357033e951fe250ec5eb4e60955132d7;
lambertArray[ 74] = 0x3528ab2867934e3a21b5412e4c4f8881;
lambertArray[ 75] = 0x34e212f66c55057f9676c80094a61d59;
lambertArray[ 76] = 0x349c66289e5b3c4b540c24f42fa4b9bb;
lambertArray[ 77] = 0x34579fbbd0c733a9c8d6af6b0f7d00f7;
lambertArray[ 78] = 0x3413bad2e712288b924b5882b5b369bf;
lambertArray[ 79] = 0x33d0b2b56286510ef730e213f71f12e9;
lambertArray[ 80] = 0x338e82ce00e2496262c64457535ba1a1;
lambertArray[ 81] = 0x334d26a96b373bb7c2f8ea1827f27a92;
lambertArray[ 82] = 0x330c99f4f4211469e00b3e18c31475ea;
lambertArray[ 83] = 0x32ccd87d6486094999c7d5e6f33237d8;
lambertArray[ 84] = 0x328dde2dd617b6665a2e8556f250c1af;
lambertArray[ 85] = 0x324fa70e9adc270f8262755af5a99af9;
lambertArray[ 86] = 0x32122f443110611ca51040f41fa6e1e3;
lambertArray[ 87] = 0x31d5730e42c0831482f0f1485c4263d8;
lambertArray[ 88] = 0x31996ec6b07b4a83421b5ebc4ab4e1f1;
lambertArray[ 89] = 0x315e1ee0a68ff46bb43ec2b85032e876;
lambertArray[ 90] = 0x31237fe7bc4deacf6775b9efa1a145f8;
lambertArray[ 91] = 0x30e98e7f1cc5a356e44627a6972ea2ff;
lambertArray[ 92] = 0x30b04760b8917ec74205a3002650ec05;
lambertArray[ 93] = 0x3077a75c803468e9132ce0cf3224241d;
lambertArray[ 94] = 0x303fab57a6a275c36f19cda9bace667a;
lambertArray[ 95] = 0x3008504beb8dcbd2cf3bc1f6d5a064f0;
lambertArray[ 96] = 0x2fd19346ed17dac61219ce0c2c5ac4b0;
lambertArray[ 97] = 0x2f9b7169808c324b5852fd3d54ba9714;
lambertArray[ 98] = 0x2f65e7e711cf4b064eea9c08cbdad574;
lambertArray[ 99] = 0x2f30f405093042ddff8a251b6bf6d103;
lambertArray[100] = 0x2efc931a3750f2e8bfe323edfe037574;
lambertArray[101] = 0x2ec8c28e46dbe56d98685278339400cb;
lambertArray[102] = 0x2e957fd933c3926d8a599b602379b851;
lambertArray[103] = 0x2e62c882c7c9ed4473412702f08ba0e5;
lambertArray[104] = 0x2e309a221c12ba361e3ed695167feee2;
lambertArray[105] = 0x2dfef25d1f865ae18dd07cfea4bcea10;
lambertArray[106] = 0x2dcdcee821cdc80decc02c44344aeb31;
lambertArray[107] = 0x2d9d2d8562b34944d0b201bb87260c83;
lambertArray[108] = 0x2d6d0c04a5b62a2c42636308669b729a;
lambertArray[109] = 0x2d3d6842c9a235517fc5a0332691528f;
lambertArray[110] = 0x2d0e402963fe1ea2834abc408c437c10;
lambertArray[111] = 0x2cdf91ae602647908aff975e4d6a2a8c;
lambertArray[112] = 0x2cb15ad3a1eb65f6d74a75da09a1b6c5;
lambertArray[113] = 0x2c8399a6ab8e9774d6fcff373d210727;
lambertArray[114] = 0x2c564c4046f64edba6883ca06bbc4535;
lambertArray[115] = 0x2c2970c431f952641e05cb493e23eed3;
lambertArray[116] = 0x2bfd0560cd9eb14563bc7c0732856c18;
lambertArray[117] = 0x2bd1084ed0332f7ff4150f9d0ef41a2c;
lambertArray[118] = 0x2ba577d0fa1628b76d040b12a82492fb;
lambertArray[119] = 0x2b7a5233cd21581e855e89dc2f1e8a92;
lambertArray[120] = 0x2b4f95cd46904d05d72bdcde337d9cc7;
lambertArray[121] = 0x2b2540fc9b4d9abba3faca6691914675;
lambertArray[122] = 0x2afb5229f68d0830d8be8adb0a0db70f;
lambertArray[123] = 0x2ad1c7c63a9b294c5bc73a3ba3ab7a2b;
lambertArray[124] = 0x2aa8a04ac3cbe1ee1c9c86361465dbb8;
lambertArray[125] = 0x2a7fda392d725a44a2c8aeb9ab35430d;
lambertArray[126] = 0x2a57741b18cde618717792b4faa216db;
lambertArray[127] = 0x2a2f6c81f5d84dd950a35626d6d5503a;
}
/**
* @dev should be executed after construction (too large for the constructor)
*/
function init() public {
initMaxExpArray();
initLambertArray();
}
/**
* @dev given a token supply, reserve balance, weight and a deposit amount (in the reserve token),
* calculates the target amount for a given conversion (in the main token)
*
* Formula:
* return = _supply * ((1 + _amount / _reserveBalance) ^ (_reserveWeight / 1000000) - 1)
*
* @param _supply smart token supply
* @param _reserveBalance reserve balance
* @param _reserveWeight reserve weight, represented in ppm (1-1000000)
* @param _amount amount of reserve tokens to get the target amount for
*
* @return smart token amount
*/
function purchaseTargetAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256)
{
// validate input
require(_supply > 0, "ERR_INVALID_SUPPLY");
require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
require(_reserveWeight > 0 && _reserveWeight <= MAX_WEIGHT, "ERR_INVALID_RESERVE_WEIGHT");
// special case for 0 deposit amount
if (_amount == 0)
return 0;
// special case if the weight = 100%
if (_reserveWeight == MAX_WEIGHT)
return _supply.mul(_amount) / _reserveBalance;
uint256 result;
uint8 precision;
uint256 baseN = _amount.add(_reserveBalance);
(result, precision) = power(baseN, _reserveBalance, _reserveWeight, MAX_WEIGHT);
uint256 temp = _supply.mul(result) >> precision;
return temp - _supply;
}
/**
* @dev given a token supply, reserve balance, weight and a sell amount (in the main token),
* calculates the target amount for a given conversion (in the reserve token)
*
* Formula:
* return = _reserveBalance * (1 - (1 - _amount / _supply) ^ (1000000 / _reserveWeight))
*
* @param _supply smart token supply
* @param _reserveBalance reserve balance
* @param _reserveWeight reserve weight, represented in ppm (1-1000000)
* @param _amount amount of smart tokens to get the target amount for
*
* @return reserve token amount
*/
function saleTargetAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256)
{
// validate input
require(_supply > 0, "ERR_INVALID_SUPPLY");
require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
require(_reserveWeight > 0 && _reserveWeight <= MAX_WEIGHT, "ERR_INVALID_RESERVE_WEIGHT");
require(_amount <= _supply, "ERR_INVALID_AMOUNT");
// special case for 0 sell amount
if (_amount == 0)
return 0;
// special case for selling the entire supply
if (_amount == _supply)
return _reserveBalance;
// special case if the weight = 100%
if (_reserveWeight == MAX_WEIGHT)
return _reserveBalance.mul(_amount) / _supply;
uint256 result;
uint8 precision;
uint256 baseD = _supply - _amount;
(result, precision) = power(_supply, baseD, MAX_WEIGHT, _reserveWeight);
uint256 temp1 = _reserveBalance.mul(result);
uint256 temp2 = _reserveBalance << precision;
return (temp1 - temp2) / result;
}
/**
* @dev given two reserve balances/weights and a sell amount (in the first reserve token),
* calculates the target amount for a conversion from the source reserve token to the target reserve token
*
* Formula:
* return = _targetReserveBalance * (1 - (_sourceReserveBalance / (_sourceReserveBalance + _amount)) ^ (_sourceReserveWeight / _targetReserveWeight))
*
* @param _sourceReserveBalance source reserve balance
* @param _sourceReserveWeight source reserve weight, represented in ppm (1-1000000)
* @param _targetReserveBalance target reserve balance
* @param _targetReserveWeight target reserve weight, represented in ppm (1-1000000)
* @param _amount source reserve amount
*
* @return target reserve amount
*/
function crossReserveTargetAmount(uint256 _sourceReserveBalance,
uint32 _sourceReserveWeight,
uint256 _targetReserveBalance,
uint32 _targetReserveWeight,
uint256 _amount)
public view returns (uint256)
{
// validate input
require(_sourceReserveBalance > 0 && _targetReserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
require(_sourceReserveWeight > 0 && _sourceReserveWeight <= MAX_WEIGHT &&
_targetReserveWeight > 0 && _targetReserveWeight <= MAX_WEIGHT, "ERR_INVALID_RESERVE_WEIGHT");
// special case for equal weights
if (_sourceReserveWeight == _targetReserveWeight)
return _targetReserveBalance.mul(_amount) / _sourceReserveBalance.add(_amount);
uint256 result;
uint8 precision;
uint256 baseN = _sourceReserveBalance.add(_amount);
(result, precision) = power(baseN, _sourceReserveBalance, _sourceReserveWeight, _targetReserveWeight);
uint256 temp1 = _targetReserveBalance.mul(result);
uint256 temp2 = _targetReserveBalance << precision;
return (temp1 - temp2) / result;
}
/**
* @dev given a smart token supply, reserve balance, reserve ratio and an amount of requested smart tokens,
* calculates the amount of reserve tokens required for purchasing the given amount of smart tokens
*
* Formula:
* return = _reserveBalance * (((_supply + _amount) / _supply) ^ (MAX_WEIGHT / _reserveRatio) - 1)
*
* @param _supply smart token supply
* @param _reserveBalance reserve balance
* @param _reserveRatio reserve ratio, represented in ppm (2-2000000)
* @param _amount requested amount of smart tokens
*
* @return reserve token amount
*/
function fundCost(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256)
{
// validate input
require(_supply > 0, "ERR_INVALID_SUPPLY");
require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
require(_reserveRatio > 1 && _reserveRatio <= MAX_WEIGHT * 2, "ERR_INVALID_RESERVE_RATIO");
// special case for 0 amount
if (_amount == 0)
return 0;
// special case if the reserve ratio = 100%
if (_reserveRatio == MAX_WEIGHT)
return (_amount.mul(_reserveBalance) - 1) / _supply + 1;
uint256 result;
uint8 precision;
uint256 baseN = _supply.add(_amount);
(result, precision) = power(baseN, _supply, MAX_WEIGHT, _reserveRatio);
uint256 temp = ((_reserveBalance.mul(result) - 1) >> precision) + 1;
return temp - _reserveBalance;
}
/**
* @dev given a smart token supply, reserve balance, reserve ratio and an amount of reserve tokens to fund with,
* calculates the amount of smart tokens received for purchasing with the given amount of reserve tokens
*
* Formula:
* return = _supply * ((_amount / _reserveBalance + 1) ^ (_reserveRatio / MAX_WEIGHT) - 1)
*
* @param _supply smart token supply
* @param _reserveBalance reserve balance
* @param _reserveRatio reserve ratio, represented in ppm (2-2000000)
* @param _amount amount of reserve tokens to fund with
*
* @return smart token amount
*/
function fundSupplyAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256)
{
// validate input
require(_supply > 0, "ERR_INVALID_SUPPLY");
require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
require(_reserveRatio > 1 && _reserveRatio <= MAX_WEIGHT * 2, "ERR_INVALID_RESERVE_RATIO");
// special case for 0 amount
if (_amount == 0)
return 0;
// special case if the reserve ratio = 100%
if (_reserveRatio == MAX_WEIGHT)
return _amount.mul(_supply) / _reserveBalance;
uint256 result;
uint8 precision;
uint256 baseN = _reserveBalance.add(_amount);
(result, precision) = power(baseN, _reserveBalance, _reserveRatio, MAX_WEIGHT);
uint256 temp = _supply.mul(result) >> precision;
return temp - _supply;
}
/**
* @dev given a smart token supply, reserve balance, reserve ratio and an amount of smart tokens to liquidate,
* calculates the amount of reserve tokens received for selling the given amount of smart tokens
*
* Formula:
* return = _reserveBalance * (1 - ((_supply - _amount) / _supply) ^ (MAX_WEIGHT / _reserveRatio))
*
* @param _supply smart token supply
* @param _reserveBalance reserve balance
* @param _reserveRatio reserve ratio, represented in ppm (2-2000000)
* @param _amount amount of smart tokens to liquidate
*
* @return reserve token amount
*/
function liquidateReserveAmount(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256)
{
// validate input
require(_supply > 0, "ERR_INVALID_SUPPLY");
require(_reserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
require(_reserveRatio > 1 && _reserveRatio <= MAX_WEIGHT * 2, "ERR_INVALID_RESERVE_RATIO");
require(_amount <= _supply, "ERR_INVALID_AMOUNT");
// special case for 0 amount
if (_amount == 0)
return 0;
// special case for liquidating the entire supply
if (_amount == _supply)
return _reserveBalance;
// special case if the reserve ratio = 100%
if (_reserveRatio == MAX_WEIGHT)
return _amount.mul(_reserveBalance) / _supply;
uint256 result;
uint8 precision;
uint256 baseD = _supply - _amount;
(result, precision) = power(_supply, baseD, MAX_WEIGHT, _reserveRatio);
uint256 temp1 = _reserveBalance.mul(result);
uint256 temp2 = _reserveBalance << precision;
return (temp1 - temp2) / result;
}
/**
* @dev The arbitrage incentive is to convert to the point where the on-chain price is equal to the off-chain price.
* We want this operation to also impact the primary reserve balance becoming equal to the primary reserve staked balance.
* In other words, we want the arbitrager to convert the difference between the reserve balance and the reserve staked balance.
*
* Formula input:
* - let t denote the primary reserve token staked balance
* - let s denote the primary reserve token balance
* - let r denote the secondary reserve token balance
* - let q denote the numerator of the rate between the tokens
* - let p denote the denominator of the rate between the tokens
* Where p primary tokens are equal to q secondary tokens
*
* Formula output:
* - compute x = W(t / r * q / p * log(s / t)) / log(s / t)
* - return x / (1 + x) as the weight of the primary reserve token
* - return 1 / (1 + x) as the weight of the secondary reserve token
* Where W is the Lambert W Function
*
* If the rate-provider provides the rates for a common unit, for example:
* - P = 2 ==> 2 primary reserve tokens = 1 ether
* - Q = 3 ==> 3 secondary reserve tokens = 1 ether
* Then you can simply use p = P and q = Q
*
* If the rate-provider provides the rates for a single unit, for example:
* - P = 2 ==> 1 primary reserve token = 2 ethers
* - Q = 3 ==> 1 secondary reserve token = 3 ethers
* Then you can simply use p = Q and q = P
*
* @param _primaryReserveStakedBalance the primary reserve token staked balance
* @param _primaryReserveBalance the primary reserve token balance
* @param _secondaryReserveBalance the secondary reserve token balance
* @param _reserveRateNumerator the numerator of the rate between the tokens
* @param _reserveRateDenominator the denominator of the rate between the tokens
*
* Note that `numerator / denominator` should represent the amount of secondary tokens equal to one primary token
*
* @return the weight of the primary reserve token and the weight of the secondary reserve token, both in ppm (0-1000000)
*/
function balancedWeights(uint256 _primaryReserveStakedBalance,
uint256 _primaryReserveBalance,
uint256 _secondaryReserveBalance,
uint256 _reserveRateNumerator,
uint256 _reserveRateDenominator)
public view returns (uint32, uint32)
{
if (_primaryReserveStakedBalance == _primaryReserveBalance)
require(_primaryReserveStakedBalance > 0 || _secondaryReserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
else
require(_primaryReserveStakedBalance > 0 && _primaryReserveBalance > 0 && _secondaryReserveBalance > 0, "ERR_INVALID_RESERVE_BALANCE");
require(_reserveRateNumerator > 0 && _reserveRateDenominator > 0, "ERR_INVALID_RESERVE_RATE");
uint256 tq = _primaryReserveStakedBalance.mul(_reserveRateNumerator);
uint256 rp = _secondaryReserveBalance.mul(_reserveRateDenominator);
if (_primaryReserveStakedBalance < _primaryReserveBalance)
return balancedWeightsByStake(_primaryReserveBalance, _primaryReserveStakedBalance, tq, rp, true);
if (_primaryReserveStakedBalance > _primaryReserveBalance)
return balancedWeightsByStake(_primaryReserveStakedBalance, _primaryReserveBalance, tq, rp, false);
return normalizedWeights(tq, rp);
}
/**
* @dev General Description:
* Determine a value of precision.
* Calculate an integer approximation of (_baseN / _baseD) ^ (_expN / _expD) * 2 ^ precision.
* Return the result along with the precision used.
*
* Detailed Description:
* Instead of calculating "base ^ exp", we calculate "e ^ (log(base) * exp)".
* The value of "log(base)" is represented with an integer slightly smaller than "log(base) * 2 ^ precision".
* The larger "precision" is, the more accurately this value represents the real value.
* However, the larger "precision" is, the more bits are required in order to store this value.
* And the exponentiation function, which takes "x" and calculates "e ^ x", is limited to a maximum exponent (maximum value of "x").
* This maximum exponent depends on the "precision" used, and it is given by "maxExpArray[precision] >> (MAX_PRECISION - precision)".
* Hence we need to determine the highest precision which can be used for the given input, before calling the exponentiation function.
* This allows us to compute "base ^ exp" with maximum accuracy and without exceeding 256 bits in any of the intermediate computations.
* This functions assumes that "_expN < 2 ^ 256 / log(MAX_NUM - 1)", otherwise the multiplication should be replaced with a "safeMul".
* Since we rely on unsigned-integer arithmetic and "base < 1" ==> "log(base) < 0", this function does not support "_baseN < _baseD".
*/
function power(uint256 _baseN, uint256 _baseD, uint32 _expN, uint32 _expD) internal view returns (uint256, uint8) {
require(_baseN < MAX_NUM);
uint256 baseLog;
uint256 base = _baseN * FIXED_1 / _baseD;
if (base < OPT_LOG_MAX_VAL) {
baseLog = optimalLog(base);
}
else {
baseLog = generalLog(base);
}
uint256 baseLogTimesExp = baseLog * _expN / _expD;
if (baseLogTimesExp < OPT_EXP_MAX_VAL) {
return (optimalExp(baseLogTimesExp), MAX_PRECISION);
}
else {
uint8 precision = findPositionInMaxExpArray(baseLogTimesExp);
return (generalExp(baseLogTimesExp >> (MAX_PRECISION - precision), precision), precision);
}
}
/**
* @dev computes log(x / FIXED_1) * FIXED_1.
* This functions assumes that "x >= FIXED_1", because the output would be negative otherwise.
*/
function generalLog(uint256 x) internal pure returns (uint256) {
uint256 res = 0;
// If x >= 2, then we compute the integer part of log2(x), which is larger than 0.
if (x >= FIXED_2) {
uint8 count = floorLog2(x / FIXED_1);
x >>= count; // now x < 2
res = count * FIXED_1;
}
// If x > 1, then we compute the fraction part of log2(x), which is larger than 0.
if (x > FIXED_1) {
for (uint8 i = MAX_PRECISION; i > 0; --i) {
x = (x * x) / FIXED_1; // now 1 < x < 4
if (x >= FIXED_2) {
x >>= 1; // now 1 < x < 2
res += ONE << (i - 1);
}
}
}
return res * LN2_NUMERATOR / LN2_DENOMINATOR;
}
/**
* @dev computes the largest integer smaller than or equal to the binary logarithm of the input.
*/
function floorLog2(uint256 _n) internal pure returns (uint8) {
uint8 res = 0;
if (_n < 256) {
// At most 8 iterations
while (_n > 1) {
_n >>= 1;
res += 1;
}
}
else {
// Exactly 8 iterations
for (uint8 s = 128; s > 0; s >>= 1) {
if (_n >= (ONE << s)) {
_n >>= s;
res |= s;
}
}
}
return res;
}
/**
* @dev the global "maxExpArray" is sorted in descending order, and therefore the following statements are equivalent:
* - This function finds the position of [the smallest value in "maxExpArray" larger than or equal to "x"]
* - This function finds the highest position of [a value in "maxExpArray" larger than or equal to "x"]
*/
function findPositionInMaxExpArray(uint256 _x) internal view returns (uint8) {
uint8 lo = MIN_PRECISION;
uint8 hi = MAX_PRECISION;
while (lo + 1 < hi) {
uint8 mid = (lo + hi) / 2;
if (maxExpArray[mid] >= _x)
lo = mid;
else
hi = mid;
}
if (maxExpArray[hi] >= _x)
return hi;
if (maxExpArray[lo] >= _x)
return lo;
require(false);
}
/**
* @dev this function can be auto-generated by the script 'PrintFunctionGeneralExp.py'.
* it approximates "e ^ x" via maclaurin summation: "(x^0)/0! + (x^1)/1! + ... + (x^n)/n!".
* it returns "e ^ (x / 2 ^ precision) * 2 ^ precision", that is, the result is upshifted for accuracy.
* the global "maxExpArray" maps each "precision" to "((maximumExponent + 1) << (MAX_PRECISION - precision)) - 1".
* the maximum permitted value for "x" is therefore given by "maxExpArray[precision] >> (MAX_PRECISION - precision)".
*/
function generalExp(uint256 _x, uint8 _precision) internal pure returns (uint256) {
uint256 xi = _x;
uint256 res = 0;
xi = (xi * _x) >> _precision; res += xi * 0x3442c4e6074a82f1797f72ac0000000; // add x^02 * (33! / 02!)
xi = (xi * _x) >> _precision; res += xi * 0x116b96f757c380fb287fd0e40000000; // add x^03 * (33! / 03!)
xi = (xi * _x) >> _precision; res += xi * 0x045ae5bdd5f0e03eca1ff4390000000; // add x^04 * (33! / 04!)
xi = (xi * _x) >> _precision; res += xi * 0x00defabf91302cd95b9ffda50000000; // add x^05 * (33! / 05!)
xi = (xi * _x) >> _precision; res += xi * 0x002529ca9832b22439efff9b8000000; // add x^06 * (33! / 06!)
xi = (xi * _x) >> _precision; res += xi * 0x00054f1cf12bd04e516b6da88000000; // add x^07 * (33! / 07!)
xi = (xi * _x) >> _precision; res += xi * 0x0000a9e39e257a09ca2d6db51000000; // add x^08 * (33! / 08!)
xi = (xi * _x) >> _precision; res += xi * 0x000012e066e7b839fa050c309000000; // add x^09 * (33! / 09!)
xi = (xi * _x) >> _precision; res += xi * 0x000001e33d7d926c329a1ad1a800000; // add x^10 * (33! / 10!)
xi = (xi * _x) >> _precision; res += xi * 0x0000002bee513bdb4a6b19b5f800000; // add x^11 * (33! / 11!)
xi = (xi * _x) >> _precision; res += xi * 0x00000003a9316fa79b88eccf2a00000; // add x^12 * (33! / 12!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000048177ebe1fa812375200000; // add x^13 * (33! / 13!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000005263fe90242dcbacf00000; // add x^14 * (33! / 14!)
xi = (xi * _x) >> _precision; res += xi * 0x000000000057e22099c030d94100000; // add x^15 * (33! / 15!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000000057e22099c030d9410000; // add x^16 * (33! / 16!)
xi = (xi * _x) >> _precision; res += xi * 0x00000000000052b6b54569976310000; // add x^17 * (33! / 17!)
xi = (xi * _x) >> _precision; res += xi * 0x00000000000004985f67696bf748000; // add x^18 * (33! / 18!)
xi = (xi * _x) >> _precision; res += xi * 0x000000000000003dea12ea99e498000; // add x^19 * (33! / 19!)
xi = (xi * _x) >> _precision; res += xi * 0x00000000000000031880f2214b6e000; // add x^20 * (33! / 20!)
xi = (xi * _x) >> _precision; res += xi * 0x000000000000000025bcff56eb36000; // add x^21 * (33! / 21!)
xi = (xi * _x) >> _precision; res += xi * 0x000000000000000001b722e10ab1000; // add x^22 * (33! / 22!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000001317c70077000; // add x^23 * (33! / 23!)
xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000cba84aafa00; // add x^24 * (33! / 24!)
xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000082573a0a00; // add x^25 * (33! / 25!)
xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000005035ad900; // add x^26 * (33! / 26!)
xi = (xi * _x) >> _precision; res += xi * 0x000000000000000000000002f881b00; // add x^27 * (33! / 27!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000001b29340; // add x^28 * (33! / 28!)
xi = (xi * _x) >> _precision; res += xi * 0x00000000000000000000000000efc40; // add x^29 * (33! / 29!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000007fe0; // add x^30 * (33! / 30!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000420; // add x^31 * (33! / 31!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000021; // add x^32 * (33! / 32!)
xi = (xi * _x) >> _precision; res += xi * 0x0000000000000000000000000000001; // add x^33 * (33! / 33!)
return res / 0x688589cc0e9505e2f2fee5580000000 + _x + (ONE << _precision); // divide by 33! and then add x^1 / 1! + x^0 / 0!
}
/**
* @dev computes log(x / FIXED_1) * FIXED_1
* Input range: FIXED_1 <= x <= OPT_LOG_MAX_VAL - 1
* Auto-generated via 'PrintFunctionOptimalLog.py'
* Detailed description:
* - Rewrite the input as a product of natural exponents and a single residual r, such that 1 < r < 2
* - The natural logarithm of each (pre-calculated) exponent is the degree of the exponent
* - The natural logarithm of r is calculated via Taylor series for log(1 + x), where x = r - 1
* - The natural logarithm of the input is calculated by summing up the intermediate results above
* - For example: log(250) = log(e^4 * e^1 * e^0.5 * 1.021692859) = 4 + 1 + 0.5 + log(1 + 0.021692859)
*/
function optimalLog(uint256 x) internal pure returns (uint256) {
uint256 res = 0;
uint256 y;
uint256 z;
uint256 w;
if (x >= 0xd3094c70f034de4b96ff7d5b6f99fcd8) {res += 0x40000000000000000000000000000000; x = x * FIXED_1 / 0xd3094c70f034de4b96ff7d5b6f99fcd8;} // add 1 / 2^1
if (x >= 0xa45af1e1f40c333b3de1db4dd55f29a7) {res += 0x20000000000000000000000000000000; x = x * FIXED_1 / 0xa45af1e1f40c333b3de1db4dd55f29a7;} // add 1 / 2^2
if (x >= 0x910b022db7ae67ce76b441c27035c6a1) {res += 0x10000000000000000000000000000000; x = x * FIXED_1 / 0x910b022db7ae67ce76b441c27035c6a1;} // add 1 / 2^3
if (x >= 0x88415abbe9a76bead8d00cf112e4d4a8) {res += 0x08000000000000000000000000000000; x = x * FIXED_1 / 0x88415abbe9a76bead8d00cf112e4d4a8;} // add 1 / 2^4
if (x >= 0x84102b00893f64c705e841d5d4064bd3) {res += 0x04000000000000000000000000000000; x = x * FIXED_1 / 0x84102b00893f64c705e841d5d4064bd3;} // add 1 / 2^5
if (x >= 0x8204055aaef1c8bd5c3259f4822735a2) {res += 0x02000000000000000000000000000000; x = x * FIXED_1 / 0x8204055aaef1c8bd5c3259f4822735a2;} // add 1 / 2^6
if (x >= 0x810100ab00222d861931c15e39b44e99) {res += 0x01000000000000000000000000000000; x = x * FIXED_1 / 0x810100ab00222d861931c15e39b44e99;} // add 1 / 2^7
if (x >= 0x808040155aabbbe9451521693554f733) {res += 0x00800000000000000000000000000000; x = x * FIXED_1 / 0x808040155aabbbe9451521693554f733;} // add 1 / 2^8
z = y = x - FIXED_1;
w = y * y / FIXED_1;
res += z * (0x100000000000000000000000000000000 - y) / 0x100000000000000000000000000000000; z = z * w / FIXED_1; // add y^01 / 01 - y^02 / 02
res += z * (0x0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - y) / 0x200000000000000000000000000000000; z = z * w / FIXED_1; // add y^03 / 03 - y^04 / 04
res += z * (0x099999999999999999999999999999999 - y) / 0x300000000000000000000000000000000; z = z * w / FIXED_1; // add y^05 / 05 - y^06 / 06
res += z * (0x092492492492492492492492492492492 - y) / 0x400000000000000000000000000000000; z = z * w / FIXED_1; // add y^07 / 07 - y^08 / 08
res += z * (0x08e38e38e38e38e38e38e38e38e38e38e - y) / 0x500000000000000000000000000000000; z = z * w / FIXED_1; // add y^09 / 09 - y^10 / 10
res += z * (0x08ba2e8ba2e8ba2e8ba2e8ba2e8ba2e8b - y) / 0x600000000000000000000000000000000; z = z * w / FIXED_1; // add y^11 / 11 - y^12 / 12
res += z * (0x089d89d89d89d89d89d89d89d89d89d89 - y) / 0x700000000000000000000000000000000; z = z * w / FIXED_1; // add y^13 / 13 - y^14 / 14
res += z * (0x088888888888888888888888888888888 - y) / 0x800000000000000000000000000000000; // add y^15 / 15 - y^16 / 16
return res;
}
/**
* @dev computes e ^ (x / FIXED_1) * FIXED_1
* input range: 0 <= x <= OPT_EXP_MAX_VAL - 1
* auto-generated via 'PrintFunctionOptimalExp.py'
* Detailed description:
* - Rewrite the input as a sum of binary exponents and a single residual r, as small as possible
* - The exponentiation of each binary exponent is given (pre-calculated)
* - The exponentiation of r is calculated via Taylor series for e^x, where x = r
* - The exponentiation of the input is calculated by multiplying the intermediate results above
* - For example: e^5.521692859 = e^(4 + 1 + 0.5 + 0.021692859) = e^4 * e^1 * e^0.5 * e^0.021692859
*/
function optimalExp(uint256 x) internal pure returns (uint256) {
uint256 res = 0;
uint256 y;
uint256 z;
z = y = x % 0x10000000000000000000000000000000; // get the input modulo 2^(-3)
z = z * y / FIXED_1; res += z * 0x10e1b3be415a0000; // add y^02 * (20! / 02!)
z = z * y / FIXED_1; res += z * 0x05a0913f6b1e0000; // add y^03 * (20! / 03!)
z = z * y / FIXED_1; res += z * 0x0168244fdac78000; // add y^04 * (20! / 04!)
z = z * y / FIXED_1; res += z * 0x004807432bc18000; // add y^05 * (20! / 05!)
z = z * y / FIXED_1; res += z * 0x000c0135dca04000; // add y^06 * (20! / 06!)
z = z * y / FIXED_1; res += z * 0x0001b707b1cdc000; // add y^07 * (20! / 07!)
z = z * y / FIXED_1; res += z * 0x000036e0f639b800; // add y^08 * (20! / 08!)
z = z * y / FIXED_1; res += z * 0x00000618fee9f800; // add y^09 * (20! / 09!)
z = z * y / FIXED_1; res += z * 0x0000009c197dcc00; // add y^10 * (20! / 10!)
z = z * y / FIXED_1; res += z * 0x0000000e30dce400; // add y^11 * (20! / 11!)
z = z * y / FIXED_1; res += z * 0x000000012ebd1300; // add y^12 * (20! / 12!)
z = z * y / FIXED_1; res += z * 0x0000000017499f00; // add y^13 * (20! / 13!)
z = z * y / FIXED_1; res += z * 0x0000000001a9d480; // add y^14 * (20! / 14!)
z = z * y / FIXED_1; res += z * 0x00000000001c6380; // add y^15 * (20! / 15!)
z = z * y / FIXED_1; res += z * 0x000000000001c638; // add y^16 * (20! / 16!)
z = z * y / FIXED_1; res += z * 0x0000000000001ab8; // add y^17 * (20! / 17!)
z = z * y / FIXED_1; res += z * 0x000000000000017c; // add y^18 * (20! / 18!)
z = z * y / FIXED_1; res += z * 0x0000000000000014; // add y^19 * (20! / 19!)
z = z * y / FIXED_1; res += z * 0x0000000000000001; // add y^20 * (20! / 20!)
res = res / 0x21c3677c82b40000 + y + FIXED_1; // divide by 20! and then add y^1 / 1! + y^0 / 0!
if ((x & 0x010000000000000000000000000000000) != 0) res = res * 0x1c3d6a24ed82218787d624d3e5eba95f9 / 0x18ebef9eac820ae8682b9793ac6d1e776; // multiply by e^2^(-3)
if ((x & 0x020000000000000000000000000000000) != 0) res = res * 0x18ebef9eac820ae8682b9793ac6d1e778 / 0x1368b2fc6f9609fe7aceb46aa619baed4; // multiply by e^2^(-2)
if ((x & 0x040000000000000000000000000000000) != 0) res = res * 0x1368b2fc6f9609fe7aceb46aa619baed5 / 0x0bc5ab1b16779be3575bd8f0520a9f21f; // multiply by e^2^(-1)
if ((x & 0x080000000000000000000000000000000) != 0) res = res * 0x0bc5ab1b16779be3575bd8f0520a9f21e / 0x0454aaa8efe072e7f6ddbab84b40a55c9; // multiply by e^2^(+0)
if ((x & 0x100000000000000000000000000000000) != 0) res = res * 0x0454aaa8efe072e7f6ddbab84b40a55c5 / 0x00960aadc109e7a3bf4578099615711ea; // multiply by e^2^(+1)
if ((x & 0x200000000000000000000000000000000) != 0) res = res * 0x00960aadc109e7a3bf4578099615711d7 / 0x0002bf84208204f5977f9a8cf01fdce3d; // multiply by e^2^(+2)
if ((x & 0x400000000000000000000000000000000) != 0) res = res * 0x0002bf84208204f5977f9a8cf01fdc307 / 0x0000003c6ab775dd0b95b4cbee7e65d11; // multiply by e^2^(+3)
return res;
}
/**
* @dev computes W(x / FIXED_1) / (x / FIXED_1) * FIXED_1
*/
function lowerStake(uint256 _x) internal view returns (uint256) {
if (_x <= LAMBERT_CONV_RADIUS)
return lambertPos1(_x);
if (_x <= LAMBERT_POS2_MAXVAL)
return lambertPos2(_x);
if (_x <= LAMBERT_POS3_MAXVAL)
return lambertPos3(_x);
require(false);
}
/**
* @dev computes W(-x / FIXED_1) / (-x / FIXED_1) * FIXED_1
*/
function higherStake(uint256 _x) internal pure returns (uint256) {
if (_x <= LAMBERT_CONV_RADIUS)
return lambertNeg1(_x);
return FIXED_1 * FIXED_1 / _x;
}
/**
* @dev computes W(x / FIXED_1) / (x / FIXED_1) * FIXED_1
* input range: 1 <= x <= 1 / e * FIXED_1
* auto-generated via 'PrintFunctionLambertPos1.py'
*/
function lambertPos1(uint256 _x) internal pure returns (uint256) {
uint256 xi = _x;
uint256 res = (FIXED_1 - _x) * 0xde1bc4d19efcac82445da75b00000000; // x^(1-1) * (34! * 1^(1-1) / 1!) - x^(2-1) * (34! * 2^(2-1) / 2!)
xi = (xi * _x) / FIXED_1; res += xi * 0x00000000014d29a73a6e7b02c3668c7b0880000000; // add x^(03-1) * (34! * 03^(03-1) / 03!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x0000000002504a0cd9a7f7215b60f9be4800000000; // sub x^(04-1) * (34! * 04^(04-1) / 04!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000000000484d0a1191c0ead267967c7a4a0000000; // add x^(05-1) * (34! * 05^(05-1) / 05!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x00000000095ec580d7e8427a4baf26a90a00000000; // sub x^(06-1) * (34! * 06^(06-1) / 06!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000000001440b0be1615a47dba6e5b3b1f10000000; // add x^(07-1) * (34! * 07^(07-1) / 07!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x000000002d207601f46a99b4112418400000000000; // sub x^(08-1) * (34! * 08^(08-1) / 08!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000000066ebaac4c37c622dd8288a7eb1b2000000; // add x^(09-1) * (34! * 09^(09-1) / 09!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x00000000ef17240135f7dbd43a1ba10cf200000000; // sub x^(10-1) * (34! * 10^(10-1) / 10!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000000233c33c676a5eb2416094a87b3657000000; // add x^(11-1) * (34! * 11^(11-1) / 11!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x0000000541cde48bc0254bed49a9f8700000000000; // sub x^(12-1) * (34! * 12^(12-1) / 12!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000000cae1fad2cdd4d4cb8d73abca0d19a400000; // add x^(13-1) * (34! * 13^(13-1) / 13!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x0000001edb2aa2f760d15c41ceedba956400000000; // sub x^(14-1) * (34! * 14^(14-1) / 14!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000004ba8d20d2dabd386c9529659841a2e200000; // add x^(15-1) * (34! * 15^(15-1) / 15!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x000000bac08546b867cdaa20000000000000000000; // sub x^(16-1) * (34! * 16^(16-1) / 16!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000001cfa8e70c03625b9db76c8ebf5bbf24820000; // add x^(17-1) * (34! * 17^(17-1) / 17!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x000004851d99f82060df265f3309b26f8200000000; // sub x^(18-1) * (34! * 18^(18-1) / 18!)
xi = (xi * _x) / FIXED_1; res += xi * 0x00000b550d19b129d270c44f6f55f027723cbb0000; // add x^(19-1) * (34! * 19^(19-1) / 19!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x00001c877dadc761dc272deb65d4b0000000000000; // sub x^(20-1) * (34! * 20^(20-1) / 20!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000048178ece97479f33a77f2ad22a81b64406c000; // add x^(21-1) * (34! * 21^(21-1) / 21!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x0000b6ca8268b9d810fedf6695ef2f8a6c00000000; // sub x^(22-1) * (34! * 22^(22-1) / 22!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0001d0e76631a5b05d007b8cb72a7c7f11ec36e000; // add x^(23-1) * (34! * 23^(23-1) / 23!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x0004a1c37bd9f85fd9c6c780000000000000000000; // sub x^(24-1) * (34! * 24^(24-1) / 24!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000bd8369f1b702bf491e2ebfcee08250313b65400; // add x^(25-1) * (34! * 25^(25-1) / 25!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x001e5c7c32a9f6c70ab2cb59d9225764d400000000; // sub x^(26-1) * (34! * 26^(26-1) / 26!)
xi = (xi * _x) / FIXED_1; res += xi * 0x004dff5820e165e910f95120a708e742496221e600; // add x^(27-1) * (34! * 27^(27-1) / 27!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x00c8c8f66db1fced378ee50e536000000000000000; // sub x^(28-1) * (34! * 28^(28-1) / 28!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0205db8dffff45bfa2938f128f599dbf16eb11d880; // add x^(29-1) * (34! * 29^(29-1) / 29!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x053a044ebd984351493e1786af38d39a0800000000; // sub x^(30-1) * (34! * 30^(30-1) / 30!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0d86dae2a4cc0f47633a544479735869b487b59c40; // add x^(31-1) * (34! * 31^(31-1) / 31!)
xi = (xi * _x) / FIXED_1; res -= xi * 0x231000000000000000000000000000000000000000; // sub x^(32-1) * (34! * 32^(32-1) / 32!)
xi = (xi * _x) / FIXED_1; res += xi * 0x5b0485a76f6646c2039db1507cdd51b08649680822; // add x^(33-1) * (34! * 33^(33-1) / 33!)
xi = (xi * _x) / FIXED_1; res -= xi * 0xec983c46c49545bc17efa6b5b0055e242200000000; // sub x^(34-1) * (34! * 34^(34-1) / 34!)
return res / 0xde1bc4d19efcac82445da75b00000000; // divide by 34!
}
/**
* @dev computes W(x / FIXED_1) / (x / FIXED_1) * FIXED_1
* input range: LAMBERT_CONV_RADIUS + 1 <= x <= LAMBERT_POS2_MAXVAL
*/
function lambertPos2(uint256 _x) internal view returns (uint256) {
uint256 x = _x - LAMBERT_CONV_RADIUS - 1;
uint256 i = x / LAMBERT_POS2_SAMPLE;
uint256 a = LAMBERT_POS2_SAMPLE * i;
uint256 b = LAMBERT_POS2_SAMPLE * (i + 1);
uint256 c = lambertArray[i];
uint256 d = lambertArray[i + 1];
return (c * (b - x) + d * (x - a)) / LAMBERT_POS2_SAMPLE;
}
/**
* @dev computes W(x / FIXED_1) / (x / FIXED_1) * FIXED_1
* input range: LAMBERT_POS2_MAXVAL + 1 <= x <= LAMBERT_POS3_MAXVAL
*/
function lambertPos3(uint256 _x) internal pure returns (uint256) {
uint256 l1 = _x < OPT_LOG_MAX_VAL ? optimalLog(_x) : generalLog(_x);
uint256 l2 = l1 < OPT_LOG_MAX_VAL ? optimalLog(l1) : generalLog(l1);
return (l1 - l2 + l2 * FIXED_1 / l1) * FIXED_1 / _x;
}
/**
* @dev computes W(-x / FIXED_1) / (-x / FIXED_1) * FIXED_1
* input range: 1 <= x <= 1 / e * FIXED_1
* auto-generated via 'PrintFunctionLambertNeg1.py'
*/
function lambertNeg1(uint256 _x) internal pure returns (uint256) {
uint256 xi = _x;
uint256 res = 0;
xi = (xi * _x) / FIXED_1; res += xi * 0x00000000014d29a73a6e7b02c3668c7b0880000000; // add x^(03-1) * (34! * 03^(03-1) / 03!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000000002504a0cd9a7f7215b60f9be4800000000; // add x^(04-1) * (34! * 04^(04-1) / 04!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000000000484d0a1191c0ead267967c7a4a0000000; // add x^(05-1) * (34! * 05^(05-1) / 05!)
xi = (xi * _x) / FIXED_1; res += xi * 0x00000000095ec580d7e8427a4baf26a90a00000000; // add x^(06-1) * (34! * 06^(06-1) / 06!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000000001440b0be1615a47dba6e5b3b1f10000000; // add x^(07-1) * (34! * 07^(07-1) / 07!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000000002d207601f46a99b4112418400000000000; // add x^(08-1) * (34! * 08^(08-1) / 08!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000000066ebaac4c37c622dd8288a7eb1b2000000; // add x^(09-1) * (34! * 09^(09-1) / 09!)
xi = (xi * _x) / FIXED_1; res += xi * 0x00000000ef17240135f7dbd43a1ba10cf200000000; // add x^(10-1) * (34! * 10^(10-1) / 10!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000000233c33c676a5eb2416094a87b3657000000; // add x^(11-1) * (34! * 11^(11-1) / 11!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000000541cde48bc0254bed49a9f8700000000000; // add x^(12-1) * (34! * 12^(12-1) / 12!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000000cae1fad2cdd4d4cb8d73abca0d19a400000; // add x^(13-1) * (34! * 13^(13-1) / 13!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000001edb2aa2f760d15c41ceedba956400000000; // add x^(14-1) * (34! * 14^(14-1) / 14!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000004ba8d20d2dabd386c9529659841a2e200000; // add x^(15-1) * (34! * 15^(15-1) / 15!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000000bac08546b867cdaa20000000000000000000; // add x^(16-1) * (34! * 16^(16-1) / 16!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000001cfa8e70c03625b9db76c8ebf5bbf24820000; // add x^(17-1) * (34! * 17^(17-1) / 17!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000004851d99f82060df265f3309b26f8200000000; // add x^(18-1) * (34! * 18^(18-1) / 18!)
xi = (xi * _x) / FIXED_1; res += xi * 0x00000b550d19b129d270c44f6f55f027723cbb0000; // add x^(19-1) * (34! * 19^(19-1) / 19!)
xi = (xi * _x) / FIXED_1; res += xi * 0x00001c877dadc761dc272deb65d4b0000000000000; // add x^(20-1) * (34! * 20^(20-1) / 20!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000048178ece97479f33a77f2ad22a81b64406c000; // add x^(21-1) * (34! * 21^(21-1) / 21!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0000b6ca8268b9d810fedf6695ef2f8a6c00000000; // add x^(22-1) * (34! * 22^(22-1) / 22!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0001d0e76631a5b05d007b8cb72a7c7f11ec36e000; // add x^(23-1) * (34! * 23^(23-1) / 23!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0004a1c37bd9f85fd9c6c780000000000000000000; // add x^(24-1) * (34! * 24^(24-1) / 24!)
xi = (xi * _x) / FIXED_1; res += xi * 0x000bd8369f1b702bf491e2ebfcee08250313b65400; // add x^(25-1) * (34! * 25^(25-1) / 25!)
xi = (xi * _x) / FIXED_1; res += xi * 0x001e5c7c32a9f6c70ab2cb59d9225764d400000000; // add x^(26-1) * (34! * 26^(26-1) / 26!)
xi = (xi * _x) / FIXED_1; res += xi * 0x004dff5820e165e910f95120a708e742496221e600; // add x^(27-1) * (34! * 27^(27-1) / 27!)
xi = (xi * _x) / FIXED_1; res += xi * 0x00c8c8f66db1fced378ee50e536000000000000000; // add x^(28-1) * (34! * 28^(28-1) / 28!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0205db8dffff45bfa2938f128f599dbf16eb11d880; // add x^(29-1) * (34! * 29^(29-1) / 29!)
xi = (xi * _x) / FIXED_1; res += xi * 0x053a044ebd984351493e1786af38d39a0800000000; // add x^(30-1) * (34! * 30^(30-1) / 30!)
xi = (xi * _x) / FIXED_1; res += xi * 0x0d86dae2a4cc0f47633a544479735869b487b59c40; // add x^(31-1) * (34! * 31^(31-1) / 31!)
xi = (xi * _x) / FIXED_1; res += xi * 0x231000000000000000000000000000000000000000; // add x^(32-1) * (34! * 32^(32-1) / 32!)
xi = (xi * _x) / FIXED_1; res += xi * 0x5b0485a76f6646c2039db1507cdd51b08649680822; // add x^(33-1) * (34! * 33^(33-1) / 33!)
xi = (xi * _x) / FIXED_1; res += xi * 0xec983c46c49545bc17efa6b5b0055e242200000000; // add x^(34-1) * (34! * 34^(34-1) / 34!)
return res / 0xde1bc4d19efcac82445da75b00000000 + _x + FIXED_1; // divide by 34! and then add x^(2-1) * (34! * 2^(2-1) / 2!) + x^(1-1) * (34! * 1^(1-1) / 1!)
}
/**
* @dev computes the weights based on "W(log(hi / lo) * tq / rp) * tq / rp", where "W" is a variation of the Lambert W function.
*/
function balancedWeightsByStake(uint256 _hi, uint256 _lo, uint256 _tq, uint256 _rp, bool _lowerStake) internal view returns (uint32, uint32) {
(_tq, _rp) = safeFactors(_tq, _rp);
uint256 f = _hi.mul(FIXED_1) / _lo;
uint256 g = f < OPT_LOG_MAX_VAL ? optimalLog(f) : generalLog(f);
uint256 x = g.mul(_tq) / _rp;
uint256 y = _lowerStake ? lowerStake(x) : higherStake(x);
return normalizedWeights(y.mul(_tq), _rp.mul(FIXED_1));
}
/**
* @dev reduces "a" and "b" while maintaining their ratio.
*/
function safeFactors(uint256 _a, uint256 _b) internal pure returns (uint256, uint256) {
if (_a <= FIXED_2 && _b <= FIXED_2)
return (_a, _b);
if (_a < FIXED_2)
return (_a * FIXED_2 / _b, FIXED_2);
if (_b < FIXED_2)
return (FIXED_2, _b * FIXED_2 / _a);
uint256 c = _a > _b ? _a : _b;
uint256 n = floorLog2(c / FIXED_1);
return (_a >> n, _b >> n);
}
/**
* @dev computes "MAX_WEIGHT * a / (a + b)" and "MAX_WEIGHT * b / (a + b)".
*/
function normalizedWeights(uint256 _a, uint256 _b) internal pure returns (uint32, uint32) {
if (_a <= _b)
return accurateWeights(_a, _b);
(uint32 y, uint32 x) = accurateWeights(_b, _a);
return (x, y);
}
/**
* @dev computes "MAX_WEIGHT * a / (a + b)" and "MAX_WEIGHT * b / (a + b)", assuming that "a <= b".
*/
function accurateWeights(uint256 _a, uint256 _b) internal pure returns (uint32, uint32) {
if (_a > MAX_UNF_WEIGHT) {
uint256 c = _a / (MAX_UNF_WEIGHT + 1) + 1;
_a /= c;
_b /= c;
}
uint256 x = roundDiv(_a * MAX_WEIGHT, _a.add(_b));
uint256 y = MAX_WEIGHT - x;
return (uint32(x), uint32(y));
}
/**
* @dev computes the nearest integer to a given quotient without overflowing or underflowing.
*/
function roundDiv(uint256 _n, uint256 _d) internal pure returns (uint256) {
return _n / _d + _n % _d / (_d - _d / 2);
}
/**
* @dev deprecated, backward compatibility
*/
function calculatePurchaseReturn(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256)
{
return purchaseTargetAmount(_supply, _reserveBalance, _reserveWeight, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function calculateSaleReturn(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256)
{
return saleTargetAmount(_supply, _reserveBalance, _reserveWeight, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function calculateCrossReserveReturn(uint256 _sourceReserveBalance,
uint32 _sourceReserveWeight,
uint256 _targetReserveBalance,
uint32 _targetReserveWeight,
uint256 _amount)
public view returns (uint256)
{
return crossReserveTargetAmount(_sourceReserveBalance, _sourceReserveWeight, _targetReserveBalance, _targetReserveWeight, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function calculateCrossConnectorReturn(uint256 _sourceReserveBalance,
uint32 _sourceReserveWeight,
uint256 _targetReserveBalance,
uint32 _targetReserveWeight,
uint256 _amount)
public view returns (uint256)
{
return crossReserveTargetAmount(_sourceReserveBalance, _sourceReserveWeight, _targetReserveBalance, _targetReserveWeight, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function calculateFundCost(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256)
{
return fundCost(_supply, _reserveBalance, _reserveRatio, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function calculateLiquidateReturn(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256)
{
return liquidateReserveAmount(_supply, _reserveBalance, _reserveRatio, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function purchaseRate(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256)
{
return purchaseTargetAmount(_supply, _reserveBalance, _reserveWeight, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function saleRate(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveWeight,
uint256 _amount)
public view returns (uint256)
{
return saleTargetAmount(_supply, _reserveBalance, _reserveWeight, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function crossReserveRate(uint256 _sourceReserveBalance,
uint32 _sourceReserveWeight,
uint256 _targetReserveBalance,
uint32 _targetReserveWeight,
uint256 _amount)
public view returns (uint256)
{
return crossReserveTargetAmount(_sourceReserveBalance, _sourceReserveWeight, _targetReserveBalance, _targetReserveWeight, _amount);
}
/**
* @dev deprecated, backward compatibility
*/
function liquidateRate(uint256 _supply,
uint256 _reserveBalance,
uint32 _reserveRatio,
uint256 _amount)
public view returns (uint256)
{
return liquidateReserveAmount(_supply, _reserveBalance, _reserveRatio, _amount);
}
}