"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-unused-expressions */
const consts = __importStar(require("../constants"));
const utils_1 = require("../../../../utils/utils");
const abis_1 = require("../../../../utils/abis");
const IOFT__factory_1 = require("../../../../typechain/factories/contracts/interfaces/IOFT.sol/IOFT__factory");
const HubPool_Fixture_1 = require("../fixtures/HubPool.Fixture");
const MerkleLib_utils_1 = require("../MerkleLib.utils");
const consts_1 = require("../../../../deploy/consts");
const typechain_1 = require("../../../../typechain");
const constants_1 = require("@across-protocol/constants");
let hubPool, arbitrumAdapter, weth, dai, usdc, usdt, ezETH, timer, mockSpoke;
let l2Weth, l2Dai, gatewayAddress, l2Usdc, l2Usdt;
let owner, dataWorker;
let liquidityProvider, refundAddress;
let l1ERC20GatewayRouter, l1Inbox, cctpMessenger, cctpTokenMinter, oftMessenger, adapterStore;
const arbitrumChainId = constants_1.CHAIN_IDs.ARBITRUM;
const oftArbitrumEid = (0, utils_1.getOftEid)(arbitrumChainId);
describe("Arbitrum Chain Adapter", function () {
    beforeEach(async function () {
        [owner, dataWorker, liquidityProvider, refundAddress] = await utils_1.ethers.getSigners();
        ({ weth, dai, l2Weth, l2Dai, hubPool, mockSpoke, timer, usdc, l2Usdc, usdt, l2Usdt } = await (0, HubPool_Fixture_1.hubPoolFixture)());
        await (0, utils_1.seedWallet)(dataWorker, [dai, usdc, usdt], weth, consts.amountToLp);
        await (0, utils_1.seedWallet)(liquidityProvider, [dai, usdc, usdt], weth, consts.amountToLp.mul(10));
        await (0, HubPool_Fixture_1.enableTokensForLP)(owner, hubPool, weth, [weth, dai, usdc, usdt]);
        for (const token of [weth, dai, usdc, usdt]) {
            await token.connect(liquidityProvider).approve(hubPool.address, consts.amountToLp);
            await hubPool.connect(liquidityProvider).addLiquidity(token.address, consts.amountToLp);
            await token.connect(dataWorker).approve(hubPool.address, consts.bondAmount.mul(10));
        }
        cctpMessenger = await (0, utils_1.createFakeFromABI)(abis_1.CCTPTokenMessengerInterface);
        cctpTokenMinter = await (0, utils_1.createFakeFromABI)(abis_1.CCTPTokenMinterInterface);
        cctpMessenger.localMinter.returns(cctpTokenMinter.address);
        cctpTokenMinter.burnLimitsPerMessage.returns((0, utils_1.toWei)("1000000"));
        oftMessenger = await (0, utils_1.createTypedFakeFromABI)([...IOFT__factory_1.IOFT__factory.abi]);
        adapterStore = await (0, utils_1.createTypedFakeFromABI)([...typechain_1.AdapterStore__factory.abi]);
        l1Inbox = await (0, utils_1.createFake)("Inbox");
        l1ERC20GatewayRouter = await (0, utils_1.createFake)("ArbitrumMockErc20GatewayRouter");
        gatewayAddress = (0, utils_1.randomAddress)();
        l1ERC20GatewayRouter.getGateway.returns(gatewayAddress);
        const oftFeeCap = (0, utils_1.toWei)("1");
        arbitrumAdapter = await (await (0, utils_1.getContractFactory)("Arbitrum_Adapter", owner)).deploy(l1Inbox.address, l1ERC20GatewayRouter.address, refundAddress.address, usdc.address, cctpMessenger.address, adapterStore.address, oftArbitrumEid, oftFeeCap);
        // Seed the HubPool some funds so it can send L1->L2 messages.
        await hubPool.connect(liquidityProvider).loadEthForL2Calls({ value: (0, utils_1.toWei)("1") });
        await hubPool.setCrossChainContracts(arbitrumChainId, arbitrumAdapter.address, mockSpoke.address);
        await hubPool.setPoolRebalanceRoute(arbitrumChainId, dai.address, l2Dai);
        await hubPool.setPoolRebalanceRoute(arbitrumChainId, weth.address, l2Weth);
        await hubPool.setPoolRebalanceRoute(arbitrumChainId, usdc.address, l2Usdc);
        await hubPool.setPoolRebalanceRoute(arbitrumChainId, usdt.address, l2Usdt);
    });
    it("relayMessage calls spoke pool functions", async function () {
        const newAdmin = (0, utils_1.randomAddress)();
        const functionCallData = mockSpoke.interface.encodeFunctionData("setCrossDomainAdmin", [newAdmin]);
        (0, utils_1.expect)(await hubPool.relaySpokePoolAdminFunction(arbitrumChainId, functionCallData)).to.changeEtherBalances([l1Inbox], [(0, utils_1.toBN)(consts.sampleL2MaxSubmissionCost).add((0, utils_1.toBN)(consts.sampleL2Gas).mul(consts.sampleL2GasPrice))]);
        (0, utils_1.expect)(l1Inbox.createRetryableTicket).to.have.been.calledOnce;
        (0, utils_1.expect)(l1Inbox.createRetryableTicket).to.have.been.calledWith(mockSpoke.address, 0, consts.sampleL2MaxSubmissionCost, refundAddress.address, refundAddress.address, consts.sampleL2Gas, consts.sampleL2GasPrice, functionCallData);
    });
    it("Correctly calls appropriate arbitrum bridge functions when making ERC20 cross chain calls", async function () {
        // Create an action that will send an L1->L2 tokens transfer and bundle. For this, create a relayer repayment bundle
        // and check that at it's finalization the L2 bridge contracts are called as expected.
        const { leaves, tree, tokensSendToL2 } = await (0, MerkleLib_utils_1.constructSingleChainTree)(dai.address, 1, arbitrumChainId);
        await hubPool
            .connect(dataWorker)
            .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
        await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
        (0, utils_1.expect)(await hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]))).to.changeEtherBalances([l1ERC20GatewayRouter], [(0, utils_1.toBN)(consts.sampleL2MaxSubmissionCost).add((0, utils_1.toBN)(consts.sampleL2Gas).mul(consts.sampleL2GasPrice))]);
        // The correct functions should have been called on the arbitrum contracts.
        (0, utils_1.expect)(l1ERC20GatewayRouter.outboundTransferCustomRefund).to.have.been.calledOnce; // One token transfer over the canonical bridge.
        // Adapter should have approved gateway to spend its ERC20.
        (0, utils_1.expect)(await dai.allowance(hubPool.address, gatewayAddress)).to.equal(tokensSendToL2);
        const message = utils_1.defaultAbiCoder.encode(["uint256", "bytes"], [consts.sampleL2MaxSubmissionCost, "0x"]);
        (0, utils_1.expect)(l1ERC20GatewayRouter.outboundTransferCustomRefund).to.have.been.calledWith(dai.address, refundAddress.address, mockSpoke.address, tokensSendToL2, consts.sampleL2GasSendTokens, consts.sampleL2GasPrice, message);
        (0, utils_1.expect)(l1Inbox.createRetryableTicket).to.have.been.calledOnce; // only 1 L1->L2 message sent.
        (0, utils_1.expect)(l1Inbox.createRetryableTicket).to.have.been.calledWith(mockSpoke.address, 0, consts.sampleL2MaxSubmissionCost, refundAddress.address, refundAddress.address, consts.sampleL2Gas, consts.sampleL2GasPrice, mockSpoke.interface.encodeFunctionData("relayRootBundle", [
            consts.mockRelayerRefundRoot,
            consts.mockSlowRelayRoot,
        ]));
    });
    it("Correctly calls the CCTP bridge adapter when attempting to bridge USDC", async function () {
        const internalChainId = arbitrumChainId;
        // Create an action that will send an L1->L2 tokens transfer and bundle. For this, create a relayer repayment bundle
        // and check that at it's finalization the L2 bridge contracts are called as expected.
        const { leaves, tree, tokensSendToL2 } = await (0, MerkleLib_utils_1.constructSingleChainTree)(usdc.address, 1, internalChainId);
        await hubPool
            .connect(dataWorker)
            .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
        await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
        await hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]));
        // Adapter should have approved gateway to spend its ERC20.
        (0, utils_1.expect)(await usdc.allowance(hubPool.address, cctpMessenger.address)).to.equal(tokensSendToL2);
        // The correct functions should have been called on the bridge contracts
        (0, utils_1.expect)(cctpMessenger.depositForBurn).to.have.been.calledOnce;
        (0, utils_1.expect)(cctpMessenger.depositForBurn).to.have.been.calledWith(utils_1.ethers.BigNumber.from(tokensSendToL2), consts_1.CIRCLE_DOMAIN_IDs[internalChainId], utils_1.ethers.utils.hexZeroPad(mockSpoke.address, 32).toLowerCase(), usdc.address);
    });
    it("Splits USDC into parts to stay under per-message limit when attempting to bridge USDC", async function () {
        const internalChainId = arbitrumChainId;
        // Create an action that will send an L1->L2 tokens transfer and bundle. For this, create a relayer repayment bundle
        // and check that at it's finalization the L2 bridge contracts are called as expected.
        const { leaves, tree, tokensSendToL2 } = await (0, MerkleLib_utils_1.constructSingleChainTree)(usdc.address, 1, internalChainId);
        await hubPool
            .connect(dataWorker)
            .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
        await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
        // 1) Set limit below amount to send and where amount does not divide evenly into limit.
        let newLimit = tokensSendToL2.div(2).sub(1);
        cctpTokenMinter.burnLimitsPerMessage.returns(newLimit);
        await hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]));
        // The correct functions should have been called on the bridge contracts
        (0, utils_1.expect)(cctpMessenger.depositForBurn).to.have.been.calledThrice;
        (0, utils_1.expect)(cctpMessenger.depositForBurn.atCall(0)).to.have.been.calledWith(newLimit, consts_1.CIRCLE_DOMAIN_IDs[internalChainId], utils_1.ethers.utils.hexZeroPad(mockSpoke.address, 32).toLowerCase(), usdc.address);
        (0, utils_1.expect)(cctpMessenger.depositForBurn.atCall(1)).to.have.been.calledWith(newLimit, consts_1.CIRCLE_DOMAIN_IDs[internalChainId], utils_1.ethers.utils.hexZeroPad(mockSpoke.address, 32).toLowerCase(), usdc.address);
        (0, utils_1.expect)(cctpMessenger.depositForBurn.atCall(2)).to.have.been.calledWith(2, // each of the above calls left a remainder of 1
        consts_1.CIRCLE_DOMAIN_IDs[internalChainId], utils_1.ethers.utils.hexZeroPad(mockSpoke.address, 32).toLowerCase(), usdc.address);
        // 2) Set limit below amount to send and where amount divides evenly into limit.
        await hubPool
            .connect(dataWorker)
            .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
        await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
        newLimit = tokensSendToL2.div(2);
        cctpTokenMinter.burnLimitsPerMessage.returns(newLimit);
        await hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]));
        // 2 more calls added to prior 3.
        (0, utils_1.expect)(cctpMessenger.depositForBurn).to.have.callCount(5);
        (0, utils_1.expect)(cctpMessenger.depositForBurn.atCall(3)).to.have.been.calledWith(newLimit, consts_1.CIRCLE_DOMAIN_IDs[internalChainId], utils_1.ethers.utils.hexZeroPad(mockSpoke.address, 32).toLowerCase(), usdc.address);
        (0, utils_1.expect)(cctpMessenger.depositForBurn.atCall(4)).to.have.been.calledWith(newLimit, consts_1.CIRCLE_DOMAIN_IDs[internalChainId], utils_1.ethers.utils.hexZeroPad(mockSpoke.address, 32).toLowerCase(), usdc.address);
    });
    it("Correctly calls the OFT bridge adapter when attempting to bridge USDT", async function () {
        const internalChainId = arbitrumChainId;
        oftMessenger.token.returns(usdt.address);
        const oftMessengerType = utils_1.ethers.utils.formatBytes32String("OFT_MESSENGER");
        await adapterStore
            .connect(owner)
            .setMessenger(oftMessengerType, oftArbitrumEid, usdt.address, oftMessenger.address);
        const { leaves, tree, tokensSendToL2 } = await (0, MerkleLib_utils_1.constructSingleChainTree)(usdt.address, 1, internalChainId, 6);
        await hubPool
            .connect(dataWorker)
            .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
        await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
        // set up correct messenger to be returned on a proper `oftMessengers` call
        adapterStore.crossChainMessengers
            .whenCalledWith(oftMessengerType, oftArbitrumEid, usdt.address)
            .returns(oftMessenger.address);
        // set up `quoteSend` return val
        const msgFeeStruct = [
            (0, utils_1.toWeiWithDecimals)("1", 9).mul(200_000), // nativeFee: 1 GWEI gas price * 200,000 gas cost
            utils_1.BigNumber.from(0), // lzTokenFee: 0
        ];
        oftMessenger.quoteSend.returns(msgFeeStruct);
        // set up `send` return val
        const msgReceipt = [
            (0, utils_1.randomBytes32)(), // guid
            utils_1.BigNumber.from("1"), // nonce
            msgFeeStruct, // fee
        ];
        const oftReceipt = [tokensSendToL2, tokensSendToL2];
        oftMessenger.send.returns([msgReceipt, oftReceipt]);
        await hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]));
        // Adapter should have approved gateway to spend its ERC20.
        (0, utils_1.expect)(await usdt.allowance(hubPool.address, oftMessenger.address)).to.equal(tokensSendToL2);
        const sendParam = {
            dstEid: oftArbitrumEid,
            to: utils_1.ethers.utils.hexZeroPad(mockSpoke.address, 32).toLowerCase(),
            amountLD: tokensSendToL2,
            minAmountLD: tokensSendToL2,
            extraOptions: "0x",
            composeMsg: "0x",
            oftCmd: "0x",
        };
        // We should have called send on the oftMessenger once with correct params
        (0, utils_1.expect)(oftMessenger.send).to.have.been.calledOnce;
        (0, utils_1.expect)(oftMessenger.send).to.have.been.calledWith(sendParam, msgFeeStruct, hubPool.address);
    });
    describe("OFT transfers", function () {
        const oftMessengerType = utils_1.ethers.utils.formatBytes32String("OFT_MESSENGER");
        const internalChainId = arbitrumChainId;
        beforeEach(async function () {
            oftMessenger.token.returns(usdt.address);
            adapterStore.crossChainMessengers
                .whenCalledWith(oftMessengerType, oftArbitrumEid, usdt.address)
                .returns(oftMessenger.address);
        });
        it("reverts with OftLzFeeNotZero if lzTokenFee is not zero", async function () {
            const { leaves, tree } = await (0, MerkleLib_utils_1.constructSingleChainTree)(usdt.address, 1, internalChainId, 6);
            await hubPool
                .connect(dataWorker)
                .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
            await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
            const msgFeeStruct = [
                (0, utils_1.toWeiWithDecimals)("1", 9).mul(200_000), // nativeFee
                utils_1.BigNumber.from(1), // lzTokenFee > 0
            ];
            oftMessenger.quoteSend.returns(msgFeeStruct);
            await (0, utils_1.expect)(hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]))).to.be.revertedWith("delegatecall failed");
        });
        it("reverts with OftFeeCapExceeded if nativeFee is too high", async function () {
            const { leaves, tree } = await (0, MerkleLib_utils_1.constructSingleChainTree)(usdt.address, 1, internalChainId, 6);
            await hubPool
                .connect(dataWorker)
                .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
            await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
            const highNativeFee = (0, utils_1.toWei)("2"); // Higher than oftFeeCap (1 ETH)
            const msgFeeStruct = [
                highNativeFee, // nativeFee
                utils_1.BigNumber.from(0), // lzTokenFee
            ];
            oftMessenger.quoteSend.returns(msgFeeStruct);
            await (0, utils_1.expect)(hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]))).to.be.revertedWith("delegatecall failed");
        });
        it("reverts with OftInsufficientBalanceForFee if hub pool has not enough ETH for fee", async function () {
            const { leaves, tree } = await (0, MerkleLib_utils_1.constructSingleChainTree)(usdt.address, 1, internalChainId, 6);
            await hubPool
                .connect(dataWorker)
                .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
            await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
            const nativeFee = (0, utils_1.toWei)("0.5");
            const msgFeeStruct = [
                nativeFee, // nativeFee
                utils_1.BigNumber.from(0), // lzTokenFee
            ];
            oftMessenger.quoteSend.returns(msgFeeStruct);
            // Drain hubPool's ETH balance
            const hubPoolBalance = await utils_1.ethers.provider.getBalance(hubPool.address);
            // leave 1 wei to avoid "sender doesn't have enough funds" error, which would precede the error we want to test
            if (hubPoolBalance.gt(0)) {
                await owner.sendTransaction({ to: (0, utils_1.randomAddress)(), value: hubPoolBalance.sub(1) });
            }
            await (0, utils_1.expect)(hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]))).to.be.revertedWith("delegatecall failed");
        });
        it("reverts with OftIncorrectAmountReceivedLD if OFT receipt has wrong received amount", async function () {
            const { leaves, tree, tokensSendToL2 } = await (0, MerkleLib_utils_1.constructSingleChainTree)(usdt.address, 1, internalChainId, 6);
            await hubPool
                .connect(dataWorker)
                .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
            await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
            const msgFeeStruct = [utils_1.BigNumber.from(0), utils_1.BigNumber.from(0)];
            oftMessenger.quoteSend.returns(msgFeeStruct);
            const msgReceipt = [
                (0, utils_1.randomBytes32)(),
                utils_1.BigNumber.from(1),
                msgFeeStruct,
            ];
            const oftReceipt = [
                tokensSendToL2.sub(1), // Incorrect received amount
                tokensSendToL2,
            ];
            oftMessenger.send.returns([msgReceipt, oftReceipt]);
            await (0, utils_1.expect)(hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]))).to.be.revertedWith("delegatecall failed");
        });
        it("reverts with OftIncorrectAmountSentLD if OFT receipt has wrong sent amount", async function () {
            const { leaves, tree, tokensSendToL2 } = await (0, MerkleLib_utils_1.constructSingleChainTree)(usdt.address, 1, internalChainId, 6);
            await hubPool
                .connect(dataWorker)
                .proposeRootBundle([3117], 1, tree.getHexRoot(), consts.mockRelayerRefundRoot, consts.mockSlowRelayRoot);
            await timer.setCurrentTime(Number(await timer.getCurrentTime()) + consts.refundProposalLiveness + 1);
            const msgFeeStruct = [utils_1.BigNumber.from(0), utils_1.BigNumber.from(0)];
            oftMessenger.quoteSend.returns(msgFeeStruct);
            const msgReceipt = [
                (0, utils_1.randomBytes32)(),
                utils_1.BigNumber.from(1),
                msgFeeStruct,
            ];
            const oftReceipt = [
                tokensSendToL2,
                tokensSendToL2.sub(1), // Incorrect sent amount
            ];
            oftMessenger.send.returns([msgReceipt, oftReceipt]);
            await (0, utils_1.expect)(hubPool.connect(dataWorker).executeRootBundle(...Object.values(leaves[0]), tree.getHexProof(leaves[0]))).to.be.revertedWith("delegatecall failed");
        });
    });
});
