@0x/contracts-asset-proxy
: Update CurveBridge
to support more varied curves.
`@0x/contracts-erc20-bridge-sampler`: Refactor. `@0x/contracts-erc20-bridge-sampler`: Add support for more varied curves. `@0x/contracts-integrations`: Update curve bridge tests.
This commit is contained in:
parent
74d9df2fb0
commit
4dac620156
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "3.5.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update `CurveBridge` to support more varied curves",
|
||||
"pr": 2633
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "3.4.0",
|
||||
"changes": [
|
||||
|
@ -26,7 +26,6 @@ import "@0x/contracts-exchange-libs/contracts/src/IWallet.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "../interfaces/IERC20Bridge.sol";
|
||||
import "../interfaces/ICurve.sol";
|
||||
import "./MixinGasToken.sol";
|
||||
|
||||
|
||||
// solhint-disable not-rely-on-time
|
||||
@ -34,14 +33,14 @@ import "./MixinGasToken.sol";
|
||||
contract CurveBridge is
|
||||
IERC20Bridge,
|
||||
IWallet,
|
||||
DeploymentConstants,
|
||||
MixinGasToken
|
||||
DeploymentConstants
|
||||
{
|
||||
struct CurveBridgeData {
|
||||
address curveAddress;
|
||||
bytes4 exchangeFunctionSelector;
|
||||
address fromTokenAddress;
|
||||
int128 fromCoinIdx;
|
||||
int128 toCoinIdx;
|
||||
int128 version;
|
||||
}
|
||||
|
||||
/// @dev Callback for `ICurve`. Tries to buy `amount` of
|
||||
@ -62,39 +61,31 @@ contract CurveBridge is
|
||||
bytes calldata bridgeData
|
||||
)
|
||||
external
|
||||
freesGasTokensFromCollector
|
||||
returns (bytes4 success)
|
||||
{
|
||||
// Decode the bridge data to get the Curve metadata.
|
||||
CurveBridgeData memory data = abi.decode(bridgeData, (CurveBridgeData));
|
||||
|
||||
address fromTokenAddress = ICurve(data.curveAddress).underlying_coins(data.fromCoinIdx);
|
||||
require(toTokenAddress != fromTokenAddress, "CurveBridge/INVALID_PAIR");
|
||||
uint256 fromTokenBalance = IERC20Token(fromTokenAddress).balanceOf(address(this));
|
||||
require(toTokenAddress != data.fromTokenAddress, "CurveBridge/INVALID_PAIR");
|
||||
uint256 fromTokenBalance = IERC20Token(data.fromTokenAddress).balanceOf(address(this));
|
||||
// Grant an allowance to the exchange to spend `fromTokenAddress` token.
|
||||
LibERC20Token.approveIfBelow(fromTokenAddress, data.curveAddress, fromTokenBalance);
|
||||
LibERC20Token.approveIfBelow(data.fromTokenAddress, data.curveAddress, fromTokenBalance);
|
||||
|
||||
// Try to sell all of this contract's `fromTokenAddress` token balance.
|
||||
if (data.version == 0) {
|
||||
ICurve(data.curveAddress).exchange_underlying(
|
||||
data.fromCoinIdx,
|
||||
data.toCoinIdx,
|
||||
// dx
|
||||
fromTokenBalance,
|
||||
// min dy
|
||||
amount,
|
||||
// expires
|
||||
block.timestamp + 1
|
||||
);
|
||||
} else {
|
||||
ICurve(data.curveAddress).exchange_underlying(
|
||||
{
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
data.curveAddress.call(abi.encodeWithSelector(
|
||||
data.exchangeFunctionSelector,
|
||||
data.fromCoinIdx,
|
||||
data.toCoinIdx,
|
||||
// dx
|
||||
fromTokenBalance,
|
||||
// min dy
|
||||
amount
|
||||
);
|
||||
));
|
||||
if (!didSucceed) {
|
||||
assembly { revert(add(resultData, 32), mload(resultData)) }
|
||||
}
|
||||
}
|
||||
|
||||
uint256 toTokenBalance = IERC20Token(toTokenAddress).balanceOf(address(this));
|
||||
@ -102,7 +93,7 @@ contract CurveBridge is
|
||||
LibERC20Token.transfer(toTokenAddress, to, toTokenBalance);
|
||||
|
||||
emit ERC20BridgeTransfer(
|
||||
fromTokenAddress,
|
||||
data.fromTokenAddress,
|
||||
toTokenAddress,
|
||||
fromTokenBalance,
|
||||
toTokenBalance,
|
||||
|
@ -22,22 +22,6 @@ pragma solidity ^0.5.9;
|
||||
// solhint-disable func-name-mixedcase
|
||||
interface ICurve {
|
||||
|
||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
||||
/// This function exists on early versions of Curve (USDC/DAI)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param sellAmount The amount of token being bought.
|
||||
/// @param minBuyAmount The minimum buy amount of the token being bought.
|
||||
/// @param deadline The time in seconds when this operation should expire.
|
||||
function exchange_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
uint256 deadline
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "1.8.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Refactor and support more varied curves",
|
||||
"pr": 2633
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.7.0",
|
||||
"changes": [
|
||||
|
123
contracts/erc20-bridge-sampler/contracts/src/ApproximateBuys.sol
Normal file
123
contracts/erc20-bridge-sampler/contracts/src/ApproximateBuys.sol
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
|
||||
|
||||
contract ApproximateBuys {
|
||||
|
||||
/// @dev Information computing buy quotes for sources that do not have native
|
||||
/// buy quote support.
|
||||
struct ApproximateBuyQuoteOpts {
|
||||
// Arbitrary maker token data to pass to `getSellQuoteCallback`.
|
||||
bytes makerTokenData;
|
||||
// Arbitrary taker token data to pass to `getSellQuoteCallback`.
|
||||
bytes takerTokenData;
|
||||
// Callback to retrieve a sell quote.
|
||||
function (bytes memory, bytes memory, uint256)
|
||||
internal
|
||||
view
|
||||
returns (uint256) getSellQuoteCallback;
|
||||
}
|
||||
|
||||
uint256 private constant ONE_HUNDED_PERCENT_BPS = 1e4;
|
||||
/// @dev Maximum approximate (positive) error rate when approximating a buy quote.
|
||||
uint256 private constant APPROXIMATE_BUY_TARGET_EPSILON_BPS = 0.0005e4;
|
||||
/// @dev Maximum iterations to perform when approximating a buy quote.
|
||||
uint256 private constant APPROXIMATE_BUY_MAX_ITERATIONS = 5;
|
||||
|
||||
function _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts memory opts,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
||||
if (makerTokenAmounts.length == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
uint256 sellAmount = opts.getSellQuoteCallback(
|
||||
opts.makerTokenData,
|
||||
opts.takerTokenData,
|
||||
makerTokenAmounts[0]
|
||||
);
|
||||
if (sellAmount == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
uint256 buyAmount = opts.getSellQuoteCallback(
|
||||
opts.takerTokenData,
|
||||
opts.makerTokenData,
|
||||
sellAmount
|
||||
);
|
||||
if (buyAmount == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
||||
for (uint256 iter = 0; iter < APPROXIMATE_BUY_MAX_ITERATIONS; iter++) {
|
||||
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
|
||||
sellAmount = LibMath.getPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmount,
|
||||
sellAmount
|
||||
);
|
||||
sellAmount = LibMath.getPartialAmountCeil(
|
||||
(ONE_HUNDED_PERCENT_BPS + APPROXIMATE_BUY_TARGET_EPSILON_BPS),
|
||||
ONE_HUNDED_PERCENT_BPS,
|
||||
sellAmount
|
||||
);
|
||||
uint256 _buyAmount = opts.getSellQuoteCallback(
|
||||
opts.takerTokenData,
|
||||
opts.makerTokenData,
|
||||
sellAmount
|
||||
);
|
||||
if (_buyAmount == 0) {
|
||||
break;
|
||||
}
|
||||
// We re-use buyAmount next iteration, only assign if it is
|
||||
// non zero
|
||||
buyAmount = _buyAmount;
|
||||
// If we've reached our goal, exit early
|
||||
if (buyAmount >= makerTokenAmounts[i]) {
|
||||
uint256 eps =
|
||||
(buyAmount - makerTokenAmounts[i]) * ONE_HUNDED_PERCENT_BPS /
|
||||
makerTokenAmounts[i];
|
||||
if (eps <= APPROXIMATE_BUY_TARGET_EPSILON_BPS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We do our best to close in on the requested amount, but we can either over buy or under buy and exit
|
||||
// if we hit a max iteration limit
|
||||
// We scale the sell amount to get the approximate target
|
||||
takerTokenAmounts[i] = LibMath.getPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmount,
|
||||
sellAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
156
contracts/erc20-bridge-sampler/contracts/src/CurveSampler.sol
Normal file
156
contracts/erc20-bridge-sampler/contracts/src/CurveSampler.sol
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./ICurve.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract CurveSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
/// @dev Information for sampling from curve sources.
|
||||
struct CurveInfo {
|
||||
address poolAddress;
|
||||
bytes4 sellQuoteFunctionSelector;
|
||||
bytes4 buyQuoteFunctionSelector;
|
||||
}
|
||||
|
||||
/// @dev Base gas limit for Curve calls. Some Curves have multiple tokens
|
||||
/// So a reasonable ceil is 150k per token. Biggest Curve has 4 tokens.
|
||||
uint256 constant private CURVE_CALL_GAS = 600e3; // 600k
|
||||
|
||||
/// @dev Sample sell quotes from Curve.
|
||||
/// @param curveInfo Curve information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index 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.
|
||||
function sampleSellsFromCurve(
|
||||
CurveInfo memory curveInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
curveInfo.sellQuoteFunctionSelector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Curve.
|
||||
/// @param curveInfo Curve information specific to this token pair.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index 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.
|
||||
function sampleBuysFromCurve(
|
||||
CurveInfo memory curveInfo,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
if (curveInfo.buyQuoteFunctionSelector == bytes4(0)) {
|
||||
// Buys not supported on this curve, so approximate it.
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(toTokenIdx, curveInfo),
|
||||
takerTokenData: abi.encode(fromTokenIdx, curveInfo),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromCurve
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
curveInfo.poolAddress.staticcall.gas(CURVE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
curveInfo.buyQuoteFunctionSelector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
makerTokenAmounts[i]
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromCurve(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(int128 takerTokenIdx, CurveInfo memory curveInfo) =
|
||||
abi.decode(takerTokenData, (int128, CurveInfo));
|
||||
(int128 makerTokenIdx) =
|
||||
abi.decode(makerTokenData, (int128));
|
||||
(bool success, bytes memory resultData) =
|
||||
address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromCurve.selector,
|
||||
curveInfo,
|
||||
takerTokenIdx,
|
||||
makerTokenIdx,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if (!success) {
|
||||
return 0;
|
||||
}
|
||||
// solhint-disable-next-line indent
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
140
contracts/erc20-bridge-sampler/contracts/src/Eth2DaiSampler.sol
Normal file
140
contracts/erc20-bridge-sampler/contracts/src/Eth2DaiSampler.sol
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "./IEth2Dai.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract Eth2DaiSampler is
|
||||
DeploymentConstants,
|
||||
SamplerUtils
|
||||
{
|
||||
/// @dev Base gas limit for Eth2Dai calls.
|
||||
uint256 constant private ETH2DAI_CALL_GAS = 1000e3; // 1m
|
||||
|
||||
/// @dev Sample sell quotes from Eth2Dai/Oasis.
|
||||
/// @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.
|
||||
function sampleSellsFromEth2Dai(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IEth2Dai(0).getBuyAmount.selector,
|
||||
makerToken,
|
||||
takerToken,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
} else{
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample sell quotes from Eth2Dai/Oasis using a hop to an intermediate token.
|
||||
/// I.e WBTC/DAI via ETH or WBTC/ETH via DAI
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param intermediateToken Address of the token to hop to.
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromEth2DaiHop(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
address intermediateToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
if (makerToken == intermediateToken || takerToken == intermediateToken) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
uint256[] memory intermediateAmounts = sampleSellsFromEth2Dai(
|
||||
takerToken,
|
||||
intermediateToken,
|
||||
takerTokenAmounts
|
||||
);
|
||||
makerTokenAmounts = sampleSellsFromEth2Dai(
|
||||
intermediateToken,
|
||||
makerToken,
|
||||
intermediateAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Eth2Dai/Oasis.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param takerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromEth2Dai(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getEth2DaiAddress().staticcall.gas(ETH2DAI_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IEth2Dai(0).getPayAmount.selector,
|
||||
takerToken,
|
||||
makerToken,
|
||||
makerTokenAmounts[i]
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
}
|
||||
}
|
@ -22,22 +22,6 @@ pragma solidity ^0.5.9;
|
||||
// solhint-disable func-name-mixedcase
|
||||
interface ICurve {
|
||||
|
||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
||||
/// This function exists on early versions of Curve (USDC/DAI)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param sellAmount The amount of token being bought.
|
||||
/// @param minBuyAmount The minimum buy amount of the token being bought.
|
||||
/// @param deadline The time in seconds when this operation should expire.
|
||||
function exchange_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 sellAmount,
|
||||
uint256 minBuyAmount,
|
||||
uint256 deadline
|
||||
)
|
||||
external;
|
||||
|
||||
/// @dev Sell `sellAmount` of `fromToken` token and receive `toToken` token.
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
|
@ -1,293 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
|
||||
|
||||
interface IERC20BridgeSampler {
|
||||
|
||||
struct FakeBuyOptions {
|
||||
uint256 targetSlippageBps;
|
||||
uint256 maxIterations;
|
||||
}
|
||||
|
||||
/// @dev Call multiple public functions on this contract in a single transaction.
|
||||
/// @param callDatas ABI-encoded call data for each function call.
|
||||
/// @return callResults ABI-encoded results data for each call.
|
||||
function batchCall(bytes[] calldata callDatas)
|
||||
external
|
||||
view
|
||||
returns (bytes[] memory callResults);
|
||||
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param devUtilsAddress Address to the DevUtils contract.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableTakerAssetAmounts(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures,
|
||||
address devUtilsAddress
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory orderFillableTakerAssetAmounts);
|
||||
|
||||
/// @dev Queries the fillable maker asset amounts of native orders.
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param devUtilsAddress Address to the DevUtils contract.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableMakerAssetAmounts(
|
||||
LibOrder.Order[] calldata orders,
|
||||
bytes[] calldata orderSignatures,
|
||||
address devUtilsAddress
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory orderFillableMakerAssetAmounts);
|
||||
|
||||
/// @dev Sample sell quotes from Kyber.
|
||||
/// @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.
|
||||
function sampleSellsFromKyberNetwork(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts);
|
||||
|
||||
/// @dev Sample buy quotes from Kyber.
|
||||
/// @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.
|
||||
/// @param opts `FakeBuyOptions` specifying target slippage and max iterations.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromKyberNetwork(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata makerTokenAmounts,
|
||||
FakeBuyOptions calldata opts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts);
|
||||
|
||||
/// @dev Sample sell quotes from Eth2Dai/Oasis.
|
||||
/// @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.
|
||||
function sampleSellsFromEth2Dai(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts);
|
||||
|
||||
/// @dev Sample sell quotes from Uniswap.
|
||||
/// @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.
|
||||
function sampleSellsFromUniswap(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts);
|
||||
|
||||
/// @dev Sample buy quotes from Uniswap.
|
||||
/// @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.
|
||||
function sampleBuysFromUniswap(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata makerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts);
|
||||
|
||||
/// @dev Sample buy quotes from Eth2Dai/Oasis.
|
||||
/// @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.
|
||||
function sampleBuysFromEth2Dai(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata makerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts);
|
||||
|
||||
/// @dev Sample sell quotes from Curve.
|
||||
/// @param curveAddress Address of the Curve contract.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index 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.
|
||||
function sampleSellsFromCurve(
|
||||
address curveAddress,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts);
|
||||
|
||||
/// @dev Sample buy quotes from Curve.
|
||||
/// @param curveAddress Address of the Curve contract.
|
||||
/// @param fromTokenIdx Index of the taker token (what to sell).
|
||||
/// @param toTokenIdx Index 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.
|
||||
function sampleBuysFromCurve(
|
||||
address curveAddress,
|
||||
int128 fromTokenIdx,
|
||||
int128 toTokenIdx,
|
||||
uint256[] calldata makerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts);
|
||||
|
||||
/// @dev Sample sell quotes from an arbitrary on-chain liquidity provider.
|
||||
/// @param registryAddress Address of the liquidity provider registry contract.
|
||||
/// @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.
|
||||
function sampleSellsFromLiquidityProviderRegistry(
|
||||
address registryAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts);
|
||||
|
||||
/// @dev Sample sell quotes from MultiBridge.
|
||||
/// @param multibridge Address of the MultiBridge contract.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param intermediateToken The address of the intermediate token to
|
||||
/// use in an indirect route.
|
||||
/// @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.
|
||||
function sampleSellsFromMultiBridge(
|
||||
address multibridge,
|
||||
address takerToken,
|
||||
address intermediateToken,
|
||||
address makerToken,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts);
|
||||
|
||||
/// @dev Sample buy quotes from an arbitrary on-chain liquidity provider.
|
||||
/// @param registryAddress Address of the liquidity provider registry contract.
|
||||
/// @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.
|
||||
/// @param opts `FakeBuyOptions` specifying target slippage and max iterations.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromLiquidityProviderRegistry(
|
||||
address registryAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] calldata makerTokenAmounts,
|
||||
FakeBuyOptions calldata opts
|
||||
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts);
|
||||
|
||||
/// @dev Returns the address of a liquidity provider for the given market
|
||||
/// (takerToken, makerToken), from a registry of liquidity providers.
|
||||
/// Returns address(0) if no such provider exists in the registry.
|
||||
/// @param takerToken Taker asset managed by liquidity provider.
|
||||
/// @param makerToken Maker asset managed by liquidity provider.
|
||||
/// @return providerAddress Address of the liquidity provider.
|
||||
function getLiquidityProviderFromRegistry(
|
||||
address registryAddress,
|
||||
address takerToken,
|
||||
address makerToken
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (address providerAddress);
|
||||
|
||||
/// @dev Sample sell quotes from UniswapV2.
|
||||
/// @param path Token route.
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromUniswapV2(
|
||||
address[] calldata path,
|
||||
uint256[] calldata takerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts);
|
||||
|
||||
/// @dev Sample buy quotes from UniswapV2.
|
||||
/// @param path Token route.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswapV2(
|
||||
address[] calldata path,
|
||||
uint256[] calldata makerTokenAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts);
|
||||
}
|
218
contracts/erc20-bridge-sampler/contracts/src/KyberSampler.sol
Normal file
218
contracts/erc20-bridge-sampler/contracts/src/KyberSampler.sol
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "./IKyberNetwork.sol";
|
||||
import "./IKyberNetworkProxy.sol";
|
||||
import "./IKyberStorage.sol";
|
||||
import "./IKyberHintHandler.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract KyberSampler is
|
||||
DeploymentConstants,
|
||||
SamplerUtils,
|
||||
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;
|
||||
|
||||
/// @dev Sample sell quotes from Kyber.
|
||||
/// @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.
|
||||
function sampleSellsFromKyberNetwork(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
address wethAddress = _getWethAddress();
|
||||
uint256 value;
|
||||
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);
|
||||
}
|
||||
}
|
||||
makerTokenAmounts[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Kyber.
|
||||
/// @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.
|
||||
function sampleBuysFromKyberNetwork(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken),
|
||||
takerTokenData: abi.encode(takerToken),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromKyber
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromKyber(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(bool success, bytes memory resultData) =
|
||||
address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromKyberNetwork.selector,
|
||||
abi.decode(takerTokenData, (address)),
|
||||
abi.decode(makerTokenData, (address)),
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if (!success) {
|
||||
return 0;
|
||||
}
|
||||
// solhint-disable-next-line indent
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
|
||||
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(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256 takerTokenAmount
|
||||
)
|
||||
private
|
||||
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));
|
||||
}
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getKyberNetworkProxyAddress().staticcall.gas(KYBER_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IKyberNetworkProxy(0).getExpectedRateAfterFee.selector,
|
||||
takerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : takerToken,
|
||||
makerToken == _getWethAddress() ? KYBER_ETH_ADDRESS : makerToken,
|
||||
takerTokenAmount,
|
||||
0, // fee
|
||||
hint
|
||||
));
|
||||
uint256 rate = 0;
|
||||
if (didSucceed) {
|
||||
(rate) = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint256 makerTokenDecimals = _getTokenDecimals(makerToken);
|
||||
uint256 takerTokenDecimals = _getTokenDecimals(takerToken);
|
||||
makerTokenAmount =
|
||||
rate *
|
||||
takerTokenAmount *
|
||||
10 ** makerTokenDecimals /
|
||||
10 ** takerTokenDecimals /
|
||||
10 ** 18;
|
||||
return makerTokenAmount;
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "./ILiquidityProvider.sol";
|
||||
import "./ILiquidityProviderRegistry.sol";
|
||||
import "./ApproximateBuys.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract LiquidityProviderSampler is
|
||||
SamplerUtils,
|
||||
ApproximateBuys
|
||||
{
|
||||
/// @dev Default gas limit for liquidity provider calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
|
||||
|
||||
/// @dev Sample sell quotes from an arbitrary on-chain liquidity provider.
|
||||
/// @param registryAddress Address of the liquidity provider registry contract.
|
||||
/// @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.
|
||||
function sampleSellsFromLiquidityProviderRegistry(
|
||||
address registryAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// Query registry for provider address.
|
||||
address providerAddress = getLiquidityProviderFromRegistry(
|
||||
registryAddress,
|
||||
takerToken,
|
||||
makerToken
|
||||
);
|
||||
// If provider doesn't exist, return all zeros.
|
||||
if (providerAddress == address(0)) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
providerAddress.staticcall.gas(DEFAULT_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
ILiquidityProvider(0).getSellQuote.selector,
|
||||
takerToken,
|
||||
makerToken,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
// Exit early if the amount is too high for the liquidity provider to serve
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from an arbitrary on-chain liquidity provider.
|
||||
/// @param registryAddress Address of the liquidity provider registry contract.
|
||||
/// @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.
|
||||
function sampleBuysFromLiquidityProviderRegistry(
|
||||
address registryAddress,
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
return _sampleApproximateBuys(
|
||||
ApproximateBuyQuoteOpts({
|
||||
makerTokenData: abi.encode(makerToken, registryAddress),
|
||||
takerTokenData: abi.encode(takerToken, registryAddress),
|
||||
getSellQuoteCallback: _sampleSellForApproximateBuyFromLiquidityProviderRegistry
|
||||
}),
|
||||
makerTokenAmounts
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Returns the address of a liquidity provider for the given market
|
||||
/// (takerToken, makerToken), from a registry of liquidity providers.
|
||||
/// Returns address(0) if no such provider exists in the registry.
|
||||
/// @param takerToken Taker asset managed by liquidity provider.
|
||||
/// @param makerToken Maker asset managed by liquidity provider.
|
||||
/// @return providerAddress Address of the liquidity provider.
|
||||
function getLiquidityProviderFromRegistry(
|
||||
address registryAddress,
|
||||
address takerToken,
|
||||
address makerToken
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (address providerAddress)
|
||||
{
|
||||
bytes memory callData = abi.encodeWithSelector(
|
||||
ILiquidityProviderRegistry(0).getLiquidityProviderForMarket.selector,
|
||||
takerToken,
|
||||
makerToken
|
||||
);
|
||||
(bool didSucceed, bytes memory returnData) = registryAddress.staticcall(callData);
|
||||
if (didSucceed && returnData.length == 32) {
|
||||
return LibBytes.readAddress(returnData, 12);
|
||||
}
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuyFromLiquidityProviderRegistry(
|
||||
bytes memory takerTokenData,
|
||||
bytes memory makerTokenData,
|
||||
uint256 sellAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 buyAmount)
|
||||
{
|
||||
(address takerToken, address plpRegistryAddress) =
|
||||
abi.decode(takerTokenData, (address, address));
|
||||
(address makerToken) =
|
||||
abi.decode(makerTokenData, (address));
|
||||
(bool success, bytes memory resultData) =
|
||||
address(this).staticcall(abi.encodeWithSelector(
|
||||
this.sampleSellsFromLiquidityProviderRegistry.selector,
|
||||
plpRegistryAddress,
|
||||
takerToken,
|
||||
makerToken,
|
||||
_toSingleValueArray(sellAmount)
|
||||
));
|
||||
if (!success) {
|
||||
return 0;
|
||||
}
|
||||
// solhint-disable-next-line indent
|
||||
return abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./IMultiBridge.sol";
|
||||
|
||||
|
||||
contract MultiBridgeSampler {
|
||||
|
||||
/// @dev Default gas limit for multibridge calls.
|
||||
uint256 constant private DEFAULT_CALL_GAS = 400e3; // 400k
|
||||
|
||||
/// @dev Sample sell quotes from MultiBridge.
|
||||
/// @param multibridge Address of the MultiBridge contract.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param intermediateToken The address of the intermediate token to
|
||||
/// use in an indirect route.
|
||||
/// @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.
|
||||
function sampleSellsFromMultiBridge(
|
||||
address multibridge,
|
||||
address takerToken,
|
||||
address intermediateToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
// Initialize array of maker token amounts.
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// If no address provided, return all zeros.
|
||||
if (multibridge == address(0)) {
|
||||
return makerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
multibridge.staticcall.gas(DEFAULT_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IMultiBridge(0).getSellQuote.selector,
|
||||
takerToken,
|
||||
intermediateToken,
|
||||
makerToken,
|
||||
takerTokenAmounts[i]
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
buyAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
// Exit early if the amount is too high for the liquidity provider to serve
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "./IDevUtils.sol";
|
||||
|
||||
|
||||
contract NativeOrderSampler {
|
||||
|
||||
/// @dev Gas limit for DevUtils calls.
|
||||
uint256 constant internal DEV_UTILS_CALL_GAS = 500e3; // 500k
|
||||
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// Effectively ignores orders that have empty signatures or
|
||||
/// maker/taker asset amounts (returning 0).
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param devUtilsAddress Address to the DevUtils contract.
|
||||
/// @return orderFillableTakerAssetAmounts How much taker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableTakerAssetAmounts(
|
||||
LibOrder.Order[] memory orders,
|
||||
bytes[] memory orderSignatures,
|
||||
address devUtilsAddress
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory orderFillableTakerAssetAmounts)
|
||||
{
|
||||
orderFillableTakerAssetAmounts = new uint256[](orders.length);
|
||||
for (uint256 i = 0; i != orders.length; i++) {
|
||||
// Ignore orders with no signature or empty maker/taker amounts.
|
||||
if (orderSignatures[i].length == 0 ||
|
||||
orders[i].makerAssetAmount == 0 ||
|
||||
orders[i].takerAssetAmount == 0) {
|
||||
orderFillableTakerAssetAmounts[i] = 0;
|
||||
continue;
|
||||
}
|
||||
// solhint-disable indent
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
devUtilsAddress
|
||||
.staticcall
|
||||
.gas(DEV_UTILS_CALL_GAS)
|
||||
(abi.encodeWithSelector(
|
||||
IDevUtils(devUtilsAddress).getOrderRelevantState.selector,
|
||||
orders[i],
|
||||
orderSignatures[i]
|
||||
));
|
||||
// solhint-enable indent
|
||||
if (!didSucceed) {
|
||||
orderFillableTakerAssetAmounts[i] = 0;
|
||||
continue;
|
||||
}
|
||||
(
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
uint256 fillableTakerAssetAmount,
|
||||
bool isValidSignature
|
||||
) = abi.decode(
|
||||
resultData,
|
||||
(LibOrder.OrderInfo, uint256, bool)
|
||||
);
|
||||
// The fillable amount is zero if the order is not fillable or if the
|
||||
// signature is invalid.
|
||||
if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE ||
|
||||
!isValidSignature) {
|
||||
orderFillableTakerAssetAmounts[i] = 0;
|
||||
} else {
|
||||
orderFillableTakerAssetAmounts[i] = fillableTakerAssetAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Queries the fillable taker asset amounts of native orders.
|
||||
/// Effectively ignores orders that have empty signatures or
|
||||
/// @param orders Native orders to query.
|
||||
/// @param orderSignatures Signatures for each respective order in `orders`.
|
||||
/// @param devUtilsAddress Address to the DevUtils contract.
|
||||
/// @return orderFillableMakerAssetAmounts How much maker asset can be filled
|
||||
/// by each order in `orders`.
|
||||
function getOrderFillableMakerAssetAmounts(
|
||||
LibOrder.Order[] memory orders,
|
||||
bytes[] memory orderSignatures,
|
||||
address devUtilsAddress
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory orderFillableMakerAssetAmounts)
|
||||
{
|
||||
orderFillableMakerAssetAmounts = getOrderFillableTakerAssetAmounts(
|
||||
orders,
|
||||
orderSignatures,
|
||||
devUtilsAddress
|
||||
);
|
||||
// `orderFillableMakerAssetAmounts` now holds taker asset amounts, so
|
||||
// convert them to maker asset amounts.
|
||||
for (uint256 i = 0; i < orders.length; ++i) {
|
||||
if (orderFillableMakerAssetAmounts[i] != 0) {
|
||||
orderFillableMakerAssetAmounts[i] = LibMath.getPartialAmountCeil(
|
||||
orderFillableMakerAssetAmounts[i],
|
||||
orders[i].takerAssetAmount,
|
||||
orders[i].makerAssetAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
|
||||
|
||||
contract SamplerUtils {
|
||||
|
||||
/// @dev Overridable way to get token decimals.
|
||||
/// @param tokenAddress Address of the token.
|
||||
/// @return decimals The decimal places for the token.
|
||||
function _getTokenDecimals(address tokenAddress)
|
||||
internal
|
||||
view
|
||||
returns (uint8 decimals)
|
||||
{
|
||||
return LibERC20Token.decimals(tokenAddress);
|
||||
}
|
||||
|
||||
function _toSingleValueArray(uint256 v)
|
||||
internal
|
||||
pure
|
||||
returns (uint256[] memory arr)
|
||||
{
|
||||
arr = new uint256[](1);
|
||||
arr[0] = v;
|
||||
}
|
||||
|
||||
/// @dev Assert that the tokens in a trade pair are valid.
|
||||
/// @param makerToken Address of the maker token.
|
||||
/// @param takerToken Address of the taker token.
|
||||
function _assertValidPair(address makerToken, address takerToken)
|
||||
internal
|
||||
pure
|
||||
{
|
||||
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
|
||||
}
|
||||
}
|
203
contracts/erc20-bridge-sampler/contracts/src/UniswapSampler.sol
Normal file
203
contracts/erc20-bridge-sampler/contracts/src/UniswapSampler.sol
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IUniswapExchangeFactory.sol";
|
||||
import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "./IUniswapExchangeQuotes.sol";
|
||||
import "./SamplerUtils.sol";
|
||||
|
||||
|
||||
contract UniswapSampler is
|
||||
DeploymentConstants,
|
||||
SamplerUtils
|
||||
{
|
||||
/// @dev Gas limit for Uniswap calls.
|
||||
uint256 constant private UNISWAP_CALL_GAS = 150e3; // 150k
|
||||
/// @dev Gas limit for UniswapV2 calls.
|
||||
uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k
|
||||
|
||||
/// @dev Sample sell quotes from Uniswap.
|
||||
/// @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.
|
||||
function sampleSellsFromUniswap(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
bool didSucceed = true;
|
||||
if (makerToken == _getWethAddress()) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == _getWethAddress()) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethBought;
|
||||
(ethBought, didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthInputPrice.selector,
|
||||
takerTokenAmounts[i]
|
||||
);
|
||||
if (ethBought != 0) {
|
||||
(makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenInputPrice.selector,
|
||||
ethBought
|
||||
);
|
||||
} else {
|
||||
makerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
if (!didSucceed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from Uniswap.
|
||||
/// @param takerToken Address of the taker token (what to sell).
|
||||
/// @param makerToken Address of the maker token (what to buy).
|
||||
/// @param makerTokenAmounts Maker token sell amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswap(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
IUniswapExchangeQuotes takerTokenExchange = takerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(takerToken);
|
||||
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
|
||||
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
bool didSucceed = true;
|
||||
if (makerToken == _getWethAddress()) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else if (takerToken == _getWethAddress()) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
} else {
|
||||
uint256 ethSold;
|
||||
(ethSold, didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(makerTokenExchange),
|
||||
makerTokenExchange.getEthToTokenOutputPrice.selector,
|
||||
makerTokenAmounts[i]
|
||||
);
|
||||
if (ethSold != 0) {
|
||||
(takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
|
||||
address(takerTokenExchange),
|
||||
takerTokenExchange.getTokenToEthOutputPrice.selector,
|
||||
ethSold
|
||||
);
|
||||
} else {
|
||||
takerTokenAmounts[i] = 0;
|
||||
}
|
||||
}
|
||||
if (!didSucceed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Gracefully calls a Uniswap pricing function.
|
||||
/// @param uniswapExchangeAddress Address of an `IUniswapExchangeQuotes` exchange.
|
||||
/// @param functionSelector Selector of the target function.
|
||||
/// @param inputAmount Quantity parameter particular to the pricing function.
|
||||
/// @return outputAmount The returned amount from the function call. Will be
|
||||
/// zero if the call fails or if `uniswapExchangeAddress` is zero.
|
||||
function _callUniswapExchangePriceFunction(
|
||||
address uniswapExchangeAddress,
|
||||
bytes4 functionSelector,
|
||||
uint256 inputAmount
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 outputAmount, bool didSucceed)
|
||||
{
|
||||
if (uniswapExchangeAddress == address(0)) {
|
||||
return (outputAmount, didSucceed);
|
||||
}
|
||||
bytes memory resultData;
|
||||
(didSucceed, resultData) =
|
||||
uniswapExchangeAddress.staticcall.gas(UNISWAP_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
functionSelector,
|
||||
inputAmount
|
||||
));
|
||||
if (didSucceed) {
|
||||
outputAmount = abi.decode(resultData, (uint256));
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Retrive an existing Uniswap exchange contract.
|
||||
/// Throws if the exchange does not exist.
|
||||
/// @param tokenAddress Address of the token contract.
|
||||
/// @return exchange `IUniswapExchangeQuotes` for the token.
|
||||
function _getUniswapExchange(address tokenAddress)
|
||||
private
|
||||
view
|
||||
returns (IUniswapExchangeQuotes exchange)
|
||||
{
|
||||
exchange = IUniswapExchangeQuotes(
|
||||
address(IUniswapExchangeFactory(_getUniswapExchangeFactoryAddress())
|
||||
.getExchange(tokenAddress))
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 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;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/DeploymentConstants.sol";
|
||||
import "./IUniswapV2Router01.sol";
|
||||
|
||||
|
||||
contract UniswapV2Sampler is
|
||||
DeploymentConstants
|
||||
{
|
||||
/// @dev Gas limit for UniswapV2 calls.
|
||||
uint256 constant private UNISWAPV2_CALL_GAS = 150e3; // 150k
|
||||
|
||||
/// @dev Sample sell quotes from UniswapV2.
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
function sampleSellsFromUniswapV2(
|
||||
address[] memory path,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory makerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = takerTokenAmounts.length;
|
||||
makerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getUniswapV2Router01Address().staticcall.gas(UNISWAPV2_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IUniswapV2Router01(0).getAmountsOut.selector,
|
||||
takerTokenAmounts[i],
|
||||
path
|
||||
));
|
||||
uint256 buyAmount = 0;
|
||||
if (didSucceed) {
|
||||
// solhint-disable-next-line indent
|
||||
buyAmount = abi.decode(resultData, (uint256[]))[path.length - 1];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
makerTokenAmounts[i] = buyAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Sample buy quotes from UniswapV2.
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswapV2(
|
||||
address[] memory path,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
_getUniswapV2Router01Address().staticcall.gas(UNISWAPV2_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
IUniswapV2Router01(0).getAmountsIn.selector,
|
||||
makerTokenAmounts[i],
|
||||
path
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
// solhint-disable-next-line indent
|
||||
sellAmount = abi.decode(resultData, (uint256[]))[0];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
}
|
||||
}
|
@ -36,9 +36,9 @@
|
||||
"compile:truffle": "truffle compile"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,IERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,ILiquidityProvider,ILiquidityProviderRegistry,DummyLiquidityProviderRegistry,DummyLiquidityProvider",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||
"abis": "./test/generated-artifacts/@(DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|ICurve|IDevUtils|IERC20BridgeSampler|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|TestERC20BridgeSampler).json"
|
||||
"abis": "./test/generated-artifacts/@(ApproximateBuys|CurveSampler|DummyLiquidityProvider|DummyLiquidityProviderRegistry|ERC20BridgeSampler|Eth2DaiSampler|ICurve|IDevUtils|IEth2Dai|IKyberHintHandler|IKyberNetwork|IKyberNetworkProxy|IKyberStorage|ILiquidityProvider|ILiquidityProviderRegistry|IMultiBridge|IUniswapExchangeQuotes|IUniswapV2Router01|KyberSampler|LiquidityProviderSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|TestERC20BridgeSampler|UniswapSampler|UniswapV2Sampler).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -8,12 +8,10 @@ import { ContractArtifact } from 'ethereum-types';
|
||||
import * as DummyLiquidityProvider from '../generated-artifacts/DummyLiquidityProvider.json';
|
||||
import * as DummyLiquidityProviderRegistry from '../generated-artifacts/DummyLiquidityProviderRegistry.json';
|
||||
import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json';
|
||||
import * as IERC20BridgeSampler from '../generated-artifacts/IERC20BridgeSampler.json';
|
||||
import * as ILiquidityProvider from '../generated-artifacts/ILiquidityProvider.json';
|
||||
import * as ILiquidityProviderRegistry from '../generated-artifacts/ILiquidityProviderRegistry.json';
|
||||
export const artifacts = {
|
||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
|
||||
ILiquidityProvider: ILiquidityProvider as ContractArtifact,
|
||||
ILiquidityProviderRegistry: ILiquidityProviderRegistry as ContractArtifact,
|
||||
DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact,
|
||||
|
@ -6,6 +6,5 @@
|
||||
export * from '../generated-wrappers/dummy_liquidity_provider';
|
||||
export * from '../generated-wrappers/dummy_liquidity_provider_registry';
|
||||
export * from '../generated-wrappers/erc20_bridge_sampler';
|
||||
export * from '../generated-wrappers/i_erc20_bridge_sampler';
|
||||
export * from '../generated-wrappers/i_liquidity_provider';
|
||||
export * from '../generated-wrappers/i_liquidity_provider_registry';
|
||||
|
@ -5,12 +5,14 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as ApproximateBuys from '../test/generated-artifacts/ApproximateBuys.json';
|
||||
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
|
||||
import * as DummyLiquidityProvider from '../test/generated-artifacts/DummyLiquidityProvider.json';
|
||||
import * as DummyLiquidityProviderRegistry from '../test/generated-artifacts/DummyLiquidityProviderRegistry.json';
|
||||
import * as ERC20BridgeSampler from '../test/generated-artifacts/ERC20BridgeSampler.json';
|
||||
import * as Eth2DaiSampler from '../test/generated-artifacts/Eth2DaiSampler.json';
|
||||
import * as ICurve from '../test/generated-artifacts/ICurve.json';
|
||||
import * as IDevUtils from '../test/generated-artifacts/IDevUtils.json';
|
||||
import * as IERC20BridgeSampler from '../test/generated-artifacts/IERC20BridgeSampler.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';
|
||||
@ -21,14 +23,23 @@ import * as ILiquidityProviderRegistry from '../test/generated-artifacts/ILiquid
|
||||
import * as IMultiBridge from '../test/generated-artifacts/IMultiBridge.json';
|
||||
import * as IUniswapExchangeQuotes from '../test/generated-artifacts/IUniswapExchangeQuotes.json';
|
||||
import * as IUniswapV2Router01 from '../test/generated-artifacts/IUniswapV2Router01.json';
|
||||
import * as KyberSampler from '../test/generated-artifacts/KyberSampler.json';
|
||||
import * as LiquidityProviderSampler from '../test/generated-artifacts/LiquidityProviderSampler.json';
|
||||
import * as MultiBridgeSampler from '../test/generated-artifacts/MultiBridgeSampler.json';
|
||||
import * as NativeOrderSampler from '../test/generated-artifacts/NativeOrderSampler.json';
|
||||
import * as SamplerUtils from '../test/generated-artifacts/SamplerUtils.json';
|
||||
import * as TestERC20BridgeSampler from '../test/generated-artifacts/TestERC20BridgeSampler.json';
|
||||
import * as UniswapSampler from '../test/generated-artifacts/UniswapSampler.json';
|
||||
import * as UniswapV2Sampler from '../test/generated-artifacts/UniswapV2Sampler.json';
|
||||
export const artifacts = {
|
||||
ApproximateBuys: ApproximateBuys as ContractArtifact,
|
||||
CurveSampler: CurveSampler as ContractArtifact,
|
||||
DummyLiquidityProvider: DummyLiquidityProvider as ContractArtifact,
|
||||
DummyLiquidityProviderRegistry: DummyLiquidityProviderRegistry as ContractArtifact,
|
||||
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
|
||||
Eth2DaiSampler: Eth2DaiSampler as ContractArtifact,
|
||||
ICurve: ICurve as ContractArtifact,
|
||||
IDevUtils: IDevUtils as ContractArtifact,
|
||||
IERC20BridgeSampler: IERC20BridgeSampler as ContractArtifact,
|
||||
IEth2Dai: IEth2Dai as ContractArtifact,
|
||||
IKyberHintHandler: IKyberHintHandler as ContractArtifact,
|
||||
IKyberNetwork: IKyberNetwork as ContractArtifact,
|
||||
@ -39,5 +50,12 @@ export const artifacts = {
|
||||
IMultiBridge: IMultiBridge as ContractArtifact,
|
||||
IUniswapExchangeQuotes: IUniswapExchangeQuotes as ContractArtifact,
|
||||
IUniswapV2Router01: IUniswapV2Router01 as ContractArtifact,
|
||||
KyberSampler: KyberSampler as ContractArtifact,
|
||||
LiquidityProviderSampler: LiquidityProviderSampler as ContractArtifact,
|
||||
MultiBridgeSampler: MultiBridgeSampler as ContractArtifact,
|
||||
NativeOrderSampler: NativeOrderSampler as ContractArtifact,
|
||||
SamplerUtils: SamplerUtils as ContractArtifact,
|
||||
UniswapSampler: UniswapSampler as ContractArtifact,
|
||||
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
|
||||
TestERC20BridgeSampler: TestERC20BridgeSampler as ContractArtifact,
|
||||
};
|
||||
|
@ -34,10 +34,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
let devUtilsAddress: string;
|
||||
const FAKE_BUY_OPTS = {
|
||||
targetSlippageBps: new BigNumber(5),
|
||||
maxIterations: new BigNumber(5),
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
|
||||
@ -443,14 +439,12 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
|
||||
it('throws if tokens are the same', async () => {
|
||||
const tx = testContract.sampleBuysFromKyberNetwork(MAKER_TOKEN, MAKER_TOKEN, [], FAKE_BUY_OPTS).callAsync();
|
||||
const tx = testContract.sampleBuysFromKyberNetwork(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, [], FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
const quotes = await testContract.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, []).callAsync();
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
const expectQuotesWithinRange = (
|
||||
@ -485,7 +479,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
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, FAKE_BUY_OPTS)
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE);
|
||||
});
|
||||
@ -495,7 +489,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
@ -504,7 +498,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE);
|
||||
});
|
||||
@ -514,7 +508,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
@ -523,7 +517,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE);
|
||||
});
|
||||
@ -533,7 +527,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
@ -924,13 +918,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
|
||||
it('should be able to query buys from the liquidity provider', async () => {
|
||||
const result = await testContract
|
||||
.sampleBuysFromLiquidityProviderRegistry(
|
||||
registryContract.address,
|
||||
yAsset,
|
||||
xAsset,
|
||||
sampleAmounts,
|
||||
FAKE_BUY_OPTS,
|
||||
)
|
||||
.sampleBuysFromLiquidityProviderRegistry(registryContract.address, yAsset, xAsset, sampleAmounts)
|
||||
.callAsync();
|
||||
result.forEach((value, idx) => {
|
||||
expect(value).is.bignumber.eql(sampleAmounts[idx].plus(1));
|
||||
@ -944,7 +932,6 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
yAsset,
|
||||
randomAddress(),
|
||||
sampleAmounts,
|
||||
FAKE_BUY_OPTS,
|
||||
)
|
||||
.callAsync();
|
||||
result.forEach(value => {
|
||||
@ -954,7 +941,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
|
||||
it('should just return zeros if the registry does not exist', async () => {
|
||||
const result = await testContract
|
||||
.sampleBuysFromLiquidityProviderRegistry(randomAddress(), yAsset, xAsset, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.sampleBuysFromLiquidityProviderRegistry(randomAddress(), yAsset, xAsset, sampleAmounts)
|
||||
.callAsync();
|
||||
result.forEach(value => {
|
||||
expect(value).is.bignumber.eql(constants.ZERO_AMOUNT);
|
||||
|
@ -3,12 +3,14 @@
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../test/generated-wrappers/approximate_buys';
|
||||
export * from '../test/generated-wrappers/curve_sampler';
|
||||
export * from '../test/generated-wrappers/dummy_liquidity_provider';
|
||||
export * from '../test/generated-wrappers/dummy_liquidity_provider_registry';
|
||||
export * from '../test/generated-wrappers/erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/eth2_dai_sampler';
|
||||
export * from '../test/generated-wrappers/i_curve';
|
||||
export * from '../test/generated-wrappers/i_dev_utils';
|
||||
export * from '../test/generated-wrappers/i_erc20_bridge_sampler';
|
||||
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';
|
||||
@ -19,4 +21,11 @@ export * from '../test/generated-wrappers/i_liquidity_provider_registry';
|
||||
export * from '../test/generated-wrappers/i_multi_bridge';
|
||||
export * from '../test/generated-wrappers/i_uniswap_exchange_quotes';
|
||||
export * from '../test/generated-wrappers/i_uniswap_v2_router01';
|
||||
export * from '../test/generated-wrappers/kyber_sampler';
|
||||
export * from '../test/generated-wrappers/liquidity_provider_sampler';
|
||||
export * from '../test/generated-wrappers/multi_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/native_order_sampler';
|
||||
export * from '../test/generated-wrappers/sampler_utils';
|
||||
export * from '../test/generated-wrappers/test_erc20_bridge_sampler';
|
||||
export * from '../test/generated-wrappers/uniswap_sampler';
|
||||
export * from '../test/generated-wrappers/uniswap_v2_sampler';
|
||||
|
@ -6,15 +6,16 @@
|
||||
"generated-artifacts/DummyLiquidityProvider.json",
|
||||
"generated-artifacts/DummyLiquidityProviderRegistry.json",
|
||||
"generated-artifacts/ERC20BridgeSampler.json",
|
||||
"generated-artifacts/IERC20BridgeSampler.json",
|
||||
"generated-artifacts/ILiquidityProvider.json",
|
||||
"generated-artifacts/ILiquidityProviderRegistry.json",
|
||||
"test/generated-artifacts/ApproximateBuys.json",
|
||||
"test/generated-artifacts/CurveSampler.json",
|
||||
"test/generated-artifacts/DummyLiquidityProvider.json",
|
||||
"test/generated-artifacts/DummyLiquidityProviderRegistry.json",
|
||||
"test/generated-artifacts/ERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/Eth2DaiSampler.json",
|
||||
"test/generated-artifacts/ICurve.json",
|
||||
"test/generated-artifacts/IDevUtils.json",
|
||||
"test/generated-artifacts/IERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/IEth2Dai.json",
|
||||
"test/generated-artifacts/IKyberHintHandler.json",
|
||||
"test/generated-artifacts/IKyberNetwork.json",
|
||||
@ -25,7 +26,14 @@
|
||||
"test/generated-artifacts/IMultiBridge.json",
|
||||
"test/generated-artifacts/IUniswapExchangeQuotes.json",
|
||||
"test/generated-artifacts/IUniswapV2Router01.json",
|
||||
"test/generated-artifacts/TestERC20BridgeSampler.json"
|
||||
"test/generated-artifacts/KyberSampler.json",
|
||||
"test/generated-artifacts/LiquidityProviderSampler.json",
|
||||
"test/generated-artifacts/MultiBridgeSampler.json",
|
||||
"test/generated-artifacts/NativeOrderSampler.json",
|
||||
"test/generated-artifacts/SamplerUtils.json",
|
||||
"test/generated-artifacts/TestERC20BridgeSampler.json",
|
||||
"test/generated-artifacts/UniswapSampler.json",
|
||||
"test/generated-artifacts/UniswapV2Sampler.json"
|
||||
],
|
||||
"exclude": ["./deploy/solc/solc_bin"]
|
||||
}
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"version": "2.7.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update curveBridge tests",
|
||||
"pr": 2633
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.6.0",
|
||||
"changes": [
|
||||
|
@ -102,6 +102,7 @@
|
||||
"@0x/contracts-multisig": "^4.1.7",
|
||||
"@0x/contracts-staking": "^2.0.14",
|
||||
"@0x/contracts-test-utils": "^5.3.4",
|
||||
"@0x/subproviders": "^6.1.1",
|
||||
"@0x/types": "^3.2.0",
|
||||
"@0x/typescript-typings": "^5.1.1",
|
||||
"@0x/utils": "^5.5.1",
|
||||
|
@ -1,16 +1,11 @@
|
||||
import { artifacts, ERC20BridgeSamplerContract } from '@0x/contracts-erc20-bridge-sampler';
|
||||
import { blockchainTests, describe, expect, toBaseUnitAmount, Web3ProviderEngine } from '@0x/contracts-test-utils';
|
||||
import { RPCSubprovider } from '@0x/dev-utils/node_modules/@0x/subproviders';
|
||||
import { RPCSubprovider } from '@0x/subproviders';
|
||||
import { BigNumber, providerUtils } from '@0x/utils';
|
||||
|
||||
export const VB = '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b';
|
||||
blockchainTests.configure({
|
||||
fork: {
|
||||
unlockedAccounts: [VB],
|
||||
},
|
||||
});
|
||||
|
||||
blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
blockchainTests.skip('Mainnet Sampler Tests', env => {
|
||||
let testContract: ERC20BridgeSamplerContract;
|
||||
const fakeSamplerAddress = '0x1111111111111111111111111111111111111111';
|
||||
const overrides = {
|
||||
@ -21,7 +16,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
before(async () => {
|
||||
const provider = new Web3ProviderEngine();
|
||||
// tslint:disable-next-line:no-non-null-assertion
|
||||
provider.addProvider(new RPCSubprovider(process.env.FORK_RPC_URL!));
|
||||
provider.addProvider(new RPCSubprovider(process.env.RPC_URL!));
|
||||
providerUtils.startProviderEngine(provider);
|
||||
testContract = new ERC20BridgeSamplerContract(fakeSamplerAddress, provider, {
|
||||
...env.txDefaults,
|
||||
@ -29,14 +24,19 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
});
|
||||
});
|
||||
describe('Curve', () => {
|
||||
const CURVE_ADDRESS = '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56';
|
||||
const CURVE_ADDRESS = '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51';
|
||||
const DAI_TOKEN_INDEX = new BigNumber(0);
|
||||
const USDC_TOKEN_INDEX = new BigNumber(1);
|
||||
const CURVE_INFO = {
|
||||
poolAddress: CURVE_ADDRESS,
|
||||
sellQuoteFunctionSelector: '0x07211ef7',
|
||||
buyQuoteFunctionSelector: '0x0e71d1b9',
|
||||
};
|
||||
|
||||
describe('sampleSellsFromCurve()', () => {
|
||||
it('samples sells from Curve DAI->USDC', async () => {
|
||||
const samples = await testContract
|
||||
.sampleSellsFromCurve(CURVE_ADDRESS, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1)])
|
||||
.sampleSellsFromCurve(CURVE_INFO, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1)])
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
@ -44,7 +44,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
|
||||
it('samples sells from Curve USDC->DAI', async () => {
|
||||
const samples = await testContract
|
||||
.sampleSellsFromCurve(CURVE_ADDRESS, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1, 6)])
|
||||
.sampleSellsFromCurve(CURVE_INFO, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1, 6)])
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
@ -56,7 +56,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
// From DAI to USDC
|
||||
// I want to buy 1 USDC
|
||||
const samples = await testContract
|
||||
.sampleBuysFromCurve(CURVE_ADDRESS, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1, 6)])
|
||||
.sampleBuysFromCurve(CURVE_INFO, DAI_TOKEN_INDEX, USDC_TOKEN_INDEX, [toBaseUnitAmount(1, 6)])
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
@ -66,7 +66,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
// From USDC to DAI
|
||||
// I want to buy 1 DAI
|
||||
const samples = await testContract
|
||||
.sampleBuysFromCurve(CURVE_ADDRESS, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1)])
|
||||
.sampleBuysFromCurve(CURVE_INFO, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1)])
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
@ -74,10 +74,6 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
});
|
||||
});
|
||||
describe('Kyber', () => {
|
||||
const FAKE_BUY_OPTS = {
|
||||
targetSlippageBps: new BigNumber(5),
|
||||
maxIterations: new BigNumber(5),
|
||||
};
|
||||
const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||
const DAI = '0x6b175474e89094c44da98b954eedeac495271d0f';
|
||||
const USDC = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
|
||||
@ -110,7 +106,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
// From ETH to DAI
|
||||
// I want to buy 1 DAI
|
||||
const samples = await testContract
|
||||
.sampleBuysFromKyberNetwork(WETH, DAI, [toBaseUnitAmount(1)], FAKE_BUY_OPTS)
|
||||
.sampleBuysFromKyberNetwork(WETH, DAI, [toBaseUnitAmount(1)])
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
@ -120,7 +116,7 @@ blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
// From USDC to DAI
|
||||
// I want to buy 1 WETH
|
||||
const samples = await testContract
|
||||
.sampleBuysFromKyberNetwork(DAI, WETH, [toBaseUnitAmount(1)], FAKE_BUY_OPTS)
|
||||
.sampleBuysFromKyberNetwork(DAI, WETH, [toBaseUnitAmount(1)])
|
||||
.callAsync({ overrides });
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
|
@ -4,107 +4,128 @@ import { ERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { blockchainTests, constants, describe, toBaseUnitAmount } from '@0x/contracts-test-utils';
|
||||
import { AbiEncoder } from '@0x/utils';
|
||||
|
||||
const USDC_WALLET = '0xF977814e90dA44bFA03b6295A0616a897441aceC';
|
||||
const DAI_WALLET = '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b';
|
||||
const WBTC_WALLET = '0x56178a0d5F301bAf6CF3e1Cd53d9863437345Bf9';
|
||||
blockchainTests.configure({
|
||||
fork: {
|
||||
unlockedAccounts: [USDC_WALLET, DAI_WALLET, WBTC_WALLET],
|
||||
},
|
||||
});
|
||||
|
||||
blockchainTests.fork.resets('Mainnet curve bridge tests', env => {
|
||||
let testContract: CurveBridgeContract;
|
||||
const receiver = '0x986ccf5234d9cfbb25246f1a5bfa51f4ccfcb308';
|
||||
const usdcWallet = '0xF977814e90dA44bFA03b6295A0616a897441aceC';
|
||||
const usdcAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
|
||||
const daiWallet = '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b';
|
||||
const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
|
||||
const curveAddressUsdcDai = '0x2e60CF74d81ac34eB21eEff58Db4D385920ef419';
|
||||
const curveAddressUsdcDaiUsdt = '0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C';
|
||||
const daiTokenIdx = 0;
|
||||
const usdcTokenIdx = 1;
|
||||
const RECEIVER = '0x986ccf5234d9cfbb25246f1a5bfa51f4ccfcb308';
|
||||
const bridgeDataEncoder = AbiEncoder.create([
|
||||
{ name: 'curveAddress', type: 'address' },
|
||||
{ name: 'exchangeFunctionSelector', type: 'bytes4' },
|
||||
{ name: 'fromTokenAddress', type: 'address' },
|
||||
{ name: 'fromTokenIdx', type: 'int128' },
|
||||
{ name: 'toTokenIdx', type: 'int128' },
|
||||
{ name: 'version', type: 'int128' },
|
||||
]);
|
||||
before(async () => {
|
||||
testContract = await CurveBridgeContract.deployFrom0xArtifactAsync(
|
||||
assetProxyArtifacts.CurveBridge,
|
||||
env.provider,
|
||||
{ ...env.txDefaults, from: daiWallet },
|
||||
{ ...env.txDefaults },
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
describe('bridgeTransferFrom()', () => {
|
||||
describe('Version 0', () => {
|
||||
const version = 0;
|
||||
describe('exchange_underlying()', () => {
|
||||
const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
|
||||
const DAI_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
|
||||
const DAI_TOKEN_IDX = 0;
|
||||
const USDC_TOKEN_IDX = 1;
|
||||
const EXCHANGE_UNDERLYING_SELECTOR = '0xa6417ed6';
|
||||
const CURVE_ADDRESS = '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51';
|
||||
it('succeeds exchanges DAI for USDC', async () => {
|
||||
const bridgeData = bridgeDataEncoder.encode([curveAddressUsdcDai, daiTokenIdx, usdcTokenIdx, version]);
|
||||
const bridgeData = bridgeDataEncoder.encode([
|
||||
CURVE_ADDRESS,
|
||||
EXCHANGE_UNDERLYING_SELECTOR,
|
||||
DAI_ADDRESS,
|
||||
DAI_TOKEN_IDX,
|
||||
USDC_TOKEN_IDX,
|
||||
]);
|
||||
// Fund the Bridge
|
||||
const dai = new ERC20TokenContract(daiAddress, env.provider, { ...env.txDefaults, from: daiWallet });
|
||||
const dai = new ERC20TokenContract(DAI_ADDRESS, env.provider, { ...env.txDefaults, from: DAI_WALLET });
|
||||
await dai
|
||||
.transfer(testContract.address, toBaseUnitAmount(1))
|
||||
.awaitTransactionSuccessAsync({ from: daiWallet }, { shouldValidate: false });
|
||||
.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||
// Exchange via Curve
|
||||
await testContract
|
||||
.bridgeTransferFrom(
|
||||
usdcAddress,
|
||||
USDC_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
receiver,
|
||||
RECEIVER,
|
||||
constants.ZERO_AMOUNT,
|
||||
bridgeData,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: daiWallet, gasPrice: 1 }, { shouldValidate: false });
|
||||
.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||
});
|
||||
it('succeeds exchanges USDC for DAI', async () => {
|
||||
const bridgeData = bridgeDataEncoder.encode([curveAddressUsdcDai, usdcTokenIdx, daiTokenIdx, version]);
|
||||
// Fund the Bridge
|
||||
const usdc = new ERC20TokenContract(usdcAddress, env.provider, { ...env.txDefaults, from: usdcWallet });
|
||||
await usdc
|
||||
.transfer(testContract.address, toBaseUnitAmount(1, 6))
|
||||
.awaitTransactionSuccessAsync({ from: usdcWallet }, { shouldValidate: false });
|
||||
// Exchange via Curve
|
||||
await testContract
|
||||
.bridgeTransferFrom(daiAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData)
|
||||
.awaitTransactionSuccessAsync({ from: usdcWallet, gasPrice: 1 }, { shouldValidate: false });
|
||||
});
|
||||
});
|
||||
describe('Version 1', () => {
|
||||
const version = 1;
|
||||
it('succeeds exchanges DAI for USDC', async () => {
|
||||
const bridgeData = bridgeDataEncoder.encode([
|
||||
curveAddressUsdcDaiUsdt,
|
||||
daiTokenIdx,
|
||||
usdcTokenIdx,
|
||||
version,
|
||||
CURVE_ADDRESS,
|
||||
EXCHANGE_UNDERLYING_SELECTOR,
|
||||
USDC_ADDRESS,
|
||||
USDC_TOKEN_IDX,
|
||||
DAI_TOKEN_IDX,
|
||||
]);
|
||||
// Fund the Bridge
|
||||
const dai = new ERC20TokenContract(daiAddress, env.provider, { ...env.txDefaults, from: daiWallet });
|
||||
await dai
|
||||
.transfer(testContract.address, toBaseUnitAmount(1))
|
||||
.awaitTransactionSuccessAsync({ from: daiWallet }, { shouldValidate: false });
|
||||
const usdc = new ERC20TokenContract(USDC_ADDRESS, env.provider, {
|
||||
...env.txDefaults,
|
||||
from: USDC_WALLET,
|
||||
});
|
||||
await usdc
|
||||
.transfer(testContract.address, toBaseUnitAmount(1, 6))
|
||||
.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||
// Exchange via Curve
|
||||
await testContract
|
||||
.bridgeTransferFrom(
|
||||
usdcAddress,
|
||||
DAI_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
receiver,
|
||||
RECEIVER,
|
||||
constants.ZERO_AMOUNT,
|
||||
bridgeData,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ from: daiWallet, gasPrice: 1 }, { shouldValidate: false });
|
||||
.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||
});
|
||||
it('succeeds exchanges USDC for DAI', async () => {
|
||||
});
|
||||
|
||||
describe('exchange()', () => {
|
||||
const WBTC_ADDRESS = '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599';
|
||||
const RENBTC_ADDRESS = '0xeb4c2781e4eba804ce9a9803c67d0893436bb27d';
|
||||
const RENBTC_TOKEN_IDX = 0;
|
||||
const WBTC_TOKEN_IDX = 1;
|
||||
const EXCHANGE_SELECTOR = '0x3df02124';
|
||||
const CURVE_ADDRESS = '0x7fc77b5c7614e1533320ea6ddc2eb61fa00a9714';
|
||||
it('succeeds exchanges WBTC for renBTC', async () => {
|
||||
const bridgeData = bridgeDataEncoder.encode([
|
||||
curveAddressUsdcDaiUsdt,
|
||||
usdcTokenIdx,
|
||||
daiTokenIdx,
|
||||
version,
|
||||
CURVE_ADDRESS,
|
||||
EXCHANGE_SELECTOR,
|
||||
WBTC_ADDRESS,
|
||||
WBTC_TOKEN_IDX,
|
||||
RENBTC_TOKEN_IDX,
|
||||
]);
|
||||
// Fund the Bridge
|
||||
const usdc = new ERC20TokenContract(usdcAddress, env.provider, { ...env.txDefaults, from: usdcWallet });
|
||||
await usdc
|
||||
.transfer(testContract.address, toBaseUnitAmount(1, 6))
|
||||
.awaitTransactionSuccessAsync({ from: usdcWallet }, { shouldValidate: false });
|
||||
const wbtc = new ERC20TokenContract(WBTC_ADDRESS, env.provider, {
|
||||
...env.txDefaults,
|
||||
from: WBTC_WALLET,
|
||||
});
|
||||
await wbtc
|
||||
.transfer(testContract.address, toBaseUnitAmount(1, 8))
|
||||
.awaitTransactionSuccessAsync({}, { shouldValidate: false });
|
||||
// Exchange via Curve
|
||||
await testContract
|
||||
.bridgeTransferFrom(daiAddress, constants.NULL_ADDRESS, receiver, constants.ZERO_AMOUNT, bridgeData)
|
||||
.awaitTransactionSuccessAsync({ from: usdcWallet, gasPrice: 1 }, { shouldValidate: false });
|
||||
.bridgeTransferFrom(
|
||||
RENBTC_ADDRESS,
|
||||
constants.NULL_ADDRESS,
|
||||
RECEIVER,
|
||||
constants.ZERO_AMOUNT,
|
||||
bridgeData,
|
||||
)
|
||||
.awaitTransactionSuccessAsync({ gas: 6e6 }, { shouldValidate: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user