diff --git a/contracts/BatchExchangeViewer.sol b/contracts/BatchExchangeViewer.sol index 782eb5c3f..249fd14c9 100644 --- a/contracts/BatchExchangeViewer.sol +++ b/contracts/BatchExchangeViewer.sol @@ -8,6 +8,7 @@ contract BatchExchangeViewer { using BytesLib for bytes; uint8 public constant AUCTION_ELEMENT_WIDTH = 112; + uint16[] public ALL_TOKEN_FILTER; BatchExchange batchExchange; @@ -16,54 +17,77 @@ contract BatchExchangeViewer { } /** @dev Queries the orderbook for the auction that is still accepting orders + * @param tokenFilter all returned order will have buy *and* sell token from this list (leave empty for "no filter") * @return encoded bytes representing orders */ - function getOpenOrderBook() public view returns (bytes memory) { - (bytes memory elements, , ) = getOpenOrderBookPaginated(address(0), 0, uint16(-1)); + function getOpenOrderBook(address[] memory tokenFilter) public view returns (bytes memory) { + (bytes memory elements, , ) = getOpenOrderBookPaginated(tokenFilter, address(0), 0, uint16(-1)); return elements; } /** @dev Queries a page of the orderbook for the auction that is still accepting orders + * @param tokenFilter all returned order will have buy *and* sell token from this list (leave empty for "no filter") * @param previousPageUser address taken from nextPageUser return value from last page (address(0) for first page) * @param previousPageUserOffset offset taken nextPageUserOffset return value from last page (0 for first page) * @param pageSize count of elements to be returned per page (same value is used for subqueries on the exchange) * @return encoded bytes representing orders and page information for next page */ - function getOpenOrderBookPaginated(address previousPageUser, uint16 previousPageUserOffset, uint16 pageSize) - public - view - returns (bytes memory elements, address nextPageUser, uint16 nextPageUserOffset) - { + function getOpenOrderBookPaginated( + address[] memory tokenFilter, + address previousPageUser, + uint16 previousPageUserOffset, + uint16 pageSize + ) public view returns (bytes memory elements, address nextPageUser, uint16 nextPageUserOffset) { uint32 batch = batchExchange.getCurrentBatchId(); - return getEncodedOrdersPaginated(batch, batch, previousPageUser, previousPageUserOffset, pageSize); + return + getEncodedOrdersPaginated( + batch, + batch, + getTokenIdsFromAdresses(tokenFilter), + previousPageUser, + previousPageUserOffset, + pageSize + ); } /** @dev Queries the orderbook for the auction that is currently being solved + * @param tokenFilter all returned order will have buy *and* sell token from this list (leave empty for "no filter") * @return encoded bytes representing orders */ - function getFinalizedOrderBook() public view returns (bytes memory) { - (bytes memory elements, , ) = getFinalizedOrderBookPaginated(address(0), 0, uint16(-1)); + function getFinalizedOrderBook(address[] memory tokenFilter) public view returns (bytes memory) { + (bytes memory elements, , ) = getFinalizedOrderBookPaginated(tokenFilter, address(0), 0, uint16(-1)); return elements; } /** @dev Queries a page of the orderbook for the auction that is currently being solved + * @param tokenFilter all returned order will have buy *and* sell token from this list (leave empty for "no filter") * @param previousPageUser address taken from nextPageUser return value from last page (address(0) for first page) * @param previousPageUserOffset offset taken nextPageUserOffset return value from last page (0 for first page) * @param pageSize count of elements to be returned per page (same value is used for subqueries on the exchange) * @return encoded bytes representing orders and page information for next page */ - function getFinalizedOrderBookPaginated(address previousPageUser, uint16 previousPageUserOffset, uint16 pageSize) - public - view - returns (bytes memory elements, address nextPageUser, uint16 nextPageUserOffset) - { + function getFinalizedOrderBookPaginated( + address[] memory tokenFilter, + address previousPageUser, + uint16 previousPageUserOffset, + uint16 pageSize + ) public view returns (bytes memory elements, address nextPageUser, uint16 nextPageUserOffset) { uint32 batch = batchExchange.getCurrentBatchId(); - return getEncodedOrdersPaginated(batch - 1, batch - 1, previousPageUser, previousPageUserOffset, pageSize); + return + getEncodedOrdersPaginated( + batch - 1, + batch - 1, + getTokenIdsFromAdresses(tokenFilter), + previousPageUser, + previousPageUserOffset, + pageSize + ); } /** @dev Queries a page in the list of all orders * @param maxValidFrom all returned orders will have a validFrom <= this value (they were placed at or before that batch) * @param minValidUntil all returned orders will have a validUntil >= this value (validity ends at or after that batch) + * @param tokenFilter all returned order will have buy *and* sell token from this list (leave empty for "no filter") * @param previousPageUser address taken from nextPageUser return value from last page (address(0) for first page) * @param previousPageUserOffset offset taken nextPageUserOffset return value from last page (0 for first page) * @param pageSize count of elements to be returned per page (same value is used for subqueries on the exchange) @@ -72,6 +96,7 @@ contract BatchExchangeViewer { function getEncodedOrdersPaginated( uint32 maxValidFrom, uint32 minValidUntil, + uint16[] memory tokenFilter, address previousPageUser, uint16 previousPageUserOffset, uint16 pageSize @@ -84,7 +109,11 @@ contract BatchExchangeViewer { hasNextPage = unfiltered.length / AUCTION_ELEMENT_WIDTH == pageSize; for (uint16 index = 0; index < unfiltered.length / AUCTION_ELEMENT_WIDTH; index++) { bytes memory element = unfiltered.slice(index * AUCTION_ELEMENT_WIDTH, AUCTION_ELEMENT_WIDTH); - if (maxValidFrom >= getValidFrom(element) && minValidUntil <= getValidUntil(element)) { + if ( + maxValidFrom >= getValidFrom(element) && + minValidUntil <= getValidUntil(element) && + matchesTokenFilter(getBuyToken(element), getSellToken(element), tokenFilter) + ) { elements = elements.concat(element); } // Update pagination info @@ -104,11 +133,38 @@ contract BatchExchangeViewer { return (elements, nextPageUser, nextPageUserOffset); } + function matchesTokenFilter(uint16 buyToken, uint16 sellToken, uint16[] memory filter) public pure returns (bool) { + // An empty filter is interpreted as "select all" + if (filter.length == 0) { + return true; + } + (bool foundBuyToken, bool foundSellToken) = (false, false); + for (uint256 index = 0; index < filter.length; index++) { + if (filter[index] == buyToken) { + foundBuyToken = true; + } + if (filter[index] == sellToken) { + foundSellToken = true; + } + } + return foundBuyToken && foundSellToken; + } + function getUser(bytes memory element) public pure returns (address) { bytes memory slice = element.slice(0, 20); return slice.toAddress(0); } + function getBuyToken(bytes memory element) public pure returns (uint16) { + bytes memory slice = element.slice(52, 2); + return slice.toUint16(0); + } + + function getSellToken(bytes memory element) public pure returns (uint16) { + bytes memory slice = element.slice(54, 2); + return slice.toUint16(0); + } + function getValidFrom(bytes memory element) public pure returns (uint32) { bytes memory slice = element.slice(56, 4); return slice.toUint32(0); @@ -118,4 +174,12 @@ contract BatchExchangeViewer { bytes memory slice = element.slice(60, 4); return slice.toUint32(0); } + + function getTokenIdsFromAdresses(address[] memory tokenIds) public view returns (uint16[] memory) { + uint16[] memory result = new uint16[](tokenIds.length); + for (uint256 index = 0; index < tokenIds.length; index++) { + result[index] = batchExchange.tokenAddressToIdMap(tokenIds[index]); + } + return result; + } } diff --git a/test/stablex/batch_exchange_viewer.js b/test/stablex/batch_exchange_viewer.js index 1c05cee88..2170c46d8 100644 --- a/test/stablex/batch_exchange_viewer.js +++ b/test/stablex/batch_exchange_viewer.js @@ -41,7 +41,7 @@ contract("BatchExchangeViewer", accounts => { ) const viewer = await BatchExchangeViewer.new(batchExchange.address) - const result = decodeAuctionElements(await viewer.getOpenOrderBook()) + const result = decodeAuctionElements(await viewer.getOpenOrderBook([])) assert.equal(result.filter(e => e.validFrom == batchId).length, 10) }) it("can be queried with pagination", async () => { @@ -64,11 +64,33 @@ contract("BatchExchangeViewer", accounts => { ) const viewer = await BatchExchangeViewer.new(batchExchange.address) - const result = await viewer.getOpenOrderBookPaginated(zero_address, 0, 5) + const result = await viewer.getOpenOrderBookPaginated([], zero_address, 0, 5) assert.equal(decodeAuctionElements(result.elements).filter(e => e.validFrom == batchId).length, 5) assert.equal(result.nextPageUser, accounts[0]) assert.equal(result.nextPageUserOffset, 15) }) + it("can filter a token pair", async () => { + const batchId = await batchExchange.getCurrentBatchId() + await batchExchange.placeValidFromOrders( + Array(3).fill(0), //buyToken + Array(3).fill(1), //sellToken + Array(3).fill(batchId), //validFrom + Array(3).fill(batchId), //validTo + Array(3).fill(0), //buyAmounts + Array(3).fill(0) //sellAmounts + ) + await batchExchange.placeValidFromOrders( + Array(5).fill(1), //buyToken + Array(5).fill(2), //sellToken + Array(5).fill(batchId), //validFrom + Array(5).fill(batchId), //validTo + Array(5).fill(0), //buyAmounts + Array(5).fill(0) //sellAmounts + ) + const viewer = await BatchExchangeViewer.new(batchExchange.address) + const result = decodeAuctionElements(await viewer.getOpenOrderBook([token_1.address, token_2.address])) + assert.equal(result.filter(e => e.validFrom == batchId).length, 5) + }) }) describe("getFinalizedOrderBook", () => { @@ -95,7 +117,7 @@ contract("BatchExchangeViewer", accounts => { await closeAuction(batchExchange) const viewer = await BatchExchangeViewer.new(batchExchange.address) - const result = decodeAuctionElements(await viewer.getFinalizedOrderBook()) + const result = decodeAuctionElements(await viewer.getFinalizedOrderBook([])) assert.equal(result.filter(e => e.validFrom == batchId).length, 10) }) it("can be queried with pagination", async () => { @@ -121,10 +143,36 @@ contract("BatchExchangeViewer", accounts => { await closeAuction(batchExchange) const viewer = await BatchExchangeViewer.new(batchExchange.address) - const result = await viewer.getFinalizedOrderBookPaginated(zero_address, 0, 5) + const result = await viewer.getFinalizedOrderBookPaginated([], zero_address, 0, 5) assert.equal(decodeAuctionElements(result.elements).filter(e => e.validFrom == batchId).length, 5) assert.equal(result.nextPageUser, accounts[0]) assert.equal(result.nextPageUserOffset, 15) }) + it("can filter a token pair", async () => { + const batchId = await batchExchange.getCurrentBatchId() + await batchExchange.placeValidFromOrders( + Array(3).fill(0), //buyToken + Array(3).fill(1), //sellToken + Array(3).fill(batchId), //validFrom + Array(3).fill(batchId), //validTo + Array(3).fill(0), //buyAmounts + Array(3).fill(0) //sellAmounts + ) + await batchExchange.placeValidFromOrders( + Array(5).fill(1), //buyToken + Array(5).fill(2), //sellToken + Array(5).fill(batchId), //validFrom + Array(5).fill(batchId), //validTo + Array(5).fill(0), //buyAmounts + Array(5).fill(0) //sellAmounts + ) + + // finalize order book + await closeAuction(batchExchange) + + const viewer = await BatchExchangeViewer.new(batchExchange.address) + const result = decodeAuctionElements(await viewer.getFinalizedOrderBook([token_1.address, token_2.address])) + assert.equal(result.filter(e => e.validFrom == batchId).length, 5) + }) }) })