Potentially dangerous out-of-bounds memory access in `BytesParsing::sliceUnchecked`
`BytesParsing::sliceUnchecked` currently bails early for the degenerate case when the slice `length` is zero; however, there is no validation on the `length` of the `encoded` bytes parameter `encoded` itself. If the `length` of `encoded` is less than the slice `length`, then it is possible to access memory out-of-bounds.\\n```\\nfunction sliceUnchecked(bytes memory encoded, uint256 offset, uint256 length)\\n    internal\\n    pure\\n    returns (bytes memory ret, uint256 nextOffset)\\n{\\n    //bail early for degenerate case\\n    if (length == 0) {\\n        return (new bytes(0), offset);\\n    }\\n\\n    assembly ("memory-safe") {\\n        nextOffset := add(offset, length)\\n        ret := mload(freeMemoryPtr)\\n\\n        /* snip: inline dev comments */\\n\\n        let shift := and(length, 31) //equivalent to `mod(length, 32)` but 2 gas cheaper\\n        if iszero(shift) { shift := wordSize }\\n\\n        let dest := add(ret, shift)\\n        let end := add(dest, length)\\n        for { let src := add(add(encoded, shift), offset) } lt(dest, end) {\\n            src := add(src, wordSize)\\n            dest := add(dest, wordSize)\\n        } { mstore(dest, mload(src)) }\\n\\n        mstore(ret, length)\\n        //When compiling with --via-ir then normally allocated memory (i.e. via new) will have 32 byte\\n        //  memory alignment and so we enforce the same memory alignment here.\\n        mstore(freeMemoryPtr, and(add(dest, 31), not(31)))\\n    }\\n}\\n```\\n\\nSince the `for` loop begins at the offset of `encoded` in memory, accounting `for` its `length` and accompanying `shift` calculation depending on the `length` supplied, and execution continues so long as `dest` is less than `end`, it is possible to continue loading additional words out of bounds simply by passing larger `length` values. Therefore, regardless of the `length` of the original bytes, the output slice will always have a size defined by the `length` parameter.\\nIt is understood that this is known behavior due to the unchecked nature of this function and the accompanying checked version, which performs validation on the `nextOffset` return value compared with the length of the encoded bytes.\\n```\\nfunction slice(bytes memory encoded, uint256 offset, uint256 length)\\n    internal\\n    pure\\n    returns (bytes memory ret, uint256 nextOffset)\\n{\\n    (ret, nextOffset) = sliceUnchecked(encoded, offset, length);\\n    checkBound(nextOffset, encoded.length);\\n}\\n```\\n\\nIt has not been possible within the constraints of this review to identify a valid scenario in which malicious calldata can make use of this behavior to launch a successful exploit; however, this is not a guarantee that the usage of this library function is bug-free since there do exist certain quirks related to the loading of calldata.
Consider bailing early if the length of the bytes from which to construct a slice is zero, and always ensure the resultant offset is correctly validated against the length when using the unchecked version of the function.
The impact is limited in the context of the library function's usage in the scope of this review; however, it is advisable to check any other usage elsewhere and in the future to ensure that this behavior cannot be weaponized. `BytesParsing::sliceUnchecked` is currently only used in `WormholeCctpMessages::_decodeBytes`, which itself is called in `WormholeCctpMessages::decodeDeposit`. This latter function is utilized in two places:
```\\nfunction sliceUnchecked(bytes memory encoded, uint256 offset, uint256 length)\\n    internal\\n    pure\\n    returns (bytes memory ret, uint256 nextOffset)\\n{\\n    //bail early for degenerate case\\n    if (length == 0) {\\n        return (new bytes(0), offset);\\n    }\\n\\n    assembly ("memory-safe") {\\n        nextOffset := add(offset, length)\\n        ret := mload(freeMemoryPtr)\\n\\n        /* snip: inline dev comments */\\n\\n        let shift := and(length, 31) //equivalent to `mod(length, 32)` but 2 gas cheaper\\n        if iszero(shift) { shift := wordSize }\\n\\n        let dest := add(ret, shift)\\n        let end := add(dest, length)\\n        for { let src := add(add(encoded, shift), offset) } lt(dest, end) {\\n            src := add(src, wordSize)\\n            dest := add(dest, wordSize)\\n        } { mstore(dest, mload(src)) }\\n\\n        mstore(ret, length)\\n        //When compiling with --via-ir then normally allocated memory (i.e. via new) will have 32 byte\\n        //  memory alignment and so we enforce the same memory alignment here.\\n        mstore(freeMemoryPtr, and(add(dest, 31), not(31)))\\n    }\\n}\\n```\\n