-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[BatchExchangeViewer] Wrapper contract to show current order book (#535)
Part of #528 This PR introduces a BatchExchange wrapper contract only consisting of view functions. The goal is for this contract to allow slicing and dicing of the on chain data while being a completely separate and thus easy to change and update as we see fit. It can also be serve as a code sharing facility between our multi-language repos (python, rust, js, etc). As a first step I introduce two functions in paginated and unpaginated form that both fetch the current order book in all dimensions. One fetches the one that is currently still open for order submission, the other one fetches the orderbook that is currently being solved. In the next step I will introduce a filtering option to get the orderbook only for certain tokens. cc @anxolin @alfetopito @W3stside @Velenir as this might be useful on the frontend as well. ### Test Plan Updated unit tests. To use the wrapper on rinkeby and fetch the entire order book in a single query do the following: 1. `export PK=< your rinkeby eth funded private key >` 2. `yarn prepack` 3. `npx truffle migrate -f 4 --network rinkeby` 4. `npx truffle console --network rinkeby` ```js const zero_address = "0x0000000000000000000000000000000000000000" const viewer = await BatchExchangeViewer.deployed() result = await viewer.contract.methods.getFinalizedOrderBookPaginated(zero_address, 0, 250).call({gas: 100000000}) result.elements.length / 112 / 2 // should be less than 250 ```
- Loading branch information
Showing
3 changed files
with
258 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
pragma solidity ^0.5.0; | ||
|
||
import "solidity-bytes-utils/contracts/BytesLib.sol"; | ||
import "./BatchExchange.sol"; | ||
|
||
|
||
contract BatchExchangeViewer { | ||
using BytesLib for bytes; | ||
|
||
uint8 public constant AUCTION_ELEMENT_WIDTH = 112; | ||
|
||
BatchExchange batchExchange; | ||
|
||
constructor(BatchExchange exchange) public { | ||
batchExchange = exchange; | ||
} | ||
|
||
/** @dev Queries the orderbook for the auction that is still accepting orders | ||
* @return encoded bytes representing orders | ||
*/ | ||
function getOpenOrderBook() public view returns (bytes memory) { | ||
(bytes memory elements, , ) = getOpenOrderBookPaginated(address(0), 0, uint16(-1)); | ||
return elements; | ||
} | ||
|
||
/** @dev Queries a page of the orderbook for the auction that is still accepting orders | ||
* @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) | ||
{ | ||
uint32 batch = batchExchange.getCurrentBatchId(); | ||
return getEncodedOrdersPaginated(batch, batch, previousPageUser, previousPageUserOffset, pageSize); | ||
} | ||
|
||
/** @dev Queries the orderbook for the auction that is currently being solved | ||
* @return encoded bytes representing orders | ||
*/ | ||
function getFinalizedOrderBook() public view returns (bytes memory) { | ||
(bytes memory elements, , ) = getFinalizedOrderBookPaginated(address(0), 0, uint16(-1)); | ||
return elements; | ||
} | ||
|
||
/** @dev Queries a page of the orderbook for the auction that is currently being solved | ||
* @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) | ||
{ | ||
uint32 batch = batchExchange.getCurrentBatchId(); | ||
return getEncodedOrdersPaginated(batch - 1, batch - 1, 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 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 getEncodedOrdersPaginated( | ||
uint32 maxValidFrom, | ||
uint32 minValidUntil, | ||
address previousPageUser, | ||
uint16 previousPageUserOffset, | ||
uint16 pageSize | ||
) public view returns (bytes memory elements, address nextPageUser, uint16 nextPageUserOffset) { | ||
nextPageUser = previousPageUser; | ||
nextPageUserOffset = previousPageUserOffset; | ||
bool hasNextPage = true; | ||
while (hasNextPage) { | ||
bytes memory unfiltered = batchExchange.getEncodedUsersPaginated(nextPageUser, nextPageUserOffset, pageSize); | ||
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)) { | ||
elements = elements.concat(element); | ||
} | ||
// Update pagination info | ||
address user = getUser(element); | ||
if (user == nextPageUser) { | ||
nextPageUserOffset += 1; | ||
} else { | ||
nextPageUserOffset = 1; | ||
nextPageUser = user; | ||
} | ||
if (elements.length / AUCTION_ELEMENT_WIDTH >= pageSize) { | ||
// We are at capacity, return | ||
return (elements, nextPageUser, nextPageUserOffset); | ||
} | ||
} | ||
} | ||
return (elements, nextPageUser, nextPageUserOffset); | ||
} | ||
|
||
function getUser(bytes memory element) public pure returns (address) { | ||
bytes memory slice = element.slice(0, 20); | ||
return slice.toAddress(0); | ||
} | ||
|
||
function getValidFrom(bytes memory element) public pure returns (uint32) { | ||
bytes memory slice = element.slice(56, 4); | ||
return slice.toUint32(0); | ||
} | ||
|
||
function getValidUntil(bytes memory element) public pure returns (uint32) { | ||
bytes memory slice = element.slice(60, 4); | ||
return slice.toUint32(0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
const BatchExchangeViewer = artifacts.require("BatchExchangeViewer.sol") | ||
const BatchExchange = artifacts.require("BatchExchange.sol") | ||
|
||
module.exports = async function(deployer) { | ||
const exchange = await BatchExchange.deployed() | ||
await deployer.deploy(BatchExchangeViewer, exchange.address) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
const BatchExchange = artifacts.require("BatchExchange") | ||
const BatchExchangeViewer = artifacts.require("BatchExchangeViewer") | ||
const MockContract = artifacts.require("MockContract") | ||
|
||
const { decodeAuctionElements } = require("../utilities") | ||
const { closeAuction } = require("../../scripts/stablex/utilities.js") | ||
|
||
const zero_address = "0x0000000000000000000000000000000000000000" | ||
|
||
contract("BatchExchangeViewer", accounts => { | ||
let batchExchange, token_1, token_2 | ||
beforeEach(async () => { | ||
const feeToken = await MockContract.new() | ||
await feeToken.givenAnyReturnBool(true) | ||
batchExchange = await BatchExchange.new(2 ** 16 - 1, feeToken.address) | ||
|
||
token_1 = await MockContract.new() | ||
token_2 = await MockContract.new() | ||
await batchExchange.addToken(token_1.address) | ||
await batchExchange.addToken(token_2.address) | ||
}) | ||
|
||
describe("getOpenOrderBook", () => { | ||
it("can be queried without pagination", async () => { | ||
const batchId = await batchExchange.getCurrentBatchId() | ||
await batchExchange.placeValidFromOrders( | ||
Array(10).fill(1), //buyToken | ||
Array(10).fill(2), //sellToken | ||
Array(10).fill(batchId + 5), //validFrom | ||
Array(10).fill(batchId + 5), //validTo | ||
Array(10).fill(0), //buyAmounts | ||
Array(10).fill(0) //sellAmounts | ||
) | ||
await batchExchange.placeValidFromOrders( | ||
Array(10).fill(1), //buyToken | ||
Array(10).fill(2), //sellToken | ||
Array(10).fill(batchId), //validFrom | ||
Array(10).fill(batchId), //validTo | ||
Array(10).fill(0), //buyAmounts | ||
Array(10).fill(0) //sellAmounts | ||
) | ||
|
||
const viewer = await BatchExchangeViewer.new(batchExchange.address) | ||
const result = decodeAuctionElements(await viewer.getOpenOrderBook()) | ||
assert.equal(result.filter(e => e.validFrom == batchId).length, 10) | ||
}) | ||
it("can be queried with pagination", async () => { | ||
const batchId = await batchExchange.getCurrentBatchId() | ||
await batchExchange.placeValidFromOrders( | ||
Array(10).fill(1), //buyToken | ||
Array(10).fill(2), //sellToken | ||
Array(10).fill(batchId + 5), //validFrom | ||
Array(10).fill(batchId + 5), //validTo | ||
Array(10).fill(0), //buyAmounts | ||
Array(10).fill(0) //sellAmounts | ||
) | ||
await batchExchange.placeValidFromOrders( | ||
Array(10).fill(1), //buyToken | ||
Array(10).fill(2), //sellToken | ||
Array(10).fill(batchId), //validFrom | ||
Array(10).fill(batchId), //validTo | ||
Array(10).fill(0), //buyAmounts | ||
Array(10).fill(0) //sellAmounts | ||
) | ||
|
||
const viewer = await BatchExchangeViewer.new(batchExchange.address) | ||
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) | ||
}) | ||
}) | ||
|
||
describe("getFinalizedOrderBook", () => { | ||
it("can be queried without pagination", async () => { | ||
const batchId = await batchExchange.getCurrentBatchId() | ||
await batchExchange.placeValidFromOrders( | ||
Array(10).fill(1), //buyToken | ||
Array(10).fill(2), //sellToken | ||
Array(10).fill(batchId + 5), //validFrom | ||
Array(10).fill(batchId + 5), //validTo | ||
Array(10).fill(0), //buyAmounts | ||
Array(10).fill(0) //sellAmounts | ||
) | ||
await batchExchange.placeValidFromOrders( | ||
Array(10).fill(1), //buyToken | ||
Array(10).fill(2), //sellToken | ||
Array(10).fill(batchId), //validFrom | ||
Array(10).fill(batchId), //validTo | ||
Array(10).fill(0), //buyAmounts | ||
Array(10).fill(0) //sellAmounts | ||
) | ||
|
||
// finalize order book | ||
await closeAuction(batchExchange) | ||
|
||
const viewer = await BatchExchangeViewer.new(batchExchange.address) | ||
const result = decodeAuctionElements(await viewer.getFinalizedOrderBook()) | ||
assert.equal(result.filter(e => e.validFrom == batchId).length, 10) | ||
}) | ||
it("can be queried with pagination", async () => { | ||
const batchId = await batchExchange.getCurrentBatchId() | ||
await batchExchange.placeValidFromOrders( | ||
Array(10).fill(1), //buyToken | ||
Array(10).fill(2), //sellToken | ||
Array(10).fill(batchId + 5), //validFrom | ||
Array(10).fill(batchId + 5), //validTo | ||
Array(10).fill(0), //buyAmounts | ||
Array(10).fill(0) //sellAmounts | ||
) | ||
await batchExchange.placeValidFromOrders( | ||
Array(10).fill(1), //buyToken | ||
Array(10).fill(2), //sellToken | ||
Array(10).fill(batchId), //validFrom | ||
Array(10).fill(batchId), //validTo | ||
Array(10).fill(0), //buyAmounts | ||
Array(10).fill(0) //sellAmounts | ||
) | ||
|
||
// finalize order book | ||
await closeAuction(batchExchange) | ||
|
||
const viewer = await BatchExchangeViewer.new(batchExchange.address) | ||
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) | ||
}) | ||
}) | ||
}) |