diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index 7b6ec1166a..61ddbe1aaa 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -21,6 +21,10 @@ { "note": "Added `MooniswapBridge`", "pr": 2675 + }, + { + "note": "Reworked `KyberBridge`", + "pr": 2683 } ] }, diff --git a/contracts/asset-proxy/contracts/src/bridges/KyberBridge.sol b/contracts/asset-proxy/contracts/src/bridges/KyberBridge.sol index 91cd9c547a..a9e311bd6a 100644 --- a/contracts/asset-proxy/contracts/src/bridges/KyberBridge.sol +++ b/contracts/asset-proxy/contracts/src/bridges/KyberBridge.sol @@ -1,6 +1,6 @@ /* - Copyright 2019 ZeroEx Intl. + Copyright 2020 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ contract KyberBridge is uint256 fromTokenBalance; uint256 payableAmount; uint256 conversionRate; + bytes hint; } /// @dev Kyber ETH pseudo-address. @@ -85,47 +86,35 @@ contract KyberBridge is state.kyber = IKyberNetworkProxy(_getKyberNetworkProxyAddress()); state.weth = IEtherToken(_getWethAddress()); // Decode the bridge data to get the `fromTokenAddress`. - (state.fromTokenAddress) = abi.decode(bridgeData, (address)); + (state.fromTokenAddress, state.hint) = abi.decode(bridgeData, (address, bytes)); // Query the balance of "from" tokens. state.fromTokenBalance = IERC20Token(state.fromTokenAddress).balanceOf(address(this)); if (state.fromTokenBalance == 0) { // Return failure if no input tokens. return BRIDGE_FAILED; } - // Compute the conversion rate, expressed in 18 decimals. - // The sequential notation is to get around stack limits. - state.conversionRate = KYBER_RATE_BASE; - state.conversionRate = state.conversionRate.safeMul(amount); - state.conversionRate = state.conversionRate.safeMul( - 10 ** uint256(LibERC20Token.decimals(state.fromTokenAddress)) - ); - state.conversionRate = state.conversionRate.safeDiv(state.fromTokenBalance); - state.conversionRate = state.conversionRate.safeDiv( - 10 ** uint256(LibERC20Token.decimals(toTokenAddress)) - ); if (state.fromTokenAddress == toTokenAddress) { // Just transfer the tokens if they're the same. LibERC20Token.transfer(state.fromTokenAddress, to, state.fromTokenBalance); return BRIDGE_SUCCESS; - } else if (state.fromTokenAddress != address(state.weth)) { - // If the input token is not WETH, grant an allowance to the exchange - // to spend them. + } + if (state.fromTokenAddress == address(state.weth)) { + // From WETH + state.fromTokenAddress = KYBER_ETH_ADDRESS; + state.payableAmount = state.fromTokenBalance; + state.weth.withdraw(state.fromTokenBalance); + } else { LibERC20Token.approveIfBelow( state.fromTokenAddress, address(state.kyber), state.fromTokenBalance ); - } else { - // If the input token is WETH, unwrap it and attach it to the call. - state.fromTokenAddress = KYBER_ETH_ADDRESS; - state.payableAmount = state.fromTokenBalance; - state.weth.withdraw(state.fromTokenBalance); } bool isToTokenWeth = toTokenAddress == address(state.weth); // Try to sell all of this contract's input token balance through // `KyberNetworkProxy.trade()`. - uint256 boughtAmount = state.kyber.trade.value(state.payableAmount)( + uint256 boughtAmount = state.kyber.tradeWithHint.value(state.payableAmount)( // Input token. state.fromTokenAddress, // Sell amount. @@ -137,11 +126,11 @@ contract KyberBridge is isToTokenWeth ? address(uint160(address(this))) : address(uint160(to)), // Buy as much as possible. uint256(-1), - // Compute the minimum conversion rate, which is expressed in units with - // 18 decimal places. - state.conversionRate, + // The minimum conversion rate + 1, // No affiliate address. - address(0) + address(0), + state.hint ); // Wrap ETH output and transfer to recipient. if (isToTokenWeth) { @@ -173,4 +162,5 @@ contract KyberBridge is { return LEGACY_WALLET_MAGIC_VALUE; } + } diff --git a/contracts/asset-proxy/contracts/src/interfaces/IKyberNetworkProxy.sol b/contracts/asset-proxy/contracts/src/interfaces/IKyberNetworkProxy.sol index b17a6f9fee..7078c28521 100644 --- a/contracts/asset-proxy/contracts/src/interfaces/IKyberNetworkProxy.sol +++ b/contracts/asset-proxy/contracts/src/interfaces/IKyberNetworkProxy.sol @@ -42,5 +42,31 @@ interface IKyberNetworkProxy { ) external payable - returns(uint256 boughtAmount); + returns (uint256 boughtAmount); + + /// @dev Sells `sellTokenAddress` tokens for `buyTokenAddress` tokens + /// using a hint for the reserve. + /// @param sellTokenAddress Token to sell. + /// @param sellAmount Amount of tokens to sell. + /// @param buyTokenAddress Token to buy. + /// @param recipientAddress Address to send bought tokens to. + /// @param maxBuyTokenAmount A limit on the amount of tokens to buy. + /// @param minConversionRate The minimal conversion rate. If actual rate + /// is lower, trade is canceled. + /// @param walletId The wallet ID to send part of the fees + /// @param hint The hint for the selective inclusion (or exclusion) of reserves + /// @return boughtAmount Amount of tokens bought. + function tradeWithHint( + address sellTokenAddress, + uint256 sellAmount, + address buyTokenAddress, + address payable recipientAddress, + uint256 maxBuyTokenAmount, + uint256 minConversionRate, + address payable walletId, + bytes calldata hint + ) + external + payable + returns (uint256 boughtAmount); } diff --git a/contracts/utils/contracts/src/DeploymentConstants.sol b/contracts/utils/contracts/src/DeploymentConstants.sol index b90eb8698d..f85cfc7dc6 100644 --- a/contracts/utils/contracts/src/DeploymentConstants.sol +++ b/contracts/utils/contracts/src/DeploymentConstants.sol @@ -1,6 +1,6 @@ /* - Copyright 2019 ZeroEx Intl. + Copyright 2020 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ contract DeploymentConstants { address constant private WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; /// @dev Mainnet address of the KyberNetworkProxy contract. address constant private KYBER_NETWORK_PROXY_ADDRESS = 0x9AAb3f75489902f3a48495025729a0AF77d4b11e; + /// @dev Mainnet address of the KyberHintHandler contract. + address constant private KYBER_HINT_HANDLER_ADDRESS = 0xa1C0Fa73c39CFBcC11ec9Eb1Afc665aba9996E2C; /// @dev Mainnet address of the `UniswapExchangeFactory` contract. address constant private UNISWAP_EXCHANGE_FACTORY_ADDRESS = 0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95; /// @dev Mainnet address of the `UniswapV2Router01` contract. @@ -155,6 +157,16 @@ contract DeploymentConstants { return KYBER_NETWORK_PROXY_ADDRESS; } + /// @dev Overridable way to get the `KyberHintHandler` address. + /// @return kyberAddress The `IKyberHintHandler` address. + function _getKyberHintHandlerAddress() + internal + view + returns (address hintHandlerAddress) + { + return KYBER_HINT_HANDLER_ADDRESS; + } + /// @dev Overridable way to get the WETH address. /// @return wethAddress The WETH address. function _getWethAddress() diff --git a/packages/asset-swapper/CHANGELOG.json b/packages/asset-swapper/CHANGELOG.json index 5908d3c6f7..88326fead8 100644 --- a/packages/asset-swapper/CHANGELOG.json +++ b/packages/asset-swapper/CHANGELOG.json @@ -73,6 +73,10 @@ { "note": "Use on-chain sampling (sometimes) for Balancer", "pr": 2647 + }, + { + "note": "Re-worked `Kyber` quotes supporting multiple reserves", + "pr": 2683 } ] }, diff --git a/packages/asset-swapper/contracts/src/KyberSampler.sol b/packages/asset-swapper/contracts/src/KyberSampler.sol index 67c6466049..17293f32ff 100644 --- a/packages/asset-swapper/contracts/src/KyberSampler.sol +++ b/packages/asset-swapper/contracts/src/KyberSampler.sol @@ -1,6 +1,6 @@ /* - Copyright 2019 ZeroEx Intl. + Copyright 2020 ZeroEx Intl. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,9 +21,6 @@ pragma experimental ABIEncoderV2; import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol"; import "./interfaces/IKyberNetwork.sol"; -import "./interfaces/IKyberNetworkProxy.sol"; -import "./interfaces/IKyberStorage.sol"; -import "./interfaces/IKyberHintHandler.sol"; import "./ApproximateBuys.sol"; import "./SamplerUtils.sol"; @@ -34,73 +31,130 @@ contract KyberSampler is ApproximateBuys { /// @dev Gas limit for Kyber calls. - uint256 constant private KYBER_CALL_GAS = 1500e3; // 1.5m - /// @dev The Kyber Uniswap Reserve address - address constant private KYBER_UNISWAP_RESERVE = 0x31E085Afd48a1d6e51Cc193153d625e8f0514C7F; - /// @dev The Kyber Uniswap V2 Reserve address - address constant private KYBER_UNISWAPV2_RESERVE = 0x10908C875D865C66f271F5d3949848971c9595C9; - /// @dev The Kyber Eth2Dai Reserve address - address constant private KYBER_ETH2DAI_RESERVE = 0x1E158c0e93c30d24e918Ef83d1e0bE23595C3c0f; + uint256 constant private KYBER_CALL_GAS = 500e3; // 500k /// @dev Sample sell quotes from Kyber. + /// @param reserveId The selected kyber reserve /// @param takerToken Address of the taker token (what to sell). /// @param makerToken Address of the maker token (what to buy). /// @param takerTokenAmounts Taker token sell amount for each sample. - /// @return makerTokenAmounts Maker amounts bought at each taker token - /// amount. + /// @return hint The hint for the selected reserve + /// @return makerTokenAmounts Maker amounts bought at each taker token amount. function sampleSellsFromKyberNetwork( + bytes32 reserveId, address takerToken, address makerToken, uint256[] memory takerTokenAmounts ) public view - returns (uint256[] memory makerTokenAmounts) + returns (bytes memory hint, uint256[] memory makerTokenAmounts) { _assertValidPair(makerToken, takerToken); uint256 numSamples = takerTokenAmounts.length; makerTokenAmounts = new uint256[](numSamples); - address wethAddress = _getWethAddress(); - uint256 value; + hint = this.encodeKyberHint(reserveId, takerToken, makerToken); for (uint256 i = 0; i < numSamples; i++) { - if (takerToken == wethAddress || makerToken == wethAddress) { - // Direct ETH based trade - value = _sampleSellFromKyberNetwork(takerToken, makerToken, takerTokenAmounts[i]); - } else { - // Hop to ETH - value = _sampleSellFromKyberNetwork(takerToken, wethAddress, takerTokenAmounts[i]); - if (value != 0) { - value = _sampleSellFromKyberNetwork(wethAddress, makerToken, value); - } + uint256 value = this.sampleSellFromKyberNetwork(hint, takerToken, makerToken, takerTokenAmounts[i]); + // Return early if the source has no liquidity + if (value == 0) { + return (hint, makerTokenAmounts); } makerTokenAmounts[i] = value; } } /// @dev Sample buy quotes from Kyber. + /// @param reserveId The selected kyber reserve /// @param takerToken Address of the taker token (what to sell). /// @param makerToken Address of the maker token (what to buy). /// @param makerTokenAmounts Maker token buy amount for each sample. - /// @return takerTokenAmounts Taker amounts sold at each maker token - /// amount. + /// @return hint The hint for the selected reserve + /// @return takerTokenAmounts Taker amounts sold at each maker token amount. function sampleBuysFromKyberNetwork( + bytes32 reserveId, address takerToken, address makerToken, uint256[] memory makerTokenAmounts ) public view - returns (uint256[] memory takerTokenAmounts) + returns (bytes memory hint, uint256[] memory takerTokenAmounts) { _assertValidPair(makerToken, takerToken); - return _sampleApproximateBuys( + hint = this.encodeKyberHint(reserveId, takerToken, makerToken); + takerTokenAmounts = _sampleApproximateBuys( ApproximateBuyQuoteOpts({ - makerTokenData: abi.encode(makerToken), - takerTokenData: abi.encode(takerToken), + makerTokenData: abi.encode(makerToken, hint), + takerTokenData: abi.encode(takerToken, hint), getSellQuoteCallback: _sampleSellForApproximateBuyFromKyber }), makerTokenAmounts ); + return (hint, takerTokenAmounts); + } + + function encodeKyberHint( + bytes32 reserveId, + address takerToken, + address makerToken + ) + public + view + returns (bytes memory hint) + { + // Build a hint selecting the single reserve + IKyberHintHandler kyberHint = IKyberHintHandler(_getKyberHintHandlerAddress()); + + // All other reserves should be ignored with this hint + bytes32[] memory selectedReserves = new bytes32[](1); + selectedReserves[0] = reserveId; + + bool didSucceed; + bytes memory resultData; + if (takerToken == _getWethAddress()) { + // ETH to Token + (didSucceed, resultData) = + address(kyberHint).staticcall.gas(KYBER_CALL_GAS)( + abi.encodeWithSelector( + IKyberHintHandler(0).buildEthToTokenHint.selector, + makerToken, + IKyberHintHandler.TradeType.MaskIn, + selectedReserves, + new uint256[](0))); + } else if (makerToken == _getWethAddress()) { + // Token to ETH + (didSucceed, resultData) = + address(kyberHint).staticcall.gas(KYBER_CALL_GAS)( + abi.encodeWithSelector( + IKyberHintHandler(0).buildTokenToEthHint.selector, + takerToken, + IKyberHintHandler.TradeType.MaskIn, + selectedReserves, + new uint256[](0))); + } else { + // Token to Token + // We use the same reserve both ways + (didSucceed, resultData) = + address(kyberHint).staticcall.gas(KYBER_CALL_GAS)( + abi.encodeWithSelector( + IKyberHintHandler(0).buildTokenToTokenHint.selector, + takerToken, + IKyberHintHandler.TradeType.MaskIn, + selectedReserves, + new uint256[](0), + makerToken, + IKyberHintHandler.TradeType.MaskIn, + selectedReserves, + new uint256[](0) + ) + ); + } + // If successful decode the hint + if (didSucceed) { + hint = abi.decode(resultData, (bytes)); + } + return hint; } function _sampleSellForApproximateBuyFromKyber( @@ -112,82 +166,40 @@ contract KyberSampler is view returns (uint256 buyAmount) { + (address makerToken, bytes memory hint) = + abi.decode(makerTokenData, (address, bytes)); + (address takerToken, ) = + abi.decode(takerTokenData, (address, bytes)); (bool success, bytes memory resultData) = address(this).staticcall(abi.encodeWithSelector( - this.sampleSellsFromKyberNetwork.selector, - abi.decode(takerTokenData, (address)), - abi.decode(makerTokenData, (address)), - _toSingleValueArray(sellAmount) + this.sampleSellFromKyberNetwork.selector, + hint, + takerToken, + makerToken, + sellAmount )); if (!success) { return 0; } // solhint-disable-next-line indent - return abi.decode(resultData, (uint256[]))[0]; + return abi.decode(resultData, (uint256)); } - function _appendToList(bytes32[] memory list, bytes32 item) private view returns (bytes32[] memory appendedList) - { - appendedList = new bytes32[](list.length + 1); - for (uint256 i = 0; i < list.length; i++) { - appendedList[i] = list[i]; - } - appendedList[appendedList.length - 1] = item; - } - - function _getKyberAddresses() - private - view - returns (IKyberHintHandler kyberHint, IKyberStorage kyberStorage) - { - (, , kyberHint, kyberStorage, ,) = IKyberNetwork( - IKyberNetworkProxy(_getKyberNetworkProxyAddress()).kyberNetwork()).getContracts(); - return (IKyberHintHandler(kyberHint), IKyberStorage(kyberStorage)); - } - - function _sampleSellFromKyberNetwork( + function sampleSellFromKyberNetwork( + bytes memory hint, address takerToken, address makerToken, uint256 takerTokenAmount ) - private + public view returns (uint256 makerTokenAmount) { - (IKyberHintHandler kyberHint, IKyberStorage kyberStorage) = _getKyberAddresses(); - // Ban reserves which can clash with our internal aggregation - bytes32[] memory reserveIds = kyberStorage.getReserveIdsPerTokenSrc( - takerToken == _getWethAddress() ? makerToken : takerToken - ); - bytes32[] memory bannedReserveIds = new bytes32[](0); - // Poor mans resize and append - for (uint256 i = 0; i < reserveIds.length; i++) { - if ( - reserveIds[i] == kyberStorage.getReserveId(KYBER_UNISWAP_RESERVE) || - reserveIds[i] == kyberStorage.getReserveId(KYBER_UNISWAPV2_RESERVE) || - reserveIds[i] == kyberStorage.getReserveId(KYBER_ETH2DAI_RESERVE) - ) { - bannedReserveIds = _appendToList(bannedReserveIds, reserveIds[i]); - } - } - // Sampler either detects X->ETH/ETH->X - // or subsamples as X->ETH-Y. So token->token here is not possible - bytes memory hint; - if (takerToken == _getWethAddress()) { - // ETH -> X - hint = kyberHint.buildEthToTokenHint( - makerToken, - IKyberHintHandler.TradeType.MaskOut, - bannedReserveIds, - new uint256[](0)); - } else { - // X->ETH - hint = kyberHint.buildEthToTokenHint( - takerToken, - IKyberHintHandler.TradeType.MaskOut, - bannedReserveIds, - new uint256[](0)); + // If there is no hint do not continue + if (hint.length == 0) { + return 0; } + (bool didSucceed, bytes memory resultData) = _getKyberNetworkProxyAddress().staticcall.gas(KYBER_CALL_GAS)( abi.encodeWithSelector( diff --git a/packages/asset-swapper/contracts/src/interfaces/IKyberHintHandler.sol b/packages/asset-swapper/contracts/src/interfaces/IKyberHintHandler.sol deleted file mode 100644 index 560c5c21c9..0000000000 --- a/packages/asset-swapper/contracts/src/interfaces/IKyberHintHandler.sol +++ /dev/null @@ -1,52 +0,0 @@ -/* - - Copyright 2020 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.5.9; - - -interface IKyberHintHandler { - - function kyberStorage() external returns (address); - - enum TradeType {BestOfAll, MaskIn, MaskOut, Split} - - function buildTokenToEthHint( - address tokenSrc, - TradeType tokenToEthType, - bytes32[] calldata tokenToEthReserveIds, - uint256[] calldata tokenToEthSplits - ) external view returns (bytes memory hint); - - function buildEthToTokenHint( - address tokenDest, - TradeType ethToTokenType, - bytes32[] calldata ethToTokenReserveIds, - uint256[] calldata ethToTokenSplits - ) external view returns (bytes memory hint); - - function buildTokenToTokenHint( - address tokenSrc, - TradeType tokenToEthType, - bytes32[] calldata tokenToEthReserveIds, - uint256[] calldata tokenToEthSplits, - address tokenDest, - TradeType ethToTokenType, - bytes32[] calldata ethToTokenReserveIds, - uint256[] calldata ethToTokenSplits - ) external view returns (bytes memory hint); -} diff --git a/packages/asset-swapper/contracts/src/interfaces/IKyberNetwork.sol b/packages/asset-swapper/contracts/src/interfaces/IKyberNetwork.sol index f3aba1cce1..ebfd45e912 100644 --- a/packages/asset-swapper/contracts/src/interfaces/IKyberNetwork.sol +++ b/packages/asset-swapper/contracts/src/interfaces/IKyberNetwork.sol @@ -18,20 +18,62 @@ pragma solidity ^0.5.9; -import "./IKyberStorage.sol"; -import "./IKyberHintHandler.sol"; - - +// Keepin everything together interface IKyberNetwork { - function getContracts() + +} + + +interface IKyberNetworkProxy { + + function getExpectedRateAfterFee( + address src, + address dest, + uint256 srcQty, + uint256 platformFeeBps, + bytes calldata hint + ) external view - returns ( - address kyberFeeHandlerAddress, - address kyberDaoAddress, - IKyberHintHandler kyberMatchingEngineAddress, - IKyberStorage kyberStorageAddress, - address gasHelperAddress, - address[] memory kyberProxyAddresses); + returns (uint256 expectedRate); +} + +interface IKyberHintHandler { + + enum TradeType {BestOfAll, MaskIn, MaskOut, Split} + + function buildTokenToEthHint( + address tokenSrc, + TradeType tokenToEthType, + bytes32[] calldata tokenToEthReserveIds, + uint256[] calldata tokenToEthSplits + ) + external + view + returns (bytes memory hint); + + function buildEthToTokenHint( + address tokenDest, + TradeType ethToTokenType, + bytes32[] calldata ethToTokenReserveIds, + uint256[] calldata ethToTokenSplits + ) + external + view + returns (bytes memory hint); + + function buildTokenToTokenHint( + address tokenSrc, + TradeType tokenToEthType, + bytes32[] calldata tokenToEthReserveIds, + uint256[] calldata tokenToEthSplits, + address tokenDest, + TradeType ethToTokenType, + bytes32[] calldata ethToTokenReserveIds, + uint256[] calldata ethToTokenSplits + ) + external + view + returns (bytes memory hint); } diff --git a/packages/asset-swapper/contracts/src/interfaces/IKyberNetworkProxy.sol b/packages/asset-swapper/contracts/src/interfaces/IKyberNetworkProxy.sol deleted file mode 100644 index 8c9c8b7e1a..0000000000 --- a/packages/asset-swapper/contracts/src/interfaces/IKyberNetworkProxy.sol +++ /dev/null @@ -1,34 +0,0 @@ -/* - - Copyright 2020 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.5.9; - - -interface IKyberNetworkProxy { - - function kyberNetwork() external view returns (address); - function kyberHintHandler() external view returns (address); - - function getExpectedRateAfterFee( - address src, - address dest, - uint256 srcQty, - uint256 platformFeeBps, - bytes calldata hint - ) external view returns (uint256 expectedRate); -} diff --git a/packages/asset-swapper/contracts/src/interfaces/IKyberStorage.sol b/packages/asset-swapper/contracts/src/interfaces/IKyberStorage.sol deleted file mode 100644 index 1ba31f0c52..0000000000 --- a/packages/asset-swapper/contracts/src/interfaces/IKyberStorage.sol +++ /dev/null @@ -1,37 +0,0 @@ -/* - - Copyright 2020 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.5.9; - - -interface IKyberStorage { - - function getReserveId( - address reserve - ) - external - view - returns (bytes32 reserveId); - - function getReserveIdsPerTokenSrc( - address token - ) - external - view - returns (bytes32[] memory reserveIds); -} diff --git a/packages/asset-swapper/contracts/test/TestERC20BridgeSampler.sol b/packages/asset-swapper/contracts/test/TestERC20BridgeSampler.sol index 2d70c54124..bf190e8707 100644 --- a/packages/asset-swapper/contracts/test/TestERC20BridgeSampler.sol +++ b/packages/asset-swapper/contracts/test/TestERC20BridgeSampler.sol @@ -23,7 +23,7 @@ import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "../src/ERC20BridgeSampler.sol"; import "../src/interfaces/IEth2Dai.sol"; -import "../src/interfaces/IKyberNetworkProxy.sol"; +import "../src/interfaces/IKyberNetwork.sol"; import "../src/interfaces/IUniswapV2Router01.sol"; @@ -253,76 +253,40 @@ contract TestERC20BridgeSamplerKyberNetwork is enum TradeType {BestOfAll, MaskIn, MaskOut, Split} - function kyberNetwork() - external - view - returns (address) - { - return address(this); - } - - // IKyberNetwork - function getContracts() - external - view - returns ( - address kyberFeeHandlerAddress, - address kyberDaoAddress, - address kyberMatchingEngineAddress, - address kyberStorageAddress, - address gasHelperAddress, - address[] memory kyberProxyAddresses - ) - { - return (kyberFeeHandlerAddress, - kyberDaoAddress, - address(this), - address(this), - gasHelperAddress, - kyberProxyAddresses - ); - } - - // IKyberStorage - function getReserveIdsPerTokenSrc( - address /* token */ - ) - external - view - returns (bytes32[] memory reserveIds) - { - return reserveIds; - } - - function getReserveId( - address /* reserve */ - ) - external - view - returns (bytes32 reserveId) - { - return reserveId; - } - // IKyberHintHandler function buildTokenToEthHint( - address /* tokenSrc */, + address tokenSrc, TradeType /* tokenToEthType */, bytes32[] calldata /* tokenToEthReserveIds */, uint256[] calldata /* tokenToEthSplits */ ) external view returns (bytes memory hint) { - return hint; + return abi.encode(tokenSrc); } function buildEthToTokenHint( - address /* tokenDest */, + address tokenDest, TradeType /* ethToTokenType */, bytes32[] calldata /* ethToTokenReserveIds */, uint256[] calldata /* ethToTokenSplits */ ) external view returns (bytes memory hint) { - return hint; + return abi.encode(tokenDest); + } + + // IKyberHintHandler + function buildTokenToTokenHint( + address tokenSrc, + TradeType /* tokenToEthType */, + bytes32[] calldata /* tokenToEthReserveIds */, + uint256[] calldata /* tokenToEthSplits */, + address /* tokenDest */, + TradeType /* EthToTokenType */, + bytes32[] calldata /* EthToTokenReserveIds */, + uint256[] calldata /* EthToTokenSplits */ + ) external view returns (bytes memory hint) + { + return abi.encode(tokenSrc); } // Deterministic `IKyberNetworkProxy.getExpectedRateAfterFee()`. @@ -375,6 +339,14 @@ contract TestERC20BridgeSamplerKyberNetwork is { return address(this); } + + function _getKyberHintHandlerAddress() + internal + view + returns (address) + { + return address(this); + } } @@ -533,4 +505,13 @@ contract TestERC20BridgeSampler is { return address(kyber); } + + // Overriden to point to a custom contract. + function _getKyberHintHandlerAddress() + internal + view + returns (address kyberAddress) + { + return address(kyber); + } } diff --git a/packages/asset-swapper/package.json b/packages/asset-swapper/package.json index 2568bc65df..c2826d5ecf 100644 --- a/packages/asset-swapper/package.json +++ b/packages/asset-swapper/package.json @@ -38,7 +38,7 @@ "config": { "publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json", + "abis": "./test/generated-artifacts/@(ApproximateBuys|BalancerSampler|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|IBalancer|ICurve|IEth2Dai|IKyberNetwork|ILiquidityProvider|ILiquidityProviderRegistry|IMStable|IMooniswap|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MStableSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler).json", "postpublish": { "assets": [] } diff --git a/packages/asset-swapper/src/index.ts b/packages/asset-swapper/src/index.ts index 0d5701c4c2..64d4b32e4e 100644 --- a/packages/asset-swapper/src/index.ts +++ b/packages/asset-swapper/src/index.ts @@ -122,8 +122,8 @@ export { SamplerContractOperation, } from './utils/market_operation_utils/sampler_contract_operation'; export { - BancorFillData, BalancerFillData, + BancorFillData, CollapsedFill, CurveFillData, CurveFunctionSelectors, @@ -135,6 +135,7 @@ export { FillData, FillFlags, GetMarketOrdersRfqtOpts, + KyberFillData, LiquidityProviderFillData, MarketDepth, MarketDepthSide, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts index a8ca2c50a7..aef4844709 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/constants.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/constants.ts @@ -132,6 +132,42 @@ export const MAINNET_CURVE_INFOS: { [name: string]: CurveInfo } = { }, }; +export const MAINNET_KYBER_RESERVE_IDS: { [name: string]: string } = { + Reserve1: '0xff4b796265722046707200000000000000000000000000000000000000000000', + Reserve2: '0xffabcd0000000000000000000000000000000000000000000000000000000000', + Reserve3: '0xff4f6e65426974205175616e7400000000000000000000000000000000000000', +}; + +export const MAINNET_KYBER_TOKEN_RESERVE_IDS: { [token: string]: string } = { + // USDC + ['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']: + '0xaa55534443303041505200000000000000000000000000000000000000000000', + // AMPL + ['0xd46ba6d942050d489dbd938a2c909a5d5039a161']: + '0xaad46ba6d942050d489dbd938a2c909a5d5039a1610000000000000000000000', + // UBT + ['0x8400d94a5cb0fa0d041a3788e395285d61c9ee5e']: + '0xaa55425400000000000000000000000000000000000000000000000000000000', + // ANT + ['0x960b236a07cf122663c4303350609a66a7b288c0']: + '0xaa414e5400000000000000000000000000000000000000000000000000000000', + // KNC + ['0xdd974d5c2e2928dea5f71b9825b8b646686bd200']: + '0xaa4b4e435f4d4547414c41444f4e000000000000000000000000000000000000', + // sUSD + ['0x57ab1ec28d129707052df4df418d58a2d46d5f51']: + '0xaa73555344000000000000000000000000000000000000000000000000000000', + // SNX + ['0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f']: + '0xaa534e5800000000000000000000000000000000000000000000000000000000', + // REN + ['0x408e41876cccdc0f92210600ef50372656052a38']: + '0xaa72656e00000000000000000000000000000000000000000000000000000000', + // BAND + ['0xba11d00c5f74255f56a5e366f4f77f5a186d7f55']: + '0xaa42414e44000000000000000000000000000000000000000000000000000000', +}; + export const ERC20_PROXY_ID = '0xf47261b0'; export const WALLET_SIGNATURE = '0x04'; export const ONE_ETHER = new BigNumber(1e18); diff --git a/packages/asset-swapper/src/utils/market_operation_utils/kyber_utils.ts b/packages/asset-swapper/src/utils/market_operation_utils/kyber_utils.ts new file mode 100644 index 0000000000..1e09fad24d --- /dev/null +++ b/packages/asset-swapper/src/utils/market_operation_utils/kyber_utils.ts @@ -0,0 +1,10 @@ +import { MAINNET_KYBER_RESERVE_IDS, MAINNET_KYBER_TOKEN_RESERVE_IDS } from './constants'; + +// tslint:disable completed-docs +export function getKyberReserveIdsForPair(takerToken: string, makerToken: string): string[] { + return [ + ...Object.values(MAINNET_KYBER_RESERVE_IDS), + MAINNET_KYBER_TOKEN_RESERVE_IDS[makerToken.toLowerCase()], + MAINNET_KYBER_TOKEN_RESERVE_IDS[takerToken.toLowerCase()], + ].filter(t => t); +} diff --git a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts index 62304e91f7..828c69016b 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/orders.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/orders.ts @@ -27,6 +27,7 @@ import { DexSample, ERC20BridgeSource, Fill, + KyberFillData, LiquidityProviderFillData, MultiBridgeFillData, MultiHopFillData, @@ -295,6 +296,14 @@ function createBridgeOrder( createMultiBridgeData(takerToken, makerToken), ); break; + case ERC20BridgeSource.Kyber: + const kyberFillData = (fill as CollapsedFill).fillData!; // tslint:disable-line:no-non-null-assertion + makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( + makerToken, + bridgeAddress, + createKyberBridgeData(takerToken, kyberFillData.hint), + ); + break; default: makerAssetData = assetDataUtils.encodeERC20BridgeAssetData( makerToken, @@ -393,6 +402,11 @@ function createBancorBridgeData(path: string[], networkAddress: string): string return encoder.encode({ path, networkAddress }); } +function createKyberBridgeData(fromTokenAddress: string, hint: string): string { + const encoder = AbiEncoder.create([{ name: 'fromTokenAddress', type: 'address' }, { name: 'hint', type: 'bytes' }]); + return encoder.encode({ fromTokenAddress, hint }); +} + function createCurveBridgeData( curveAddress: string, exchangeFunctionSelector: string, diff --git a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts index e2311c8e11..e06e31a627 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/sampler_operations.ts @@ -8,6 +8,7 @@ import { BalancerPoolsCache, computeBalancerBuyQuote, computeBalancerSellQuote } import { BancorService } from './bancor_service'; import { MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } from './constants'; import { getCurveInfosForPair } from './curve_utils'; +import { getKyberReserveIdsForPair } from './kyber_utils'; import { getMultiBridgeIntermediateToken } from './multibridge_utils'; import { getIntermediateTokens } from './multihop_utils'; import { SamplerContractOperation } from './sampler_contract_operation'; @@ -20,6 +21,7 @@ import { DexSample, ERC20BridgeSource, HopInfo, + KyberFillData, LiquidityProviderFillData, MultiBridgeFillData, MultiHopFillData, @@ -71,6 +73,7 @@ export class SamplerOperations { } public getKyberSellQuotes( + reserveId: string, makerToken: string, takerToken: string, takerFillAmounts: BigNumber[], @@ -79,11 +82,21 @@ export class SamplerOperations { source: ERC20BridgeSource.Kyber, contract: this._samplerContract, function: this._samplerContract.sampleSellsFromKyberNetwork, - params: [takerToken, makerToken, takerFillAmounts], + params: [reserveId, takerToken, makerToken, takerFillAmounts], + callback: (callResults: string, fillData: KyberFillData): BigNumber[] => { + const [hint, samples] = this._samplerContract.getABIDecodedReturnData<[string, BigNumber[]]>( + 'sampleSellsFromKyberNetwork', + callResults, + ); + fillData.hint = hint; + fillData.reserveId = reserveId; + return samples; + }, }); } public getKyberBuyQuotes( + reserveId: string, makerToken: string, takerToken: string, makerFillAmounts: BigNumber[], @@ -92,7 +105,16 @@ export class SamplerOperations { source: ERC20BridgeSource.Kyber, contract: this._samplerContract, function: this._samplerContract.sampleBuysFromKyberNetwork, - params: [takerToken, makerToken, makerFillAmounts], + params: [reserveId, takerToken, makerToken, makerFillAmounts], + callback: (callResults: string, fillData: KyberFillData): BigNumber[] => { + const [hint, samples] = this._samplerContract.getABIDecodedReturnData<[string, BigNumber[]]>( + 'sampleBuysFromKyberNetwork', + callResults, + ); + fillData.hint = hint; + fillData.reserveId = reserveId; + return samples; + }, }); } @@ -746,7 +768,9 @@ export class SamplerOperations { } return ops; case ERC20BridgeSource.Kyber: - return this.getKyberSellQuotes(makerToken, takerToken, takerFillAmounts); + return getKyberReserveIdsForPair(takerToken, makerToken).map(reserveId => + this.getKyberSellQuotes(reserveId, makerToken, takerToken, takerFillAmounts), + ); case ERC20BridgeSource.Curve: return getCurveInfosForPair(takerToken, makerToken).map(curve => this.getCurveSellQuotes( @@ -825,7 +849,9 @@ export class SamplerOperations { } return ops; case ERC20BridgeSource.Kyber: - return this.getKyberBuyQuotes(makerToken, takerToken, makerFillAmounts); + return getKyberReserveIdsForPair(takerToken, makerToken).map(reserveId => + this.getKyberBuyQuotes(reserveId, makerToken, takerToken, makerFillAmounts), + ); case ERC20BridgeSource.Curve: return getCurveInfosForPair(takerToken, makerToken).map(curve => this.getCurveBuyQuotes( diff --git a/packages/asset-swapper/src/utils/market_operation_utils/types.ts b/packages/asset-swapper/src/utils/market_operation_utils/types.ts index 0f98dbd704..f59932d8b7 100644 --- a/packages/asset-swapper/src/utils/market_operation_utils/types.ts +++ b/packages/asset-swapper/src/utils/market_operation_utils/types.ts @@ -108,6 +108,11 @@ export interface BancorFillData extends FillData { networkAddress: string; } +export interface KyberFillData extends FillData { + hint: string; + reserveId: string; +} + export interface Quote { amount: BigNumber; fillData?: TFillData; diff --git a/packages/asset-swapper/test/artifacts.ts b/packages/asset-swapper/test/artifacts.ts index a00a3aaa77..c26f4db846 100644 --- a/packages/asset-swapper/test/artifacts.ts +++ b/packages/asset-swapper/test/artifacts.ts @@ -15,10 +15,7 @@ import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json import * as IBalancer from '../test/generated-artifacts/IBalancer.json'; import * as ICurve from '../test/generated-artifacts/ICurve.json'; import * as IEth2Dai from '../test/generated-artifacts/IEth2Dai.json'; -import * as IKyberHintHandler from '../test/generated-artifacts/IKyberHintHandler.json'; import * as IKyberNetwork from '../test/generated-artifacts/IKyberNetwork.json'; -import * as IKyberNetworkProxy from '../test/generated-artifacts/IKyberNetworkProxy.json'; -import * as IKyberStorage from '../test/generated-artifacts/IKyberStorage.json'; import * as ILiquidityProvider from '../test/generated-artifacts/ILiquidityProvider.json'; import * as ILiquidityProviderRegistry from '../test/generated-artifacts/ILiquidityProviderRegistry.json'; import * as IMooniswap from '../test/generated-artifacts/IMooniswap.json'; @@ -58,10 +55,7 @@ export const artifacts = { IBalancer: IBalancer as ContractArtifact, ICurve: ICurve as ContractArtifact, IEth2Dai: IEth2Dai as ContractArtifact, - IKyberHintHandler: IKyberHintHandler as ContractArtifact, IKyberNetwork: IKyberNetwork as ContractArtifact, - IKyberNetworkProxy: IKyberNetworkProxy as ContractArtifact, - IKyberStorage: IKyberStorage as ContractArtifact, ILiquidityProvider: ILiquidityProvider as ContractArtifact, ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact, IMStable: IMStable as ContractArtifact, diff --git a/packages/asset-swapper/test/contracts/bridge_sampler_mainnet_test.ts b/packages/asset-swapper/test/contracts/bridge_sampler_mainnet_test.ts index 0b394793dd..bddb3fd922 100644 --- a/packages/asset-swapper/test/contracts/bridge_sampler_mainnet_test.ts +++ b/packages/asset-swapper/test/contracts/bridge_sampler_mainnet_test.ts @@ -81,24 +81,25 @@ blockchainTests.skip('Mainnet Sampler Tests', env => { const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; const DAI = '0x6b175474e89094c44da98b954eedeac495271d0f'; const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; + const RESERVE_ID = '0xff4b796265722046707200000000000000000000000000000000000000000000'; describe('sampleSellsFromKyberNetwork()', () => { it('samples sells from Kyber DAI->WETH', async () => { - const samples = await testContract - .sampleSellsFromKyberNetwork(DAI, WETH, [toBaseUnitAmount(1)]) + const [, samples] = await testContract + .sampleSellsFromKyberNetwork(RESERVE_ID, DAI, WETH, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); }); it('samples sells from Kyber WETH->DAI', async () => { - const samples = await testContract - .sampleSellsFromKyberNetwork(WETH, DAI, [toBaseUnitAmount(1)]) + const [, samples] = await testContract + .sampleSellsFromKyberNetwork(RESERVE_ID, WETH, DAI, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); }); it('samples sells from Kyber DAI->USDC', async () => { - const samples = await testContract - .sampleSellsFromKyberNetwork(DAI, USDC, [toBaseUnitAmount(1)]) + const [, samples] = await testContract + .sampleSellsFromKyberNetwork(RESERVE_ID, DAI, USDC, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); @@ -109,8 +110,8 @@ blockchainTests.skip('Mainnet Sampler Tests', env => { it('samples buys from Kyber WETH->DAI', async () => { // From ETH to DAI // I want to buy 1 DAI - const samples = await testContract - .sampleBuysFromKyberNetwork(WETH, DAI, [toBaseUnitAmount(1)]) + const [, samples] = await testContract + .sampleBuysFromKyberNetwork(RESERVE_ID, WETH, DAI, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); @@ -119,8 +120,8 @@ blockchainTests.skip('Mainnet Sampler Tests', env => { it('samples buys from Kyber DAI->WETH', async () => { // From USDC to DAI // I want to buy 1 WETH - const samples = await testContract - .sampleBuysFromKyberNetwork(DAI, WETH, [toBaseUnitAmount(1)]) + const [, samples] = await testContract + .sampleBuysFromKyberNetwork(RESERVE_ID, DAI, WETH, [toBaseUnitAmount(1)]) .callAsync({ overrides }); expect(samples.length).to.be.bignumber.greaterThan(0); expect(samples[0]).to.be.bignumber.greaterThan(0); diff --git a/packages/asset-swapper/test/contracts/erc20_bridge_sampler_test.ts b/packages/asset-swapper/test/contracts/erc20_bridge_sampler_test.ts index 535b86b213..ce167bcf1c 100644 --- a/packages/asset-swapper/test/contracts/erc20_bridge_sampler_test.ts +++ b/packages/asset-swapper/test/contracts/erc20_bridge_sampler_test.ts @@ -38,6 +38,7 @@ blockchainTests('erc20-bridge-sampler', env => { const MAKER_TOKEN = randomAddress(); const TAKER_TOKEN = randomAddress(); const INTERMEDIATE_TOKEN = randomAddress(); + const KYBER_RESERVE_ID = '0x'; before(async () => { testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync( @@ -330,31 +331,25 @@ blockchainTests('erc20-bridge-sampler', env => { }); it('throws if tokens are the same', async () => { - const tx = testContract.sampleSellsFromKyberNetwork(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + const tx = testContract + .sampleSellsFromKyberNetwork(KYBER_RESERVE_ID, MAKER_TOKEN, MAKER_TOKEN, []) + .callAsync(); return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); }); it('can return no quotes', async () => { - const quotes = await testContract.sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); - expect(quotes).to.deep.eq([]); - }); - - it('can quote token -> token', async () => { - const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [takerToEthQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); - const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, MAKER_TOKEN, ['Kyber'], takerToEthQuotes); - const quotes = await testContract - .sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + const [, quotes] = await testContract + .sampleSellsFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, MAKER_TOKEN, []) .callAsync(); - expect(quotes).to.deep.eq(expectedQuotes); + expect(quotes).to.deep.eq([]); }); it('returns zero if token -> token fails', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); - const quotes = await testContract - .sampleSellsFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + const [, quotes] = await testContract + .sampleSellsFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -362,8 +357,17 @@ blockchainTests('erc20-bridge-sampler', env => { it('can quote token -> ETH', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); - const quotes = await testContract - .sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + const [, quotes] = await testContract + .sampleSellsFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + .callAsync(); + expect(quotes).to.deep.eq(expectedQuotes); + }); + + it('can quote token -> token', async () => { + const sampleAmounts = getSampleAmounts(TAKER_TOKEN); + const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); + const [, quotes] = await testContract + .sampleSellsFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -372,8 +376,8 @@ blockchainTests('erc20-bridge-sampler', env => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); - const quotes = await testContract - .sampleSellsFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + const [, quotes] = await testContract + .sampleSellsFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -381,8 +385,8 @@ blockchainTests('erc20-bridge-sampler', env => { it('can quote ETH -> token', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicSellQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); - const quotes = await testContract - .sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + const [, quotes] = await testContract + .sampleSellsFromKyberNetwork(KYBER_RESERVE_ID, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -391,8 +395,8 @@ blockchainTests('erc20-bridge-sampler', env => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); - const quotes = await testContract - .sampleSellsFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + const [, quotes] = await testContract + .sampleSellsFromKyberNetwork(KYBER_RESERVE_ID, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -405,21 +409,24 @@ blockchainTests('erc20-bridge-sampler', env => { }); it('throws if tokens are the same', async () => { - const tx = testContract.sampleBuysFromKyberNetwork(MAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + const tx = testContract + .sampleBuysFromKyberNetwork(KYBER_RESERVE_ID, MAKER_TOKEN, MAKER_TOKEN, []) + .callAsync(); return expect(tx).to.revertWith(INVALID_TOKEN_PAIR_ERROR); }); it('can return no quotes', async () => { - const quotes = await testContract.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, []).callAsync(); + const [, quotes] = await testContract + .sampleBuysFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, MAKER_TOKEN, []) + .callAsync(); expect(quotes).to.deep.eq([]); }); it('can quote token -> token', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); - const [ethToMakerQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, MAKER_TOKEN, ['Kyber'], sampleAmounts); - const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], ethToMakerQuotes); - const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts); + const [, quotes] = await testContract + .sampleBuysFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); }); @@ -428,8 +435,8 @@ blockchainTests('erc20-bridge-sampler', env => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); - const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) + const [, quotes] = await testContract + .sampleBuysFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, MAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -437,8 +444,8 @@ blockchainTests('erc20-bridge-sampler', env => { it('can quote token -> ETH', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts); - const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + const [, quotes] = await testContract + .sampleBuysFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); }); @@ -447,8 +454,8 @@ blockchainTests('erc20-bridge-sampler', env => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); - const quotes = await testContract - .sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) + const [, quotes] = await testContract + .sampleBuysFromKyberNetwork(KYBER_RESERVE_ID, TAKER_TOKEN, WETH_ADDRESS, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); @@ -456,8 +463,8 @@ blockchainTests('erc20-bridge-sampler', env => { it('can quote ETH -> token', async () => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts); - const quotes = await testContract - .sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + const [, quotes] = await testContract + .sampleBuysFromKyberNetwork(KYBER_RESERVE_ID, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE); }); @@ -466,8 +473,8 @@ blockchainTests('erc20-bridge-sampler', env => { const sampleAmounts = getSampleAmounts(TAKER_TOKEN); const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT); await enableFailTriggerAsync(); - const quotes = await testContract - .sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) + const [, quotes] = await testContract + .sampleBuysFromKyberNetwork(KYBER_RESERVE_ID, WETH_ADDRESS, TAKER_TOKEN, sampleAmounts) .callAsync(); expect(quotes).to.deep.eq(expectedQuotes); }); diff --git a/packages/asset-swapper/test/dex_sampler_test.ts b/packages/asset-swapper/test/dex_sampler_test.ts index 7aecbf8232..3fb83b18d6 100644 --- a/packages/asset-swapper/test/dex_sampler_test.ts +++ b/packages/asset-swapper/test/dex_sampler_test.ts @@ -135,16 +135,21 @@ describe('DexSampler tests', () => { const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10); const expectedMakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 10); const sampler = new MockSamplerContract({ - sampleSellsFromKyberNetwork: (takerToken, makerToken, fillAmounts) => { + sampleSellsFromKyberNetwork: (_reserveId, takerToken, makerToken, fillAmounts) => { expect(takerToken).to.eq(expectedTakerToken); expect(makerToken).to.eq(expectedMakerToken); expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); - return expectedMakerFillAmounts; + return ['0x', expectedMakerFillAmounts]; }, }); const dexOrderSampler = new DexOrderSampler(sampler); const [fillableAmounts] = await dexOrderSampler.executeAsync( - dexOrderSampler.getKyberSellQuotes(expectedMakerToken, expectedTakerToken, expectedTakerFillAmounts), + dexOrderSampler.getKyberSellQuotes( + '0x', + expectedMakerToken, + expectedTakerToken, + expectedTakerFillAmounts, + ), ); expect(fillableAmounts).to.deep.eq(expectedMakerFillAmounts); }); @@ -373,12 +378,7 @@ describe('DexSampler tests', () => { it('getSellQuotes()', async () => { const expectedTakerToken = randomAddress(); const expectedMakerToken = randomAddress(); - const sources = [ - ERC20BridgeSource.Kyber, - ERC20BridgeSource.Eth2Dai, - ERC20BridgeSource.Uniswap, - ERC20BridgeSource.UniswapV2, - ]; + const sources = [ERC20BridgeSource.Eth2Dai, ERC20BridgeSource.Uniswap, ERC20BridgeSource.UniswapV2]; const ratesBySource: RatesBySource = { [ERC20BridgeSource.Kyber]: getRandomFloat(0, 100), [ERC20BridgeSource.Eth2Dai]: getRandomFloat(0, 100), @@ -387,12 +387,6 @@ describe('DexSampler tests', () => { }; const expectedTakerFillAmounts = getSampleAmounts(new BigNumber(100e18), 3); const sampler = new MockSamplerContract({ - sampleSellsFromKyberNetwork: (takerToken, makerToken, fillAmounts) => { - expect(takerToken).to.eq(expectedTakerToken); - expect(makerToken).to.eq(expectedMakerToken); - expect(fillAmounts).to.deep.eq(expectedTakerFillAmounts); - return fillAmounts.map(a => a.times(ratesBySource[ERC20BridgeSource.Kyber]).integerValue()); - }, sampleSellsFromUniswap: (takerToken, makerToken, fillAmounts) => { expect(takerToken).to.eq(expectedTakerToken); expect(makerToken).to.eq(expectedMakerToken); @@ -449,7 +443,8 @@ describe('DexSampler tests', () => { })), ]; // extra quote for Uniswap V2, which provides a direct quote (tokenA -> tokenB) AND an ETH quote (tokenA -> ETH -> tokenB) - expect(quotes).to.have.lengthOf(sources.length + 1); + const additionalSourceCount = 1; + expect(quotes).to.have.lengthOf(sources.length + additionalSourceCount); expect(quotes).to.deep.eq(expectedQuotes.concat(uniswapV2ETHQuotes)); }); it('getSellQuotes() uses samples from Balancer', async () => { diff --git a/packages/asset-swapper/test/market_operation_utils_test.ts b/packages/asset-swapper/test/market_operation_utils_test.ts index 610c1b8562..c2a9c9da2c 100644 --- a/packages/asset-swapper/test/market_operation_utils_test.ts +++ b/packages/asset-swapper/test/market_operation_utils_test.ts @@ -261,6 +261,7 @@ describe('MarketOperationUtils tests', () => { [ERC20BridgeSource.UniswapV2]: { tokenAddressPath: [] }, [ERC20BridgeSource.Balancer]: { poolAddress: randomAddress() }, [ERC20BridgeSource.Bancor]: { path: [], networkAddress: randomAddress() }, + [ERC20BridgeSource.Kyber]: { hint: '0x', reserveId: '0x' }, [ERC20BridgeSource.Curve]: { curve: { poolAddress: randomAddress(), diff --git a/packages/asset-swapper/test/utils/mock_sampler_contract.ts b/packages/asset-swapper/test/utils/mock_sampler_contract.ts index 6c51833821..558f17a123 100644 --- a/packages/asset-swapper/test/utils/mock_sampler_contract.ts +++ b/packages/asset-swapper/test/utils/mock_sampler_contract.ts @@ -23,6 +23,18 @@ export type SampleBuysHandler = ( makerToken: string, makerTokenAmounts: BigNumber[], ) => SampleResults; +export type SampleSellsKyberHandler = ( + reserveId: string, + takerToken: string, + makerToken: string, + takerTokenAmounts: BigNumber[], +) => [string, SampleResults]; +export type SampleBuysKyberHandler = ( + reserveId: string, + takerToken: string, + makerToken: string, + makerTokenAmounts: BigNumber[], +) => [string, SampleResults]; export type SampleBuysMultihopHandler = (path: string[], takerTokenAmounts: BigNumber[]) => SampleResults; export type SampleSellsLPHandler = ( registryAddress: string, @@ -48,7 +60,7 @@ const DUMMY_PROVIDER = { interface Handlers { getOrderFillableMakerAssetAmounts: GetOrderFillableAssetAmountHandler; getOrderFillableTakerAssetAmounts: GetOrderFillableAssetAmountHandler; - sampleSellsFromKyberNetwork: SampleSellsHandler; + sampleSellsFromKyberNetwork: SampleSellsKyberHandler; sampleSellsFromLiquidityProviderRegistry: SampleSellsLPHandler; sampleSellsFromMultiBridge: SampleSellsMBHandler; sampleSellsFromEth2Dai: SampleSellsHandler; @@ -104,13 +116,15 @@ export class MockSamplerContract extends ERC20BridgeSamplerContract { } public sampleSellsFromKyberNetwork( + reserveId: string, takerToken: string, makerToken: string, takerAssetAmounts: BigNumber[], - ): ContractFunctionObj { + ): ContractFunctionObj<[string, BigNumber[]]> { return this._wrapCall( super.sampleSellsFromKyberNetwork, this._handlers.sampleSellsFromKyberNetwork, + reserveId, takerToken, makerToken, takerAssetAmounts, diff --git a/packages/asset-swapper/test/wrappers.ts b/packages/asset-swapper/test/wrappers.ts index 30ea705ee3..9206cd21fd 100644 --- a/packages/asset-swapper/test/wrappers.ts +++ b/packages/asset-swapper/test/wrappers.ts @@ -13,10 +13,7 @@ export * from '../test/generated-wrappers/eth2_dai_sampler'; export * from '../test/generated-wrappers/i_balancer'; export * from '../test/generated-wrappers/i_curve'; export * from '../test/generated-wrappers/i_eth2_dai'; -export * from '../test/generated-wrappers/i_kyber_hint_handler'; export * from '../test/generated-wrappers/i_kyber_network'; -export * from '../test/generated-wrappers/i_kyber_network_proxy'; -export * from '../test/generated-wrappers/i_kyber_storage'; export * from '../test/generated-wrappers/i_liquidity_provider'; export * from '../test/generated-wrappers/i_liquidity_provider_registry'; export * from '../test/generated-wrappers/i_m_stable'; diff --git a/packages/asset-swapper/tsconfig.json b/packages/asset-swapper/tsconfig.json index 768accac03..5c7b1bccea 100644 --- a/packages/asset-swapper/tsconfig.json +++ b/packages/asset-swapper/tsconfig.json @@ -18,10 +18,7 @@ "test/generated-artifacts/IBalancer.json", "test/generated-artifacts/ICurve.json", "test/generated-artifacts/IEth2Dai.json", - "test/generated-artifacts/IKyberHintHandler.json", "test/generated-artifacts/IKyberNetwork.json", - "test/generated-artifacts/IKyberNetworkProxy.json", - "test/generated-artifacts/IKyberStorage.json", "test/generated-artifacts/ILiquidityProvider.json", "test/generated-artifacts/ILiquidityProviderRegistry.json", "test/generated-artifacts/IMStable.json", diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index c0f07134b5..f2cd6dc01b 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -37,6 +37,10 @@ { "note": "Redeploy `MooniswapBridge` on Mainnet", "pr": 2681 + }, + { + "note": "Redeploy `KyberBridge` on Mainnet", + "pr": 2683 } ] }, diff --git a/packages/contract-addresses/addresses.json b/packages/contract-addresses/addresses.json index 0990d1423e..224a8237fb 100644 --- a/packages/contract-addresses/addresses.json +++ b/packages/contract-addresses/addresses.json @@ -22,7 +22,7 @@ "uniswapBridge": "0x36691c4f426eb8f42f150ebde43069a31cb080ad", "uniswapV2Bridge": "0xdcd6011f4c6b80e470d9487f5871a0cba7c93f48", "erc20BridgeSampler": "0xd8c38704c9937ea3312de29f824b4ad3450a5e61", - "kyberBridge": "0x1c29670f7a77f1052d30813a0a4f632c78a02610", + "kyberBridge": "0xadd97271402590564ddd8ad23cb5317b1fb0fffb", "eth2DaiBridge": "0x991c745401d5b5e469b8c3e2cb02c748f08754f1", "chaiBridge": "0x77c31eba23043b9a72d13470f3a3a311344d7438", "dydxBridge": "0x92af95e37afddac412e5688a9dcc1dd815d4ae53",