"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 spl_token_1 = require("@solana/spl-token");
const web3_js_1 = require("@solana/web3.js");
const chai_1 = require("chai");
const crypto = __importStar(require("crypto"));
const ethers_1 = require("ethers");
const SponsoredCctpSrc_common_1 = require("./SponsoredCctpSrc.common");
const web3_v1_1 = require("../../src/svm/web3-v1");
const utils_1 = require("./utils");
describe("sponsored_cctp_src_periphery.deposit", () => {
    anchor.setProvider(SponsoredCctpSrc_common_1.provider);
    const tokenMessengerMinterV2Program = anchor_1.workspace.TokenMessengerMinterV2;
    const messageTransmitterV2Program = anchor_1.workspace.MessageTransmitterV2;
    const { payer } = anchor.AnchorProvider.env().wallet;
    const depositor = web3_js_1.Keypair.generate();
    const operator = web3_js_1.Keypair.generate();
    const { quoteSigner, quoteSignerPubkey } = (0, SponsoredCctpSrc_common_1.createQuoteSigner)();
    const tokenDecimals = 6;
    const seedBalance = BigInt(ethers_1.ethers.utils.parseUnits("1000000", tokenDecimals).toString());
    const burnAmount = ethers_1.ethers.utils.parseUnits("1000", 6);
    const remoteDomain = new anchor_1.BN(0); // Ethereum
    const mintRecipient = ethers_1.ethers.utils.arrayify(ethers_1.ethers.utils.id("mintRecipient"));
    const destinationCaller = ethers_1.ethers.utils.arrayify(ethers_1.ethers.utils.id("destinationCaller"));
    const finalRecipient = ethers_1.ethers.utils.arrayify(ethers_1.ethers.utils.id("finalRecipient"));
    const finalToken = ethers_1.ethers.utils.arrayify(ethers_1.ethers.utils.id("finalToken"));
    const maxFee = 100;
    const minFinalityThreshold = 5;
    const maxBpsToSponsor = 500;
    const maxUserSlippageBps = 1000;
    const executionMode = 0; // DirectToCore
    const actionData = "0x"; // Empty in DirectToCore mode
    let sourceDomain;
    let messageSentEventData;
    let lookupTableAccount;
    let state, tokenProgram, burnToken, depositorTokenAccount, denylistAccount, tokenMessengerMinterSenderAuthority, messageTransmitter, tokenMessenger, remoteTokenMessenger, tokenMinter, localToken, cctpEventAuthority, rentFund, minimumDeposit, programData, rentClaim;
    const getDenyList = (user) => {
        const [denyList] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("denylist_account"), user.toBuffer()], tokenMessengerMinterV2Program.programId);
        return denyList;
    };
    const signSponsoredCCTPQuote = (signer, quoteData) => {
        const encodedPart1 = ethers_1.ethers.utils.defaultAbiCoder.encode(["uint32", "uint32", "bytes32", "uint256", "bytes32", "bytes32", "uint256", "uint32"], [
            quoteData.sourceDomain,
            quoteData.destinationDomain,
            quoteData.mintRecipient,
            quoteData.amount,
            quoteData.burnToken,
            quoteData.destinationCaller,
            quoteData.maxFee,
            quoteData.minFinalityThreshold,
        ]);
        const encodedPart2 = ethers_1.ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint256", "uint256", "bytes32", "bytes32", "uint8", "bytes32"], [
            quoteData.nonce,
            quoteData.deadline,
            quoteData.maxBpsToSponsor,
            quoteData.maxUserSlippageBps,
            quoteData.finalRecipient,
            quoteData.finalToken,
            quoteData.executionMode,
            ethers_1.ethers.utils.keccak256(quoteData.actionData),
        ]);
        const hash1 = ethers_1.ethers.utils.keccak256(encodedPart1);
        const hash2 = ethers_1.ethers.utils.keccak256(encodedPart2);
        const encodedHexString = ethers_1.ethers.utils.defaultAbiCoder.encode(["bytes32", "bytes32"], [hash1, hash2]);
        const digest = ethers_1.ethers.utils.keccak256(encodedHexString);
        // Create simple ECDSA signature over the encoded quote data hash.
        const signatureHexString = ethers_1.ethers.utils.joinSignature(signer._signingKey().signDigest(digest));
        return Buffer.from(ethers_1.ethers.utils.arrayify(signatureHexString));
    };
    const encodeQuoteForSVM = (quote) => {
        return {
            sourceDomain: quote.sourceDomain,
            destinationDomain: quote.destinationDomain,
            mintRecipient: new web3_js_1.PublicKey(ethers_1.ethers.utils.arrayify(quote.mintRecipient)),
            amount: new anchor_1.BN(quote.amount.toString()),
            burnToken: new web3_js_1.PublicKey(ethers_1.ethers.utils.arrayify(quote.burnToken)),
            destinationCaller: new web3_js_1.PublicKey(ethers_1.ethers.utils.arrayify(quote.destinationCaller)),
            maxFee: new anchor_1.BN(quote.maxFee.toString()),
            minFinalityThreshold: quote.minFinalityThreshold,
            nonce: Array.from(ethers_1.ethers.utils.arrayify(quote.nonce)),
            deadline: new anchor_1.BN(quote.deadline.toString()),
            maxBpsToSponsor: new anchor_1.BN(quote.maxBpsToSponsor.toString()),
            maxUserSlippageBps: new anchor_1.BN(quote.maxUserSlippageBps.toString()),
            finalRecipient: new web3_js_1.PublicKey(ethers_1.ethers.utils.arrayify(quote.finalRecipient)),
            finalToken: new web3_js_1.PublicKey(ethers_1.ethers.utils.arrayify(quote.finalToken)),
            executionMode: quote.executionMode,
            actionData: Buffer.from(ethers_1.ethers.utils.arrayify(quote.actionData)),
        };
    };
    const getEncodedQuoteWithSignature = (signer, quoteData) => {
        const encodedQuote = encodeQuoteForSVM(quoteData);
        const signature = Array.from(signSponsoredCCTPQuote(signer, quoteData));
        return { quote: encodedQuote, signature };
    };
    const getHookDataFromQuote = (quoteData) => {
        const encodedHexString = ethers_1.ethers.utils.defaultAbiCoder.encode(["bytes32", "uint256", "uint256", "uint256", "bytes32", "bytes32", "uint8", "bytes"], [
            quoteData.nonce,
            quoteData.deadline,
            quoteData.maxBpsToSponsor,
            quoteData.maxUserSlippageBps,
            quoteData.finalRecipient,
            quoteData.finalToken,
            quoteData.executionMode,
            quoteData.actionData,
        ]);
        return Buffer.from(ethers_1.ethers.utils.arrayify(encodedHexString));
    };
    const decodeHookData = (data) => {
        const ABI_TYPES = [
            "bytes32", // nonce
            "uint256", // deadline
            "uint256", // maxBpsToSponsor
            "uint256", // maxUserSlippageBps
            "bytes32", // finalRecipient
            "bytes32", // finalToken
            "uint8", // executionMode
            "bytes", // actionData
        ];
        const decoded = ethers_1.ethers.utils.defaultAbiCoder.decode(ABI_TYPES, data);
        const [nonce, deadline, maxBpsToSponsor, maxUserSlippageBps, finalRecipient, finalToken, executionMode, actionData,] = decoded;
        return {
            nonce,
            deadline,
            maxBpsToSponsor,
            maxUserSlippageBps,
            finalRecipient,
            finalToken,
            executionMode,
            actionData,
        };
    };
    const setupBurnToken = async () => {
        burnToken = await (0, spl_token_1.createMint)(SponsoredCctpSrc_common_1.connection, payer, SponsoredCctpSrc_common_1.owner, SponsoredCctpSrc_common_1.owner, tokenDecimals, undefined, undefined, tokenProgram);
        depositorTokenAccount = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(SponsoredCctpSrc_common_1.connection, payer, burnToken, depositor.publicKey, undefined, undefined, undefined, tokenProgram)).address;
        await (0, spl_token_1.mintTo)(SponsoredCctpSrc_common_1.connection, payer, burnToken, depositorTokenAccount, SponsoredCctpSrc_common_1.owner, seedBalance, undefined, undefined, tokenProgram);
        [minimumDeposit] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("minimum_deposit"), burnToken.toBuffer()], SponsoredCctpSrc_common_1.program.programId);
        // Add local CCTP token (test wallet is overridden as token controller in Anchor.toml).
        [localToken] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("local_token"), burnToken.toBuffer()], tokenMessengerMinterV2Program.programId);
        const [custodyTokenAccount] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("custody"), burnToken.toBuffer()], tokenMessengerMinterV2Program.programId);
        const addLocalTokenAccounts = {
            tokenController: SponsoredCctpSrc_common_1.owner,
            tokenMinter,
            localToken,
            custodyTokenAccount,
            localTokenMint: burnToken,
            tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
            systemProgram: web3_js_1.SystemProgram.programId,
            program: tokenMessengerMinterV2Program.programId,
            eventAuthority: cctpEventAuthority,
        };
        await tokenMessengerMinterV2Program.methods.addLocalToken({}).accounts(addLocalTokenAccounts).rpc();
        // Set max burn amount per CCTP message for local token to total mint amount.
        const setMaxBurnAmountPerMessageAccounts = {
            tokenMinter,
            localToken,
            program: tokenMessengerMinterV2Program.programId,
            eventAuthority: cctpEventAuthority,
        };
        await tokenMessengerMinterV2Program.methods
            .setMaxBurnAmountPerMessage({ burnLimitPerMessage: new anchor_1.BN(seedBalance.toString()) })
            .accounts(setMaxBurnAmountPerMessageAccounts)
            .rpc();
    };
    const setupCctpAccounts = () => {
        denylistAccount = getDenyList(depositor.publicKey);
        tokenMessengerMinterSenderAuthority = (0, web3_v1_1.findProgramAddress)("sender_authority", tokenMessengerMinterV2Program.programId).publicKey;
        messageTransmitter = (0, web3_v1_1.findProgramAddress)("message_transmitter", messageTransmitterV2Program.programId).publicKey;
        tokenMessenger = (0, web3_v1_1.findProgramAddress)("token_messenger", tokenMessengerMinterV2Program.programId).publicKey;
        remoteTokenMessenger = (0, web3_v1_1.findProgramAddress)("remote_token_messenger", tokenMessengerMinterV2Program.programId, [
            remoteDomain.toString(),
        ]).publicKey;
        tokenMinter = (0, web3_v1_1.findProgramAddress)("token_minter", tokenMessengerMinterV2Program.programId).publicKey;
        cctpEventAuthority = (0, web3_v1_1.findProgramAddress)("__event_authority", tokenMessengerMinterV2Program.programId).publicKey;
    };
    const setupLookupTable = async () => {
        // These accounts should be the same for all deposits that have the same burnToken.
        const eventAuthority = (0, web3_v1_1.findProgramAddress)("__event_authority", SponsoredCctpSrc_common_1.program.programId).publicKey;
        rentFund = (0, web3_v1_1.findProgramAddress)("rent_fund", SponsoredCctpSrc_common_1.program.programId).publicKey;
        const lookupAddresses = [
            state,
            burnToken,
            tokenMessengerMinterSenderAuthority,
            messageTransmitter,
            tokenMessenger,
            tokenMinter,
            localToken,
            cctpEventAuthority,
            messageTransmitterV2Program.programId,
            tokenMessengerMinterV2Program.programId,
            tokenProgram,
            web3_js_1.SystemProgram.programId,
            eventAuthority,
            rentFund,
            minimumDeposit,
        ];
        // Create instructions for creating and extending the ALT.
        const [lookupTableInstruction, lookupTableAddress] = web3_js_1.AddressLookupTableProgram.createLookupTable({
            authority: SponsoredCctpSrc_common_1.owner,
            payer: SponsoredCctpSrc_common_1.owner,
            recentSlot: await SponsoredCctpSrc_common_1.connection.getSlot(),
        });
        // Submit the ALT creation transaction
        await (0, web3_js_1.sendAndConfirmTransaction)(SponsoredCctpSrc_common_1.connection, new web3_js_1.Transaction().add(lookupTableInstruction), [payer], {
            commitment: "confirmed",
            skipPreflight: true,
        });
        // Extend the ALT with all accounts.
        const extendInstruction = web3_js_1.AddressLookupTableProgram.extendLookupTable({
            lookupTable: lookupTableAddress,
            authority: SponsoredCctpSrc_common_1.owner,
            payer: SponsoredCctpSrc_common_1.owner,
            addresses: lookupAddresses,
        });
        await (0, web3_js_1.sendAndConfirmTransaction)(SponsoredCctpSrc_common_1.connection, new web3_js_1.Transaction().add(extendInstruction), [payer], {
            commitment: "confirmed",
            skipPreflight: true,
        });
        // Wait for slot to advance. ALTs only active after slot advance.
        const initialSlot = await SponsoredCctpSrc_common_1.connection.getSlot();
        while ((await SponsoredCctpSrc_common_1.connection.getSlot()) === initialSlot) {
            await new Promise((resolve) => setTimeout(resolve, 50));
        }
        // Fetch the AddressLookupTableAccount.
        const fetchedLookupTableAccount = (await SponsoredCctpSrc_common_1.connection.getAddressLookupTable(lookupTableAddress)).value;
        if (fetchedLookupTableAccount === null)
            throw new Error("AddressLookupTableAccount not fetched");
        lookupTableAccount = fetchedLookupTableAccount;
    };
    // Ensures the same rent_fund balance before each test.
    const setupRentFund = async () => {
        rentFund = (0, web3_v1_1.findProgramAddress)("rent_fund", SponsoredCctpSrc_common_1.program.programId).publicKey;
        const rentFundBalance = await SponsoredCctpSrc_common_1.connection.getBalance(rentFund);
        if (rentFundBalance > 0) {
            await SponsoredCctpSrc_common_1.program.methods
                .withdrawRentFund({ amount: new anchor_1.BN(rentFundBalance.toString()) })
                .accounts({ recipient: SponsoredCctpSrc_common_1.owner, programData })
                .rpc();
        }
        await (0, utils_1.requestAndConfirmAirdrop)(SponsoredCctpSrc_common_1.connection, rentFund, 1_000_000_000); // 1 SOL should be sufficient for rent.
    };
    const getUsedNonce = (nonce) => {
        const [usedNonce] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("used_nonce"), nonce], SponsoredCctpSrc_common_1.program.programId);
        return usedNonce;
    };
    before(async () => {
        await (0, utils_1.requestAndConfirmAirdrop)(SponsoredCctpSrc_common_1.connection, depositor.publicKey, 10_000_000_000); // 10 SOL
        await (0, utils_1.requestAndConfirmAirdrop)(SponsoredCctpSrc_common_1.connection, operator.publicKey, 10_000_000_000); // 10 SOL
        setupCctpAccounts();
        ({ state, sourceDomain, programData } = await (0, SponsoredCctpSrc_common_1.initializeState)({ signer: quoteSignerPubkey }));
        tokenProgram = spl_token_1.TOKEN_PROGRAM_ID;
        await setupBurnToken();
        await setupLookupTable();
    });
    beforeEach(async () => {
        await SponsoredCctpSrc_common_1.program.methods
            .setMinimumDepositAmount({ amount: new anchor_1.BN(0) })
            .accounts({ programData, burnToken })
            .rpc();
        await setupRentFund();
        messageSentEventData = web3_js_1.Keypair.generate();
        // Set rent_claim to None as normally rent_fund should have been sufficiently funded. Some tests might override this.
        rentClaim = SponsoredCctpSrc_common_1.program.programId;
    });
    it("Sponsored CCTP deposit", async () => {
        const nonce = crypto.randomBytes(32);
        const usedNonce = getUsedNonce(nonce);
        const deadline = ethers_1.ethers.BigNumber.from(Math.floor(Date.now() / 1000) + 3600);
        const quoteData = {
            sourceDomain,
            destinationDomain: remoteDomain.toNumber(),
            mintRecipient: ethers_1.ethers.utils.hexlify(mintRecipient),
            amount: burnAmount,
            burnToken: ethers_1.ethers.utils.hexlify(burnToken.toBuffer()),
            destinationCaller: ethers_1.ethers.utils.hexlify(destinationCaller),
            maxFee,
            minFinalityThreshold,
            nonce: ethers_1.ethers.utils.hexlify(nonce),
            deadline,
            maxBpsToSponsor,
            maxUserSlippageBps,
            finalRecipient: ethers_1.ethers.utils.hexlify(finalRecipient),
            finalToken: ethers_1.ethers.utils.hexlify(finalToken),
            executionMode,
            actionData,
        };
        const { quote, signature } = getEncodedQuoteWithSignature(quoteSigner, quoteData);
        const depositAccounts = {
            signer: depositor.publicKey,
            payer: depositor.publicKey,
            state,
            rentFund,
            usedNonce,
            rentClaim,
            depositorTokenAccount,
            burnToken,
            denylistAccount,
            tokenMessengerMinterSenderAuthority,
            messageTransmitter,
            tokenMessenger,
            remoteTokenMessenger,
            tokenMinter,
            localToken,
            cctpEventAuthority,
            tokenProgram,
            messageSentEventData: messageSentEventData.publicKey,
            program: SponsoredCctpSrc_common_1.program.programId,
        };
        const depositIx = await SponsoredCctpSrc_common_1.program.methods
            .depositForBurn({ quote, signature })
            .accounts(depositAccounts)
            .instruction();
        const txSignature = await (0, web3_v1_1.sendTransactionWithExistingLookupTable)(SponsoredCctpSrc_common_1.connection, [depositIx], lookupTableAccount, depositor, [messageSentEventData]);
        const depositorTokenAmount = (await (0, spl_token_1.getAccount)(SponsoredCctpSrc_common_1.connection, depositorTokenAccount)).amount;
        const expectedDepositorTokenAmount = seedBalance - BigInt(burnAmount.toString());
        chai_1.assert.strictEqual(depositorTokenAmount.toString(), expectedDepositorTokenAmount.toString(), "Depositor token amount mismatch");
        const events = await (0, web3_v1_1.readEventsUntilFound)(SponsoredCctpSrc_common_1.connection, txSignature, [SponsoredCctpSrc_common_1.program]);
        const depositEvent = events.find((event) => event.name === "sponsoredDepositForBurn")?.data;
        chai_1.assert.isNotNull(depositEvent, "SponsoredDepositForBurn event should be emitted");
        chai_1.assert.isTrue(depositEvent.quoteNonce.equals(nonce), "Invalid quoteNonce");
        chai_1.assert.strictEqual(depositEvent.originSender.toString(), depositor.publicKey.toString(), "Invalid originSender");
        chai_1.assert.strictEqual(depositEvent.finalRecipient.toString(), new web3_js_1.PublicKey(finalRecipient).toString(), "Invalid finalRecipient");
        chai_1.assert.strictEqual(depositEvent.quoteDeadline.toString(), deadline.toString(), "Invalid quoteDeadline");
        chai_1.assert.strictEqual(depositEvent.maxBpsToSponsor.toString(), maxBpsToSponsor.toString(), "Invalid maxBpsToSponsor");
        chai_1.assert.strictEqual(depositEvent.maxUserSlippageBps.toString(), maxUserSlippageBps.toString(), "Invalid maxUserSlippageBps");
        chai_1.assert.strictEqual(depositEvent.finalToken.toString(), new web3_js_1.PublicKey(finalToken).toString(), "Invalid finalToken");
        chai_1.assert.strictEqual(depositEvent.finalToken.toString(), new web3_js_1.PublicKey(finalToken).toString(), "Invalid finalToken");
        chai_1.assert.isTrue(depositEvent.signature.equals(Buffer.from(signature)), "Invalid signature");
        const createdEventAccountEvent = events.find((event) => event.name === "createdEventAccount")?.data;
        chai_1.assert.strictEqual(createdEventAccountEvent.messageSentEventData.toString(), messageSentEventData.publicKey.toString(), "Invalid messageSentEventData");
        const message = (0, web3_v1_1.decodeMessageSentDataV2)((await messageTransmitterV2Program.account.messageSent.fetch(messageSentEventData.publicKey)).message);
        chai_1.assert.strictEqual(message.destinationDomain, remoteDomain.toNumber(), "Invalid destination domain");
        chai_1.assert.strictEqual(message.destinationCaller.toString(), new web3_js_1.PublicKey(destinationCaller).toString(), "Invalid destinationCaller");
        chai_1.assert.strictEqual(message.minFinalityThreshold, minFinalityThreshold, "Invalid minFinalityThreshold");
        chai_1.assert.strictEqual(message.messageBody.burnToken.toString(), burnToken.toString(), "Invalid burnToken");
        chai_1.assert.strictEqual(message.messageBody.mintRecipient.toString(), new web3_js_1.PublicKey(mintRecipient).toString(), "Invalid mintRecipient");
        chai_1.assert.strictEqual(message.messageBody.amount.toString(), burnAmount.toString(), "Invalid amount");
        chai_1.assert.strictEqual(message.messageBody.messageSender.toString(), depositor.publicKey.toString(), "Invalid messageSender");
        chai_1.assert.strictEqual(message.messageBody.maxFee.toString(), maxFee.toString(), "Invalid maxFee");
        const expectedHookData = getHookDataFromQuote(quoteData);
        chai_1.assert.isTrue(message.messageBody.hookData.equals(expectedHookData), "Invalid hookData");
        const usedNonceCloseInfo = await SponsoredCctpSrc_common_1.program.methods.getUsedNonceCloseInfo({ nonce: Array.from(nonce) }).view();
        chai_1.assert.strictEqual(usedNonceCloseInfo.canCloseAfter.toString(), deadline.toString(), "Invalid canCloseAfter");
        chai_1.assert.isFalse(usedNonceCloseInfo.canCloseNow, "Used nonce should not be closable now");
    });
    it("Reclaim used_nonce account", async () => {
        const nonce = crypto.randomBytes(32);
        const usedNonce = getUsedNonce(nonce);
        const deadline = ethers_1.ethers.BigNumber.from(Math.floor(Date.now() / 1000) + 3600);
        const quoteData = {
            sourceDomain,
            destinationDomain: remoteDomain.toNumber(),
            mintRecipient: ethers_1.ethers.utils.hexlify(mintRecipient),
            amount: burnAmount,
            burnToken: ethers_1.ethers.utils.hexlify(burnToken.toBuffer()),
            destinationCaller: ethers_1.ethers.utils.hexlify(destinationCaller),
            maxFee,
            minFinalityThreshold,
            nonce: ethers_1.ethers.utils.hexlify(nonce),
            deadline,
            maxBpsToSponsor,
            maxUserSlippageBps,
            finalRecipient: ethers_1.ethers.utils.hexlify(finalRecipient),
            finalToken: ethers_1.ethers.utils.hexlify(finalToken),
            executionMode,
            actionData,
        };
        const { quote, signature } = getEncodedQuoteWithSignature(quoteSigner, quoteData);
        const depositAccounts = {
            signer: depositor.publicKey,
            payer: depositor.publicKey,
            state,
            rentFund,
            usedNonce,
            rentClaim,
            depositorTokenAccount,
            burnToken,
            denylistAccount,
            tokenMessengerMinterSenderAuthority,
            messageTransmitter,
            tokenMessenger,
            remoteTokenMessenger,
            tokenMinter,
            localToken,
            cctpEventAuthority,
            tokenProgram,
            messageSentEventData: messageSentEventData.publicKey,
            program: SponsoredCctpSrc_common_1.program.programId,
        };
        const depositIx = await SponsoredCctpSrc_common_1.program.methods
            .depositForBurn({ quote, signature })
            .accounts(depositAccounts)
            .instruction();
        await (0, web3_v1_1.sendTransactionWithExistingLookupTable)(SponsoredCctpSrc_common_1.connection, [depositIx], lookupTableAccount, depositor, [
            messageSentEventData,
        ]);
        const reclaimIx = await SponsoredCctpSrc_common_1.program.methods
            .reclaimUsedNonceAccount({ nonce: Array.from(nonce) })
            .accounts({ program: SponsoredCctpSrc_common_1.program.programId })
            .instruction();
        try {
            await (0, web3_js_1.sendAndConfirmTransaction)(SponsoredCctpSrc_common_1.connection, new web3_js_1.Transaction().add(reclaimIx), [operator]);
            chai_1.assert.fail("Reclaim used nonce account should have failed");
        }
        catch (err) {
            chai_1.assert.instanceOf(err, web3_js_1.SendTransactionError);
            const logs = await err.getLogs(SponsoredCctpSrc_common_1.connection);
            chai_1.assert.isTrue(logs.some((log) => log.includes("QuoteDeadlineNotPassed")), "Expected QuoteDeadlineNotPassed error log");
        }
        const usedNonceLamports = await SponsoredCctpSrc_common_1.connection.getBalance(usedNonce);
        const rentFundLamportsBefore = await SponsoredCctpSrc_common_1.connection.getBalance(rentFund);
        await SponsoredCctpSrc_common_1.program.methods.setCurrentTime({ newTime: new anchor_1.BN(deadline.add(1).toString()) }).rpc();
        await (0, web3_js_1.sendAndConfirmTransaction)(SponsoredCctpSrc_common_1.connection, new web3_js_1.Transaction().add(reclaimIx), [operator]);
        try {
            await SponsoredCctpSrc_common_1.program.account.usedNonce.fetch(usedNonce);
            chai_1.assert.fail("Fetching closed account should have failed");
        }
        catch (err) {
            chai_1.assert.instanceOf(err, Error);
            chai_1.assert.include(err.message, "Account does not exist", "Expected account not found error");
        }
        const rentFundLamportsAfter = await SponsoredCctpSrc_common_1.connection.getBalance(rentFund);
        chai_1.assert.strictEqual(rentFundLamportsAfter, rentFundLamportsBefore + usedNonceLamports, "Rent fund should receive all lamports from reclaimed used nonce account");
    });
    it("Deposit with maximum actionData length", async () => {
        const nonce = crypto.randomBytes(32);
        const usedNonce = getUsedNonce(nonce);
        const deadline = ethers_1.ethers.BigNumber.from(Math.floor(Date.now() / 1000) + 3600);
        const executionMode = 1; // ArbitraryActionsToCore
        const actionDataLenth = 442; // Larger actionData would exceed the transaction message size limits on Solana.
        const actionData = crypto.randomBytes(actionDataLenth);
        const quoteData = {
            sourceDomain,
            destinationDomain: remoteDomain.toNumber(),
            mintRecipient: ethers_1.ethers.utils.hexlify(mintRecipient),
            amount: burnAmount,
            burnToken: ethers_1.ethers.utils.hexlify(burnToken.toBuffer()),
            destinationCaller: ethers_1.ethers.utils.hexlify(destinationCaller),
            maxFee,
            minFinalityThreshold,
            nonce: ethers_1.ethers.utils.hexlify(nonce),
            deadline,
            maxBpsToSponsor,
            maxUserSlippageBps,
            finalRecipient: ethers_1.ethers.utils.hexlify(finalRecipient),
            finalToken: ethers_1.ethers.utils.hexlify(finalToken),
            executionMode,
            actionData: ethers_1.ethers.utils.hexlify(actionData),
        };
        const { quote, signature } = getEncodedQuoteWithSignature(quoteSigner, quoteData);
        const depositAccounts = {
            signer: depositor.publicKey,
            payer: depositor.publicKey,
            state,
            rentFund,
            usedNonce,
            rentClaim,
            depositorTokenAccount,
            burnToken,
            denylistAccount,
            tokenMessengerMinterSenderAuthority,
            messageTransmitter,
            tokenMessenger,
            remoteTokenMessenger,
            tokenMinter,
            localToken,
            cctpEventAuthority,
            tokenProgram,
            messageSentEventData: messageSentEventData.publicKey,
            program: SponsoredCctpSrc_common_1.program.programId,
        };
        const depositIx = await SponsoredCctpSrc_common_1.program.methods
            .depositForBurn({ quote, signature })
            .accounts(depositAccounts)
            .instruction();
        await (0, web3_v1_1.sendTransactionWithExistingLookupTable)(SponsoredCctpSrc_common_1.connection, [depositIx], lookupTableAccount, depositor, [
            messageSentEventData,
        ]);
        const message = (0, web3_v1_1.decodeMessageSentDataV2)((await messageTransmitterV2Program.account.messageSent.fetch(messageSentEventData.publicKey)).message);
        const expectedHookData = getHookDataFromQuote(quoteData);
        chai_1.assert.isTrue(message.messageBody.hookData.equals(expectedHookData), "Invalid hookData");
        // Above check for encoded hookData should implicitly verify action data, but add explicit test for clarity.
        const decodedHookData = decodeHookData(message.messageBody.hookData);
        chai_1.assert.strictEqual(decodedHookData.actionData, ethers_1.ethers.utils.hexlify(actionData), "Invalid actionData");
    });
    it("Deposit below minimum amount fails", async () => {
        const minimumAmount = new anchor_1.BN(burnAmount.add(1).toString());
        await SponsoredCctpSrc_common_1.program.methods.setMinimumDepositAmount({ amount: minimumAmount }).accounts({ programData, burnToken }).rpc();
        const nonce = crypto.randomBytes(32);
        const usedNonce = getUsedNonce(nonce);
        const deadline = ethers_1.ethers.BigNumber.from(Math.floor(Date.now() / 1000) + 3600);
        const quoteData = {
            sourceDomain,
            destinationDomain: remoteDomain.toNumber(),
            mintRecipient: ethers_1.ethers.utils.hexlify(mintRecipient),
            amount: burnAmount,
            burnToken: ethers_1.ethers.utils.hexlify(burnToken.toBuffer()),
            destinationCaller: ethers_1.ethers.utils.hexlify(destinationCaller),
            maxFee,
            minFinalityThreshold,
            nonce: ethers_1.ethers.utils.hexlify(nonce),
            deadline,
            maxBpsToSponsor,
            maxUserSlippageBps,
            finalRecipient: ethers_1.ethers.utils.hexlify(finalRecipient),
            finalToken: ethers_1.ethers.utils.hexlify(finalToken),
            executionMode,
            actionData,
        };
        const { quote, signature } = getEncodedQuoteWithSignature(quoteSigner, quoteData);
        const depositAccounts = {
            signer: depositor.publicKey,
            payer: depositor.publicKey,
            state,
            rentFund,
            usedNonce,
            depositorTokenAccount,
            burnToken,
            denylistAccount,
            tokenMessengerMinterSenderAuthority,
            messageTransmitter,
            tokenMessenger,
            remoteTokenMessenger,
            tokenMinter,
            localToken,
            cctpEventAuthority,
            tokenProgram,
            messageSentEventData: messageSentEventData.publicKey,
            program: SponsoredCctpSrc_common_1.program.programId,
        };
        const depositIx = await SponsoredCctpSrc_common_1.program.methods
            .depositForBurn({ quote, signature })
            .accounts(depositAccounts)
            .instruction();
        try {
            await (0, web3_v1_1.sendTransactionWithExistingLookupTable)(SponsoredCctpSrc_common_1.connection, [depositIx], lookupTableAccount, depositor, [
                messageSentEventData,
            ]);
            chai_1.assert.fail("Deposit should have failed due to amount below minimum");
        }
        catch (err) {
            chai_1.assert.instanceOf(err, web3_js_1.SendTransactionError);
            const logs = await err.getLogs(SponsoredCctpSrc_common_1.connection);
            chai_1.assert.isTrue(logs.some((log) => log.includes("DepositAmountBelowMinimum")), "Expected DepositAmountBelowMinimum error log");
        }
    });
    it("Accrue and repay rent_fund debt", async () => {
        [rentClaim] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("rent_claim"), depositor.publicKey.toBuffer()], SponsoredCctpSrc_common_1.program.programId);
        let nonce = crypto.randomBytes(32);
        const deadline = ethers_1.ethers.BigNumber.from(Math.floor(Date.now() / 1000) + 3600);
        const quoteData = {
            sourceDomain,
            destinationDomain: remoteDomain.toNumber(),
            mintRecipient: ethers_1.ethers.utils.hexlify(mintRecipient),
            amount: burnAmount,
            burnToken: ethers_1.ethers.utils.hexlify(burnToken.toBuffer()),
            destinationCaller: ethers_1.ethers.utils.hexlify(destinationCaller),
            maxFee,
            minFinalityThreshold,
            nonce: ethers_1.ethers.utils.hexlify(nonce),
            deadline,
            maxBpsToSponsor,
            maxUserSlippageBps,
            finalRecipient: ethers_1.ethers.utils.hexlify(finalRecipient),
            finalToken: ethers_1.ethers.utils.hexlify(finalToken),
            executionMode,
            actionData,
        };
        let { quote, signature } = getEncodedQuoteWithSignature(quoteSigner, quoteData);
        const depositAccounts = {
            signer: depositor.publicKey,
            payer: depositor.publicKey,
            state,
            rentFund,
            usedNonce: getUsedNonce(nonce),
            rentClaim,
            depositorTokenAccount,
            burnToken,
            denylistAccount,
            tokenMessengerMinterSenderAuthority,
            messageTransmitter,
            tokenMessenger,
            remoteTokenMessenger,
            tokenMinter,
            localToken,
            cctpEventAuthority,
            tokenProgram,
            messageSentEventData: messageSentEventData.publicKey,
            program: SponsoredCctpSrc_common_1.program.programId,
        };
        let depositIx = await SponsoredCctpSrc_common_1.program.methods.depositForBurn({ quote, signature }).accounts(depositAccounts).instruction();
        await (0, web3_v1_1.sendTransactionWithExistingLookupTable)(SponsoredCctpSrc_common_1.connection, [depositIx], lookupTableAccount, depositor, [
            messageSentEventData,
        ]);
        let rentClaimAccount = await SponsoredCctpSrc_common_1.program.account.rentClaim.fetchNullable(rentClaim);
        chai_1.assert.isNull(rentClaimAccount, "No debt should be accrued and account closed when rent_fund had funding");
        // Withdraw all rent_fund balance to test debt accrual.
        let rentFundBalance = await SponsoredCctpSrc_common_1.connection.getBalance(rentFund);
        await SponsoredCctpSrc_common_1.program.methods
            .withdrawRentFund({ amount: new anchor_1.BN(rentFundBalance.toString()) })
            .accounts({
            recipient: SponsoredCctpSrc_common_1.owner,
            programData,
        })
            .rpc();
        nonce = crypto.randomBytes(32);
        messageSentEventData = web3_js_1.Keypair.generate();
        depositAccounts.usedNonce = getUsedNonce(nonce);
        depositAccounts.messageSentEventData = messageSentEventData.publicKey;
        quoteData.nonce = ethers_1.ethers.utils.hexlify(nonce);
        ({ quote, signature } = getEncodedQuoteWithSignature(quoteSigner, quoteData));
        depositIx = await SponsoredCctpSrc_common_1.program.methods.depositForBurn({ quote, signature }).accounts(depositAccounts).instruction();
        await (0, web3_v1_1.sendTransactionWithExistingLookupTable)(SponsoredCctpSrc_common_1.connection, [depositIx], lookupTableAccount, depositor, [
            messageSentEventData,
        ]);
        rentClaimAccount = await SponsoredCctpSrc_common_1.program.account.rentClaim.fetch(rentClaim);
        const usedNonceBalance = await SponsoredCctpSrc_common_1.connection.getBalance(depositAccounts.usedNonce);
        const messageSentEventDataBalance = await SponsoredCctpSrc_common_1.connection.getBalance(depositAccounts.messageSentEventData);
        rentFundBalance = await SponsoredCctpSrc_common_1.connection.getBalance(rentFund);
        chai_1.assert.isTrue(rentClaimAccount.amount.eq(new anchor_1.BN(usedNonceBalance + messageSentEventDataBalance + rentFundBalance)), "Rent claim should have accrued debt for account creation");
        // Test repayment after rent_fund has balance.
        await (0, utils_1.requestAndConfirmAirdrop)(SponsoredCctpSrc_common_1.connection, rentFund, 1_000_000_000);
        const userBalanceBefore = await SponsoredCctpSrc_common_1.connection.getBalance(depositor.publicKey);
        const rentClaimBalance = await SponsoredCctpSrc_common_1.connection.getBalance(rentClaim); // Also should be refunded upon closing.
        await SponsoredCctpSrc_common_1.program.methods
            .repayRentFundDebt()
            .accounts({ recipient: depositor.publicKey, program: SponsoredCctpSrc_common_1.program.programId })
            .rpc();
        const userBalanceAfter = await SponsoredCctpSrc_common_1.connection.getBalance(depositor.publicKey);
        chai_1.assert.strictEqual(userBalanceAfter - userBalanceBefore, rentClaimAccount.amount.toNumber() + rentClaimBalance, "User should have been refunded the claim and proceeds from closing rent_claim");
        chai_1.assert.isNull(await SponsoredCctpSrc_common_1.program.account.rentClaim.fetchNullable(rentClaim), "Rent claim account should be closed");
    });
    it("Accrue and partially repay rent_fund debt", async () => {
        // Withdraw all rent_fund balance to test debt accrual.
        let rentFundBalance = await SponsoredCctpSrc_common_1.connection.getBalance(rentFund);
        await SponsoredCctpSrc_common_1.program.methods
            .withdrawRentFund({ amount: new anchor_1.BN(rentFundBalance.toString()) })
            .accounts({
            recipient: SponsoredCctpSrc_common_1.owner,
            programData,
        })
            .rpc();
        [rentClaim] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("rent_claim"), depositor.publicKey.toBuffer()], SponsoredCctpSrc_common_1.program.programId);
        let nonce = crypto.randomBytes(32);
        const deadline = ethers_1.ethers.BigNumber.from(Math.floor(Date.now() / 1000) + 3600);
        const quoteData = {
            sourceDomain,
            destinationDomain: remoteDomain.toNumber(),
            mintRecipient: ethers_1.ethers.utils.hexlify(mintRecipient),
            amount: burnAmount,
            burnToken: ethers_1.ethers.utils.hexlify(burnToken.toBuffer()),
            destinationCaller: ethers_1.ethers.utils.hexlify(destinationCaller),
            maxFee,
            minFinalityThreshold,
            nonce: ethers_1.ethers.utils.hexlify(nonce),
            deadline,
            maxBpsToSponsor,
            maxUserSlippageBps,
            finalRecipient: ethers_1.ethers.utils.hexlify(finalRecipient),
            finalToken: ethers_1.ethers.utils.hexlify(finalToken),
            executionMode,
            actionData,
        };
        let { quote, signature } = getEncodedQuoteWithSignature(quoteSigner, quoteData);
        const depositAccounts = {
            signer: depositor.publicKey,
            payer: depositor.publicKey,
            state,
            rentFund,
            usedNonce: getUsedNonce(nonce),
            rentClaim,
            depositorTokenAccount,
            burnToken,
            denylistAccount,
            tokenMessengerMinterSenderAuthority,
            messageTransmitter,
            tokenMessenger,
            remoteTokenMessenger,
            tokenMinter,
            localToken,
            cctpEventAuthority,
            tokenProgram,
            messageSentEventData: messageSentEventData.publicKey,
            program: SponsoredCctpSrc_common_1.program.programId,
        };
        let depositIx = await SponsoredCctpSrc_common_1.program.methods.depositForBurn({ quote, signature }).accounts(depositAccounts).instruction();
        await (0, web3_v1_1.sendTransactionWithExistingLookupTable)(SponsoredCctpSrc_common_1.connection, [depositIx], lookupTableAccount, depositor, [
            messageSentEventData,
        ]);
        let rentClaimAccount = await SponsoredCctpSrc_common_1.program.account.rentClaim.fetch(rentClaim);
        const usedNonceBalance = await SponsoredCctpSrc_common_1.connection.getBalance(depositAccounts.usedNonce);
        const messageSentEventDataBalance = await SponsoredCctpSrc_common_1.connection.getBalance(depositAccounts.messageSentEventData);
        rentFundBalance = await SponsoredCctpSrc_common_1.connection.getBalance(rentFund);
        const fullClaimAmount = new anchor_1.BN(usedNonceBalance + messageSentEventDataBalance + rentFundBalance);
        chai_1.assert.isTrue(rentClaimAccount.amount.eq(fullClaimAmount), "Rent claim should have accrued debt for account creation");
        // Without funding rent claim account should keep the debt.
        await SponsoredCctpSrc_common_1.program.methods
            .repayRentFundDebt()
            .accounts({ recipient: depositor.publicKey, program: SponsoredCctpSrc_common_1.program.programId })
            .rpc();
        rentClaimAccount = await SponsoredCctpSrc_common_1.program.account.rentClaim.fetch(rentClaim);
        chai_1.assert.isTrue(rentClaimAccount.amount.eq(fullClaimAmount), "Rent claim should not been repaid");
        // Test partial repayment of 1 lamport (rent_fund should already hold its minimum rent-free balance)
        const partialRepayment = 1;
        await (0, utils_1.requestAndConfirmAirdrop)(SponsoredCctpSrc_common_1.connection, rentFund, partialRepayment);
        const userBalanceBefore = await SponsoredCctpSrc_common_1.connection.getBalance(depositor.publicKey);
        await SponsoredCctpSrc_common_1.program.methods
            .repayRentFundDebt()
            .accounts({ recipient: depositor.publicKey, program: SponsoredCctpSrc_common_1.program.programId })
            .rpc();
        const userBalanceAfter = await SponsoredCctpSrc_common_1.connection.getBalance(depositor.publicKey);
        chai_1.assert.strictEqual(userBalanceAfter - userBalanceBefore, partialRepayment, "User should have been refunded only part of the claim");
        rentClaimAccount = await SponsoredCctpSrc_common_1.program.account.rentClaim.fetch(rentClaim);
        chai_1.assert.isTrue(rentClaimAccount.amount.eq(fullClaimAmount.sub(new anchor_1.BN(partialRepayment))), "Rent claim should have been partially repaid");
    });
});
