feat: better Kyber quotes (#2683)

* feat: Kyber rework

* Get additional reserves per token
This commit is contained in:
Jacob Evans 2020-08-27 15:58:57 +10:00 committed by GitHub
parent 2f9b894d71
commit a2f0d5eedf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 444 additions and 394 deletions

View File

@ -21,6 +21,10 @@
{
"note": "Added `MooniswapBridge`",
"pr": 2675
},
{
"note": "Reworked `KyberBridge`",
"pr": 2683
}
]
},

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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()

View File

@ -73,6 +73,10 @@
{
"note": "Use on-chain sampling (sometimes) for Balancer",
"pr": 2647
},
{
"note": "Re-worked `Kyber` quotes supporting multiple reserves",
"pr": 2683
}
]
},

View File

@ -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(

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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": []
}

View File

@ -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,

View File

@ -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);

View File

@ -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);
}

View File

@ -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<KyberFillData>).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,

View File

@ -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(

View File

@ -108,6 +108,11 @@ export interface BancorFillData extends FillData {
networkAddress: string;
}
export interface KyberFillData extends FillData {
hint: string;
reserveId: string;
}
export interface Quote<TFillData = FillData> {
amount: BigNumber;
fillData?: TFillData;

View File

@ -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,

View File

@ -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);

View File

@ -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);
});

View File

@ -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 () => {

View File

@ -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(),

View File

@ -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<BigNumber[]> {
): ContractFunctionObj<[string, BigNumber[]]> {
return this._wrapCall(
super.sampleSellsFromKyberNetwork,
this._handlers.sampleSellsFromKyberNetwork,
reserveId,
takerToken,
makerToken,
takerAssetAmounts,

View File

@ -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';

View File

@ -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",

View File

@ -37,6 +37,10 @@
{
"note": "Redeploy `MooniswapBridge` on Mainnet",
"pr": 2681
},
{
"note": "Redeploy `KyberBridge` on Mainnet",
"pr": 2683
}
]
},

View File

@ -22,7 +22,7 @@
"uniswapBridge": "0x36691c4f426eb8f42f150ebde43069a31cb080ad",
"uniswapV2Bridge": "0xdcd6011f4c6b80e470d9487f5871a0cba7c93f48",
"erc20BridgeSampler": "0xd8c38704c9937ea3312de29f824b4ad3450a5e61",
"kyberBridge": "0x1c29670f7a77f1052d30813a0a4f632c78a02610",
"kyberBridge": "0xadd97271402590564ddd8ad23cb5317b1fb0fffb",
"eth2DaiBridge": "0x991c745401d5b5e469b8c3e2cb02c748f08754f1",
"chaiBridge": "0x77c31eba23043b9a72d13470f3a3a311344d7438",
"dydxBridge": "0x92af95e37afddac412e5688a9dcc1dd815d4ae53",