Vulnerability: Queued transfers can become stuck on the source chain if new Transceivers are added or existing Transceivers are modified before completion
Severity: high

Description:
When a sender transfers an amount that exceeds the current outbound capacity, such transfers are sent to a queue for delayed execution within `NttManager::_transferEntrypoint`. The rate limit duration is defined as an immutable variable determining the temporal lag between queueing and execution, with a typical rate limit duration being 24 hours.\\n```\\n/* snip */\\n// now check rate limits\\nbool isAmountRateLimited = _isOutboundAmountRateLimited(internalAmount);\\nif (!shouldQueue && isAmountRateLimited) {\\n    revert NotEnoughCapacity(getCurrentOutboundCapacity(), amount);\\n}\\nif (shouldQueue && isAmountRateLimited) {\\n    // emit an event to notify the user that the transfer is rate limited\\n    emit OutboundTransferRateLimited(\\n        msg.sender, sequence, amount, getCurrentOutboundCapacity()\\n    );\\n\\n    // queue up and return\\n    _enqueueOutboundTransfer(\\n        sequence,\\n        trimmedAmount,\\n        recipientChain,\\n        recipient,\\n        msg.sender,\\n        transceiverInstructions\\n    );\\n\\n    // refund price quote back to sender\\n    _refundToSender(msg.value);\\n\\n    // return the sequence in the queue\\n    return sequence;\\n}\\n/* snip */\\n```\\n\\nIn the event that new Transceivers are added or existing Transceivers are removed from the NTT Manager, any pending queued transfers within the rate limit duration can potentially revert. This is because senders might not have correctly packed the Transceiver instructions for a given Transceiver based on the new configuration, and a missing Transceiver instruction can potentially cause an array index out-of-bounds exception while calculating the delivery price when the instructions are finally parsed. For example, if there are initially two Transceivers but an additional Transceiver is added while the transfer is rate-limited, the instructions array as shown below will be declared with a length of three, corresponding to the new number of enabled Transceivers; however, the transfer will have only encoded two Transceiver instructions based on the configuration at the time it was initiated.\\n```\\nfunction parseTransceiverInstructions(\\n    bytes memory encoded,\\n    uint256 numEnabledTransceivers\\n) public pure returns (TransceiverInstruction[] memory) {\\n    uint256 offset = 0;\\n    uint256 instructionsLength;\\n    (instructionsLength, offset) = encoded.asUint8Unchecked(offset);\\n\\n    // We allocate an array with the length of the number of enabled transceivers\\n    // This gives us the flexibility to not have to pass instructions for transceivers that\\n    // don't need them\\n    TransceiverInstruction[] memory instructions =\\n        new TransceiverInstruction[](numEnabledTransceivers);\\n\\n    uint256 lastIndex = 0;\\n    for (uint256 i = 0; i < instructionsLength; i++) {\\n        TransceiverInstruction memory instruction;\\n        (instruction, offset) = parseTransceiverInstructionUnchecked(encoded, offset);\\n\\n        uint8 instructionIndex = instruction.index;\\n\\n        // The instructions passed in have to be strictly increasing in terms of transceiver index\\n        if (i != 0 && instructionIndex <= lastIndex) {\\n            revert UnorderedInstructions();\\n        }\\n        lastIndex = instructionIndex;\\n\\n        instructions[instructionIndex] = instruction;\\n    }\\n\\n    encoded.checkLength(offset);\\n\\n    return instructions;\\n}\\n```\\n

