"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 });
const anchor = __importStar(require("@coral-xyz/anchor"));
const anchor_1 = require("@coral-xyz/anchor");
const ethers_1 = require("ethers");
const spl_token_1 = require("@solana/spl-token");
const web3_js_1 = require("@solana/web3.js");
const SvmSpoke_common_1 = require("./SvmSpoke.common");
const web3_v1_1 = require("../../src/svm/web3-v1");
const test_utils_1 = require("../../test-utils");
const { provider, connection, program, owner, seedBalance, initializeState, depositData } = SvmSpoke_common_1.common;
const { createRoutePda, getVaultAta, assertSE, assert, getCurrentTime, depositQuoteTimeBuffer, fillDeadlineBuffer } = SvmSpoke_common_1.common;
const maxExclusivityOffsetSeconds = new anchor_1.BN(test_utils_1.MAX_EXCLUSIVITY_OFFSET_SECONDS); // 1 year in seconds
describe("svm_spoke.deposit", () => {
    anchor.setProvider(provider);
    const depositor = web3_js_1.Keypair.generate();
    const payer = anchor.AnchorProvider.env().wallet.payer;
    const tokenDecimals = 6;
    let state, inputToken, depositorTA, vault, tokenProgram;
    let seed;
    let depositAccounts;
    let setEnableRouteAccounts; // Common variable for setEnableRoute accounts
    const setupInputToken = async () => {
        inputToken = await (0, spl_token_1.createMint)(connection, payer, owner, owner, tokenDecimals, undefined, undefined, tokenProgram);
        depositorTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, inputToken, depositor.publicKey, undefined, undefined, undefined, tokenProgram)).address;
        await (0, spl_token_1.mintTo)(connection, payer, inputToken, depositorTA, owner, seedBalance, undefined, undefined, tokenProgram);
    };
    const enableRoute = async () => {
        const routeChainId = new anchor_1.BN(1);
        const route = createRoutePda(inputToken, seed, routeChainId);
        vault = await getVaultAta(inputToken, state);
        setEnableRouteAccounts = {
            signer: owner,
            payer: owner,
            state,
            route,
            vault,
            originTokenMint: inputToken, // Note the Sol expects this to be named originTokenMint.
            tokenProgram: tokenProgram ?? spl_token_1.TOKEN_PROGRAM_ID,
            associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
            systemProgram: anchor.web3.SystemProgram.programId,
        };
        await program.methods.setEnableRoute(inputToken, routeChainId, true).accounts(setEnableRouteAccounts).rpc();
        // Set known fields in the depositData.
        depositData.depositor = depositor.publicKey;
        depositData.inputToken = inputToken;
        depositAccounts = {
            state,
            route,
            signer: depositor.publicKey,
            depositorTokenAccount: depositorTA,
            vault,
            mint: inputToken,
            tokenProgram: tokenProgram ?? spl_token_1.TOKEN_PROGRAM_ID,
            program: program.programId,
        };
    };
    const approvedDeposit = async (depositDataValues, calledDepositAccounts = depositAccounts) => {
        // Delegate state PDA to pull depositor tokens.
        const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(calledDepositAccounts.depositorTokenAccount, calledDepositAccounts.mint, calledDepositAccounts.state, depositor.publicKey, BigInt(depositData.inputAmount.toString()), tokenDecimals, undefined, tokenProgram);
        const depositIx = await program.methods
            .deposit(...depositDataValues)
            .accounts(calledDepositAccounts)
            .instruction();
        const depositTx = new web3_js_1.Transaction().add(approveIx, depositIx);
        const tx = await (0, web3_js_1.sendAndConfirmTransaction)(connection, depositTx, [payer, depositor]);
        return tx;
    };
    beforeEach(async () => {
        ({ state, seed } = await initializeState());
        tokenProgram = spl_token_1.TOKEN_PROGRAM_ID; // Some tests might override this.
        await setupInputToken();
        await enableRoute();
    });
    it("Deposits tokens via deposit function and checks balances", async () => {
        // Verify vault balance is zero before the deposit
        let vaultAccount = await (0, spl_token_1.getAccount)(connection, vault);
        assertSE(vaultAccount.amount, "0", "Vault balance should be zero before the deposit");
        // Execute the deposit call
        let depositDataValues = Object.values(depositData);
        await approvedDeposit(depositDataValues);
        // Verify tokens leave the depositor's account
        let depositorAccount = await (0, spl_token_1.getAccount)(connection, depositorTA);
        assertSE(depositorAccount.amount, seedBalance - depositData.inputAmount.toNumber(), "Depositor's balance should be reduced by the deposited amount");
        // Verify tokens are credited into the vault
        vaultAccount = await (0, spl_token_1.getAccount)(connection, vault);
        assertSE(vaultAccount.amount, depositData.inputAmount, "Vault balance should be increased by the deposited amount");
        // Modify depositData for the second deposit
        const secondInputAmount = new anchor_1.BN(300000);
        // Execute the second deposit call
        depositDataValues = Object.values({ ...depositData, inputAmount: secondInputAmount });
        await approvedDeposit(depositDataValues);
        // Verify tokens leave the depositor's account again
        depositorAccount = await (0, spl_token_1.getAccount)(connection, depositorTA);
        assertSE(depositorAccount.amount, seedBalance - depositData.inputAmount.toNumber() - secondInputAmount.toNumber(), "Depositor's balance should be reduced by the total deposited amount");
        // Verify tokens are credited into the vault again
        vaultAccount = await (0, spl_token_1.getAccount)(connection, vault);
        assertSE(vaultAccount.amount, depositData.inputAmount.add(secondInputAmount), "Vault balance should be increased by the total deposited amount");
    });
    it("Verifies FundsDeposited after deposits", async () => {
        depositData.inputAmount = depositData.inputAmount.add(new anchor_1.BN(69));
        // Execute the first deposit call
        let depositDataValues = Object.values(depositData);
        const tx = await approvedDeposit(depositDataValues);
        let events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        let event = events[0].data; // 0th event is the latest event
        const expectedValues1 = { ...depositData, depositId: (0, web3_v1_1.intToU8Array32)(1) }; // Verify the event props emitted match the depositData.
        for (let [key, value] of Object.entries(expectedValues1)) {
            if (key === "exclusivityParameter")
                key = "exclusivityDeadline"; // the prop and the event names differ on this key.
            assertSE(event[key], value, `${key} should match`);
        }
        // Test the id recovery with the conversion utils
        assertSE((0, web3_v1_1.u8Array32ToInt)(event.depositId), 1, `depositId should recover to 1`);
        assertSE((0, web3_v1_1.u8Array32ToBigNumber)(event.depositId), ethers_1.BigNumber.from(1), `depositId should recover to 1`);
        // Execute the second deposit_v3 call
        const tx2 = await approvedDeposit(depositDataValues);
        events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx2, [program]);
        event = events[0].data; // 0th event is the latest event.
        const expectedValues2 = { ...expectedValues1, depositId: (0, web3_v1_1.intToU8Array32)(2) }; // Verify the event props emitted match the depositData.
        for (let [key, value] of Object.entries(expectedValues2)) {
            if (key === "exclusivityParameter")
                key = "exclusivityDeadline"; // the prop and the event names differ on this key.
            assertSE(event[key], value, `${key} should match`);
        }
        // Test the id recovery with the conversion utils
        assertSE((0, web3_v1_1.u8Array32ToInt)(event.depositId), 2, `depositId should recover to 2`);
        assertSE((0, web3_v1_1.u8Array32ToBigNumber)(event.depositId), ethers_1.BigNumber.from(2), `depositId should recover to 2`);
    });
    it("Deposit with deadline before current time succeeds", async () => {
        const currentTime = await getCurrentTime(program, state);
        // Fill deadline is before current time on the contract
        let fillDeadline = currentTime - 1; // 1 second before current time on the contract.
        depositData.fillDeadline = new anchor_1.BN(fillDeadline);
        depositData.quoteTimestamp = new anchor_1.BN(currentTime - 1); // 1 second before current time on the contract to reset.
        const depositDataValues = Object.values(depositData);
        const tx = await approvedDeposit(depositDataValues);
        const events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        const event = events[0].data; // 0th event is the latest event.
        assertSE(event.fillDeadline, fillDeadline, "Fill deadline should match");
    });
    it("Fails to deposit tokens to a route that is uninitalized", async () => {
        const differentChainId = new anchor_1.BN(2); // Different chain ID
        if (!depositData.inputToken) {
            throw new Error("Input token is null");
        }
        const differentRoutePda = createRoutePda(depositData.inputToken, seed, differentChainId);
        depositAccounts.route = differentRoutePda;
        try {
            const depositDataValues = Object.values({
                ...depositData,
                destinationChainId: differentChainId,
            });
            await approvedDeposit(depositDataValues);
            assert.fail("Deposit should have failed for a route that is not initialized");
        }
        catch (err) {
            assert.include(err.toString(), "AccountNotInitialized", "Expected AccountNotInitialized error");
        }
    });
    it("Fails to deposit tokens to a route that is explicitly disabled", async () => {
        // Disable the route
        await program.methods
            .setEnableRoute(depositData.inputToken, depositData.destinationChainId, false)
            .accounts(setEnableRouteAccounts)
            .rpc();
        try {
            const depositDataValues = Object.values(depositData);
            await approvedDeposit(depositDataValues);
            assert.fail("Deposit should have failed for a route that is explicitly disabled");
        }
        catch (err) {
            assert.include(err.toString(), "DisabledRoute", "Expected DisabledRoute error");
        }
    });
    it("Fails to process deposit when deposits are paused", async () => {
        // Pause deposits
        const pauseDepositsAccounts = { state, signer: owner, program: program.programId };
        await program.methods.pauseDeposits(true).accounts(pauseDepositsAccounts).rpc();
        const stateAccountData = await program.account.state.fetch(state);
        assert.isTrue(stateAccountData.pausedDeposits, "Deposits should be paused");
        // Try to deposit. This should fail because deposits are paused.
        try {
            const depositDataValues = Object.values(depositData);
            await approvedDeposit(depositDataValues);
            assert.fail("Should not be able to process deposit when deposits are paused");
        }
        catch (err) {
            assert.include(err.toString(), "Error Code: DepositsArePaused", "Expected DepositsArePaused error");
        }
    });
    it("Fails to deposit tokens with InvalidQuoteTimestamp when quote timestamp is in the future", async () => {
        const currentTime = await getCurrentTime(program, state);
        const futureQuoteTimestamp = new anchor_1.BN(currentTime + 10); // 10 seconds in the future
        depositData.quoteTimestamp = futureQuoteTimestamp;
        try {
            const depositDataValues = Object.values(depositData);
            await approvedDeposit(depositDataValues);
            assert.fail("Deposit should have failed due to InvalidQuoteTimestamp");
        }
        catch (err) {
            assert.include(err.toString(), "Error Code: InvalidQuoteTimestamp", "Expected InvalidQuoteTimestamp error");
        }
    });
    it("Fails to deposit tokens with quoteTimestamp is too old", async () => {
        const currentTime = await getCurrentTime(program, state);
        const futureQuoteTimestamp = new anchor_1.BN(currentTime - depositQuoteTimeBuffer.toNumber() - 1); // older than buffer.
        depositData.quoteTimestamp = futureQuoteTimestamp;
        try {
            const depositDataValues = Object.values(depositData);
            await approvedDeposit(depositDataValues);
            assert.fail("Deposit should have failed due to InvalidQuoteTimestamp");
        }
        catch (err) {
            assert.include(err.toString(), "Error Code: InvalidQuoteTimestamp", "Expected InvalidQuoteTimestamp error");
        }
    });
    it("Fails to deposit tokens with InvalidFillDeadline when fill deadline is invalid", async () => {
        const currentTime = await getCurrentTime(program, state);
        // Fill deadline is too far ahead (longer than fill_deadline_buffer + currentTime)
        const invalidFillDeadline = currentTime + fillDeadlineBuffer.toNumber() + 1; // 1 seconds beyond the buffer
        depositData.fillDeadline = new anchor_1.BN(invalidFillDeadline);
        depositData.quoteTimestamp = new anchor_1.BN(currentTime);
        try {
            const depositDataValues = Object.values(depositData);
            await approvedDeposit(depositDataValues);
            assert.fail("Deposit should have failed due to InvalidFillDeadline (future deadline)");
        }
        catch (err) {
            assert.include(err.toString(), "InvalidFillDeadline", "Expected InvalidFillDeadline error for future deadline");
        }
    });
    it("Fails to process deposit for mint inconsistent input_token", async () => {
        // Save the correct data and accounts from global scope before changing it when creating a new input token.
        const firstInputToken = inputToken;
        const firstDepositAccounts = depositAccounts;
        // Create a new input token and enable the route (this updates global scope variables).
        await setupInputToken();
        await enableRoute();
        // Try to execute the deposit call with malformed inputs where the first input token and its derived route is
        // passed combined with mint, vault and user token account from the second input token.
        const malformedDepositData = { ...depositData, inputToken: firstInputToken };
        const malformedDepositAccounts = { ...depositAccounts, route: firstDepositAccounts.route };
        try {
            const depositDataValues = Object.values(malformedDepositData);
            await approvedDeposit(depositDataValues, malformedDepositAccounts);
            assert.fail("Should not be able to process deposit for inconsistent mint");
        }
        catch (err) {
            assert.include(err.toString(), "Error Code: InvalidMint", "Expected InvalidMint error");
        }
    });
    it("Tests deposit with a fake route PDA", async () => {
        // Create fake program state
        const fakeState = await initializeState();
        const fakeVault = await getVaultAta(inputToken, fakeState.state);
        const fakeRouteChainId = new anchor_1.BN(3);
        const fakeRoutePda = createRoutePda(inputToken, fakeState.seed, fakeRouteChainId);
        // A seeds constraint was violated.
        const fakeSetEnableRouteAccounts = {
            signer: owner,
            payer: owner,
            state: fakeState.state,
            route: fakeRoutePda,
            vault: fakeVault,
            originTokenMint: inputToken,
            tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
            associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
            systemProgram: anchor.web3.SystemProgram.programId,
            program: program.programId,
        };
        await program.methods.setEnableRoute(inputToken, fakeRouteChainId, true).accounts(fakeSetEnableRouteAccounts).rpc();
        const fakeDepositAccounts = {
            state: fakeState.state,
            route: fakeRoutePda,
            signer: depositor.publicKey,
            depositorTokenAccount: depositorTA,
            vault: fakeVault,
            mint: inputToken,
            tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
            program: program.programId,
        };
        // Deposit with the fake state and route PDA should succeed.
        const depositDataValues = Object.values({
            ...depositData,
            destinationChainId: fakeRouteChainId,
        });
        const tx = await approvedDeposit(depositDataValues, fakeDepositAccounts);
        let events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        let event = events[0].data; // 0th event is the latest event.
        const expectedValues = {
            ...{ ...depositData, destinationChainId: fakeRouteChainId },
            depositId: (0, web3_v1_1.intToU8Array32)(1),
        }; // Verify the event props emitted match the depositData.
        for (let [key, value] of Object.entries(expectedValues)) {
            if (key === "exclusivityParameter")
                key = "exclusivityDeadline"; // the prop and the event names differ on this key.
            assertSE(event[key], value, `${key} should match`);
        }
        // Check fake vault acount balance
        const fakeVaultAccount = await (0, spl_token_1.getAccount)(connection, fakeVault);
        assertSE(fakeVaultAccount.amount, depositData.inputAmount.toNumber(), "Fake vault balance should be increased by the deposited amount");
        // Deposit with the fake route in the original program state should fail.
        try {
            const depositDataValues = Object.values({
                ...{ ...depositData, destinationChainId: fakeRouteChainId },
            });
            await approvedDeposit(depositDataValues, { ...depositAccounts, route: fakeRoutePda });
            assert.fail("Deposit should have failed for a fake route PDA");
        }
        catch (err) {
            assert.include(err.toString(), "A seeds constraint was violated");
        }
        const vaultAccount = await (0, spl_token_1.getAccount)(connection, vault);
        assertSE(vaultAccount.amount, 0, "Vault balance should not be changed by the fake route deposit");
    });
    it("depositV3Now behaves as deposit but forces the quote timestamp as expected", async () => {
        // Set up initial deposit data. Note that this method has a slightly different interface to deposit, using
        // fillDeadlineOffset rather than fillDeadline. current chain time is added to fillDeadlineOffset to set the
        // fillDeadline for the deposit. exclusivityPeriod operates the same as in standard deposit.
        // Equally, depositV3Now does not have `quoteTimestamp`. this is set to the current time from the program.
        const fillDeadlineOffset = 60; // 60 seconds offset
        // Delegate state PDA to pull depositor tokens.
        const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(depositAccounts.depositorTokenAccount, depositAccounts.mint, depositAccounts.state, depositor.publicKey, BigInt(depositData.inputAmount.toString()), tokenDecimals, undefined, tokenProgram);
        // Execute the deposit_now call. Remove the quoteTimestamp from the depositData as not needed for this method.
        const depositIx = await program.methods
            .depositNow(depositData.depositor, depositData.recipient, depositData.inputToken, depositData.outputToken, depositData.inputAmount, depositData.outputAmount, depositData.destinationChainId, depositData.exclusiveRelayer, fillDeadlineOffset, 0, depositData.message)
            .accounts(depositAccounts)
            .instruction();
        const depositTx = new web3_js_1.Transaction().add(approveIx, depositIx);
        const tx = await (0, web3_js_1.sendAndConfirmTransaction)(connection, depositTx, [payer, depositor]);
        const events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        const event = events[0].data; // 0th event is the latest event.
        // Verify the event props emitted match the expected values
        const currentTime = await getCurrentTime(program, state);
        const expectedValues = {
            ...depositData,
            quoteTimestamp: currentTime,
            fillDeadline: currentTime + fillDeadlineOffset,
            depositId: (0, web3_v1_1.intToU8Array32)(1),
        };
        for (let [key, value] of Object.entries(expectedValues)) {
            if (key === "exclusivityParameter")
                key = "exclusivityDeadline"; // the prop and the event names differ on this key.
            assertSE(event[key], value, `${key} should match`);
        }
    });
    it("Fails with invalid exclusivity params", async () => {
        const currentTime = new anchor_1.BN(await getCurrentTime(program, state));
        depositData.quoteTimestamp = currentTime;
        // If exclusivityParameter is not zero, then exclusiveRelayer must be set.
        depositData.exclusiveRelayer = new web3_js_1.PublicKey("11111111111111111111111111111111");
        depositData.exclusivityParameter = new anchor_1.BN(1);
        try {
            const depositDataValues = Object.values(depositData);
            await approvedDeposit(depositDataValues);
            assert.fail("Should have failed due to InvalidExclusiveRelayer");
        }
        catch (err) {
            assert.include(err.toString(), "InvalidExclusiveRelayer");
        }
        // Test with other invalid exclusivityDeadline values
        const invalidExclusivityDeadlines = [
            maxExclusivityOffsetSeconds,
            maxExclusivityOffsetSeconds.add(new anchor_1.BN(1)),
            currentTime.sub(new anchor_1.BN(1)),
            currentTime.add(new anchor_1.BN(1)),
        ];
        for (const exclusivityDeadline of invalidExclusivityDeadlines) {
            depositData.exclusivityParameter = exclusivityDeadline;
            try {
                const depositDataValues = Object.values(depositData);
                await approvedDeposit(depositDataValues);
                assert.fail("Should have failed due to InvalidExclusiveRelayer");
            }
            catch (err) {
                assert.include(err.toString(), "InvalidExclusiveRelayer");
            }
        }
        // Test with exclusivityDeadline set to 0
        depositData.exclusivityParameter = new anchor_1.BN(0);
        const depositDataValues = Object.values(depositData);
        await approvedDeposit(depositDataValues);
    });
    it("Exclusivity param is used as an offset", async () => {
        const currentTime = new anchor_1.BN(await getCurrentTime(program, state));
        depositData.quoteTimestamp = currentTime;
        depositData.exclusiveRelayer = depositor.publicKey;
        depositData.exclusivityParameter = maxExclusivityOffsetSeconds;
        const depositDataValues = Object.values(depositData);
        const tx = await approvedDeposit(depositDataValues);
        const events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        const event = events[0].data; // 0th event is the latest event
        assertSE(event.exclusivityDeadline, currentTime.add(maxExclusivityOffsetSeconds), "exclusivityDeadline should be current time + offset");
    });
    it("Exclusivity param is used as a timestamp", async () => {
        const currentTime = new anchor_1.BN(await getCurrentTime(program, state));
        depositData.quoteTimestamp = currentTime;
        const exclusivityDeadlineTimestamp = maxExclusivityOffsetSeconds.add(new anchor_1.BN(1)); // 1 year + 1 second
        depositData.exclusiveRelayer = depositor.publicKey;
        depositData.exclusivityParameter = exclusivityDeadlineTimestamp;
        const depositDataValues = Object.values(depositData);
        const tx = await approvedDeposit(depositDataValues);
        const events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        const event = events[0].data; // 0th event is the latest event;
        assertSE(event.exclusivityDeadline, exclusivityDeadlineTimestamp, "exclusivityDeadline should be passed in time");
    });
    it("Exclusivity param is set to 0", async () => {
        const currentTime = new anchor_1.BN(await getCurrentTime(program, state));
        depositData.quoteTimestamp = currentTime;
        const zeroExclusivity = new anchor_1.BN(0);
        depositData.exclusiveRelayer = depositor.publicKey;
        depositData.exclusivityParameter = zeroExclusivity;
        const depositDataValues = Object.values(depositData);
        const tx = await approvedDeposit(depositDataValues);
        const events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        const event = events[0].data; // 0th event is the latest event;
        assertSE(event.exclusivityDeadline, zeroExclusivity, "Exclusivity deadline should always be 0");
    });
    it("unsafe deposit ID", async () => {
        const forcedDepositId = new anchor_1.BN(99);
        // Convert the inputs to byte arrays
        const msgSenderBytes = ethers_1.ethers.utils.arrayify(depositAccounts.signer.toBytes());
        const depositorBytes = ethers_1.ethers.utils.arrayify(depositData.depositor.toBytes());
        const depositNonceBytes = ethers_1.ethers.utils.zeroPad(forcedDepositId.toArrayLike(Buffer, "le", 8), 8);
        const data = ethers_1.ethers.utils.concat([msgSenderBytes, depositorBytes, depositNonceBytes]); // Concatenate the byte arrays
        const expectedDepositId = ethers_1.ethers.utils.keccak256(data); // Hash the concatenated data using keccak256
        const expectedDepositIdArray = ethers_1.ethers.utils.arrayify(expectedDepositId);
        // Call the method to get the unsafe deposit ID
        const unsafeDepositIdTx = await program.methods
            .getUnsafeDepositId(depositAccounts.signer, depositData.depositor, forcedDepositId)
            .view();
        assert.strictEqual(expectedDepositIdArray.toString(), unsafeDepositIdTx.toString(), "Deposit ID should match the expected hash");
        // Delegate state PDA to pull depositor tokens.
        const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(depositAccounts.depositorTokenAccount, depositAccounts.mint, depositAccounts.state, depositor.publicKey, BigInt(depositData.inputAmount.toString()), tokenDecimals, undefined, tokenProgram);
        // Create the transaction for unsafeDepositV3
        const unsafeDepositIx = await program.methods
            .unsafeDeposit(depositData.depositor, depositData.recipient, depositData.inputToken, depositData.outputToken, depositData.inputAmount, depositData.outputAmount, depositData.destinationChainId, depositData.exclusiveRelayer, forcedDepositId, // deposit nonce
        depositData.quoteTimestamp.toNumber(), depositData.fillDeadline.toNumber(), depositData.exclusivityParameter.toNumber(), depositData.message)
            .accounts(depositAccounts) // Assuming depositAccounts is already set up correctly
            .instruction();
        const unsafeDepositTx = new web3_js_1.Transaction().add(approveIx, unsafeDepositIx);
        const tx = await (0, web3_js_1.sendAndConfirmTransaction)(connection, unsafeDepositTx, [payer, depositor]);
        // Wait for a short period to ensure the event is emitted
        // Read and verify the event
        const events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        const event = events[0].data; // Assuming the latest event is the one we want
        const expectedValues = { ...depositData, depositId: expectedDepositIdArray };
        for (let [key, value] of Object.entries(expectedValues)) {
            if (key === "exclusivityParameter")
                key = "exclusivityDeadline"; // Adjust for any key differences
            assertSE(event[key], value, `${key} should match`);
        }
    });
    it("Deposit with enabled CPI-guard", async () => {
        // CPI-guard is available only for the 2022 token program.
        tokenProgram = spl_token_1.TOKEN_2022_PROGRAM_ID;
        await setupInputToken();
        await enableRoute();
        // Enable CPI-guard for the depositor (requires TA reallocation).
        const enableCpiGuardTx = new web3_js_1.Transaction().add((0, spl_token_1.createReallocateInstruction)(depositorTA, payer.publicKey, [spl_token_1.ExtensionType.CpiGuard], depositor.publicKey), (0, spl_token_1.createEnableCpiGuardInstruction)(depositorTA, depositor.publicKey));
        await (0, web3_js_1.sendAndConfirmTransaction)(connection, enableCpiGuardTx, [payer, depositor]);
        // Verify vault balance is zero before the deposit
        let vaultAccount = await (0, spl_token_1.getAccount)(connection, vault, undefined, tokenProgram);
        assertSE(vaultAccount.amount, "0", "Vault balance should be zero before the deposit");
        // Execute the deposit call
        const depositDataValues = Object.values(depositData);
        await approvedDeposit(depositDataValues);
        // Verify tokens leave the depositor's account
        const depositorAccount = await (0, spl_token_1.getAccount)(connection, depositorTA, undefined, tokenProgram);
        assertSE(depositorAccount.amount, seedBalance - depositData.inputAmount.toNumber(), "Depositor's balance should be reduced by the deposited amount");
        // Verify tokens are credited into the vault
        vaultAccount = await (0, spl_token_1.getAccount)(connection, vault, undefined, tokenProgram);
        assertSE(vaultAccount.amount, depositData.inputAmount, "Vault balance should be increased by the deposited amount");
    });
    it("Deposit without approval fails", async () => {
        const depositDataValues = Object.values(depositData);
        try {
            await program.methods
                .deposit(...depositDataValues)
                .accounts(depositAccounts)
                .signers([depositor])
                .rpc();
            assert.fail("Deposit should have failed due to missing approval");
        }
        catch (err) {
            assert.include(err.toString(), "owner does not match");
        }
    });
});
