"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const BatchExchange = artifacts.require("BatchExchange");
const BatchExchangeViewer = artifacts.require("BatchExchangeViewer");
const MockContract = artifacts.require("MockContract");
const bn_js_1 = __importDefault(require("bn.js"));
const truffle_assertions_1 = __importDefault(require("truffle-assertions"));
const encoding_1 = require("../src/encoding");
const utilities_1 = require("./utilities");
const zero_address = "0x0000000000000000000000000000000000000000";
// The contract can't be profiled with solcover as we rely on invoking a staticcall with
// minimal gas amount (which gets burned in case the call fails). Coverage adds solidity
// instructions to determine which lines were touched which increases the amount of gas used.
contract("BatchExchangeViewer [ @skip-on-coverage ]", (accounts) => {
    const [user_1, user_2, user_3] = accounts;
    let batchExchange, token_1, token_2;
    beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
        const feeToken = yield MockContract.new();
        yield feeToken.givenAnyReturnBool(true);
        batchExchange = yield BatchExchange.new(Math.pow(2, 16) - 1, feeToken.address);
        token_1 = yield MockContract.new();
        token_2 = yield MockContract.new();
        yield batchExchange.addToken(token_1.address);
        yield batchExchange.addToken(token_2.address);
    }));
    describe("getOpenOrderBook", () => {
        it("takes pending deposits and withdraws for the next batch into account", () => __awaiter(void 0, void 0, void 0, function* () {
            yield token_2.givenAnyReturnBool(true);
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeOrder(1, 2, batchId.addn(1), 200, 300);
            yield batchExchange.deposit(token_2.address, 100);
            yield batchExchange.requestWithdraw(token_2.address, 50);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const result = encoding_1.decodeIndexedOrders(yield viewer.getOpenOrderBook([]));
            assert.equal(result[0].sellTokenBalance.toNumber(), 50);
        }));
        it("does not count already matured deposits twice", () => __awaiter(void 0, void 0, void 0, function* () {
            yield token_2.givenAnyReturnBool(true);
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeOrder(1, 2, batchId.addn(2), 200, 300);
            yield batchExchange.deposit(token_2.address, 100);
            yield batchExchange.requestWithdraw(token_2.address, 50);
            // Mature the pending withdraw
            yield utilities_1.closeAuction(batchExchange);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const result = encoding_1.decodeIndexedOrders(yield viewer.getOpenOrderBook([]));
            assert.equal(result[0].sellTokenBalance.toNumber(), 50);
        }));
        it("can be queried without pagination", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeValidFromOrders(Array(10).fill(1), //buyToken
            Array(10).fill(2), //sellToken
            Array(10).fill(batchId.addn(5)), //validFrom
            Array(10).fill(batchId.addn(5)), //validTo
            Array(10).fill(0), //buyAmounts
            Array(10).fill(0));
            yield 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));
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const result = encoding_1.decodeIndexedOrders(yield viewer.getOpenOrderBook([]));
            assert.equal(result.filter((e) => batchId.eqn(e.validFrom)).length, 10);
        }));
        it("can be queried with pagination", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeValidFromOrders(Array(10).fill(1), //buyToken
            Array(10).fill(2), //sellToken
            Array(10).fill(batchId.addn(5)), //validFrom
            Array(10).fill(batchId.addn(5)), //validTo
            Array(10).fill(0), //buyAmounts
            Array(10).fill(0));
            yield 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));
            const viewer = (yield BatchExchangeViewer.new(batchExchange.address))
                .contract;
            const result = yield viewer.methods
                .getOpenOrderBookPaginated([], zero_address, 0, 5)
                .call();
            assert.equal(encoding_1.decodeIndexedOrders(result.elements).filter((e) => batchId.eqn(e.validFrom)).length, 5);
            assert.equal(result.nextPageUser, accounts[0]);
            assert.equal(result.nextPageUserOffset, "15");
        }));
        it("can filter a token pair", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = yield batchExchange.getCurrentBatchId();
            yield 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));
            yield 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));
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const result = encoding_1.decodeIndexedOrders(yield viewer.getOpenOrderBook([token_1.address, token_2.address]));
            assert.deepEqual(result.map((e) => e.orderId), [3, 4, 5, 6, 7]);
        }));
    });
    describe("getFinalizedOrderBook", () => {
        it("ignores pending deposits and withdraws for the next batch", () => __awaiter(void 0, void 0, void 0, function* () {
            yield token_2.givenAnyReturnBool(true);
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeOrder(1, 2, batchId.addn(1), 200, 300);
            yield utilities_1.closeAuction(batchExchange);
            yield batchExchange.deposit(token_2.address, 100);
            yield batchExchange.requestWithdraw(token_2.address, 50);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const result = encoding_1.decodeIndexedOrders(yield viewer.getFinalizedOrderBook([]));
            assert.equal(result[0].sellTokenBalance.toNumber(), 0);
        }));
        it("can be queried without pagination", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeValidFromOrders(Array(10).fill(1), //buyToken
            Array(10).fill(2), //sellToken
            Array(10).fill(batchId.addn(5)), //validFrom
            Array(10).fill(batchId.addn(5)), //validTo
            Array(10).fill(0), //buyAmounts
            Array(10).fill(0));
            yield 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));
            // finalize order book
            yield utilities_1.closeAuction(batchExchange);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const result = encoding_1.decodeIndexedOrders(yield viewer.getFinalizedOrderBook([]));
            assert.equal(result.filter((e) => batchId.eqn(e.validFrom)).length, 10);
        }));
        it("can be queried with pagination", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeValidFromOrders(Array(10).fill(1), //buyToken
            Array(10).fill(2), //sellToken
            Array(10).fill(batchId.addn(5)), //validFrom
            Array(10).fill(batchId.addn(5)), //validTo
            Array(10).fill(0), //buyAmounts
            Array(10).fill(0));
            yield 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));
            // finalize order book
            yield utilities_1.closeAuction(batchExchange);
            const viewer = (yield BatchExchangeViewer.new(batchExchange.address))
                .contract;
            const result = yield viewer.methods
                .getFinalizedOrderBookPaginated([], zero_address, 0, 5)
                .call();
            assert.equal(encoding_1.decodeIndexedOrders(result.elements).filter((e) => batchId.eqn(e.validFrom)).length, 5);
            assert.equal(result.nextPageUser, accounts[0]);
            assert.equal(result.nextPageUserOffset, "15");
        }));
        it("can filter a token pair", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = yield batchExchange.getCurrentBatchId();
            yield 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));
            yield 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));
            // finalize order book
            yield utilities_1.closeAuction(batchExchange);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const result = encoding_1.decodeIndexedOrders(yield viewer.getFinalizedOrderBook([token_1.address, token_2.address]));
            assert.deepEqual(result.map((e) => e.orderId), [3, 4, 5, 6, 7]);
        }));
    });
    describe("getFilteredOrdersPaginated", () => [
        it("hasNextPage if pageSize is reached (regression)", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = (yield batchExchange.getCurrentBatchId()).toNumber();
            yield 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));
            yield batchExchange.placeValidFromOrders(Array(6).fill(1), //buyToken
            Array(6).fill(2), //sellToken
            Array(6).fill(batchId), //validFrom
            Array(6).fill(batchId), //validTo
            Array(6).fill(0), //buyAmounts
            Array(6).fill(0));
            const viewer = (yield BatchExchangeViewer.new(batchExchange.address))
                .contract;
            // We are querying two subpages which contain 6 elements in total, but due to our
            // page size constraint only return 5. Thus we should have a nextPage.
            const result = yield viewer.methods
                .getFilteredOrdersPaginated([batchId, batchId, batchId], [1, 2], zero_address, 0, 5)
                .call();
            assert.equal(encoding_1.decodeIndexedOrders(result.elements).length, 5);
            assert.equal(result.hasNextPage, true);
        })),
        it("zeros the unfilted order page buffer for filtered tokens", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = (yield batchExchange.getCurrentBatchId()).toNumber();
            const nextBatchId = batchId + 1;
            yield batchExchange.placeValidFromOrders(Array(6).fill(0), //buyToken
            [1, 1, 1, 2, 2, 2], //sellToken
            Array(6).fill(batchId), //validFrom
            [
                nextBatchId,
                batchId,
                nextBatchId,
                nextBatchId,
                nextBatchId,
                nextBatchId,
            ], //validTo
            [0, 1, 2, 3, 4, 5], //buyAmounts
            Array(6).fill(0));
            const viewer = (yield BatchExchangeViewer.new(batchExchange.address))
                .contract;
            // We want to make sure that the orders with token ID 2 that should be
            // filtered indeed are. If the unfiltered order buffer was not getting
            // 0-ed when an order was filtered by token ID then the following request
            // would return 3 orders (the third one having duplicate data of first two
            // orders).
            const result = yield viewer.methods
                .getFilteredOrdersPaginated([batchId, nextBatchId, nextBatchId], [0, 1], zero_address, 0, 3)
                .call();
            const orders = encoding_1.decodeIndexedOrders(result.elements);
            assert.equal(orders.length, 2, `unexpected third order ${JSON.stringify(orders[2])}`);
        })),
    ]);
    describe("getEncodedOrdersPaginated", () => __awaiter(void 0, void 0, void 0, function* () {
        it("returns empty bytes when no users", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchExchange = yield utilities_1.setupGenericStableX();
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const auctionElements = yield viewer.getEncodedOrdersPaginated(zero_address, 0, 10);
            assert.equal(auctionElements, null);
        }));
        it("returns three orders one per page", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchExchange = yield utilities_1.setupGenericStableX(3);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const batchId = (yield batchExchange.getCurrentBatchId()).toNumber();
            yield batchExchange.placeOrder(0, 1, batchId + 10, 100, 100, {
                from: user_1,
            });
            yield batchExchange.placeOrder(1, 2, batchId + 10, 100, 100, {
                from: user_1,
            });
            yield batchExchange.placeOrder(0, 1, batchId + 10, 100, 100, {
                from: user_2,
            });
            const firstPage = encoding_1.decodeOrders(yield viewer.getEncodedOrdersPaginated(zero_address, 0, 1));
            assert.equal(JSON.stringify(firstPage), JSON.stringify([
                {
                    user: user_1.toLowerCase(),
                    sellTokenBalance: new bn_js_1.default(0),
                    buyToken: 0,
                    sellToken: 1,
                    validFrom: batchId,
                    validUntil: batchId + 10,
                    priceNumerator: new bn_js_1.default(100),
                    priceDenominator: new bn_js_1.default(100),
                    remainingAmount: new bn_js_1.default(100),
                },
            ]));
            const secondPage = encoding_1.decodeOrders(yield viewer.getEncodedOrdersPaginated(user_1, 1, 1));
            assert.equal(JSON.stringify(secondPage), JSON.stringify([
                {
                    user: user_1.toLowerCase(),
                    sellTokenBalance: new bn_js_1.default(0),
                    buyToken: 1,
                    sellToken: 2,
                    validFrom: batchId,
                    validUntil: batchId + 10,
                    priceNumerator: new bn_js_1.default(100),
                    priceDenominator: new bn_js_1.default(100),
                    remainingAmount: new bn_js_1.default(100),
                },
            ]));
            const thirdPage = encoding_1.decodeOrders(yield viewer.getEncodedOrdersPaginated(user_1, 2, 1));
            assert.equal(JSON.stringify(thirdPage), JSON.stringify([
                {
                    user: user_2.toLowerCase(),
                    sellTokenBalance: new bn_js_1.default(0),
                    buyToken: 0,
                    sellToken: 1,
                    validFrom: batchId,
                    validUntil: batchId + 10,
                    priceNumerator: new bn_js_1.default(100),
                    priceDenominator: new bn_js_1.default(100),
                    remainingAmount: new bn_js_1.default(100),
                },
            ]));
            // 4th page is empty
            assert.equal(yield viewer.getEncodedOrdersPaginated(user_2, 1, 1), null);
        }));
        it("returns three orders when page size is overlapping users", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchExchange = yield utilities_1.setupGenericStableX(3);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeOrder(0, 1, batchId.addn(10), 100, 100, {
                from: user_1,
            });
            yield batchExchange.placeOrder(1, 2, batchId.addn(10), 100, 100, {
                from: user_1,
            });
            yield batchExchange.placeOrder(0, 1, batchId.addn(10), 100, 100, {
                from: user_2,
            });
            const page = encoding_1.decodeOrders(yield viewer.getEncodedOrdersPaginated(user_1, 1, 2));
            assert.equal(page[0].user, user_1.toLowerCase());
            assert.equal(page[1].user, user_2.toLowerCase());
        }));
        it("returns three orders from three users with larger page size", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchExchange = yield utilities_1.setupGenericStableX(3);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeOrder(0, 1, batchId.addn(10), 100, 100, {
                from: user_1,
            });
            yield batchExchange.placeOrder(1, 2, batchId.addn(10), 100, 100, {
                from: user_2,
            });
            yield batchExchange.placeOrder(0, 1, batchId.addn(10), 100, 100, {
                from: user_3,
            });
            const page = encoding_1.decodeOrders(yield viewer.getEncodedOrdersPaginated(zero_address, 0, 5));
            assert.equal(page.length, 3);
            assert.equal(page[0].user, user_1.toLowerCase());
            assert.equal(page[1].user, user_2.toLowerCase());
            assert.equal(page[2].user, user_3.toLowerCase());
        }));
    }));
    describe("getEncodedOrdersPaginatedWithTokenFilter", () => {
        it("Does not query balance for filtered tokens", () => __awaiter(void 0, void 0, void 0, function* () {
            yield token_1.givenAnyReturnBool(true);
            const batchId = yield batchExchange.getCurrentBatchId();
            yield batchExchange.placeOrder(2, 1, batchId.addn(10), 200, 300);
            yield batchExchange.deposit(token_1.address, new bn_js_1.default(2).pow(new bn_js_1.default(255)));
            yield utilities_1.closeAuction(batchExchange);
            // getBalance(token1) now reverts due to math overflow
            yield batchExchange.deposit(token_1.address, new bn_js_1.default(2).pow(new bn_js_1.default(255)));
            yield utilities_1.closeAuction(batchExchange);
            const viewer = yield BatchExchangeViewer.new(batchExchange.address);
            yield truffle_assertions_1.default.reverts(viewer.getEncodedOrdersPaginatedWithTokenFilter([], zero_address, 0, 10));
            const result = encoding_1.decodeOrders(yield viewer.getEncodedOrdersPaginatedWithTokenFilter([0, 2], zero_address, 0, 10));
            // Filtered orders still show up as 0s to preserve order indices
            assert.equal(result.length, 1);
            assert.equal(result[0].validFrom, 0);
            assert.equal(result[0].validUntil, 0);
        }));
        it("Allows filtered paginating while filtering (regression test)", () => __awaiter(void 0, void 0, void 0, function* () {
            const batchId = (yield batchExchange.getCurrentBatchId()).toNumber();
            yield batchExchange.placeOrder(0, 1, batchId, 200, 300);
            yield batchExchange.placeOrder(2, 1, batchId, 200, 300);
            const viewer = (yield BatchExchangeViewer.new(batchExchange.address))
                .contract;
            const result = yield viewer.methods
                .getFilteredOrdersPaginated([batchId, batchId, batchId], [1, 2], accounts[0], 0, 1)
                .call();
            assert.equal(encoding_1.decodeIndexedOrders(result.elements).length, 1);
        }));
    });
});
