@0x/contracts-erc20-bridge-sampler: Increase gas forwarded to kyber and eth2dai.

`@0x/contracts-erc20-bridge-sampler`: Bail as soon as any quote from a DEX fails.

`@0x/contracts-erc20-bridge-sampler`: Fix broken tests.
This commit is contained in:
Lawrence Forman 2020-01-15 16:49:06 -05:00 committed by Jacob Evans
parent 7f56091fbd
commit 0cf3ff8209
No known key found for this signature in database
GPG Key ID: 2036DA2ADDFB0842
4 changed files with 49 additions and 25 deletions

View File

@ -5,6 +5,10 @@
{ {
"note": "Add batch functions to query quotes", "note": "Add batch functions to query quotes",
"pr": 2427 "pr": 2427
},
{
"note": "Early exit if a DEX sample fails",
"pr": 2427
} }
] ]
}, },

View File

@ -37,9 +37,9 @@ contract ERC20BridgeSampler is
DeploymentConstants DeploymentConstants
{ {
bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)")); bytes4 constant internal ERC20_PROXY_ID = 0xf47261b0; // bytes4(keccak256("ERC20Token(address)"));
uint256 constant internal KYBER_SAMPLE_CALL_GAS = 600e3; uint256 constant internal KYBER_SAMPLE_CALL_GAS = 1500e3;
uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3; uint256 constant internal UNISWAP_SAMPLE_CALL_GAS = 150e3;
uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 250e3; uint256 constant internal ETH2DAI_SAMPLE_CALL_GAS = 1000e3;
/// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once. /// @dev Query batches of native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Batches of Native orders to query. /// @param orders Batches of Native orders to query.
@ -105,7 +105,6 @@ contract ERC20BridgeSampler is
} }
} }
/// @dev Query native orders and sample sell quotes on multiple DEXes at once. /// @dev Query native orders and sample sell quotes on multiple DEXes at once.
/// @param orders Native orders to query. /// @param orders Native orders to query.
/// @param orderSignatures Signatures for each respective order in `orders`. /// @param orderSignatures Signatures for each respective order in `orders`.
@ -346,6 +345,8 @@ contract ERC20BridgeSampler is
uint256 rate = 0; uint256 rate = 0;
if (didSucceed) { if (didSucceed) {
rate = abi.decode(resultData, (uint256)); rate = abi.decode(resultData, (uint256));
} else {
break;
} }
makerTokenAmounts[i] = makerTokenAmounts[i] =
rate * rate *
@ -386,6 +387,8 @@ contract ERC20BridgeSampler is
uint256 buyAmount = 0; uint256 buyAmount = 0;
if (didSucceed) { if (didSucceed) {
buyAmount = abi.decode(resultData, (uint256)); buyAmount = abi.decode(resultData, (uint256));
} else{
break;
} }
makerTokenAmounts[i] = buyAmount; makerTokenAmounts[i] = buyAmount;
} }
@ -421,6 +424,8 @@ contract ERC20BridgeSampler is
uint256 sellAmount = 0; uint256 sellAmount = 0;
if (didSucceed) { if (didSucceed) {
sellAmount = abi.decode(resultData, (uint256)); sellAmount = abi.decode(resultData, (uint256));
} else {
break;
} }
takerTokenAmounts[i] = sellAmount; takerTokenAmounts[i] = sellAmount;
} }
@ -449,26 +454,28 @@ contract ERC20BridgeSampler is
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
bool didSucceed = true;
if (makerToken == _getWethAddress()) { if (makerToken == _getWethAddress()) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction( (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange), address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector, takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
} else if (takerToken == _getWethAddress()) { } else if (takerToken == _getWethAddress()) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction( (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange), address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector, makerTokenExchange.getEthToTokenInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
} else { } else {
uint256 ethBought = _callUniswapExchangePriceFunction( uint256 ethBought;
(ethBought, didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange), address(takerTokenExchange),
takerTokenExchange.getTokenToEthInputPrice.selector, takerTokenExchange.getTokenToEthInputPrice.selector,
takerTokenAmounts[i] takerTokenAmounts[i]
); );
if (ethBought != 0) { if (ethBought != 0) {
makerTokenAmounts[i] = _callUniswapExchangePriceFunction( (makerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange), address(makerTokenExchange),
makerTokenExchange.getEthToTokenInputPrice.selector, makerTokenExchange.getEthToTokenInputPrice.selector,
ethBought ethBought
@ -477,6 +484,9 @@ contract ERC20BridgeSampler is
makerTokenAmounts[i] = 0; makerTokenAmounts[i] = 0;
} }
} }
if (!didSucceed) {
break;
}
} }
} }
@ -503,26 +513,28 @@ contract ERC20BridgeSampler is
IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ? IUniswapExchangeQuotes makerTokenExchange = makerToken == _getWethAddress() ?
IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken); IUniswapExchangeQuotes(0) : _getUniswapExchange(makerToken);
for (uint256 i = 0; i < numSamples; i++) { for (uint256 i = 0; i < numSamples; i++) {
bool didSucceed = true;
if (makerToken == _getWethAddress()) { if (makerToken == _getWethAddress()) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction( (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange), address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector, takerTokenExchange.getTokenToEthOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
} else if (takerToken == _getWethAddress()) { } else if (takerToken == _getWethAddress()) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction( (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange), address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector, makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
} else { } else {
uint256 ethSold = _callUniswapExchangePriceFunction( uint256 ethSold;
(ethSold, didSucceed) = _callUniswapExchangePriceFunction(
address(makerTokenExchange), address(makerTokenExchange),
makerTokenExchange.getEthToTokenOutputPrice.selector, makerTokenExchange.getEthToTokenOutputPrice.selector,
makerTokenAmounts[i] makerTokenAmounts[i]
); );
if (ethSold != 0) { if (ethSold != 0) {
takerTokenAmounts[i] = _callUniswapExchangePriceFunction( (takerTokenAmounts[i], didSucceed) = _callUniswapExchangePriceFunction(
address(takerTokenExchange), address(takerTokenExchange),
takerTokenExchange.getTokenToEthOutputPrice.selector, takerTokenExchange.getTokenToEthOutputPrice.selector,
ethSold ethSold
@ -531,6 +543,9 @@ contract ERC20BridgeSampler is
takerTokenAmounts[i] = 0; takerTokenAmounts[i] = 0;
} }
} }
if (!didSucceed) {
break;
}
} }
} }
@ -558,12 +573,13 @@ contract ERC20BridgeSampler is
) )
private private
view view
returns (uint256 outputAmount) returns (uint256 outputAmount, bool didSucceed)
{ {
if (uniswapExchangeAddress == address(0)) { if (uniswapExchangeAddress == address(0)) {
return 0; return (outputAmount, didSucceed);
} }
(bool didSucceed, bytes memory resultData) = bytes memory resultData;
(didSucceed, resultData) =
uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)( uniswapExchangeAddress.staticcall.gas(UNISWAP_SAMPLE_CALL_GAS)(
abi.encodeWithSelector( abi.encodeWithSelector(
functionSelector, functionSelector,

View File

@ -338,7 +338,11 @@ contract TestERC20BridgeSampler is
bytes32 orderHash = keccak256(abi.encode(order.salt)); bytes32 orderHash = keccak256(abi.encode(order.salt));
// Everything else is derived from the hash. // Everything else is derived from the hash.
orderInfo.orderHash = orderHash; orderInfo.orderHash = orderHash;
orderInfo.orderStatus = LibOrder.OrderStatus(uint256(orderHash) % MAX_ORDER_STATUS); if (uint256(orderHash) % 100 > 90) {
orderInfo.orderStatus = LibOrder.OrderStatus.FULLY_FILLED;
} else {
orderInfo.orderStatus = LibOrder.OrderStatus.FILLABLE;
}
orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount; orderInfo.orderTakerAssetFilledAmount = uint256(orderHash) % order.takerAssetAmount;
fillableTakerAssetAmount = fillableTakerAssetAmount =
order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount; order.takerAssetAmount - orderInfo.orderTakerAssetFilledAmount;

View File

@ -195,7 +195,7 @@ blockchainTests('erc20-bridge-sampler', env => {
function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber { function getDeterministicFillableTakerAssetAmount(order: Order): BigNumber {
const hash = getPackedHash(hexUtils.toHex(order.salt, 32)); const hash = getPackedHash(hexUtils.toHex(order.salt, 32));
const orderStatus = new BigNumber(hash).mod(255).toNumber(); const orderStatus = new BigNumber(hash).mod(100).toNumber() > 90 ? 5 : 3;
const isValidSignature = !!new BigNumber(hash).mod(2).toNumber(); const isValidSignature = !!new BigNumber(hash).mod(2).toNumber();
if (orderStatus !== 3 || !isValidSignature) { if (orderStatus !== 3 || !isValidSignature) {
return constants.ZERO_AMOUNT; return constants.ZERO_AMOUNT;
@ -208,7 +208,7 @@ blockchainTests('erc20-bridge-sampler', env => {
return order.makerAssetAmount return order.makerAssetAmount
.times(takerAmount) .times(takerAmount)
.div(order.takerAssetAmount) .div(order.takerAssetAmount)
.integerValue(BigNumber.ROUND_DOWN); .integerValue(BigNumber.ROUND_UP);
} }
function getERC20AssetData(tokenAddress: string): string { function getERC20AssetData(tokenAddress: string): string {
@ -255,7 +255,7 @@ blockchainTests('erc20-bridge-sampler', env => {
describe('getOrderFillableTakerAssetAmounts()', () => { describe('getOrderFillableTakerAssetAmounts()', () => {
it('returns the expected amount for each order', async () => { it('returns the expected amount for each order', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const signatures: string[] = _.times(orders.length, hexUtils.random); const signatures: string[] = _.times(orders.length, i => hexUtils.random());
const expected = orders.map(getDeterministicFillableTakerAssetAmount); const expected = orders.map(getDeterministicFillableTakerAssetAmount);
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync(); const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq(expected); expect(actual).to.deep.eq(expected);
@ -269,7 +269,7 @@ blockchainTests('erc20-bridge-sampler', env => {
it('returns zero for an order with zero maker asset amount', async () => { it('returns zero for an order with zero maker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].makerAssetAmount = constants.ZERO_AMOUNT; orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random); const signatures: string[] = _.times(orders.length, i => hexUtils.random());
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync(); const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
}); });
@ -277,7 +277,7 @@ blockchainTests('erc20-bridge-sampler', env => {
it('returns zero for an order with zero taker asset amount', async () => { it('returns zero for an order with zero taker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].takerAssetAmount = constants.ZERO_AMOUNT; orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random); const signatures: string[] = _.times(orders.length, i => hexUtils.random());
const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync(); const actual = await testContract.getOrderFillableTakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
}); });
@ -293,7 +293,7 @@ blockchainTests('erc20-bridge-sampler', env => {
describe('getOrderFillableMakerAssetAmounts()', () => { describe('getOrderFillableMakerAssetAmounts()', () => {
it('returns the expected amount for each order', async () => { it('returns the expected amount for each order', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const signatures: string[] = _.times(orders.length, hexUtils.random); const signatures: string[] = _.times(orders.length, i => hexUtils.random());
const expected = orders.map(getDeterministicFillableMakerAssetAmount); const expected = orders.map(getDeterministicFillableMakerAssetAmount);
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync(); const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq(expected); expect(actual).to.deep.eq(expected);
@ -307,7 +307,7 @@ blockchainTests('erc20-bridge-sampler', env => {
it('returns zero for an order with zero maker asset amount', async () => { it('returns zero for an order with zero maker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].makerAssetAmount = constants.ZERO_AMOUNT; orders[0].makerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random); const signatures: string[] = _.times(orders.length, i => hexUtils.random());
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync(); const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
}); });
@ -315,7 +315,7 @@ blockchainTests('erc20-bridge-sampler', env => {
it('returns zero for an order with zero taker asset amount', async () => { it('returns zero for an order with zero taker asset amount', async () => {
const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1); const orders = createOrders(MAKER_TOKEN, TAKER_TOKEN, 1);
orders[0].takerAssetAmount = constants.ZERO_AMOUNT; orders[0].takerAssetAmount = constants.ZERO_AMOUNT;
const signatures: string[] = _.times(orders.length, hexUtils.random); const signatures: string[] = _.times(orders.length, i => hexUtils.random());
const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync(); const actual = await testContract.getOrderFillableMakerAssetAmounts(orders, signatures).callAsync();
expect(actual).to.deep.eq([constants.ZERO_AMOUNT]); expect(actual).to.deep.eq([constants.ZERO_AMOUNT]);
}); });
@ -330,7 +330,7 @@ blockchainTests('erc20-bridge-sampler', env => {
describe('queryOrdersAndSampleSells()', () => { describe('queryOrdersAndSampleSells()', () => {
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN); const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random); const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random());
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();
@ -436,7 +436,7 @@ blockchainTests('erc20-bridge-sampler', env => {
describe('queryOrdersAndSampleBuys()', () => { describe('queryOrdersAndSampleBuys()', () => {
const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN); const ORDERS = createOrders(MAKER_TOKEN, TAKER_TOKEN);
const SIGNATURES: string[] = _.times(ORDERS.length, hexUtils.random); const SIGNATURES: string[] = _.times(ORDERS.length, i => hexUtils.random());
before(async () => { before(async () => {
await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync(); await testContract.createTokenExchanges([MAKER_TOKEN, TAKER_TOKEN]).awaitTransactionSuccessAsync();