Impact:
Missing Transceiver instructions prevents the total delivery price for the corresponding message from being calculated. This prevents any queued Transfers from being executed with the current list of transceivers. As a result, underlying sender funds will be stuck in the `NttManager` contract. Note that a similar issue occurs if the peer NTT manager contract is updated on the destination (say, after a redeployment on the source chain) before an in-flight attestation is received and executed, reverting with an invalid peer error.\\nProof of Concept: Run the following test:\\n```\\ncontract TestTransceiverModification is Test, INttManagerEvents, IRateLimiterEvents {\\n    NttManager nttManagerChain1;\\n    NttManager nttManagerChain2;\\n\\n    using TrimmedAmountLib for uint256;\\n    using TrimmedAmountLib for TrimmedAmount;\\n\\n    uint16 constant chainId1 = 7;\\n    uint16 constant chainId2 = 100;\\n    uint8 constant FAST_CONSISTENCY_LEVEL = 200;\\n    uint256 constant GAS_LIMIT = 500000;\\n\\n    uint16 constant SENDING_CHAIN_ID = 1;\\n    uint256 constant DEVNET_GUARDIAN_PK =\\n        0xcfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0;\\n    WormholeSimulator guardian;\\n    uint256 initialBlockTimestamp;\\n\\n    WormholeTransceiver wormholeTransceiverChain1;\\n    WormholeTransceiver wormholeTransceiver2Chain1;\\n    WormholeTransceiver wormholeTransceiver3Chain1;\\n\\n    WormholeTransceiver wormholeTransceiverChain2;\\n    address userA = address(0x123);\\n    address userB = address(0x456);\\n    address userC = address(0x789);\\n    address userD = address(0xABC);\\n\\n    address relayer = address(0x28D8F1Be96f97C1387e94A53e00eCcFb4E75175a);\\n    IWormhole wormhole = IWormhole(0x706abc4E45D419950511e474C7B9Ed348A4a716c);\\n\\n    function setUp() public {\\n        string memory url = "https://goerli.blockpi.network/v1/rpc/public";\\n        vm.createSelectFork(url);\\n        initialBlockTimestamp = vm.getBlockTimestamp();\\n\\n        guardian = new WormholeSimulator(address(wormhole), DEVNET_GUARDIAN_PK);\\n\\n        vm.chainId(chainId1);\\n        DummyToken t1 = new DummyToken();\\n        NttManager implementation =\\n            new MockNttManagerContract(address(t1), INttManager.Mode.LOCKING, chainId1, 1 days);\\n\\n        nttManagerChain1 =\\n            MockNttManagerContract(address(new ERC1967Proxy(address(implementation), "")));\\n        nttManagerChain1.initialize();\\n\\n        // transceiver 1\\n        WormholeTransceiver wormholeTransceiverChain1Implementation = new MockWormholeTransceiverContract(\\n            address(nttManagerChain1),\\n            address(wormhole),\\n            address(relayer),\\n            address(0x0),\\n            FAST_CONSISTENCY_LEVEL,\\n            GAS_LIMIT\\n        );\\n        wormholeTransceiverChain1 = MockWormholeTransceiverContract(\\n            address(new ERC1967Proxy(address(wormholeTransceiverChain1Implementation), ""))\\n        );\\n\\n        // transceiver 2\\n        WormholeTransceiver wormholeTransceiverChain1Implementation2 = new MockWormholeTransceiverContract(\\n            address(nttManagerChain1),\\n            address(wormhole),\\n            address(relayer),\\n            address(0x0),\\n            FAST_CONSISTENCY_LEVEL,\\n            GAS_LIMIT\\n        );\\n        wormholeTransceiver2Chain1 = MockWormholeTransceiverContract(\\n            address(new ERC1967Proxy(address(wormholeTransceiverChain1Implementation2), ""))\\n        );\\n\\n        // transceiver 3\\n        WormholeTransceiver wormholeTransceiverChain1Implementation3 = new MockWormholeTransceiverContract(\\n            address(nttManagerChain1),\\n            address(wormhole),\\n            address(relayer),\\n            address(0x0),\\n            FAST_CONSISTENCY_LEVEL,\\n            GAS_LIMIT\\n        );\\n        wormholeTransceiver3Chain1 = MockWormholeTransceiverContract(\\n            address(new ERC1967Proxy(address(wormholeTransceiverChain1Implementation3), ""))\\n        );\\n\\n\\n        // Actually initialize properly now\\n        wormholeTransceiverChain1.initialize();\\n        wormholeTransceiver2Chain1.initialize();\\n        wormholeTransceiver3Chain1.initialize();\\n\\n\\n        nttManagerChain1.setTransceiver(address(wormholeTransceiverChain1));\\n        nttManagerChain1.setTransceiver(address(wormholeTransceiver2Chain1));\\n\\n        // third transceiver is NOT set at this point for nttManagerChain1\\n        nttManagerChain1.setOutboundLimit(type(uint64).max);\\n        nttManagerChain1.setInboundLimit(type(uint64).max, chainId2);\\n\\n        // Chain 2 setup\\n        vm.chainId(chainId2);\\n        DummyToken t2 = new DummyTokenMintAndBurn();\\n        NttManager implementationChain2 =\\n            new MockNttManagerContract(address(t2), INttManager.Mode.BURNING, chainId2, 1 days);\\n\\n        nttManagerChain2 =\\n            MockNttManagerContract(address(new ERC1967Proxy(address(implementationChain2), "")));\\n        nttManagerChain2.initialize();\\n\\n        WormholeTransceiver wormholeTransceiverChain2Implementation = new MockWormholeTransceiverContract(\\n            address(nttManagerChain2),\\n            address(wormhole),\\n            address(relayer),\\n            address(0x0),\\n            FAST_CONSISTENCY_LEVEL,\\n            GAS_LIMIT\\n        );\\n\\n        wormholeTransceiverChain2 = MockWormholeTransceiverContract(\\n            address(new ERC1967Proxy(address(wormholeTransceiverChain2Implementation), ""))\\n        );\\n        wormholeTransceiverChain2.initialize();\\n\\n        nttManagerChain2.setTransceiver(address(wormholeTransceiverChain2));\\n        nttManagerChain2.setOutboundLimit(type(uint64).max);\\n        nttManagerChain2.setInboundLimit(type(uint64).max, chainId1);\\n\\n        // Register peer contracts for the nttManager and transceiver. Transceivers and nttManager each have the concept of peers here.\\n        nttManagerChain1.setPeer(chainId2, bytes32(uint256(uint160(address(nttManagerChain2)))), 9);\\n        nttManagerChain2.setPeer(chainId1, bytes32(uint256(uint160(address(nttManagerChain1)))), 7);\\n\\n        // Set peers for the transceivers\\n        wormholeTransceiverChain1.setWormholePeer(\\n            chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2))))\\n        );\\n\\n       wormholeTransceiver2Chain1.setWormholePeer(\\n            chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2))))\\n        );\\n\\n       wormholeTransceiver3Chain1.setWormholePeer(\\n            chainId2, bytes32(uint256(uint160(address(wormholeTransceiverChain2))))\\n        );\\n\\n\\n        wormholeTransceiverChain2.setWormholePeer(\\n            chainId1, bytes32(uint256(uint160(address(wormholeTransceiverChain1))))\\n        );\\n\\n\\n        require(nttManagerChain1.getThreshold() != 0, "Threshold is zero with active transceivers");\\n\\n        // Actually set it\\n        nttManagerChain1.setThreshold(2);\\n        nttManagerChain2.setThreshold(1);\\n    }\\n\\n    function testTransceiverModification() external {\\n        vm.chainId(chainId1);\\n\\n        // Setting up the transfer\\n        DummyToken token1 = DummyToken(nttManagerChain1.token());\\n        uint8 decimals = token1.decimals();\\n\\n        token1.mintDummy(address(userA), 5 * 10 ** decimals);\\n        uint256 outboundLimit = 4 * 10 ** decimals;\\n        nttManagerChain1.setOutboundLimit(outboundLimit);\\n\\n        vm.startPrank(userA);\\n\\n        uint256 transferAmount = 5 * 10 ** decimals;\\n        token1.approve(address(nttManagerChain1), transferAmount);\\n\\n        // transfer with shouldQueue == true\\n        uint64 qSeq = nttManagerChain1.transfer(\\n            transferAmount, chainId2, toWormholeFormat(userB), true, encodeTransceiverInstructions(true)\\n        );\\n        vm.stopPrank();\\n\\n        assertEq(qSeq, 0);\\n        IRateLimiter.OutboundQueuedTransfer memory qt = nttManagerChain1.getOutboundQueuedTransfer(0);\\n        assertEq(qt.amount.getAmount(), transferAmount.trim(decimals, decimals).getAmount());\\n        assertEq(qt.recipientChain, chainId2);\\n        assertEq(qt.recipient, toWormholeFormat(userB));\\n        assertEq(qt.txTimestamp, initialBlockTimestamp);\\n\\n        // assert that the contract also locked funds from the user\\n        assertEq(token1.balanceOf(address(userA)), 0);\\n        assertEq(token1.balanceOf(address(nttManagerChain1)), transferAmount);\\n\\n\\n        // elapse some random time - 60 seconds\\n        uint256 durationElapsedTime = initialBlockTimestamp + 60;\\n\\n        // now add a third transceiver\\n        nttManagerChain1.setTransceiver(address(wormholeTransceiver3Chain1));\\n\\n        // verify that the third transceiver is added\\n        assertEq(nttManagerChain1.getTransceivers().length, 3);\\n\\n        // remove second transceiver\\n        nttManagerChain1.removeTransceiver(address(wormholeTransceiver2Chain1));\\n\\n          // verify that the second transceiver is removed\\n        assertEq(nttManagerChain1.getTransceivers().length, 2);\\n\\n         // elapse rate limit duration\\n         durationElapsedTime = initialBlockTimestamp + nttManagerChain1.rateLimitDuration();\\n\\n        vm.warp(durationElapsedTime);\\n\\n        vm.expectRevert(stdError.indexOOBError); //index out of bounds - transceiver instructions array does not have a third element to access\\n        nttManagerChain1.completeOutboundQueuedTransfer(0);\\n    }\\n\\n    // Encode an instruction for each of the relayers\\n  function encodeTransceiverInstructions(bool relayer_off) public view returns (bytes memory) {\\n        WormholeTransceiver.WormholeTransceiverInstruction memory instruction =\\n            IWormholeTransceiver.WormholeTransceiverInstruction(relayer_off);\\n\\n        bytes memory encodedInstructionWormhole =\\n            wormholeTransceiverChain1.encodeWormholeTransceiverInstruction(instruction);\\n\\n        TransceiverStructs.TransceiverInstruction memory TransceiverInstruction1 =\\n        TransceiverStructs.TransceiverInstruction({index: 0, payload: encodedInstructionWormhole});\\n        TransceiverStructs.TransceiverInstruction memory TransceiverInstruction2 =\\n        TransceiverStructs.TransceiverInstruction({index: 1, payload: encodedInstructionWormhole});\\n\\n        TransceiverStructs.TransceiverInstruction[] memory TransceiverInstructions =\\n            new TransceiverStructs.TransceiverInstruction[](2);\\n\\n        TransceiverInstructions[0] = TransceiverInstruction1;\\n        TransceiverInstructions[1] = TransceiverInstruction2;\\n\\n        return TransceiverStructs.encodeTransceiverInstructions(TransceiverInstructions);\\n    }\\n}\\n```\\n

Recommended mitigation:
Consider passing no instructions into the delivery price estimation when the Transceiver index does not exist.