"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("../constants");
const utils_1 = require("../../../../utils/utils");
const utils_hre_1 = require("../../../../utils/utils.hre");
const HubPool_Fixture_1 = require("../fixtures/HubPool.Fixture");
const MerkleLib_utils_1 = require("../MerkleLib.utils");
const smock_1 = require("@defi-wonderland/smock");
let hubPool, zkSyncSpokePool, dai, weth;
let l2Dai, crossDomainAliasAddress, crossDomainAlias;
let owner, relayer, rando;
let zkErc20Bridge, l2Eth;
// TODO: Grab the following from relayer/CONTRACT_ADDRESSES dictionary?
const abiData = {
    erc20DefaultBridge: {
        address: "0x11f943b2c77b743ab90f4a0ae7d5a4e7fca3e102",
        abi: [
            {
                inputs: [
                    { internalType: "address", name: "_l1Receiver", type: "address" },
                    { internalType: "address", name: "_l2Token", type: "address" },
                    { internalType: "uint256", name: "_amount", type: "uint256" },
                ],
                name: "withdraw",
                outputs: [],
                payable: false,
                stateMutability: "nonpayable",
                type: "function",
            },
        ],
    },
    eth: {
        address: "0x000000000000000000000000000000000000800A",
        abi: [
            {
                inputs: [{ internalType: "address", name: "_l1Receiver", type: "address" }],
                name: "withdraw",
                outputs: [],
                payable: true,
                stateMutability: "payable",
                type: "function",
            },
        ],
    },
};
describe("ZkSync Spoke Pool", function () {
    beforeEach(async function () {
        [owner, relayer, rando] = await utils_1.ethers.getSigners();
        ({ weth, dai, l2Dai, hubPool } = await (0, HubPool_Fixture_1.hubPoolFixture)());
        // Create an alias for the Owner. Impersonate the account. Crate a signer for it and send it ETH.
        crossDomainAliasAddress = (0, utils_1.avmL1ToL2Alias)(owner.address); // @dev Uses same aliasing algorithm as Arbitrum
        await utils_hre_1.hre.network.provider.request({ method: "hardhat_impersonateAccount", params: [crossDomainAliasAddress] });
        crossDomainAlias = await utils_1.ethers.getSigner(crossDomainAliasAddress);
        await owner.sendTransaction({ to: crossDomainAliasAddress, value: (0, utils_1.toWei)("1") });
        zkErc20Bridge = await smock_1.smock.fake(abiData.erc20DefaultBridge.abi, { address: abiData.erc20DefaultBridge.address });
        l2Eth = await smock_1.smock.fake(abiData.eth.abi, { address: abiData.eth.address });
        zkSyncSpokePool = await utils_hre_1.hre.upgrades.deployProxy(await (0, utils_1.getContractFactory)("ZkSync_SpokePool", owner), [0, zkErc20Bridge.address, owner.address, hubPool.address], { kind: "uups", unsafeAllow: ["delegatecall"], constructorArgs: [weth.address, 60 * 60, 9 * 60 * 60] });
        await (0, utils_1.seedContract)(zkSyncSpokePool, relayer, [dai], weth, constants_1.amountHeldByPool);
    });
    it("Only cross domain owner upgrade logic contract", async function () {
        // TODO: Could also use upgrades.prepareUpgrade but I'm unclear of differences
        const implementation = await utils_hre_1.hre.upgrades.deployImplementation(await (0, utils_1.getContractFactory)("ZkSync_SpokePool", owner), { kind: "uups", unsafeAllow: ["delegatecall"], constructorArgs: [weth.address, 60 * 60, 9 * 60 * 60] });
        // upgradeTo fails unless called by cross domain admin
        await (0, utils_1.expect)(zkSyncSpokePool.upgradeTo(implementation)).to.be.revertedWith("ONLY_COUNTERPART_GATEWAY");
        await zkSyncSpokePool.connect(crossDomainAlias).upgradeTo(implementation);
    });
    it("Only cross domain owner can set ZKBridge", async function () {
        await (0, utils_1.expect)(zkSyncSpokePool.setZkBridge(rando.address)).to.be.reverted;
        await zkSyncSpokePool.connect(crossDomainAlias).setZkBridge(rando.address);
        (0, utils_1.expect)(await zkSyncSpokePool.zkErc20Bridge()).to.equal(rando.address);
    });
    it("Only cross domain owner can relay admin root bundles", async function () {
        const { tree } = await (0, MerkleLib_utils_1.constructSingleRelayerRefundTree)(l2Dai, await zkSyncSpokePool.callStatic.chainId());
        await (0, utils_1.expect)(zkSyncSpokePool.relayRootBundle(tree.getHexRoot(), constants_1.mockTreeRoot)).to.be.revertedWith("ONLY_COUNTERPART_GATEWAY");
    });
    it("Bridge tokens to hub pool correctly calls the Standard L2 Bridge for ERC20", async function () {
        const { leaves, tree } = await (0, MerkleLib_utils_1.constructSingleRelayerRefundTree)(l2Dai, await zkSyncSpokePool.callStatic.chainId());
        await zkSyncSpokePool.connect(crossDomainAlias).relayRootBundle(tree.getHexRoot(), constants_1.mockTreeRoot);
        await zkSyncSpokePool.connect(relayer).executeRelayerRefundLeaf(0, leaves[0], tree.getHexProof(leaves[0]));
        // This should have sent tokens back to L1. Check the correct methods on the gateway are correctly called.
        (0, utils_1.expect)(zkErc20Bridge.withdraw).to.have.been.calledOnce;
        (0, utils_1.expect)(zkErc20Bridge.withdraw).to.have.been.calledWith(hubPool.address, l2Dai, constants_1.amountToReturn);
    });
    it("Bridge ETH to hub pool correctly calls the Standard L2 Bridge for WETH, including unwrap", async function () {
        const { leaves, tree } = await (0, MerkleLib_utils_1.constructSingleRelayerRefundTree)(weth.address, await zkSyncSpokePool.callStatic.chainId());
        await zkSyncSpokePool.connect(crossDomainAlias).relayRootBundle(tree.getHexRoot(), constants_1.mockTreeRoot);
        // Executing the refund leaf should cause spoke pool to unwrap WETH to ETH to prepare to send it as msg.value
        // to the ERC20 bridge. This results in a net decrease in WETH balance.
        await (0, utils_1.expect)(() => zkSyncSpokePool.connect(relayer).executeRelayerRefundLeaf(0, leaves[0], tree.getHexProof(leaves[0]))).to.changeTokenBalance(weth, zkSyncSpokePool, constants_1.amountToReturn.mul(-1));
        (0, utils_1.expect)(l2Eth.withdraw).to.have.been.calledWithValue(constants_1.amountToReturn);
        (0, utils_1.expect)(l2Eth.withdraw).to.have.been.calledWith(hubPool.address);
    });
});
