"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findFillEvent = exports.findFillBlock = exports.fillStatusArray = exports.relayFillStatus = exports.findDepositBlock = exports.getDepositIdAtBlock = exports.getMaxFillDeadlineInRange = exports.getTimestampForBlock = exports.getTimeAt = exports.populateV3Relay = void 0;
var tslib_1 = require("tslib");
var assert_1 = tslib_1.__importDefault(require("assert"));
var constants_1 = require("../../constants");
var interfaces_1 = require("../../interfaces");
var utils_1 = require("../../utils");
function populateV3Relay(spokePool, relayData, repaymentAddress, repaymentChainId) {
    if (repaymentChainId === void 0) { repaymentChainId = relayData.destinationChainId; }
    (0, assert_1.default)(repaymentAddress.isValidOn(repaymentChainId), "Invalid repayment address for chain ".concat(repaymentChainId, ": ").concat(repaymentAddress.toNative(), "."));
    var evmRelayData = {
        depositor: relayData.depositor.toBytes32(),
        recipient: relayData.recipient.toBytes32(),
        inputToken: relayData.inputToken.toBytes32(),
        outputToken: relayData.outputToken.toBytes32(),
        inputAmount: relayData.inputAmount,
        outputAmount: relayData.outputAmount,
        originChainId: relayData.originChainId,
        depositId: relayData.depositId,
        fillDeadline: relayData.fillDeadline,
        exclusivityDeadline: relayData.exclusivityDeadline,
        message: relayData.message,
        exclusiveRelayer: relayData.exclusiveRelayer.toBytes32(),
    };
    if ((0, utils_1.isDefined)(relayData.speedUpSignature)) {
        (0, assert_1.default)((0, utils_1.isDefined)(relayData.updatedRecipient) && !relayData.updatedRecipient.isZeroAddress());
        (0, assert_1.default)((0, utils_1.isDefined)(relayData.updatedOutputAmount));
        (0, assert_1.default)((0, utils_1.isDefined)(relayData.updatedMessage));
        return spokePool.populateTransaction.fillRelayWithUpdatedDeposit(relayData, repaymentChainId, repaymentAddress.toBytes32(), relayData.updatedOutputAmount, relayData.updatedRecipient.toBytes32(), relayData.updatedMessage, relayData.speedUpSignature);
    }
    return spokePool.populateTransaction.fillRelay(evmRelayData, repaymentChainId, repaymentAddress.toBytes32());
}
exports.populateV3Relay = populateV3Relay;
function getTimeAt(spokePool, blockNumber) {
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var currentTime;
        return tslib_1.__generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4, spokePool.getCurrentTime({ blockTag: blockNumber })];
                case 1:
                    currentTime = _a.sent();
                    (0, assert_1.default)(utils_1.BigNumber.isBigNumber(currentTime) && currentTime.lt(utils_1.bnUint32Max));
                    return [2, currentTime.toNumber()];
            }
        });
    });
}
exports.getTimeAt = getTimeAt;
function getTimestampForBlock(provider, blockNumber) {
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var block;
        return tslib_1.__generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4, provider.getBlock(blockNumber)];
                case 1:
                    block = _a.sent();
                    return [2, block.timestamp];
            }
        });
    });
}
exports.getTimestampForBlock = getTimestampForBlock;
function getMaxFillDeadlineInRange(spokePool, startBlock, endBlock) {
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var fillDeadlineBuffers;
        return tslib_1.__generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4, Promise.all([
                        spokePool.fillDeadlineBuffer({ blockTag: startBlock }),
                        spokePool.fillDeadlineBuffer({ blockTag: endBlock }),
                    ])];
                case 1:
                    fillDeadlineBuffers = _a.sent();
                    return [2, Math.max(fillDeadlineBuffers[0], fillDeadlineBuffers[1])];
            }
        });
    });
}
exports.getMaxFillDeadlineInRange = getMaxFillDeadlineInRange;
function getDepositIdAtBlock(contract, blockTag) {
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var _depositIdAtBlock, depositIdAtBlock;
        return tslib_1.__generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4, contract.numberOfDeposits({ blockTag: blockTag })];
                case 1:
                    _depositIdAtBlock = _a.sent();
                    depositIdAtBlock = (0, utils_1.toBN)(_depositIdAtBlock);
                    if (depositIdAtBlock.lt(utils_1.bnZero)) {
                        throw new Error("Invalid deposit count");
                    }
                    return [2, depositIdAtBlock];
            }
        });
    });
}
exports.getDepositIdAtBlock = getDepositIdAtBlock;
function findDepositBlock(spokePool, depositId, lowBlock, highBlock) {
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var _a, _b, nDepositsLow, nDepositsHigh, midBlock, nDeposits, _c;
        return tslib_1.__generator(this, function (_d) {
            switch (_d.label) {
                case 0:
                    if ((0, utils_1.isUnsafeDepositId)(depositId)) {
                        throw new Error("Cannot binary search for depositId ".concat(depositId));
                    }
                    if (!(highBlock !== null && highBlock !== void 0)) return [3, 1];
                    _a = highBlock;
                    return [3, 3];
                case 1: return [4, spokePool.provider.getBlockNumber()];
                case 2:
                    _a = (highBlock = _d.sent());
                    _d.label = 3;
                case 3:
                    _a;
                    (0, assert_1.default)(highBlock > lowBlock, "Block numbers out of range (".concat(lowBlock, " >= ").concat(highBlock, ")"));
                    return [4, Promise.all([
                            spokePool.numberOfDeposits({ blockTag: lowBlock }),
                            spokePool.numberOfDeposits({ blockTag: highBlock }),
                        ])];
                case 4:
                    _b = (_d.sent()).map(function (n) { return (0, utils_1.toBN)(n); }), nDepositsLow = _b[0], nDepositsHigh = _b[1];
                    if (nDepositsLow.gt(depositId) || nDepositsHigh.lte(depositId)) {
                        return [2, undefined];
                    }
                    _d.label = 5;
                case 5:
                    midBlock = Math.floor((highBlock + lowBlock) / 2);
                    _c = utils_1.toBN;
                    return [4, spokePool.numberOfDeposits({ blockTag: midBlock })];
                case 6:
                    nDeposits = _c.apply(void 0, [_d.sent()]);
                    if (nDeposits.gt(depositId)) {
                        highBlock = midBlock;
                    }
                    else {
                        lowBlock = midBlock + 1;
                    }
                    _d.label = 7;
                case 7:
                    if (lowBlock < highBlock) return [3, 5];
                    _d.label = 8;
                case 8: return [2, lowBlock];
            }
        });
    });
}
exports.findDepositBlock = findDepositBlock;
function relayFillStatus(spokePool, relayData, blockTag, destinationChainId) {
    if (blockTag === void 0) { blockTag = "latest"; }
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var _a, hash, _fillStatus, fillStatus, originChainId, depositId;
        return tslib_1.__generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    if (!(destinationChainId !== null && destinationChainId !== void 0)) return [3, 1];
                    _a = destinationChainId;
                    return [3, 3];
                case 1: return [4, spokePool.chainId()];
                case 2:
                    _a = (destinationChainId = _b.sent());
                    _b.label = 3;
                case 3:
                    _a;
                    (0, assert_1.default)((0, utils_1.isDefined)(destinationChainId));
                    hash = (0, utils_1.getRelayDataHash)(relayData, destinationChainId);
                    return [4, spokePool.fillStatuses(hash, { blockTag: blockTag })];
                case 4:
                    _fillStatus = _b.sent();
                    fillStatus = Number(_fillStatus);
                    if (![interfaces_1.FillStatus.Unfilled, interfaces_1.FillStatus.RequestedSlowFill, interfaces_1.FillStatus.Filled].includes(fillStatus)) {
                        originChainId = relayData.originChainId, depositId = relayData.depositId;
                        throw new Error("relayFillStatus: Unexpected fillStatus for ".concat(originChainId, " deposit ").concat(depositId.toString(), " (").concat(fillStatus, ")"));
                    }
                    return [2, fillStatus];
            }
        });
    });
}
exports.relayFillStatus = relayFillStatus;
function fillStatusArray(spokePool, relayData, blockTag) {
    if (blockTag === void 0) { blockTag = "latest"; }
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var fillStatuses, destinationChainId, queries, chunkSize, chunkedQueries, multicalls, status, bnUnfilled, bnFilled;
        return tslib_1.__generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    fillStatuses = "fillStatuses";
                    return [4, spokePool.chainId()];
                case 1:
                    destinationChainId = _a.sent();
                    queries = relayData.map(function (relayData) {
                        var hash = (0, utils_1.getRelayDataHash)(relayData, destinationChainId);
                        return spokePool.interface.encodeFunctionData(fillStatuses, [hash]);
                    });
                    chunkSize = 250;
                    chunkedQueries = (0, utils_1.chunk)(queries, chunkSize);
                    return [4, Promise.all(chunkedQueries.map(function (queries) { return spokePool.callStatic.multicall(queries, { blockTag: blockTag }); }))];
                case 2:
                    multicalls = _a.sent();
                    status = multicalls
                        .map(function (multicall) {
                        return multicall.map(function (result) { return spokePool.interface.decodeFunctionResult(fillStatuses, result)[0]; });
                    })
                        .flat();
                    bnUnfilled = (0, utils_1.toBN)(interfaces_1.FillStatus.Unfilled);
                    bnFilled = (0, utils_1.toBN)(interfaces_1.FillStatus.Filled);
                    return [2, status.map(function (status) {
                            return utils_1.BigNumber.isBigNumber(status) && status.gte(bnUnfilled) && status.lte(bnFilled)
                                ? status.toNumber()
                                : undefined;
                        })];
            }
        });
    });
}
exports.fillStatusArray = fillStatusArray;
function findFillBlock(spokePool, relayData, lowBlockNumber, highBlockNumber) {
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var provider, _a, destinationChainId, _b, _c, _d, initialFillStatus, finalFillStatus, depositId, originChainId, _e, srcChain, dstChain, midBlockNumber, fillStatus;
        return tslib_1.__generator(this, function (_f) {
            switch (_f.label) {
                case 0:
                    provider = spokePool.provider;
                    if (!(highBlockNumber !== null && highBlockNumber !== void 0)) return [3, 1];
                    _a = highBlockNumber;
                    return [3, 3];
                case 1: return [4, provider.getBlockNumber()];
                case 2:
                    _a = (highBlockNumber = _f.sent());
                    _f.label = 3;
                case 3:
                    _a;
                    (0, assert_1.default)(highBlockNumber > lowBlockNumber, "Block numbers out of range (".concat(lowBlockNumber, " >= ").concat(highBlockNumber, ")"));
                    if (!Object.values(constants_1.CHAIN_IDs).includes(relayData.originChainId)) return [3, 5];
                    return [4, provider.getNetwork()];
                case 4:
                    _b = (_f.sent()).chainId;
                    return [3, 7];
                case 5:
                    _c = Number;
                    return [4, spokePool.chainId()];
                case 6:
                    _b = _c.apply(void 0, [_f.sent()]);
                    _f.label = 7;
                case 7:
                    destinationChainId = _b;
                    (0, assert_1.default)(relayData.originChainId !== destinationChainId, "Origin & destination chain IDs must not be equal (".concat(destinationChainId, ")"));
                    return [4, Promise.all([
                            relayFillStatus(spokePool, relayData, lowBlockNumber, destinationChainId),
                            relayFillStatus(spokePool, relayData, highBlockNumber, destinationChainId),
                        ])];
                case 8:
                    _d = (_f.sent()).map(Number), initialFillStatus = _d[0], finalFillStatus = _d[1];
                    if (finalFillStatus !== interfaces_1.FillStatus.Filled) {
                        return [2, undefined];
                    }
                    if (initialFillStatus === interfaces_1.FillStatus.Filled) {
                        depositId = relayData.depositId, originChainId = relayData.originChainId;
                        _e = [(0, utils_1.getNetworkName)(originChainId), (0, utils_1.getNetworkName)(destinationChainId)], srcChain = _e[0], dstChain = _e[1];
                        throw new Error("".concat(srcChain, " deposit ").concat(depositId.toString(), " filled on ").concat(dstChain, " before block ").concat(lowBlockNumber));
                    }
                    _f.label = 9;
                case 9:
                    midBlockNumber = Math.floor((highBlockNumber + lowBlockNumber) / 2);
                    return [4, relayFillStatus(spokePool, relayData, midBlockNumber, destinationChainId)];
                case 10:
                    fillStatus = _f.sent();
                    if (fillStatus === interfaces_1.FillStatus.Filled) {
                        highBlockNumber = midBlockNumber;
                    }
                    else {
                        lowBlockNumber = midBlockNumber + 1;
                    }
                    _f.label = 11;
                case 11:
                    if (lowBlockNumber < highBlockNumber) return [3, 9];
                    _f.label = 12;
                case 12: return [2, lowBlockNumber];
            }
        });
    });
}
exports.findFillBlock = findFillBlock;
function findFillEvent(spokePool, relayData, lowBlockNumber, highBlockNumber) {
    return tslib_1.__awaiter(this, void 0, void 0, function () {
        var blockNumber, maxLookBack, _a, from, to, query, event, destinationChainId, _b, _c, fillEvent, fill;
        return tslib_1.__generator(this, function (_d) {
            switch (_d.label) {
                case 0: return [4, findFillBlock(spokePool, relayData, lowBlockNumber, highBlockNumber)];
                case 1:
                    blockNumber = _d.sent();
                    if (!blockNumber)
                        return [2, undefined];
                    maxLookBack = 0;
                    _a = [blockNumber, blockNumber], from = _a[0], to = _a[1];
                    return [4, Promise.all([
                            (0, utils_1.paginatedEventQuery)(spokePool, spokePool.filters.FilledRelay(null, null, null, null, null, relayData.originChainId, relayData.depositId), { from: from, to: to, maxLookBack: maxLookBack }),
                            (0, utils_1.paginatedEventQuery)(spokePool, spokePool.filters.FilledV3Relay(null, null, null, null, null, relayData.originChainId, relayData.depositId), { from: from, to: to, maxLookBack: maxLookBack }),
                        ])];
                case 2:
                    query = (_d.sent()).flat();
                    if (query.length === 0)
                        throw new Error("Failed to find fill event at block ".concat(blockNumber));
                    event = query[0];
                    if (!Object.values(constants_1.CHAIN_IDs).includes(relayData.originChainId)) return [3, 4];
                    return [4, spokePool.provider.getNetwork()];
                case 3:
                    _b = (_d.sent()).chainId;
                    return [3, 6];
                case 4:
                    _c = Number;
                    return [4, spokePool.chainId()];
                case 5:
                    _b = _c.apply(void 0, [_d.sent()]);
                    _d.label = 6;
                case 6:
                    destinationChainId = _b;
                    fillEvent = (0, utils_1.spreadEventWithBlockNumber)(event);
                    fill = tslib_1.__assign(tslib_1.__assign({}, fillEvent), { inputToken: (0, utils_1.toAddressType)(fillEvent.inputToken, relayData.originChainId), outputToken: (0, utils_1.toAddressType)(fillEvent.outputToken, destinationChainId), depositor: (0, utils_1.toAddressType)(fillEvent.depositor, relayData.originChainId), recipient: (0, utils_1.toAddressType)(fillEvent.recipient, destinationChainId), exclusiveRelayer: (0, utils_1.toAddressType)(fillEvent.exclusiveRelayer, destinationChainId), relayer: (0, utils_1.toAddressType)(fillEvent.relayer, destinationChainId), relayExecutionInfo: tslib_1.__assign(tslib_1.__assign({}, fillEvent.relayExecutionInfo), { updatedRecipient: (0, utils_1.toAddressType)(fillEvent.relayExecutionInfo.updatedRecipient, destinationChainId) }), destinationChainId: destinationChainId, messageHash: (0, utils_1.getMessageHash)(event.args.message) });
                    return [2, fill];
            }
        });
    });
}
exports.findFillEvent = findFillEvent;
//# sourceMappingURL=SpokeUtils.js.map