ERC20BridgeSampler: Additional Buy support (#2551)
* ERC20BridgeSampler: Sample Curve Buy * Fake Buy Kyber/PLP * Deploy mainnet * Add Kyber rates for buy tests * CHANGELOGs * Provide maxIterations and targetSlippage as options * Cleanup ERC20BridgeSampler for re-use * Redeploy Mainnet Kovan * Feedback fixes * Handle OOG/revert 0s * Redeploy Mainnet refactor
This commit is contained in:
@@ -65,7 +65,6 @@ interface ICurve {
|
||||
returns (uint256 dy);
|
||||
|
||||
/// @dev Get the amount of `fromToken` by buying `buyAmount` of `toToken`
|
||||
/// This function exists on later versions of Curve (USDC/DAI/USDT)
|
||||
/// @param i The token index being sold.
|
||||
/// @param j The token index being bought.
|
||||
/// @param buyAmount The amount of token being bought.
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
{
|
||||
"note": "Pass in `DevUtils` address as a constructor parameter",
|
||||
"pr": 2531
|
||||
},
|
||||
{
|
||||
"note": "Sample `Curve` for buy amounts",
|
||||
"pr": 2551
|
||||
},
|
||||
{
|
||||
"note": "Added `sampleBuysFromKyberNetwork` ",
|
||||
"pr": 2551
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -213,6 +213,33 @@ contract ERC20BridgeSampler is
|
||||
}
|
||||
}
|
||||
|
||||
/// @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[] memory makerTokenAmounts,
|
||||
FakeBuyOptions memory opts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
return _sampleApproximateBuysFromSource(
|
||||
takerToken,
|
||||
makerToken,
|
||||
makerTokenAmounts,
|
||||
opts,
|
||||
this.sampleSellsFromKyberNetwork.selector,
|
||||
address(0) // PLP registry address
|
||||
);
|
||||
}
|
||||
|
||||
/// @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).
|
||||
@@ -443,6 +470,44 @@ contract ERC20BridgeSampler is
|
||||
}
|
||||
}
|
||||
|
||||
/// @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[] 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) =
|
||||
curveAddress.staticcall.gas(CURVE_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
ICurve(0).get_dx_underlying.selector,
|
||||
fromTokenIdx,
|
||||
toTokenIdx,
|
||||
makerTokenAmounts[i]
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @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).
|
||||
@@ -500,52 +565,28 @@ contract ERC20BridgeSampler is
|
||||
/// @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[] memory makerTokenAmounts
|
||||
uint256[] memory makerTokenAmounts,
|
||||
FakeBuyOptions memory opts
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
// Initialize array of taker token amounts.
|
||||
uint256 numSamples = makerTokenAmounts.length;
|
||||
takerTokenAmounts = new uint256[](numSamples);
|
||||
|
||||
// Query registry for provider address.
|
||||
address providerAddress = getLiquidityProviderFromRegistry(
|
||||
registryAddress,
|
||||
return _sampleApproximateBuysFromSource(
|
||||
takerToken,
|
||||
makerToken
|
||||
makerToken,
|
||||
makerTokenAmounts,
|
||||
opts,
|
||||
this.sampleSellsFromLiquidityProviderRegistry.selector,
|
||||
registryAddress
|
||||
);
|
||||
// If provider doesn't exist, return all zeros.
|
||||
if (providerAddress == address(0)) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
// Otherwise, query liquidity provider for quotes.
|
||||
for (uint256 i = 0; i < numSamples; i++) {
|
||||
(bool didSucceed, bytes memory resultData) =
|
||||
providerAddress.staticcall.gas(DEFAULT_CALL_GAS)(
|
||||
abi.encodeWithSelector(
|
||||
ILiquidityProvider(0).getBuyQuote.selector,
|
||||
takerToken,
|
||||
makerToken,
|
||||
makerTokenAmounts[i]
|
||||
));
|
||||
uint256 sellAmount = 0;
|
||||
if (didSucceed) {
|
||||
sellAmount = abi.decode(resultData, (uint256));
|
||||
} else {
|
||||
// Exit early if the amount is too high for the liquidity provider to serve
|
||||
break;
|
||||
}
|
||||
takerTokenAmounts[i] = sellAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Returns the address of a liquidity provider for the given market
|
||||
@@ -639,4 +680,131 @@ contract ERC20BridgeSampler is
|
||||
{
|
||||
require(makerToken != takerToken, "ERC20BridgeSampler/INVALID_TOKEN_PAIR");
|
||||
}
|
||||
|
||||
function _sampleSellForApproximateBuy(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256 takerTokenAmount,
|
||||
bytes4 selector,
|
||||
address plpRegistryAddress
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 makerTokenAmount)
|
||||
{
|
||||
bytes memory callData;
|
||||
uint256[] memory tmpTakerAmounts = new uint256[](1);
|
||||
tmpTakerAmounts[0] = takerTokenAmount;
|
||||
if (selector == this.sampleSellsFromKyberNetwork.selector) {
|
||||
callData = abi.encodeWithSelector(
|
||||
this.sampleSellsFromKyberNetwork.selector,
|
||||
takerToken,
|
||||
makerToken,
|
||||
tmpTakerAmounts
|
||||
);
|
||||
} else {
|
||||
callData = abi.encodeWithSelector(
|
||||
this.sampleSellsFromLiquidityProviderRegistry.selector,
|
||||
plpRegistryAddress,
|
||||
takerToken,
|
||||
makerToken,
|
||||
tmpTakerAmounts
|
||||
);
|
||||
}
|
||||
(bool success, bytes memory resultData) = address(this).staticcall(callData);
|
||||
if (!success) {
|
||||
return 0;
|
||||
}
|
||||
// solhint-disable indent
|
||||
makerTokenAmount = abi.decode(resultData, (uint256[]))[0];
|
||||
}
|
||||
|
||||
function _sampleApproximateBuysFromSource(
|
||||
address takerToken,
|
||||
address makerToken,
|
||||
uint256[] memory makerTokenAmounts,
|
||||
FakeBuyOptions memory opts,
|
||||
bytes4 selector,
|
||||
address plpRegistryAddress
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256[] memory takerTokenAmounts)
|
||||
{
|
||||
_assertValidPair(makerToken, takerToken);
|
||||
if (makerTokenAmounts.length == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
uint256 sellAmount;
|
||||
uint256 buyAmount;
|
||||
uint256 slippageFromTarget;
|
||||
takerTokenAmounts = new uint256[](makerTokenAmounts.length);
|
||||
sellAmount = _sampleSellForApproximateBuy(
|
||||
makerToken,
|
||||
takerToken,
|
||||
makerTokenAmounts[0],
|
||||
selector,
|
||||
plpRegistryAddress
|
||||
);
|
||||
|
||||
if (sellAmount == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
buyAmount = _sampleSellForApproximateBuy(
|
||||
takerToken,
|
||||
makerToken,
|
||||
sellAmount,
|
||||
selector,
|
||||
plpRegistryAddress
|
||||
);
|
||||
if (buyAmount == 0) {
|
||||
return takerTokenAmounts;
|
||||
}
|
||||
|
||||
for (uint256 i = 0; i < makerTokenAmounts.length; i++) {
|
||||
for (uint256 iter = 0; iter < opts.maxIterations; iter++) {
|
||||
// adjustedSellAmount = previousSellAmount * (target/actual) * JUMP_MULTIPLIER
|
||||
sellAmount = LibMath.getPartialAmountCeil(
|
||||
makerTokenAmounts[i],
|
||||
buyAmount,
|
||||
sellAmount
|
||||
);
|
||||
sellAmount = LibMath.getPartialAmountCeil(
|
||||
(10000 + opts.targetSlippageBps),
|
||||
10000,
|
||||
sellAmount
|
||||
);
|
||||
uint256 _buyAmount = _sampleSellForApproximateBuy(
|
||||
takerToken,
|
||||
makerToken,
|
||||
sellAmount,
|
||||
selector,
|
||||
plpRegistryAddress
|
||||
);
|
||||
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 slippageFromTarget = (buyAmount - makerTokenAmounts[i]) * 10000 /
|
||||
makerTokenAmounts[i];
|
||||
if (slippageFromTarget <= opts.targetSlippageBps) {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ 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.
|
||||
@@ -73,6 +78,23 @@ interface IERC20BridgeSampler {
|
||||
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).
|
||||
@@ -106,7 +128,7 @@ interface IERC20BridgeSampler {
|
||||
/// @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.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromUniswap(
|
||||
@@ -121,7 +143,7 @@ interface IERC20BridgeSampler {
|
||||
/// @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.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
function sampleBuysFromEth2Dai(
|
||||
@@ -150,6 +172,23 @@ interface IERC20BridgeSampler {
|
||||
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).
|
||||
@@ -172,13 +211,16 @@ interface IERC20BridgeSampler {
|
||||
/// @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
|
||||
uint256[] calldata makerTokenAmounts,
|
||||
FakeBuyOptions calldata opts
|
||||
|
||||
)
|
||||
external
|
||||
view
|
||||
|
||||
@@ -32,6 +32,10 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
const INVALID_TOKEN_PAIR_ERROR = 'ERC20BridgeSampler/INVALID_TOKEN_PAIR';
|
||||
const MAKER_TOKEN = randomAddress();
|
||||
const TAKER_TOKEN = randomAddress();
|
||||
const FAKE_BUY_OPTS = {
|
||||
targetSlippageBps: new BigNumber(5),
|
||||
maxIterations: new BigNumber(5),
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
|
||||
@@ -169,8 +173,15 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
for (const source of sources) {
|
||||
const sampleOutputs = [];
|
||||
for (const amount of sampleAmounts) {
|
||||
if (source === 'Eth2Dai') {
|
||||
sampleOutputs.push(getDeterministicBuyQuote(ETH2DAI_SALT, sellToken, buyToken, amount));
|
||||
if (source === 'Kyber' || source === 'Eth2Dai') {
|
||||
sampleOutputs.push(
|
||||
getDeterministicBuyQuote(
|
||||
source === 'Kyber' ? KYBER_SALT : ETH2DAI_SALT,
|
||||
sellToken,
|
||||
buyToken,
|
||||
amount,
|
||||
),
|
||||
);
|
||||
} else if (source === 'Uniswap') {
|
||||
sampleOutputs.push(getDeterministicUniswapBuyQuote(sellToken, buyToken, amount));
|
||||
}
|
||||
@@ -204,7 +215,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
|
||||
function getSampleAmounts(tokenAddress: string, count?: number): BigNumber[] {
|
||||
const tokenDecimals = getDeterministicTokenDecimals(tokenAddress);
|
||||
const _upperLimit = getRandomPortion(getRandomInteger(1, 1000).times(10 ** tokenDecimals));
|
||||
const _upperLimit = getRandomPortion(getRandomInteger(1000, 50000).times(10 ** tokenDecimals));
|
||||
const _count = count || _.random(1, 16);
|
||||
const d = _upperLimit.div(_count);
|
||||
return _.times(_count, i => d.times((i + 1) / _count).integerValue());
|
||||
@@ -330,7 +341,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it('can quote token - token', async () => {
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicSellQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
@@ -388,6 +399,108 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('sampleBuysFromKyberNetwork()', () => {
|
||||
const ACCEPTABLE_SLIPPAGE = 0.0005;
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
});
|
||||
|
||||
it('throws if tokens are the same', async () => {
|
||||
const tx = testContract.sampleBuysFromKyberNetwork(MAKER_TOKEN, MAKER_TOKEN, [], FAKE_BUY_OPTS).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();
|
||||
expect(quotes).to.deep.eq([]);
|
||||
});
|
||||
const expectQuotesWithinRange = (
|
||||
quotes: BigNumber[],
|
||||
expectedQuotes: BigNumber[],
|
||||
maxSlippage: BigNumber | number,
|
||||
) => {
|
||||
quotes.forEach((_q, i) => {
|
||||
// If we're within 1 base unit of a low decimal token
|
||||
// then that's as good as we're going to get (and slippage is "high")
|
||||
if (
|
||||
expectedQuotes[i].isZero() ||
|
||||
BigNumber.max(expectedQuotes[i], quotes[i])
|
||||
.minus(BigNumber.min(expectedQuotes[i], quotes[i]))
|
||||
.eq(1)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const slippage = quotes[i]
|
||||
.dividedBy(expectedQuotes[i])
|
||||
.minus(1)
|
||||
.decimalPlaces(4);
|
||||
expect(slippage, `quote[${i}]: ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.gte(0);
|
||||
expect(slippage, `quote[${i}] ${slippage} ${quotes[i]} ${expectedQuotes[i]}`).to.be.bignumber.lte(
|
||||
new BigNumber(maxSlippage),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
it('can quote token -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, MAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE);
|
||||
});
|
||||
|
||||
it('returns zero if token -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, MAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote token -> ETH', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(TAKER_TOKEN, WETH_ADDRESS, ['Kyber'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE);
|
||||
});
|
||||
|
||||
it('returns zero if token -> ETH fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(TAKER_TOKEN, WETH_ADDRESS, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
|
||||
it('can quote ETH -> token', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const [expectedQuotes] = getDeterministicBuyQuotes(WETH_ADDRESS, TAKER_TOKEN, ['Kyber'], sampleAmounts);
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
expectQuotesWithinRange(quotes, expectedQuotes, ACCEPTABLE_SLIPPAGE);
|
||||
});
|
||||
|
||||
it('returns zero if ETH -> token fails', async () => {
|
||||
const sampleAmounts = getSampleAmounts(TAKER_TOKEN);
|
||||
const expectedQuotes = _.times(sampleAmounts.length, () => constants.ZERO_AMOUNT);
|
||||
await enableFailTriggerAsync();
|
||||
const quotes = await testContract
|
||||
.sampleBuysFromKyberNetwork(WETH_ADDRESS, TAKER_TOKEN, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
expect(quotes).to.deep.eq(expectedQuotes);
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('sampleSellsFromEth2Dai()', () => {
|
||||
before(async () => {
|
||||
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
|
||||
@@ -773,7 +886,13 @@ 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)
|
||||
.sampleBuysFromLiquidityProviderRegistry(
|
||||
registryContract.address,
|
||||
yAsset,
|
||||
xAsset,
|
||||
sampleAmounts,
|
||||
FAKE_BUY_OPTS,
|
||||
)
|
||||
.callAsync();
|
||||
result.forEach((value, idx) => {
|
||||
expect(value).is.bignumber.eql(sampleAmounts[idx].plus(1));
|
||||
@@ -787,6 +906,7 @@ blockchainTests('erc20-bridge-sampler', env => {
|
||||
yAsset,
|
||||
randomAddress(),
|
||||
sampleAmounts,
|
||||
FAKE_BUY_OPTS,
|
||||
)
|
||||
.callAsync();
|
||||
result.forEach(value => {
|
||||
@@ -796,7 +916,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)
|
||||
.sampleBuysFromLiquidityProviderRegistry(randomAddress(), yAsset, xAsset, sampleAmounts, FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
result.forEach(value => {
|
||||
expect(value).is.bignumber.eql(constants.ZERO_AMOUNT);
|
||||
|
||||
@@ -2,37 +2,97 @@ import { artifacts, ERC20BridgeSamplerContract } from '@0x/contracts-erc20-bridg
|
||||
import { blockchainTests, constants, describe, expect, toBaseUnitAmount } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
export const VB = '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b';
|
||||
blockchainTests.configure({
|
||||
fork: {
|
||||
unlockedAccounts: [VB],
|
||||
},
|
||||
});
|
||||
|
||||
blockchainTests.fork.resets('Mainnet Sampler Tests', env => {
|
||||
let testContract: ERC20BridgeSamplerContract;
|
||||
before(async () => {
|
||||
testContract = await ERC20BridgeSamplerContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ERC20BridgeSampler,
|
||||
env.provider,
|
||||
{ ...env.txDefaults, from: '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b' },
|
||||
{ ...env.txDefaults, from: VB },
|
||||
{},
|
||||
constants.NULL_ADDRESS,
|
||||
);
|
||||
});
|
||||
describe('Curve', () => {
|
||||
const CURVE_ADDRESS = '0xa2b47e3d5c44877cca798226b7b8118f9bfb7a56';
|
||||
const DAI_TOKEN_INDEX = new BigNumber(0);
|
||||
const USDC_TOKEN_INDEX = new BigNumber(1);
|
||||
|
||||
describe('sampleSellsFromCurve()', () => {
|
||||
const curveAddress = '0x45f783cce6b7ff23b2ab2d70e416cdb7d6055f51';
|
||||
const daiTokenIdx = new BigNumber(0);
|
||||
const usdcTokenIdx = new BigNumber(1);
|
||||
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)])
|
||||
.callAsync();
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
});
|
||||
|
||||
it('samples sells from Curve DAI->USDC', async () => {
|
||||
const samples = await testContract
|
||||
.sampleSellsFromCurve(curveAddress, daiTokenIdx, usdcTokenIdx, [toBaseUnitAmount(1)])
|
||||
.callAsync();
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
it('samples sells from Curve USDC->DAI', async () => {
|
||||
const samples = await testContract
|
||||
.sampleSellsFromCurve(CURVE_ADDRESS, USDC_TOKEN_INDEX, DAI_TOKEN_INDEX, [toBaseUnitAmount(1, 6)])
|
||||
.callAsync();
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('samples sells from Curve USDC->DAI', async () => {
|
||||
const samples = await testContract
|
||||
.sampleSellsFromCurve(curveAddress, usdcTokenIdx, daiTokenIdx, [toBaseUnitAmount(1, 6)])
|
||||
.callAsync();
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
describe('sampleBuysFromCurve()', () => {
|
||||
it('samples buys from Curve DAI->USDC', async () => {
|
||||
// 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)])
|
||||
.callAsync();
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
});
|
||||
|
||||
it('samples buys from Curve USDC->DAI', async () => {
|
||||
// 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)])
|
||||
.callAsync();
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Kyber', () => {
|
||||
const FAKE_BUY_OPTS = {
|
||||
targetSlippageBps: new BigNumber(5),
|
||||
maxIterations: new BigNumber(5),
|
||||
};
|
||||
const DAI_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
|
||||
const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
|
||||
|
||||
describe('sampleBuysFromKyber()', () => {
|
||||
it('samples buys from Kyber WETH->DAI', async () => {
|
||||
// From ETH to DAI
|
||||
// I want to buy 1 DAI
|
||||
const samples = await testContract
|
||||
.sampleBuysFromKyberNetwork(WETH_ADDRESS, DAI_ADDRESS, [toBaseUnitAmount(1)], FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
});
|
||||
|
||||
it('samples buys from Kyber DAI->WETH', async () => {
|
||||
// From USDC to DAI
|
||||
// I want to buy 1 WETH
|
||||
const samples = await testContract
|
||||
.sampleBuysFromKyberNetwork(DAI_ADDRESS, WETH_ADDRESS, [toBaseUnitAmount(1)], FAKE_BUY_OPTS)
|
||||
.callAsync();
|
||||
expect(samples.length).to.be.bignumber.greaterThan(0);
|
||||
expect(samples[0]).to.be.bignumber.greaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user