"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../../../utils/utils");
const SpokePool_Fixture_1 = require("./fixtures/SpokePool.Fixture");
const constants_1 = require("./constants");
const { AddressZero: ZERO_ADDRESS } = utils_1.ethers.constants;
describe("SpokePool Depositor Logic", async function () {
    let spokePool, weth, erc20, unwhitelistedErc20;
    let depositor, recipient;
    let quoteTimestamp;
    let amount = constants_1.amountToDeposit;
    const relayerFeePct = (0, utils_1.toBN)(constants_1.depositRelayerFeePct).add(constants_1.realizedLpFeePct);
    beforeEach(async function () {
        [depositor, recipient] = await utils_1.ethers.getSigners();
        ({ weth, erc20, spokePool, unwhitelistedErc20 } = await (0, SpokePool_Fixture_1.spokePoolFixture)());
        // mint some fresh tokens and deposit ETH for weth for the depositor.
        await (0, utils_1.seedWallet)(depositor, [erc20], weth, constants_1.amountToSeedWallets);
        // Approve spokepool to spend tokens
        await erc20.connect(depositor).approve(spokePool.address, constants_1.amountToDeposit.mul(10));
        await weth.connect(depositor).approve(spokePool.address, constants_1.amountToDeposit.mul(10));
        // Whitelist origin token => destination chain ID routes:
        await (0, SpokePool_Fixture_1.enableRoutes)(spokePool, [{ originToken: erc20.address }, { originToken: weth.address }]);
        quoteTimestamp = (await spokePool.getCurrentTime()).toNumber();
    });
    it("Depositing ERC20 tokens correctly pulls tokens and changes contract state", async function () {
        const revertReason = "DepositsArePaused";
        // Can't deposit when paused:
        await spokePool.connect(depositor).pauseDeposits(true);
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount: constants_1.amountToDeposit,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }))).to.be.revertedWith(revertReason);
        await spokePool.connect(depositor).pauseDeposits(false);
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            recipient: recipient.address,
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        })))
            .to.emit(spokePool, "FundsDeposited")
            .withArgs((0, utils_1.addressToBytes)(erc20.address), (0, utils_1.addressToBytes)(ZERO_ADDRESS), constants_1.amountToDeposit, constants_1.amountReceived, constants_1.destinationChainId, 0, quoteTimestamp, constants_1.MAX_UINT32, 0, (0, utils_1.addressToBytes)(depositor.address), (0, utils_1.addressToBytes)(recipient.address), (0, utils_1.addressToBytes)(ZERO_ADDRESS), "0x");
        // The collateral should have transferred from depositor to contract.
        (0, utils_1.expect)(await erc20.balanceOf(depositor.address)).to.equal(constants_1.amountToSeedWallets.sub(constants_1.amountToDeposit));
        (0, utils_1.expect)(await erc20.balanceOf(spokePool.address)).to.equal(constants_1.amountToDeposit);
        // Deposit nonce should increment.
        (0, utils_1.expect)(await spokePool.numberOfDeposits()).to.equal(1);
    });
    it("DepositFor overrrides the depositor", async function () {
        const newDepositor = (0, utils_1.randomAddress)();
        await (0, utils_1.expect)(spokePool.connect(depositor).depositFor(newDepositor, ...(0, SpokePool_Fixture_1.getDepositParams)({
            recipient: recipient.address,
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        })))
            .to.emit(spokePool, "FundsDeposited")
            .withArgs((0, utils_1.addressToBytes)(erc20.address), (0, utils_1.addressToBytes)(ZERO_ADDRESS), constants_1.amountToDeposit, constants_1.amountReceived, constants_1.destinationChainId, 0, quoteTimestamp, utils_1.BigNumber.from("0xFFFFFFFF"), 0, (0, utils_1.addressToBytes)(newDepositor), // Depositor is overridden.
        (0, utils_1.addressToBytes)(recipient.address), (0, utils_1.addressToBytes)(ZERO_ADDRESS), "0x");
    });
    it("Depositing ETH correctly wraps into WETH", async function () {
        const revertReason = "MsgValueDoesNotMatchInputAmount";
        // Fails if msg.value > 0 but doesn't match amount to deposit.
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: weth.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }), { value: 1 })).to.be.revertedWith(revertReason);
        await (0, utils_1.expect)(() => spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            recipient: recipient.address,
            originToken: weth.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }), { value: constants_1.amountToDeposit })).to.changeEtherBalances([depositor, weth], [constants_1.amountToDeposit.mul((0, utils_1.toBN)("-1")), constants_1.amountToDeposit]); // ETH should transfer from depositor to WETH contract.
        // WETH balance for user should be same as start, but WETH balancein pool should increase.
        (0, utils_1.expect)(await weth.balanceOf(depositor.address)).to.equal(constants_1.amountToSeedWallets);
        (0, utils_1.expect)(await weth.balanceOf(spokePool.address)).to.equal(constants_1.amountToDeposit);
    });
    it("Depositing ETH with msg.value = 0 pulls WETH from depositor", async function () {
        await (0, utils_1.expect)(() => spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: weth.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }), { value: 0 })).to.changeTokenBalances(weth, [depositor, spokePool], [constants_1.amountToDeposit.mul((0, utils_1.toBN)("-1")), constants_1.amountToDeposit]);
    });
    it("SpokePool is not approved to spend originToken", async function () {
        const insufficientAllowance = "ERC20: insufficient allowance";
        await erc20.connect(depositor).approve(spokePool.address, 0);
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }))).to.be.reverted;
        await erc20.connect(depositor).approve(spokePool.address, constants_1.amountToDeposit);
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }))).to.emit(spokePool, "FundsDeposited");
    });
    it("Deposit route is disabled", async function () {
        const revertReason = "DisabledRoute";
        // Verify that routes are disabled by default.
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: unwhitelistedErc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }))).to.be.revertedWith(revertReason);
        // Verify that the route is enabled.
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }))).to.emit(spokePool, "FundsDeposited");
        // Disable the route.
        await spokePool.connect(depositor).setEnableRoute(erc20.address, constants_1.destinationChainId, false);
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }))).to.be.revertedWith(revertReason);
        // Re-enable the route and verify that it works again.
        await spokePool.connect(depositor).setEnableRoute(erc20.address, constants_1.destinationChainId, true);
        await erc20.connect(depositor).approve(spokePool.address, constants_1.amountToDeposit);
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp,
        }))).to.emit(spokePool, "FundsDeposited");
    });
    it("Relayer fee is invalid", async function () {
        const revertReason = "InvalidRelayerFee";
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct: (0, utils_1.toWei)("1"), // Fee > 50%
            quoteTimestamp,
        }))).to.be.revertedWith(revertReason);
    });
    it("quoteTimestamp is out of range", async function () {
        const revertReason = "InvalidQuoteTimestamp";
        const quoteTimeBuffer = await spokePool.depositQuoteTimeBuffer();
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp: quoteTimestamp + 1,
        }))).to.be.revertedWith("underflowed");
        await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
            originToken: erc20.address,
            amount,
            destinationChainId: constants_1.destinationChainId,
            relayerFeePct,
            quoteTimestamp: quoteTimestamp - (quoteTimeBuffer + 1),
        }))).to.be.revertedWith(revertReason);
        // quoteTimestamp at the exact margins should succeed.
        for (const offset of [0, quoteTimeBuffer]) {
            await erc20.connect(depositor).approve(spokePool.address, constants_1.amountToDeposit);
            await (0, utils_1.expect)(spokePool.connect(depositor).depositDeprecated_5947912356(...(0, SpokePool_Fixture_1.getDepositParams)({
                originToken: erc20.address,
                amount,
                destinationChainId: constants_1.destinationChainId,
                relayerFeePct,
                quoteTimestamp: quoteTimestamp - offset,
            }))).to.emit(spokePool, "FundsDeposited");
        }
    });
    it("should call legacy deposit through overloaded interface", async function () {
        // Define the deprecated interface
        const DeprecatedSpokePoolInterface = new utils_1.ethers.utils.Interface([
            "function deposit(address recipient, address originToken, uint256 amount, uint256 destinationChainId, int64 relayerFeePct, uint32 quoteTimestamp, bytes memory message, uint256 maxCount) external payable",
        ]);
        // Create a new instance of the SpokePool with the deprecated interface
        const deprecatedSpokePool = new utils_1.ethers.Contract(spokePool.address, DeprecatedSpokePoolInterface, depositor);
        // Call the deprecated deposit method
        await (0, utils_1.expect)(await deprecatedSpokePool.deposit(depositor.address, // recipient
        erc20.address, // originToken
        constants_1.amountToDeposit, // amount
        constants_1.destinationChainId, // destinationChainId
        0, // relayerFeePct
        quoteTimestamp, // quoteTimestamp
        "0x", // message
        0 // maxCount
        )).to.emit(spokePool, "FundsDeposited");
        // Test depositing native ETH directly
        await (0, utils_1.expect)(deprecatedSpokePool.deposit(depositor.address, // recipient
        weth.address, // originToken - still WETH address for native deposits
        constants_1.amountToDeposit, // amount
        constants_1.destinationChainId, // destinationChainId
        0, // relayerFeePct
        quoteTimestamp, // quoteTimestamp
        "0x", // message
        0, // maxCount
        { value: constants_1.amountToDeposit } // Send ETH
        )).to.emit(spokePool, "FundsDeposited");
    });
    describe("deposit V3", function () {
        let relayData, depositArgs;
        function getDepositArgsFromRelayData(_relayData, _destinationChainId = constants_1.destinationChainId, _quoteTimestamp = quoteTimestamp, _isAddressOverload = false) {
            return [
                _isAddressOverload ? (0, utils_1.bytes32ToAddress)(_relayData.depositor) : (0, utils_1.addressToBytes)(_relayData.depositor),
                _isAddressOverload ? (0, utils_1.bytes32ToAddress)(_relayData.recipient) : (0, utils_1.addressToBytes)(_relayData.recipient),
                _isAddressOverload ? (0, utils_1.bytes32ToAddress)(_relayData.inputToken) : (0, utils_1.addressToBytes)(_relayData.inputToken),
                _isAddressOverload ? (0, utils_1.bytes32ToAddress)(_relayData.outputToken) : (0, utils_1.addressToBytes)(_relayData.outputToken),
                _relayData.inputAmount,
                _relayData.outputAmount,
                _destinationChainId,
                _isAddressOverload
                    ? (0, utils_1.bytes32ToAddress)(_relayData.exclusiveRelayer)
                    : (0, utils_1.addressToBytes)(_relayData.exclusiveRelayer),
                _quoteTimestamp,
                _relayData.fillDeadline,
                _relayData.exclusivityDeadline,
                _relayData.message,
            ];
        }
        function getUnsafeDepositArgsFromRelayData(_relayData, _depositId, _destinationChainId = constants_1.destinationChainId, _quoteTimestamp = quoteTimestamp) {
            return [
                (0, utils_1.addressToBytes)(_relayData.depositor),
                (0, utils_1.addressToBytes)(_relayData.recipient),
                (0, utils_1.addressToBytes)(_relayData.inputToken),
                (0, utils_1.addressToBytes)(_relayData.outputToken),
                _relayData.inputAmount,
                _relayData.outputAmount,
                _destinationChainId,
                (0, utils_1.addressToBytes)(_relayData.exclusiveRelayer),
                _depositId,
                _quoteTimestamp,
                _relayData.fillDeadline,
                _relayData.exclusivityDeadline,
                _relayData.message,
            ];
        }
        beforeEach(async function () {
            relayData = {
                depositor: (0, utils_1.addressToBytes)(depositor.address),
                recipient: (0, utils_1.addressToBytes)(recipient.address),
                exclusiveRelayer: (0, utils_1.addressToBytes)(ZERO_ADDRESS),
                inputToken: (0, utils_1.addressToBytes)(erc20.address),
                outputToken: (0, utils_1.addressToBytes)((0, utils_1.randomAddress)()),
                inputAmount: constants_1.amountToDeposit,
                outputAmount: constants_1.amountToDeposit.sub(19),
                originChainId: constants_1.originChainId,
                depositId: (0, utils_1.toBN)(0),
                fillDeadline: quoteTimestamp + 1000,
                exclusivityDeadline: 0,
                message: "0x",
            };
            depositArgs = getDepositArgsFromRelayData(relayData);
        });
        it("placeholder: gas test", async function () {
            await spokePool.connect(depositor).deposit(...depositArgs);
        });
        it("should allow depositv3 with address overload", async function () {
            await spokePool
                .connect(depositor)
                .depositV3(...getDepositArgsFromRelayData(relayData, constants_1.destinationChainId, quoteTimestamp, true));
        });
        it("route disabled", async function () {
            // Verify that routes are disabled by default for a new route
            const _depositArgs = getDepositArgsFromRelayData(relayData, 999);
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(..._depositArgs)).to.be.revertedWith("DisabledRoute");
            // Enable the route:
            await spokePool.connect(depositor).setEnableRoute(erc20.address, 999, true);
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(..._depositArgs)).to.not.be.reverted;
        });
        it("invalid quoteTimestamp", async function () {
            const quoteTimeBuffer = await spokePool.depositQuoteTimeBuffer();
            const currentTime = await spokePool.getCurrentTime();
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(
            // quoteTimestamp too far into past (i.e. beyond the buffer)
            ...getDepositArgsFromRelayData(relayData, constants_1.destinationChainId, currentTime.sub(quoteTimeBuffer).sub(1)))).to.be.revertedWith("InvalidQuoteTimestamp");
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(
            // quoteTimestamp in the future should also revert with InvalidQuoteTimestamp
            ...getDepositArgsFromRelayData(relayData, constants_1.destinationChainId, currentTime.add(500)))).to.be.revertedWith("InvalidQuoteTimestamp");
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(
            // quoteTimestamp right at the buffer is OK
            ...getDepositArgsFromRelayData(relayData, constants_1.destinationChainId, currentTime.sub(quoteTimeBuffer)))).to.not.be.reverted;
        });
        it("invalid fillDeadline", async function () {
            const fillDeadlineBuffer = await spokePool.fillDeadlineBuffer();
            const currentTime = await spokePool.getCurrentTime();
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(
            // fillDeadline too far into future (i.e. beyond the buffer)
            ...getDepositArgsFromRelayData({ ...relayData, fillDeadline: currentTime.add(fillDeadlineBuffer).add(1) }))).to.be.revertedWith("InvalidFillDeadline");
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(
            // fillDeadline in past
            ...getDepositArgsFromRelayData({ ...relayData, fillDeadline: currentTime.sub(1) }))).to.not.be.reverted;
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(
            // fillDeadline right at the buffer is OK
            ...getDepositArgsFromRelayData({ ...relayData, fillDeadline: currentTime.add(fillDeadlineBuffer) }))).to.not.be.reverted;
        });
        it("invalid exclusivity params", async function () {
            const currentTime = await spokePool.getCurrentTime();
            // If exclusive deadline is not zero, then exclusive relayer must be set.
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: constants_1.zeroAddress,
                exclusivityDeadline: 1,
            }))).to.be.revertedWith("InvalidExclusiveRelayer");
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: constants_1.zeroAddress,
                exclusivityDeadline: constants_1.MAX_EXCLUSIVITY_OFFSET_SECONDS,
            }))).to.be.revertedWith("InvalidExclusiveRelayer");
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: constants_1.zeroAddress,
                exclusivityDeadline: constants_1.MAX_EXCLUSIVITY_OFFSET_SECONDS + 1,
            }))).to.be.revertedWith("InvalidExclusiveRelayer");
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: constants_1.zeroAddress,
                exclusivityDeadline: currentTime.sub(1),
            }))).to.be.revertedWith("InvalidExclusiveRelayer");
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: constants_1.zeroAddress,
                exclusivityDeadline: currentTime.add(1),
            }))).to.be.revertedWith("InvalidExclusiveRelayer");
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: constants_1.zeroAddress,
                exclusivityDeadline: 0,
            }))).to.not.be.reverted;
        });
        it("exclusivity param is used as an offset", async function () {
            const currentTime = (await spokePool.getCurrentTime()).toNumber();
            const fillDeadlineOffset = 1000;
            const exclusivityDeadlineOffset = constants_1.MAX_EXCLUSIVITY_OFFSET_SECONDS;
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: depositor.address,
                exclusivityDeadline: exclusivityDeadlineOffset,
            }, undefined, currentTime)))
                .to.emit(spokePool, "FundsDeposited")
                .withArgs(relayData.inputToken, relayData.outputToken, relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, 
            // deposit ID is 0 for first deposit
            0, currentTime, // quoteTimestamp should be current time
            currentTime + fillDeadlineOffset, // fillDeadline should be current time + offset
            currentTime + exclusivityDeadlineOffset, // exclusivityDeadline should be current time + offset
            relayData.depositor, relayData.recipient, (0, utils_1.addressToBytes)(depositor.address), relayData.message);
        });
        it("exclusivity param is used as a timestamp", async function () {
            const currentTime = (await spokePool.getCurrentTime()).toNumber();
            const fillDeadlineOffset = 1000;
            const exclusivityDeadlineTimestamp = constants_1.MAX_EXCLUSIVITY_OFFSET_SECONDS + 1;
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: depositor.address,
                exclusivityDeadline: exclusivityDeadlineTimestamp,
            }, undefined, currentTime)))
                .to.emit(spokePool, "FundsDeposited")
                .withArgs(relayData.inputToken, relayData.outputToken, relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, 
            // deposit ID is 0 for first deposit
            0, currentTime, // quoteTimestamp should be current time
            currentTime + fillDeadlineOffset, // fillDeadline should be current time + offset
            exclusivityDeadlineTimestamp, // exclusivityDeadline should be passed in time
            relayData.depositor, relayData.recipient, (0, utils_1.addressToBytes)(depositor.address), relayData.message);
        });
        it("exclusivity param is set to 0", async function () {
            const currentTime = (await spokePool.getCurrentTime()).toNumber();
            const fillDeadlineOffset = 1000;
            const zeroExclusivity = 0;
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({
                ...relayData,
                exclusiveRelayer: depositor.address,
                exclusivityDeadline: zeroExclusivity,
            }, undefined, currentTime)))
                .to.emit(spokePool, "FundsDeposited")
                .withArgs(relayData.inputToken, relayData.outputToken, relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, 
            // deposit ID is 0 for first deposit
            0, currentTime, // quoteTimestamp should be current time
            currentTime + fillDeadlineOffset, // fillDeadline should be current time + offset
            0, // Exclusivity deadline should always be 0
            relayData.depositor, relayData.recipient, (0, utils_1.addressToBytes)(depositor.address), relayData.message);
        });
        it("if input token is WETH and msg.value > 0, msg.value must match inputAmount", async function () {
            await (0, utils_1.expect)(spokePool
                .connect(depositor)
                .deposit(...getDepositArgsFromRelayData({ ...relayData, inputToken: weth.address }), {
                value: 1,
            })).to.be.revertedWith("MsgValueDoesNotMatchInputAmount");
            // Pulls ETH from depositor and deposits it into WETH via the wrapped contract.
            await (0, utils_1.expect)(() => spokePool
                .connect(depositor)
                .deposit(...getDepositArgsFromRelayData({ ...relayData, inputToken: weth.address }), {
                value: constants_1.amountToDeposit,
            })).to.changeEtherBalances([depositor, weth], [constants_1.amountToDeposit.mul((0, utils_1.toBN)("-1")), constants_1.amountToDeposit]); // ETH should transfer from depositor to WETH contract.
            // WETH balance for user should be same as start, but WETH balance in pool should increase.
            (0, utils_1.expect)(await weth.balanceOf(spokePool.address)).to.equal(constants_1.amountToDeposit);
        });
        it("if input token is not WETH then msg.value must be 0", async function () {
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData(relayData), { value: 1 })).to.be.revertedWith("MsgValueDoesNotMatchInputAmount");
        });
        it("if input token is WETH and msg.value = 0, pulls ERC20 from depositor", async function () {
            await (0, utils_1.expect)(() => spokePool
                .connect(depositor)
                .deposit(...getDepositArgsFromRelayData({ ...relayData, inputToken: weth.address }), {
                value: 0,
            })).to.changeTokenBalances(weth, [depositor, spokePool], [constants_1.amountToDeposit.mul((0, utils_1.toBN)("-1")), constants_1.amountToDeposit]);
        });
        it("pulls input token from caller", async function () {
            await (0, utils_1.expect)(() => spokePool.connect(depositor).deposit(...depositArgs)).to.changeTokenBalances(erc20, [depositor, spokePool], [constants_1.amountToDeposit.mul((0, utils_1.toBN)("-1")), constants_1.amountToDeposit]);
        });
        it("depositV3Now uses current time as quote time", async function () {
            const currentTime = (await spokePool.getCurrentTime()).toNumber();
            const fillDeadlineOffset = 1000;
            const exclusivityDeadline = 0;
            await (0, utils_1.expect)(spokePool
                .connect(depositor)
                .depositNow((0, utils_1.addressToBytes)(relayData.depositor), (0, utils_1.addressToBytes)(relayData.recipient), (0, utils_1.addressToBytes)(relayData.inputToken), (0, utils_1.addressToBytes)(relayData.outputToken), relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, (0, utils_1.addressToBytes)(relayData.exclusiveRelayer), fillDeadlineOffset, exclusivityDeadline, relayData.message))
                .to.emit(spokePool, "FundsDeposited")
                .withArgs((0, utils_1.addressToBytes)(relayData.inputToken), (0, utils_1.addressToBytes)(relayData.outputToken), relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, 
            // deposit ID is 0 for first deposit
            0, currentTime, // quoteTimestamp should be current time
            currentTime + fillDeadlineOffset, // fillDeadline should be current time + offset
            exclusivityDeadline, (0, utils_1.addressToBytes)(relayData.depositor), (0, utils_1.addressToBytes)(relayData.recipient), (0, utils_1.addressToBytes)(relayData.exclusiveRelayer), relayData.message);
        });
        it("should allow depositV3Now with address overload", async function () {
            const currentTime = (await spokePool.getCurrentTime()).toNumber();
            const fillDeadlineOffset = 1000;
            const exclusivityDeadline = 0;
            await (0, utils_1.expect)(spokePool
                .connect(depositor)
                .depositV3Now((0, utils_1.bytes32ToAddress)(relayData.depositor), (0, utils_1.bytes32ToAddress)(relayData.recipient), (0, utils_1.bytes32ToAddress)(relayData.inputToken), (0, utils_1.bytes32ToAddress)(relayData.outputToken), relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, (0, utils_1.bytes32ToAddress)(relayData.exclusiveRelayer), fillDeadlineOffset, exclusivityDeadline, relayData.message))
                .to.emit(spokePool, "FundsDeposited")
                .withArgs((0, utils_1.addressToBytes)(relayData.inputToken), (0, utils_1.addressToBytes)(relayData.outputToken), relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, 
            // deposit ID is 0 for first deposit
            0, currentTime, // quoteTimestamp should be current time
            currentTime + fillDeadlineOffset, // fillDeadline should be current time + offset
            exclusivityDeadline, (0, utils_1.addressToBytes)(relayData.depositor), (0, utils_1.addressToBytes)(relayData.recipient), (0, utils_1.addressToBytes)(relayData.exclusiveRelayer), relayData.message);
        });
        it("emits FundsDeposited event with correct deposit ID", async function () {
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...depositArgs))
                .to.emit(spokePool, "FundsDeposited")
                .withArgs((0, utils_1.addressToBytes)(relayData.inputToken), (0, utils_1.addressToBytes)(relayData.outputToken), relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, 
            // deposit ID is 0 for first deposit
            0, quoteTimestamp, relayData.fillDeadline, relayData.exclusivityDeadline, (0, utils_1.addressToBytes)(relayData.depositor), (0, utils_1.addressToBytes)(relayData.recipient), (0, utils_1.addressToBytes)(relayData.exclusiveRelayer), relayData.message);
        });
        it("deposit ID state variable incremented", async function () {
            await spokePool.connect(depositor).deposit(...depositArgs);
            (0, utils_1.expect)(await spokePool.numberOfDeposits()).to.equal(1);
        });
        it("tokens are always pulled from caller, even if different from specified depositor", async function () {
            const currentTime = (await spokePool.getCurrentTime()).toNumber();
            const balanceBefore = await erc20.balanceOf(depositor.address);
            const newDepositor = (0, utils_1.randomAddress)();
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...getDepositArgsFromRelayData({ ...relayData, depositor: newDepositor })))
                .to.emit(spokePool, "FundsDeposited")
                .withArgs((0, utils_1.addressToBytes)(relayData.inputToken), (0, utils_1.addressToBytes)(relayData.outputToken), relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, 0, quoteTimestamp, relayData.fillDeadline, 0, 
            // New depositor
            (0, utils_1.addressToBytes)(newDepositor), (0, utils_1.addressToBytes)(relayData.recipient), (0, utils_1.addressToBytes)(relayData.exclusiveRelayer), relayData.message);
            (0, utils_1.expect)(await erc20.balanceOf(depositor.address)).to.equal(balanceBefore.sub(constants_1.amountToDeposit));
        });
        it("deposits are not paused", async function () {
            await spokePool.pauseDeposits(true);
            await (0, utils_1.expect)(spokePool.connect(depositor).deposit(...depositArgs)).to.be.revertedWith("DepositsArePaused");
        });
        it("reentrancy protected", async function () {
            const functionCalldata = spokePool.interface.encodeFunctionData("deposit", [...depositArgs]);
            await (0, utils_1.expect)(spokePool.connect(depositor).callback(functionCalldata)).to.be.reverted;
        });
        it("depositor must be valid evm address", async function () {
            const functionCalldata = spokePool.interface.encodeFunctionData("deposit", [
                ...getDepositArgsFromRelayData({
                    ...relayData,
                    depositor: "0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d",
                }),
            ]);
            await (0, utils_1.expect)(spokePool.connect(depositor).callback(functionCalldata)).to.be.reverted;
        });
        it("unsafe deposit ID", async function () {
            // new deposit ID should be the uint256 equivalent of the keccak256 hash of packed {msg.sender, depositor, forcedDepositId}.
            const forcedDepositId = "99";
            const expectedDepositId = utils_1.BigNumber.from(utils_1.ethers.utils.solidityKeccak256(["address", "bytes32", "uint256"], [depositor.address, (0, utils_1.addressToBytes)(recipient.address), forcedDepositId]));
            (0, utils_1.expect)(await spokePool.getUnsafeDepositId(depositor.address, (0, utils_1.addressToBytes)(recipient.address), forcedDepositId)).to.equal(expectedDepositId);
            // Note: we deliberately set the depositor != msg.sender to test that the hashing algorithm correctly includes
            // both addresses in the hash.
            await (0, utils_1.expect)(spokePool
                .connect(depositor)
                .unsafeDeposit(...getUnsafeDepositArgsFromRelayData({ ...relayData, depositor: recipient.address }, forcedDepositId)))
                .to.emit(spokePool, "FundsDeposited")
                .withArgs(relayData.inputToken, relayData.outputToken, relayData.inputAmount, relayData.outputAmount, constants_1.destinationChainId, expectedDepositId, quoteTimestamp, relayData.fillDeadline, 0, (0, utils_1.addressToBytes)(recipient.address), relayData.recipient, relayData.exclusiveRelayer, relayData.message);
        });
    });
    describe("speed up V3 deposit", function () {
        const updatedOutputAmount = constants_1.amountToDeposit.add(1);
        const updatedRecipient = (0, utils_1.randomAddress)();
        const updatedMessage = "0x1234";
        const depositId = (0, utils_1.toBN)(100);
        it("_verifyUpdateV3DepositMessage", async function () {
            const signature = await (0, SpokePool_Fixture_1.getUpdatedV3DepositSignature)(depositor, depositId, constants_1.originChainId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage);
            await spokePool.verifyUpdateV3DepositMessageBytes32((0, utils_1.addressToBytes)(depositor.address), depositId, constants_1.originChainId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage, signature);
            // Reverts if passed in depositor is the signer or if signature is incorrect
            await (0, utils_1.expect)(spokePool.verifyUpdateV3DepositMessageBytes32((0, utils_1.addressToBytes)(updatedRecipient), depositId, constants_1.originChainId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage, signature)).to.be.revertedWith("InvalidDepositorSignature");
            // @dev Creates an invalid signature using different params
            const invalidSignature = await (0, SpokePool_Fixture_1.getUpdatedV3DepositSignature)(depositor, depositId.add((0, utils_1.toBN)(1)), constants_1.originChainId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage);
            await (0, utils_1.expect)(spokePool.verifyUpdateV3DepositMessageBytes32((0, utils_1.addressToBytes)(depositor.address), depositId, constants_1.originChainId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage, invalidSignature)).to.be.revertedWith("InvalidDepositorSignature");
        });
        it("passes spoke pool's chainId() as origin chainId", async function () {
            const spokePoolChainId = await spokePool.chainId();
            const expectedSignature = await (0, SpokePool_Fixture_1.getUpdatedV3DepositSignature)(depositor, depositId, spokePoolChainId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage);
            await (0, utils_1.expect)(spokePool
                .connect(depositor)
                .speedUpDeposit((0, utils_1.addressToBytes)(depositor.address), depositId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage, expectedSignature))
                .to.emit(spokePool, "RequestedSpeedUpDeposit")
                .withArgs(updatedOutputAmount, depositId, (0, utils_1.addressToBytes)(depositor.address), (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage, expectedSignature);
            // Can't use a signature for a different chain ID, even if the signature is valid otherwise for the depositor.
            const otherChainId = spokePoolChainId.add(1);
            const invalidSignatureForChain = await (0, SpokePool_Fixture_1.getUpdatedV3DepositSignature)(depositor, depositId, otherChainId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage);
            await (0, utils_1.expect)(spokePool.verifyUpdateV3DepositMessageBytes32((0, utils_1.addressToBytes)(depositor.address), depositId, otherChainId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage, invalidSignatureForChain)).to.not.be.reverted;
            await (0, utils_1.expect)(spokePool
                .connect(depositor)
                .speedUpDeposit((0, utils_1.addressToBytes)(depositor.address), depositId, updatedOutputAmount, (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage, invalidSignatureForChain)).to.be.revertedWith("InvalidDepositorSignature");
        });
        it("should allow speeding up V3 deposit with address overload", async function () {
            const updatedOutputAmount = constants_1.amountToDeposit.add(1);
            const updatedRecipient = (0, utils_1.randomAddress)();
            const updatedMessage = "0x1234";
            const depositId = (0, utils_1.toBN)(100);
            const spokePoolChainId = await spokePool.chainId();
            const signature = await (0, SpokePool_Fixture_1.getUpdatedV3DepositSignature)(depositor, depositId, spokePoolChainId, updatedOutputAmount, updatedRecipient, updatedMessage, true);
            await spokePool.verifyUpdateV3DepositMessage(depositor.address, depositId, spokePoolChainId, updatedOutputAmount, updatedRecipient, updatedMessage, signature);
            await (0, utils_1.expect)(spokePool
                .connect(depositor)
                .speedUpV3Deposit(depositor.address, depositId, updatedOutputAmount, updatedRecipient, updatedMessage, signature))
                .to.emit(spokePool, "RequestedSpeedUpDeposit")
                .withArgs(updatedOutputAmount, depositId, (0, utils_1.addressToBytes)(depositor.address), (0, utils_1.addressToBytes)(updatedRecipient), updatedMessage, signature);
        });
    });
});
