"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVMSpokePoolClient = void 0;
const evm_1 = require("../../arch/evm");
const tvm_1 = require("../../arch/tvm");
const utils_1 = require("../../utils");
const EventUtils_1 = require("../../utils/EventUtils");
const BaseAbstractClient_1 = require("../BaseAbstractClient");
const SpokePoolClient_1 = require("./SpokePoolClient");
const types_1 = require("./types");
class EVMSpokePoolClient extends SpokePoolClient_1.SpokePoolClient {
    spokePool;
    type = types_1.EVM_SPOKE_POOL_CLIENT_TYPE;
    tvm;
    constructor(logger, spokePool, hubPoolClient, chainId, deploymentBlock, eventSearchConfig = { from: 0, maxLookBack: 0 }) {
        super(logger, hubPoolClient, chainId, deploymentBlock, eventSearchConfig);
        this.spokePool = spokePool;
        this.spokePoolAddress = utils_1.EvmAddress.from(spokePool.address);
        this.tvm = (0, utils_1.chainIsTvm)(this.chainId);
    }
    relayFillStatus(relayData, atHeight) {
        const fillStatusHandler = this.tvm ? tvm_1.relayFillStatus : evm_1.relayFillStatus;
        return fillStatusHandler(this.spokePool, relayData, atHeight, this.chainId);
    }
    fillStatusArray(relayData, atHeight) {
        return (0, evm_1.fillStatusArray)(this.spokePool, relayData, atHeight);
    }
    getMaxFillDeadlineInRange(startBlock, endBlock) {
        const effectiveStartBlock = Math.max(startBlock, this.deploymentBlock);
        const maxFillDeadlineInRangeHandler = this.tvm ? tvm_1.getMaxFillDeadlineInRange : evm_1.getMaxFillDeadlineInRange;
        return maxFillDeadlineInRangeHandler(this.spokePool, effectiveStartBlock, endBlock);
    }
    _availableEventsOnSpoke(eventNames = SpokePoolClient_1.knownEventNames) {
        return Object.fromEntries(this.spokePool.interface.fragments
            .filter(({ name, type }) => type === "event" && eventNames.includes(name))
            .map(({ name }) => [name, this.spokePool.filters[name]()]));
    }
    _queryableEventNames() {
        return Object.keys(this._availableEventsOnSpoke(SpokePoolClient_1.knownEventNames));
    }
    async _getCurrentTime(blockNumber) {
        const block = await this.spokePool.provider.getBlock(blockNumber);
        const currentTime = block.timestamp;
        if (currentTime < this.currentTime) {
            throw new Error(`EVMSpokePoolClient::_getCurrentTime: currentTime: ${currentTime} < ${this.currentTime}`);
        }
        return currentTime;
    }
    async _update(eventsToQuery) {
        const searchConfig = await this.updateSearchConfig(this.spokePool.provider);
        if ((0, BaseAbstractClient_1.isUpdateFailureReason)(searchConfig)) {
            const reason = searchConfig;
            return { success: false, reason };
        }
        const eventSearchConfigs = eventsToQuery.map((eventName) => {
            if (!this._queryableEventNames().includes(eventName)) {
                throw new Error(`SpokePoolClient: Cannot query unrecognised SpokePool event name: ${eventName}`);
            }
            const _searchConfig = { ...searchConfig };
            if (eventName === "EnabledDepositRoute" && !this.isUpdated) {
                _searchConfig.from = this.deploymentBlock;
            }
            return {
                filter: this._availableEventsOnSpoke()[eventName],
                searchConfig: _searchConfig,
            };
        });
        const { spokePool } = this;
        this.log("debug", `Updating SpokePool client for chain ${this.chainId}`, {
            eventsToQuery,
            searchConfig,
            spokePool: spokePool.address,
        });
        const timerStart = Date.now();
        const [currentTime, ...events] = await Promise.all([
            this._getCurrentTime(searchConfig.to),
            ...eventSearchConfigs.map((config) => (0, EventUtils_1.paginatedEventQuery)(this.spokePool, config.filter, config.searchConfig)),
        ]);
        this.log("debug", `Time to query new events from RPC for ${this.chainId}: ${Date.now() - timerStart} ms`);
        if (currentTime < this.currentTime) {
            throw new Error(`EVMSpokePoolClient::update: currentTime: ${currentTime} < ${this.currentTime}`);
        }
        events.forEach((events) => (0, EventUtils_1.sortEventsAscendingInPlace)(events.map(EventUtils_1.logToSortableEvent)));
        const eventsWithBlockNumber = events.map((eventList) => eventList.map((event) => (0, EventUtils_1.spreadEventWithBlockNumber)(event)));
        return {
            success: true,
            currentTime,
            searchEndBlock: searchConfig.to,
            events: eventsWithBlockNumber,
        };
    }
    getTimeAt(blockNumber) {
        const getTimeAtHandler = this.tvm ? tvm_1.getTimeAt : evm_1.getTimeAt;
        return getTimeAtHandler(this.spokePool, blockNumber);
    }
    async findDeposit(depositId) {
        let deposit = this.getDeposit(depositId);
        if (deposit) {
            return { found: true, deposit };
        }
        const result = await this.queryDepositEvents(depositId);
        if ("reason" in result) {
            return { found: false, code: utils_1.InvalidFill.DepositIdNotFound, reason: result.reason };
        }
        const { event, elapsedMs } = result;
        const partialDeposit = (0, utils_1.unpackDepositEvent)((0, EventUtils_1.spreadEventWithBlockNumber)(event), this.chainId);
        const quoteBlockNumber = await this.getBlockNumber(partialDeposit.quoteTimestamp);
        const outputToken = partialDeposit.outputToken.isZeroAddress()
            ? this.getDestinationTokenForDeposit({ ...partialDeposit, quoteBlockNumber })
            : partialDeposit.outputToken;
        deposit = {
            ...partialDeposit,
            outputToken,
            quoteBlockNumber,
            fromLiteChain: this.isOriginLiteChain(partialDeposit),
            toLiteChain: this.isDestinationLiteChain(partialDeposit),
        };
        this.logger.debug({
            at: "SpokePoolClient#findDeposit",
            message: "Located deposit outside of SpokePoolClient's search range",
            deposit,
            elapsedMs,
        });
        return { found: true, deposit };
    }
    getTimestampForBlock(blockNumber) {
        return (0, evm_1.getTimestampForBlock)(this.spokePool.provider, blockNumber);
    }
    _findDepositBlock(depositId, lowBlock, highBlock) {
        const findDepositBlockHandler = this.tvm ? tvm_1.findDepositBlock : evm_1.findDepositBlock;
        return findDepositBlockHandler(this.spokePool, depositId, lowBlock, highBlock);
    }
    async queryDepositEvents(depositId) {
        const tStart = Date.now();
        const upperBound = this.latestHeightSearched || undefined;
        const from = await this._findDepositBlock(depositId, this.deploymentBlock, upperBound);
        const chain = (0, utils_1.getNetworkName)(this.chainId);
        if (!from) {
            return {
                reason: `Unable to find ${chain} depositId ${depositId} within blocks [${this.deploymentBlock}, ${upperBound ?? "latest"}].`,
            };
        }
        const to = from;
        const { maxLookBack } = this.eventSearchConfig;
        const events = (await Promise.all([
            (0, EventUtils_1.paginatedEventQuery)(this.spokePool, this.spokePool.filters.V3FundsDeposited(null, null, null, null, null, depositId), { from, to, maxLookBack }),
            (0, EventUtils_1.paginatedEventQuery)(this.spokePool, this.spokePool.filters.FundsDeposited(null, null, null, null, null, depositId), { from, to, maxLookBack }),
        ]))
            .flat()
            .filter(({ args }) => args["depositId"].eq(depositId));
        const tStop = Date.now();
        const [event] = events;
        if (!event) {
            return {
                reason: `Unable to find ${chain} depositId ${depositId} within blocks [${from}, ${upperBound ?? "latest"}].`,
            };
        }
        return { event, elapsedMs: tStop - tStart };
    }
}
exports.EVMSpokePoolClient = EVMSpokePoolClient;
//# sourceMappingURL=EVMSpokePoolClient.js.map