"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRefundsFromBundle = getRefundsFromBundle;
exports.prettyPrintV3SpokePoolEvents = prettyPrintV3SpokePoolEvents;
exports.getEndBlockBuffers = getEndBlockBuffers;
exports._buildPoolRebalanceRoot = _buildPoolRebalanceRoot;
exports._buildHistoricalPoolRebalanceRoot = _buildHistoricalPoolRebalanceRoot;
exports._buildOptimisticPoolRebalanceRoot = _buildOptimisticPoolRebalanceRoot;
exports._getMarginalRunningBalances = _getMarginalRunningBalances;
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const utils_1 = require("../../../utils");
const PoolRebalanceUtils_1 = require("./PoolRebalanceUtils");
const MerkleTreeUtils_1 = require("./MerkleTreeUtils");
function getRefundsFromBundle(bundleFillsV3, expiredDepositsToRefundV3) {
    const combinedRefunds = {};
    Object.entries(bundleFillsV3).forEach(([repaymentChainId, fillsForChain]) => {
        combinedRefunds[repaymentChainId] ??= {};
        Object.entries(fillsForChain).forEach(([l2TokenAddress, { refunds }]) => {
            if (refunds === undefined) {
                return;
            }
            const refundsShallowCopy = { ...refunds };
            if (combinedRefunds[repaymentChainId][l2TokenAddress] === undefined) {
                combinedRefunds[repaymentChainId][l2TokenAddress] = refundsShallowCopy;
            }
            else {
                combinedRefunds[repaymentChainId][l2TokenAddress] = {
                    ...combinedRefunds[repaymentChainId][l2TokenAddress],
                    ...refundsShallowCopy,
                };
            }
        });
    });
    Object.entries(expiredDepositsToRefundV3).forEach(([originChainId, depositsForChain]) => {
        combinedRefunds[originChainId] ??= {};
        Object.entries(depositsForChain).forEach(([l2TokenAddress, deposits]) => {
            deposits.forEach((deposit) => {
                if (combinedRefunds[originChainId][l2TokenAddress] === undefined) {
                    combinedRefunds[originChainId][l2TokenAddress] = { [deposit.depositor.toBytes32()]: deposit.inputAmount };
                }
                else {
                    const existingRefundAmount = combinedRefunds[originChainId][l2TokenAddress][deposit.depositor.toBytes32()];
                    combinedRefunds[originChainId][l2TokenAddress][deposit.depositor.toBytes32()] = deposit.inputAmount.add(existingRefundAmount ?? utils_1.bnZero);
                }
            });
        });
    });
    return combinedRefunds;
}
function prettyPrintV3SpokePoolEvents(bundleDepositsV3, bundleFillsV3, bundleSlowFillsV3, expiredDepositsToRefundV3, unexecutableSlowFills) {
    return {
        bundleDepositsV3: (0, utils_1.count2DDictionaryValues)(bundleDepositsV3),
        bundleFillsV3: (0, utils_1.count3DDictionaryValues)(bundleFillsV3, "fills"),
        bundleSlowFillsV3: (0, utils_1.count2DDictionaryValues)(bundleSlowFillsV3),
        expiredDepositsToRefundV3: (0, utils_1.count2DDictionaryValues)(expiredDepositsToRefundV3),
        unexecutableSlowFills: (0, utils_1.count2DDictionaryValues)(unexecutableSlowFills),
    };
}
function getEndBlockBuffers(chainIdListForBundleEvaluationBlockNumbers, blockRangeEndBlockBuffer) {
    return chainIdListForBundleEvaluationBlockNumbers.map((chainId) => blockRangeEndBlockBuffer[chainId] ?? 0);
}
async function _buildPoolRebalanceRoot(latestMainnetBlock, mainnetBundleEndBlock, bundleV3Deposits, bundleFillsV3, bundleSlowFillsV3, unexecutableSlowFills, expiredDepositsToRefundV3, clients, maxL1TokenCountOverride) {
    if (clients.hubPoolClient.hasPendingProposal() &&
        clients.hubPoolClient.getPendingRootBundle().bundleEvaluationBlockNumbers[0] < mainnetBundleEndBlock) {
        return await _buildOptimisticPoolRebalanceRoot(latestMainnetBlock, mainnetBundleEndBlock, bundleV3Deposits, bundleFillsV3, bundleSlowFillsV3, unexecutableSlowFills, expiredDepositsToRefundV3, clients, maxL1TokenCountOverride);
    }
    return _buildHistoricalPoolRebalanceRoot(latestMainnetBlock, mainnetBundleEndBlock, bundleV3Deposits, bundleFillsV3, bundleSlowFillsV3, unexecutableSlowFills, expiredDepositsToRefundV3, clients, maxL1TokenCountOverride);
}
function _buildHistoricalPoolRebalanceRoot(latestMainnetBlock, mainnetBundleEndBlock, bundleV3Deposits, bundleFillsV3, bundleSlowFillsV3, unexecutableSlowFills, expiredDepositsToRefundV3, clients, maxL1TokenCountOverride) {
    const { runningBalances, realizedLpFees, chainWithRefundsOnly } = _getMarginalRunningBalances(mainnetBundleEndBlock, bundleV3Deposits, bundleFillsV3, bundleSlowFillsV3, unexecutableSlowFills, expiredDepositsToRefundV3, clients);
    (0, PoolRebalanceUtils_1.addLastRunningBalance)(latestMainnetBlock, runningBalances, clients.hubPoolClient);
    const leaves = (0, PoolRebalanceUtils_1.constructPoolRebalanceLeaves)(mainnetBundleEndBlock, runningBalances, realizedLpFees, Array.from(chainWithRefundsOnly).filter((chainId) => !Object.keys(runningBalances).includes(chainId.toString())), clients.configStoreClient, maxL1TokenCountOverride);
    return {
        runningBalances,
        realizedLpFees,
        leaves,
        tree: (0, MerkleTreeUtils_1.buildPoolRebalanceLeafTree)(leaves),
    };
}
async function _buildOptimisticPoolRebalanceRoot(latestMainnetBlock, mainnetBundleEndBlock, bundleV3Deposits, bundleFillsV3, bundleSlowFillsV3, unexecutableSlowFills, expiredDepositsToRefundV3, clients, maxL1TokenCountOverride) {
    const { runningBalances, realizedLpFees, chainWithRefundsOnly } = _getMarginalRunningBalances(mainnetBundleEndBlock, bundleV3Deposits, bundleFillsV3, bundleSlowFillsV3, unexecutableSlowFills, expiredDepositsToRefundV3, clients);
    const mostRecentProposedRootBundle = clients.hubPoolClient.getLatestProposedRootBundle();
    const blockRangesForChains = (0, utils_1.getImpliedBundleBlockRanges)(clients.hubPoolClient, clients.configStoreClient, mostRecentProposedRootBundle);
    const pendingRootBundleData = await clients.bundleDataClient.loadData(blockRangesForChains, clients.spokePoolClients, true);
    const { leaves, tree } = _buildHistoricalPoolRebalanceRoot(latestMainnetBlock, blockRangesForChains[0][1], pendingRootBundleData.bundleDepositsV3, pendingRootBundleData.bundleFillsV3, pendingRootBundleData.bundleSlowFillsV3, pendingRootBundleData.unexecutableSlowFills, pendingRootBundleData.expiredDepositsToRefundV3, clients, maxL1TokenCountOverride);
    (0, assert_1.default)(tree.getHexRoot() === mostRecentProposedRootBundle.poolRebalanceRoot);
    Object.keys(runningBalances).forEach((_repaymentChainId) => {
        Object.keys(runningBalances[Number(_repaymentChainId)]).forEach((_l1TokenAddress) => {
            const repaymentChainId = Number(_repaymentChainId);
            const l1TokenAddress = utils_1.EvmAddress.from(_l1TokenAddress);
            const pendingPoolRebalanceLeaf = leaves.find((leaf) => leaf.chainId === repaymentChainId && leaf.l1Tokens.some((l1Token) => l1Token.eq(l1TokenAddress)));
            if ((0, utils_1.isDefined)(pendingPoolRebalanceLeaf)) {
                const pendingLeafTokenIdx = pendingPoolRebalanceLeaf.l1Tokens.findIndex((l1Token) => l1Token.eq(l1TokenAddress));
                (0, assert_1.default)(pendingLeafTokenIdx !== -1);
                const pendingRunningBalanceAmount = pendingPoolRebalanceLeaf.runningBalances[pendingLeafTokenIdx];
                if (!pendingRunningBalanceAmount.eq(utils_1.bnZero)) {
                    (0, PoolRebalanceUtils_1.updateRunningBalance)(runningBalances, repaymentChainId, _l1TokenAddress, pendingRunningBalanceAmount);
                }
            }
            else {
                const { runningBalance: lastExecutedBundleRunningBalance } = clients.hubPoolClient.getRunningBalanceBeforeBlockForChain(latestMainnetBlock, repaymentChainId, l1TokenAddress);
                if (!lastExecutedBundleRunningBalance.eq(utils_1.bnZero)) {
                    (0, PoolRebalanceUtils_1.updateRunningBalance)(runningBalances, repaymentChainId, _l1TokenAddress, lastExecutedBundleRunningBalance);
                }
            }
        });
    });
    const poolRebalanceLeaves = (0, PoolRebalanceUtils_1.constructPoolRebalanceLeaves)(mainnetBundleEndBlock, runningBalances, realizedLpFees, Array.from(chainWithRefundsOnly).filter((chainId) => !Object.keys(runningBalances).includes(chainId.toString())), clients.configStoreClient, maxL1TokenCountOverride);
    return {
        runningBalances,
        realizedLpFees,
        leaves: poolRebalanceLeaves,
        tree: (0, MerkleTreeUtils_1.buildPoolRebalanceLeafTree)(poolRebalanceLeaves),
    };
}
function _getMarginalRunningBalances(mainnetBundleEndBlock, bundleV3Deposits, bundleFillsV3, bundleSlowFillsV3, unexecutableSlowFills, expiredDepositsToRefundV3, clients) {
    const runningBalances = {};
    const realizedLpFees = {};
    const chainWithRefundsOnly = new Set();
    Object.entries(bundleFillsV3).forEach(([_repaymentChainId, fillsForChain]) => {
        const repaymentChainId = Number(_repaymentChainId);
        Object.entries(fillsForChain).forEach(([l2TokenAddress, { realizedLpFees: totalRealizedLpFee, totalRefundAmount }]) => {
            const l1Token = clients.hubPoolClient.getL1TokenForL2TokenAtBlock((0, utils_1.toAddressType)(l2TokenAddress, repaymentChainId), repaymentChainId, mainnetBundleEndBlock);
            if (!l1Token) {
                chainWithRefundsOnly.add(repaymentChainId);
                return;
            }
            const l1TokenAddr = l1Token.toNative();
            (0, assert_1.default)(l1Token.isEVM(), `Expected an EVM address: ${l1TokenAddr}`);
            (0, PoolRebalanceUtils_1.updateRunningBalance)(runningBalances, repaymentChainId, l1TokenAddr, totalRefundAmount);
            (0, PoolRebalanceUtils_1.updateRunningBalance)(realizedLpFees, repaymentChainId, l1TokenAddr, totalRealizedLpFee);
        });
    });
    Object.entries(bundleSlowFillsV3).forEach(([_destinationChainId, depositsForChain]) => {
        const destinationChainId = Number(_destinationChainId);
        Object.entries(depositsForChain).forEach(([outputToken, deposits]) => {
            deposits.forEach((deposit) => {
                const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock((0, utils_1.toAddressType)(outputToken, destinationChainId), destinationChainId, mainnetBundleEndBlock);
                (0, assert_1.default)((0, utils_1.isDefined)(l1TokenCounterpart), "getRefundInformationFromFill: l1TokenCounterpart is undefined");
                const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(utils_1.fixedPointAdjustment);
                (0, PoolRebalanceUtils_1.updateRunningBalance)(runningBalances, destinationChainId, l1TokenCounterpart.toEvmAddress(), deposit.inputAmount.sub(lpFee));
            });
        });
    });
    Object.entries(unexecutableSlowFills).forEach(([_destinationChainId, slowFilledDepositsForChain]) => {
        const destinationChainId = Number(_destinationChainId);
        Object.entries(slowFilledDepositsForChain).forEach(([outputToken, slowFilledDeposits]) => {
            slowFilledDeposits.forEach((deposit) => {
                const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock((0, utils_1.toAddressType)(outputToken, destinationChainId), destinationChainId, mainnetBundleEndBlock);
                (0, assert_1.default)((0, utils_1.isDefined)(l1TokenCounterpart), "getRefundInformationFromFill: l1TokenCounterpart is undefined");
                const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(utils_1.fixedPointAdjustment);
                (0, PoolRebalanceUtils_1.updateRunningBalance)(runningBalances, destinationChainId, l1TokenCounterpart.toEvmAddress(), lpFee.sub(deposit.inputAmount));
            });
        });
    });
    Object.entries(bundleV3Deposits).forEach(([, depositsForChain]) => {
        Object.entries(depositsForChain).forEach(([, deposits]) => {
            deposits.forEach((deposit) => {
                if (!clients.hubPoolClient.l2TokenHasPoolRebalanceRoute(deposit.inputToken, deposit.originChainId, mainnetBundleEndBlock)) {
                    return;
                }
                (0, PoolRebalanceUtils_1.updateRunningBalanceForDeposit)(runningBalances, clients.hubPoolClient, deposit, deposit.inputAmount.mul(-1), mainnetBundleEndBlock);
            });
        });
    });
    Object.entries(expiredDepositsToRefundV3).forEach(([_originChainId, depositsForChain]) => {
        const originChainId = Number(_originChainId);
        Object.entries(depositsForChain).forEach(([inputToken, deposits]) => {
            deposits.forEach((deposit) => {
                const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock((0, utils_1.toAddressType)(inputToken, originChainId), originChainId, mainnetBundleEndBlock);
                if (!l1TokenCounterpart) {
                    chainWithRefundsOnly.add(deposit.originChainId);
                    return;
                }
                (0, PoolRebalanceUtils_1.updateRunningBalance)(runningBalances, originChainId, l1TokenCounterpart.toEvmAddress(), deposit.inputAmount);
            });
        });
    });
    return { runningBalances, chainWithRefundsOnly, realizedLpFees };
}
//# sourceMappingURL=DataworkerUtils.js.map