import { __assign, __awaiter, __extends, __generator, __rest, __spreadArray } from "tslib";
import { utils } from "@uma/sdk";
import assert from "assert";
import { isError } from "../../typeguards";
import { isArrayOf, isDefined, isPositiveInteger, max, paginatedEventQuery, sortEventsAscendingInPlace, sortEventsDescending, spreadEventWithBlockNumber, toBN, toWei, utf8ToHex, } from "../../utils";
import { PROTOCOL_DEFAULT_CHAIN_ID_INDICES } from "../../constants";
import { parseJSONWithNumericString } from "../../utils/JSONUtils";
import { BaseAbstractClient, isUpdateFailureReason, UpdateFailureReason } from "../BaseAbstractClient";
import { parseAndReturnRateModelFromString } from "../../lpFeeCalculator/rateModel";
// Version 0 is the implicit ConfigStore version from before the version attribute was introduced.
// @dev Do not change this value.
export var DEFAULT_CONFIG_STORE_VERSION = 0;
export var GLOBAL_CONFIG_STORE_KEYS;
(function (GLOBAL_CONFIG_STORE_KEYS) {
    GLOBAL_CONFIG_STORE_KEYS["MAX_RELAYER_REPAYMENT_LEAF_SIZE"] = "MAX_RELAYER_REPAYMENT_LEAF_SIZE";
    GLOBAL_CONFIG_STORE_KEYS["MAX_POOL_REBALANCE_LEAF_SIZE"] = "MAX_POOL_REBALANCE_LEAF_SIZE";
    GLOBAL_CONFIG_STORE_KEYS["VERSION"] = "VERSION";
    GLOBAL_CONFIG_STORE_KEYS["DISABLED_CHAINS"] = "DISABLED_CHAINS";
    GLOBAL_CONFIG_STORE_KEYS["CHAIN_ID_INDICES"] = "CHAIN_ID_INDICES";
    GLOBAL_CONFIG_STORE_KEYS["LITE_CHAIN_ID_INDICES"] = "LITE_CHAIN_ID_INDICES";
})(GLOBAL_CONFIG_STORE_KEYS || (GLOBAL_CONFIG_STORE_KEYS = {}));
// Conveniently store known invalid token config update hashes to avoid spamming debug logs.
var KNOWN_INVALID_TOKEN_CONFIG_UPDATE_HASHES = [
    "0x422abc617c6598e4b91859f99c392939d2034c1a839a342a963a34a2f0390195",
    "0x36c85e388279714b2c98d46e3377dc37a1575665b2cac5e52fe97d8d77efcd2b",
    "0x6f0a93119e538dd84e02adfce821fb4e6dd9baddcceb041977e8ba3c39185ab8",
    "0xc28d8bb445e0b747201e6f98ee62aa03009f4c04b8d6f9fad8f214ec1166463d",
    "0x3b0719ef1e3cae2dc1a854a1012332a288e50ad24adc52861d42bcc30fd3deaf",
    "0xbae5c792f74d9f0b6554acf793df0d6b3610868bd6f6a377371b9dec10038003",
    "0xd983142980ac2451e913b152413e769f7a7007fe7305c2e8a03db432e892f84c",
    "0xf64610347950488503428fd720132f8188aa26dcc48e3fc9a89b7bc24aa7fda2",
    "0x1970fcd1e5d5d6cf3bbb640d30d5e471ce5161d65580cedb388526a32b2f7638",
    "0xf098c547d726be8fda419faaee1850280ded1ea75a1b10f4a1614805fa4207d3",
    "0xbfa181663761a78c66dd2c7012604eb910c4c39bad17089e2cc4a011ccc0e981",
    "0x89830f5e81b9e8b44ac2f8966b2fa4bf8e71d7f546e2bc0e773d8ee8df4bdb36",
    "0xb0ad6270124c925a234d9c4f87b60396f2b52fdc250cd2fc9cac792d0d62e467",
    "0x779bc3bf2dba1128d5dda6be8ae99b503cae23343a7265a86bca3d5572ed4268",
].map(function (hash) { return hash.toLowerCase(); });
var AcrossConfigStoreClient = /** @class */ (function (_super) {
    __extends(AcrossConfigStoreClient, _super);
    function AcrossConfigStoreClient(logger, configStore, eventSearchConfig, configStoreVersion) {
        if (eventSearchConfig === void 0) { eventSearchConfig = { from: 0, maxLookBack: 0 }; }
        var _this = _super.call(this, eventSearchConfig) || this;
        _this.logger = logger;
        _this.configStore = configStore;
        _this.configStoreVersion = configStoreVersion;
        _this.cumulativeRateModelUpdates = [];
        _this.cumulativeRouteRateModelUpdates = [];
        _this.cumulativeMaxRefundCountUpdates = [];
        _this.cumulativeMaxL1TokenCountUpdates = [];
        _this.chainIdIndicesUpdates = [];
        _this.liteChainIndicesUpdates = [];
        _this.cumulativeSpokeTargetBalanceUpdates = [];
        _this.cumulativeConfigStoreVersionUpdates = [];
        _this.cumulativeDisabledChainUpdates = [];
        _this.hasLatestConfigStoreVersion = false;
        _this.firstHeightToSearch = eventSearchConfig.from;
        _this.latestHeightSearched = 0;
        return _this;
    }
    AcrossConfigStoreClient.prototype.getRateModelForBlockNumber = function (l1Token, originChainId, destinationChainId, blockNumber) {
        if (blockNumber === void 0) { blockNumber = undefined; }
        // Use route-rate model if available, otherwise use default rate model for l1Token.
        var route = "".concat(originChainId, "-").concat(destinationChainId);
        var routeRateModel = this.getRouteRateModelForBlockNumber(l1Token, route, blockNumber);
        if (routeRateModel) {
            return routeRateModel;
        }
        var defaultRateModelUpdate = sortEventsDescending(this.cumulativeRateModelUpdates).find(function (config) {
            return config.blockNumber <= (blockNumber !== null && blockNumber !== void 0 ? blockNumber : 0) &&
                config.l1Token === l1Token.toEvmAddress() &&
                config.rateModel !== undefined;
        });
        if (!defaultRateModelUpdate) {
            throw new Error("Could not find TokenConfig update for ".concat(l1Token, " at block ").concat(blockNumber));
        }
        return parseAndReturnRateModelFromString(defaultRateModelUpdate.rateModel);
    };
    AcrossConfigStoreClient.prototype.getRouteRateModelForBlockNumber = function (l1Token, route, blockNumber) {
        if (blockNumber === void 0) { blockNumber = undefined; }
        var config = sortEventsDescending(this.cumulativeRouteRateModelUpdates).find(function (config) { return config.blockNumber <= (blockNumber !== null && blockNumber !== void 0 ? blockNumber : 0) && config.l1Token === l1Token.toEvmAddress(); });
        if ((config === null || config === void 0 ? void 0 : config.routeRateModel[route]) === undefined) {
            return undefined;
        }
        return parseAndReturnRateModelFromString(config.routeRateModel[route]);
    };
    /**
     * Resolve the implied set of chain ID indices based on the chain ID of the ConfigStore.
     * @param chainId Chain ID of the ConfigStore.
     * @dev If the resolved chain ID is part of the default set, assume the protocol defaults.
     *      Otherwise, assume this is a test deployment with a lone chain ID.
     * @dev The protocol defaults are [1, 10, 137, 288, 42161] (outlined in UMIP-157).
     * @dev chainId is marked optional to appease tsc. It must always be passed in.
     */
    AcrossConfigStoreClient.prototype.implicitChainIdIndices = function (chainId) {
        assert(isDefined(chainId), "ConfigStoreClient used before update");
        return PROTOCOL_DEFAULT_CHAIN_ID_INDICES[0] === chainId ? PROTOCOL_DEFAULT_CHAIN_ID_INDICES : [chainId];
    };
    /**
     * Resolves the chain ids that were available to the protocol at a given block range.
     * @param blockNumber Block number to search for. Defaults to latest block.
     * @returns List of chain IDs that were available to the protocol at the given block number.
     * @note This dynamic functionality has been added after the launch of Across.
     * @note This function will return a default list of chain IDs if the block requested
     *       existed before the initial inclusion of this dynamic key/value entry. In the
     *       case that a block number is requested that is before the initial inclusion of
     *       this key/value entry, the function will return the default list of chain IDs as
     *       outlined per the UMIP (https://github.com/UMAprotocol/UMIPs/pull/590).
     */
    AcrossConfigStoreClient.prototype.getChainIdIndicesForBlock = function (blockNumber) {
        var _a;
        if (blockNumber === void 0) { blockNumber = Number.MAX_SAFE_INTEGER; }
        // Resolve the chain ID indices for the block number requested.
        var chainIdUpdates = sortEventsDescending(this.chainIdIndicesUpdates);
        // Iterate through each of the chain ID updates and resolve the first update that is
        // less than or equal to the block number requested.
        var chainIdIndices = (_a = chainIdUpdates.find(function (update) { return update.blockNumber <= blockNumber; })) === null || _a === void 0 ? void 0 : _a.value;
        // Return either the found value or the protocol default.
        return chainIdIndices !== null && chainIdIndices !== void 0 ? chainIdIndices : this.implicitChainIdIndices(this.chainId);
    };
    /**
     * Resolves the lite chain ids that were available to the protocol at a given block range.
     * @param blockNumber Block number to search for. Defaults to latest block.
     * @returns List of lite chain IDs that were available to the protocol at the given block number.
     * @note This dynamic functionality has been added after the launch of Across.
     */
    AcrossConfigStoreClient.prototype.getLiteChainIdIndicesForBlock = function (blockNumber) {
        var _a, _b;
        if (blockNumber === void 0) { blockNumber = Number.MAX_SAFE_INTEGER; }
        var liteChainIdList = sortEventsDescending(this.liteChainIndicesUpdates);
        return (_b = (_a = liteChainIdList.find(function (update) { return update.blockNumber <= blockNumber; })) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : [];
    };
    /**
     * Resolves the lite chain ids that were available to the protocol at a given timestamp.
     * @param timestamp Timestamp to search for. Defaults to latest time - in seconds.
     * @returns List of lite chain IDs that were available to the protocol at the given timestamp.
     * @note This dynamic functionality has been added after the launch of Across.
     */
    AcrossConfigStoreClient.prototype.getLiteChainIdIndicesForTimestamp = function (timestamp) {
        var _a, _b;
        if (timestamp === void 0) { timestamp = Number.MAX_SAFE_INTEGER; }
        var liteChainIdList = sortEventsDescending(this.liteChainIndicesUpdates);
        return (_b = (_a = liteChainIdList.find(function (update) { return update.timestamp <= timestamp; })) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : [];
    };
    /**
     * Checks if a chain ID was a lite chain at a given timestamp.
     * @param chainId The chain ID to check.
     * @param timestamp The timestamp to check. Defaults to latest time - in seconds.
     * @returns True if the chain ID was a lite chain at the given timestamp. False otherwise.
     */
    AcrossConfigStoreClient.prototype.isChainLiteChainAtTimestamp = function (chainId, timestamp) {
        if (timestamp === void 0) { timestamp = Number.MAX_SAFE_INTEGER; }
        return this.getLiteChainIdIndicesForTimestamp(timestamp).includes(chainId);
    };
    AcrossConfigStoreClient.prototype.getSpokeTargetBalancesForBlock = function (l1Token, chainId, blockNumber) {
        var _a;
        if (blockNumber === void 0) { blockNumber = Number.MAX_SAFE_INTEGER; }
        var config = sortEventsDescending(this.cumulativeSpokeTargetBalanceUpdates).find(function (config) { return config.l1Token === l1Token && config.blockNumber <= blockNumber; });
        var targetBalance = (_a = config === null || config === void 0 ? void 0 : config.spokeTargetBalances) === null || _a === void 0 ? void 0 : _a[chainId];
        return targetBalance || { target: toBN(0), threshold: toBN(0) };
    };
    // <-- END LEGACY CONFIGURATION OBJECTS -->
    AcrossConfigStoreClient.prototype.getMaxRefundCountForRelayerRefundLeafForBlock = function (blockNumber) {
        if (blockNumber === void 0) { blockNumber = Number.MAX_SAFE_INTEGER; }
        var config = sortEventsDescending(this.cumulativeMaxRefundCountUpdates).find(function (config) { return config.blockNumber <= blockNumber; });
        if (!config) {
            throw new Error("Could not find MaxRefundCount before block ".concat(blockNumber));
        }
        return Number(config.value);
    };
    AcrossConfigStoreClient.prototype.getMaxL1TokenCountForPoolRebalanceLeafForBlock = function (blockNumber) {
        if (blockNumber === void 0) { blockNumber = Number.MAX_SAFE_INTEGER; }
        var config = sortEventsDescending(this.cumulativeMaxL1TokenCountUpdates).find(function (config) { return config.blockNumber <= blockNumber; });
        if (!config) {
            throw new Error("Could not find MaxL1TokenCount before block ".concat(blockNumber));
        }
        return Number(config.value);
    };
    /**
     * Returns list of chains that have been enabled at least once in the block range.
     * If a chain was disabled in the block range, it will be included in the list provided it was enabled
     * at some point in the block range.
     * @dev If fromBlock == toBlock then defaults to returning enabled chains at fromBlock
     * @param fromBlock Start block to search inclusive
     * @param toBlock End block to search inclusive. Defaults to MAX_SAFE_INTEGER, so grabs all disabled chain events
     * up until `latest`.
     * @returns List of chain IDs that have been enabled at least once in the block range. Sorted from lowest to highest.
     */
    AcrossConfigStoreClient.prototype.getEnabledChainsInBlockRange = function (fromBlock, toBlock) {
        if (toBlock === void 0) { toBlock = Number.MAX_SAFE_INTEGER; }
        // If our fromBlock is greater than our toBlock, then we have an invalid range.
        if (fromBlock > toBlock) {
            throw new Error("Invalid block range: fromBlock ".concat(fromBlock, " > toBlock ").concat(toBlock));
        }
        // Initiate list with all possible chains enabled at the toBlock while removing any chains
        // that were disabled at the from block.
        var disabledChainsAtFromBlock = this.getDisabledChainsForBlock(fromBlock);
        var allPossibleChains = this.getChainIdIndicesForBlock(toBlock);
        var enabledChainsInBlockRange = allPossibleChains.filter(function (chainId) { return !disabledChainsAtFromBlock.includes(chainId); });
        // If there are any disabled chain updates in the block range, then we might need to update the list of enabled
        // chains in the block range.
        this.cumulativeDisabledChainUpdates
            .filter(function (e) { return e.blockNumber <= toBlock && e.blockNumber >= fromBlock; })
            .forEach(function (e) {
            // If disabled chain update no longer includes a previously disabled chain, then add it back to the enabled chains
            // list.
            var newDisabledSet = e.chainIds;
            disabledChainsAtFromBlock.forEach(function (disabledChain) {
                // New disabled set doesn't include this chain that was previously disabled so it was re-enabled at this point
                // in the block range.
                if (!newDisabledSet.includes(disabledChain)) {
                    enabledChainsInBlockRange.push(disabledChain);
                }
            });
        });
        // Return the enabled chains in the block range sorted in the same order as the chain indices.
        return allPossibleChains.filter(function (chainId) { return enabledChainsInBlockRange.includes(chainId); });
    };
    AcrossConfigStoreClient.prototype.getEnabledChains = function (block) {
        if (block === void 0) { block = Number.MAX_SAFE_INTEGER; }
        // Get most recent disabled chain list before the block specified.
        var currentlyDisabledChains = this.getDisabledChainsForBlock(block);
        return this.getChainIdIndicesForBlock(block).filter(function (chainId) { return !currentlyDisabledChains.includes(chainId); });
    };
    AcrossConfigStoreClient.prototype.getDisabledChainsForBlock = function (blockNumber) {
        var _a, _b;
        if (blockNumber === void 0) { blockNumber = Number.MAX_SAFE_INTEGER; }
        return ((_b = (_a = sortEventsDescending(this.cumulativeDisabledChainUpdates).find(function (config) { return config.blockNumber <= blockNumber; })) === null || _a === void 0 ? void 0 : _a.chainIds) !== null && _b !== void 0 ? _b : []);
    };
    AcrossConfigStoreClient.prototype.getConfigStoreVersionForTimestamp = function (timestamp) {
        if (timestamp === void 0) { timestamp = Number.MAX_SAFE_INTEGER; }
        var config = this.cumulativeConfigStoreVersionUpdates.find(function (config) { return config.timestamp <= timestamp; });
        return isDefined(config) ? Number(config.value) : DEFAULT_CONFIG_STORE_VERSION;
    };
    AcrossConfigStoreClient.prototype.getConfigStoreVersionForBlock = function (blockNumber) {
        if (blockNumber === void 0) { blockNumber = Number.MAX_SAFE_INTEGER; }
        var config = this.cumulativeConfigStoreVersionUpdates.find(function (config) { return config.blockNumber <= blockNumber; });
        return isDefined(config) ? Number(config.value) : DEFAULT_CONFIG_STORE_VERSION;
    };
    AcrossConfigStoreClient.prototype.hasValidConfigStoreVersionForTimestamp = function (timestamp) {
        if (timestamp === void 0) { timestamp = Number.MAX_SAFE_INTEGER; }
        var version = this.getConfigStoreVersionForTimestamp(timestamp);
        return this.configStoreVersion >= version;
    };
    /**
     * Resolve the chain ID for the ConfigStore Provider instance.
     * @dev When the provider is a StatisJsonRpcProvider instance, the getNetwork() is non-blocking.
     * @returns Chain ID for the ConfigStore deployment.
     */
    AcrossConfigStoreClient.prototype.resolveChainId = function () {
        var _a;
        return __awaiter(this, void 0, void 0, function () {
            var _b;
            return __generator(this, function (_c) {
                switch (_c.label) {
                    case 0:
                        if (!((_a = this.chainId) !== null && _a !== void 0)) return [3 /*break*/, 1];
                        _b = _a;
                        return [3 /*break*/, 3];
                    case 1: return [4 /*yield*/, this.configStore.provider.getNetwork()];
                    case 2:
                        _b = (_c.sent()).chainId;
                        _c.label = 3;
                    case 3: return [2 /*return*/, _b];
                }
            });
        });
    };
    AcrossConfigStoreClient.prototype._update = function () {
        return __awaiter(this, void 0, void 0, function () {
            var chainId, searchConfig, reason, _a, updatedTokenConfigEvents, updatedGlobalConfigEvents, updatedTokenConfigSortableEvents, updatedGlobalConfigSortableEvents, globalConfigUpdateTimes;
            var _this = this;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0: return [4 /*yield*/, this.resolveChainId()];
                    case 1:
                        chainId = _b.sent();
                        return [4 /*yield*/, this.updateSearchConfig(this.configStore.provider)];
                    case 2:
                        searchConfig = _b.sent();
                        if (isUpdateFailureReason(searchConfig)) {
                            reason = searchConfig;
                            return [2 /*return*/, { success: false, reason: reason }];
                        }
                        this.logger.debug({ at: "AcrossConfigStore", message: "Updating ConfigStore client", searchConfig: searchConfig });
                        return [4 /*yield*/, Promise.all([
                                paginatedEventQuery(this.configStore, this.configStore.filters.UpdatedTokenConfig(), searchConfig),
                                paginatedEventQuery(this.configStore, this.configStore.filters.UpdatedGlobalConfig(), searchConfig),
                            ])];
                    case 3:
                        _a = _b.sent(), updatedTokenConfigEvents = _a[0], updatedGlobalConfigEvents = _a[1];
                        updatedTokenConfigSortableEvents = updatedTokenConfigEvents.map(spreadEventWithBlockNumber);
                        updatedGlobalConfigSortableEvents = updatedGlobalConfigEvents.map(spreadEventWithBlockNumber);
                        // Events *should* normally be received in ascending order, but explicitly enforce the ordering.
                        [updatedTokenConfigSortableEvents, updatedGlobalConfigSortableEvents].forEach(function (events) {
                            return sortEventsAscendingInPlace(events);
                        });
                        return [4 /*yield*/, Promise.all(updatedGlobalConfigEvents.map(function (event) { return _this.configStore.provider.getBlock(event.blockNumber); }))];
                    case 4:
                        globalConfigUpdateTimes = (_b.sent()).map(function (block) { return block.timestamp; });
                        return [2 /*return*/, {
                                success: true,
                                chainId: chainId,
                                searchEndBlock: searchConfig.to,
                                events: {
                                    updatedTokenConfigEvents: updatedTokenConfigSortableEvents,
                                    updatedGlobalConfigEvents: updatedGlobalConfigSortableEvents,
                                    globalConfigUpdateTimes: globalConfigUpdateTimes,
                                },
                            }];
                }
            });
        });
    };
    AcrossConfigStoreClient.prototype.update = function () {
        var _a, _b, _c, _d, _e;
        return __awaiter(this, void 0, void 0, function () {
            var result, chainId, _f, updatedTokenConfigEvents, updatedGlobalConfigEvents, globalConfigUpdateTimes, _i, updatedTokenConfigEvents_1, event_1, args, _g, rateModel, routeRateModel, spokeTargetBalances, value, l1Token, eventData, err_1, maxWarnAge, _h, errMsg, _loop_1, this_1, i;
            return __generator(this, function (_j) {
                switch (_j.label) {
                    case 0: return [4 /*yield*/, this._update()];
                    case 1:
                        result = _j.sent();
                        if (!result.success) {
                            if (result.reason !== UpdateFailureReason.AlreadyUpdated) {
                                throw new Error("Unable to update ConfigStoreClient: ".concat(result.reason));
                            }
                            // No need to touch `this.isUpdated` because it should already be set from a previous update.
                            return [2 /*return*/];
                        }
                        chainId = result.chainId;
                        _f = result.events, updatedTokenConfigEvents = _f.updatedTokenConfigEvents, updatedGlobalConfigEvents = _f.updatedGlobalConfigEvents, globalConfigUpdateTimes = _f.globalConfigUpdateTimes;
                        assert(updatedGlobalConfigEvents.length === globalConfigUpdateTimes.length, "GlobalConfigUpdate events mismatch (".concat(updatedGlobalConfigEvents.length, " != ").concat(globalConfigUpdateTimes.length, ")"));
                        _i = 0, updatedTokenConfigEvents_1 = updatedTokenConfigEvents;
                        _j.label = 2;
                    case 2:
                        if (!(_i < updatedTokenConfigEvents_1.length)) return [3 /*break*/, 7];
                        event_1 = updatedTokenConfigEvents_1[_i];
                        // If transaction hash is known to be invalid, skip it immediately to avoid creating extra logs.
                        if (KNOWN_INVALID_TOKEN_CONFIG_UPDATE_HASHES.includes(event_1.txnRef.toLowerCase())) {
                            return [3 /*break*/, 6];
                        }
                        args = event_1;
                        _j.label = 3;
                    case 3:
                        _j.trys.push([3, 4, , 6]);
                        _g = this.validateTokenConfigUpdate(args), rateModel = _g.rateModel, routeRateModel = _g.routeRateModel, spokeTargetBalances = _g.spokeTargetBalances;
                        value = args.value, l1Token = args.key, eventData = __rest(args, ["value", "key"]);
                        if (rateModel !== undefined) {
                            this.cumulativeRateModelUpdates.push(__assign(__assign({}, eventData), { rateModel: rateModel, l1Token: l1Token }));
                            this.cumulativeSpokeTargetBalanceUpdates.push(__assign(__assign({}, eventData), { spokeTargetBalances: spokeTargetBalances, l1Token: l1Token }));
                            this.cumulativeRouteRateModelUpdates.push(__assign(__assign({}, eventData), { routeRateModel: routeRateModel, l1Token: l1Token }));
                        }
                        return [3 /*break*/, 6];
                    case 4:
                        err_1 = _j.sent();
                        _h = (24 * 60 * 60);
                        return [4 /*yield*/, utils.averageBlockTimeSeconds()];
                    case 5:
                        maxWarnAge = _h / (_j.sent());
                        if (result.searchEndBlock - event_1.blockNumber < maxWarnAge) {
                            errMsg = isError(err_1) ? err_1.message : "unknown error";
                            // This will emit warning logs for any invalid historical updates and it will be very noisy, so
                            // developer should move over known invalid hashes to KNOWN_INVALID_TOKEN_CONFIG_UPDATE_HASHES to
                            // suppress these warnings.
                            this.logger.warn({
                                at: "ConfigStore::update",
                                message: "Caught error during ConfigStore update: ".concat(errMsg),
                                update: args,
                            });
                        }
                        else {
                            this.logger.debug({
                                at: "ConfigStoreClient::update",
                                message: "Skipping invalid historical update at block ".concat(event_1.blockNumber),
                                txnRef: event_1.txnRef,
                            });
                        }
                        return [3 /*break*/, 6];
                    case 6:
                        _i++;
                        return [3 /*break*/, 2];
                    case 7:
                        _loop_1 = function (i) {
                            var args = updatedGlobalConfigEvents[i];
                            if (args.key === utf8ToHex(GLOBAL_CONFIG_STORE_KEYS.MAX_RELAYER_REPAYMENT_LEAF_SIZE)) {
                                if (!isNaN(args.value)) {
                                    this_1.cumulativeMaxRefundCountUpdates.push(args);
                                }
                            }
                            else if (args.key === utf8ToHex(GLOBAL_CONFIG_STORE_KEYS.LITE_CHAIN_ID_INDICES)) {
                                // We need to parse the chain ID indices array from the stringified JSON. However,
                                // the on-chain string has quotes around the array, which will parse our JSON as a
                                // string instead of an array. We need to remove these quotes before parsing.
                                // To be sure, we can check for single quotes, double quotes, and spaces.
                                // Use a regular expression to check if the string is a valid array. We need to check for
                                // leading and trailing quotes, as well as leading and trailing whitespace. We also need to
                                // check for commas between the numbers. Alternatively, this can be an empty array.
                                if (!/^\s*["']?\[(\d+(,\d+)*)?\]["']?\s*$/.test(args.value)) {
                                    this_1.logger.warn({ at: "ConfigStore", message: "The lite chain indices array ".concat(args.value, " is invalid.") });
                                    return "continue";
                                }
                                var chainIndices = JSON.parse(args.value.replace(/['"\s]/g, ""));
                                // Check that the array is valid and that every element is a number.
                                if (!isArrayOf(chainIndices, isPositiveInteger)) {
                                    this_1.logger.warn({ at: "ConfigStore", message: "The array ".concat(chainIndices, " is invalid.") });
                                    return "continue";
                                }
                                // Let's also check that the array doesn't contain any duplicates.
                                if (new Set(chainIndices).size !== chainIndices.length) {
                                    this_1.logger.warn({
                                        at: "ConfigStore",
                                        message: "The array ".concat(chainIndices, " contains duplicates making it invalid."),
                                    });
                                    return "continue";
                                }
                                // If all else passes, we can add this update.
                                this_1.liteChainIndicesUpdates.push(__assign(__assign({}, args), { value: chainIndices, timestamp: globalConfigUpdateTimes[i] }));
                            }
                            else if (args.key === utf8ToHex(GLOBAL_CONFIG_STORE_KEYS.CHAIN_ID_INDICES)) {
                                try {
                                    // We need to parse the chain ID indices array from the stringified JSON. However,
                                    // the on-chain string has quotes around the array, which will parse our JSON as a
                                    // string instead of an array. We need to remove these quotes before parsing.
                                    // To be sure, we can check for single quotes, double quotes, and spaces.
                                    var chainIndices_1 = JSON.parse(args.value.replace(/['"\s]/g, ""));
                                    // Check that the array is valid and that every element is a number.
                                    if (!isArrayOf(chainIndices_1, isPositiveInteger)) {
                                        this_1.logger.warn({ at: "ConfigStore", message: "The array ".concat(chainIndices_1, " is invalid.") });
                                        return "continue";
                                    }
                                    // Let's also check that the array doesn't contain any duplicates.
                                    if (new Set(chainIndices_1).size !== chainIndices_1.length) {
                                        this_1.logger.warn({
                                            at: "ConfigStore",
                                            message: "The array ".concat(chainIndices_1, " contains duplicates making it invalid."),
                                        });
                                        return "continue";
                                    }
                                    // Now check that we're only appending positive integers to the chainIndices array on each
                                    // update. If this isn't the case, skip the update & warn. If there is no previous update,
                                    // resolve an implcit chain ID list.
                                    var previousUpdate = (_b = (_a = this_1.chainIdIndicesUpdates.at(-1)) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : this_1.implicitChainIdIndices(chainId);
                                    // We should now check that previousUpdate is a subset of chainIndices.
                                    if (!previousUpdate.every(function (chainId, idx) { return chainIndices_1[idx] === chainId; })) {
                                        this_1.logger.warn({
                                            at: "ConfigStoreClient#update",
                                            message: "The array ".concat(chainIndices_1, " is invalid. It must be a superset of the previous array ").concat(previousUpdate),
                                        });
                                        return "continue";
                                    }
                                    // If all else passes, we can add this update.
                                    this_1.chainIdIndicesUpdates.push(__assign(__assign({}, args), { value: chainIndices_1 }));
                                }
                                catch (e) {
                                    this_1.logger.warn({ at: "ConfigStore::update", message: "Failed to parse chain ID indices: ".concat(args.value) });
                                }
                            }
                            else if (args.key === utf8ToHex(GLOBAL_CONFIG_STORE_KEYS.MAX_POOL_REBALANCE_LEAF_SIZE)) {
                                if (!isNaN(args.value)) {
                                    this_1.cumulativeMaxL1TokenCountUpdates.push(args);
                                }
                            }
                            else if (args.key === utf8ToHex(GLOBAL_CONFIG_STORE_KEYS.VERSION)) {
                                // If not a number, skip.
                                if (isNaN(args.value)) {
                                    return "continue";
                                }
                                var value = Number(args.value);
                                // If not an integer, skip.
                                if (!Number.isInteger(value)) {
                                    return "continue";
                                }
                                // Extract the current highest version. Require that the version always increments, otherwise skip the update.
                                var lastValue = Number((_d = (_c = this_1.cumulativeConfigStoreVersionUpdates[0]) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : DEFAULT_CONFIG_STORE_VERSION);
                                if (value <= lastValue) {
                                    return "continue";
                                }
                                // Prepend the update to impose descending ordering for version updates.
                                this_1.cumulativeConfigStoreVersionUpdates = __spreadArray([
                                    __assign(__assign({}, args), { timestamp: globalConfigUpdateTimes[i] })
                                ], this_1.cumulativeConfigStoreVersionUpdates, true);
                            }
                            else if (args.key === utf8ToHex(GLOBAL_CONFIG_STORE_KEYS.DISABLED_CHAINS)) {
                                try {
                                    var chainIds = this_1.filterDisabledChains(JSON.parse(args.value));
                                    this_1.cumulativeDisabledChainUpdates.push(__assign(__assign({}, args), { chainIds: chainIds }));
                                }
                                catch (err) {
                                    // Can't parse list, skip.
                                }
                            }
                            else {
                                return "continue";
                            }
                        };
                        this_1 = this;
                        // Save new Global config updates.
                        for (i = 0; i < updatedGlobalConfigEvents.length; i++) {
                            _loop_1(i);
                        }
                        this.hasLatestConfigStoreVersion = this.hasValidConfigStoreVersionForTimestamp();
                        this.latestHeightSearched = result.searchEndBlock;
                        this.firstHeightToSearch = result.searchEndBlock + 1; // Next iteration should start off from where this one ended.
                        this.eventSearchConfig.to = undefined; // Caller can re-set on subsequent updates if necessary
                        this.chainId = (_e = this.chainId) !== null && _e !== void 0 ? _e : chainId; // Update on the first run only.
                        this.isUpdated = true;
                        this.logger.debug({ at: "ConfigStore", message: "ConfigStore client updated!" });
                        return [2 /*return*/];
                }
            });
        });
    };
    AcrossConfigStoreClient.prototype.validateTokenConfigUpdate = function (args) {
        var _this = this;
        var value = args.value, key = args.key, txnRef = args.txnRef;
        var parsedValue = parseJSONWithNumericString(value);
        var l1Token = key;
        // Return the following parameters if the TokenConfig update is valid, otherwise throw an error.
        // Remove any config updates with invalid rate models by throwing an error if any part of the TokenConfig
        // is wrong before we push any events into this client's state.
        var rateModelForToken = undefined;
        var spokeTargetBalances = {};
        var routeRateModel = {};
        // Drop value and key before passing args.
        if ((parsedValue === null || parsedValue === void 0 ? void 0 : parsedValue.rateModel) !== undefined) {
            var rateModel = parsedValue.rateModel;
            assert(this.isValidRateModel(rateModel), "Invalid rateModel UBar for ".concat(l1Token, " at transaction ").concat(txnRef, ", ").concat(JSON.stringify(rateModel)));
            rateModelForToken = JSON.stringify(rateModel);
            // Store spokeTargetBalances
            if (parsedValue === null || parsedValue === void 0 ? void 0 : parsedValue.spokeTargetBalances) {
                // Note: cast is required because fromEntries always produces string keys, despite the function returning a
                // numerical key.
                spokeTargetBalances = Object.fromEntries(Object.entries(parsedValue.spokeTargetBalances).map(function (_a) {
                    var chainId = _a[0], targetBalance = _a[1];
                    var target = max(toBN(targetBalance.target), toBN(0));
                    var threshold = max(toBN(targetBalance.threshold), toBN(0));
                    return [chainId, { target: target, threshold: threshold }];
                }));
            }
            // Store route-specific rate models
            if (parsedValue === null || parsedValue === void 0 ? void 0 : parsedValue.routeRateModel) {
                routeRateModel = Object.fromEntries(Object.entries(parsedValue.routeRateModel).map(function (_a) {
                    var path = _a[0], routeRateModel = _a[1];
                    assert(_this.isValidRateModel(routeRateModel) &&
                        "Invalid routeRateModel UBar for ".concat(path, " for ").concat(l1Token, " at transaction ").concat(txnRef, ", ").concat(JSON.stringify(routeRateModel)));
                    return [path, JSON.stringify(routeRateModel)];
                }));
            }
        }
        return {
            spokeTargetBalances: spokeTargetBalances,
            rateModel: rateModelForToken,
            routeRateModel: routeRateModel,
        };
    };
    AcrossConfigStoreClient.prototype.isValidRateModel = function (rateModel) {
        // UBar should be between 0% and 100%.
        return toBN(rateModel.UBar).gt(0) && toBN(rateModel.UBar).lt(toWei("1"));
    };
    AcrossConfigStoreClient.prototype.filterDisabledChains = function (disabledChains) {
        // If any chain ID's are not integers then ignore. UMIP-157 requires that this key cannot include
        // the chain ID 1.
        return disabledChains.filter(function (chainId) { return !isNaN(chainId) && Number.isInteger(chainId) && chainId !== 1; });
    };
    return AcrossConfigStoreClient;
}(BaseAbstractClient));
export { AcrossConfigStoreClient };
//# sourceMappingURL=AcrossConfigStoreClient.js.map