abstract contract StakingMsgProcessor {
    bytes4 internal constant STAKE_ACTION = bytes4(keccak256("stake"));
    bytes4 internal constant UNSTAKE_ACTION = bytes4(keccak256("unstake"));

    function _encodeStakeActionType(bytes4 stakeType)
        internal
        pure
        returns (bytes4)
    {
        return bytes4(keccak256(abi.encodePacked(STAKE_ACTION, stakeType)));
    }

    function _encodeUnstakeActionType(bytes4 stakeType)
        internal
        pure
        returns (bytes4)
    {
        return bytes4(keccak256(abi.encodePacked(UNSTAKE_ACTION, stakeType)));
    }

    function _packStakingActionMsg(
        address staker,
        IStakingTypes.Stake memory stake,
        bytes calldata data
    ) internal pure returns (bytes memory) {
        return
            abi.encodePacked(
                staker, // address
                stake.amount, // uint96
                stake.id, // uint32
                stake.stakedAt, // uint32
                stake.lockedTill, // uint32
                stake.claimedAt, // uint32
                data // bytes
            );
    }

    // For efficiency we use "packed" (rather than "ABI") encoding.
    // It results in shorter data, but requires custom unpack function.
    function _unpackStakingActionMsg(bytes memory message)
        internal
        pure
        returns (
            address staker,
            uint96 amount,
            uint32 id,
            uint32 stakedAt,
            uint32 lockedTill,
            uint32 claimedAt,
            bytes memory data
        )
    {
        // staker, amount, id and 3 timestamps occupy exactly 48 bytes
        // (`data` may be of zero length)
        require(message.length >= 48, "SMP: unexpected msg length");

        uint256 stakerAndAmount;
        uint256 idAndStamps;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            // the 1st word (32 bytes) contains the `message.length`
            // we need the (entire) 2nd word ..
            stakerAndAmount := mload(add(message, 0x20))
            // .. and (16 bytes of) the 3rd word
            idAndStamps := mload(add(message, 0x40))
        }

        staker = address(uint160(stakerAndAmount >> 96));
        amount = uint96(stakerAndAmount & 0xFFFFFFFFFFFFFFFFFFFFFFFF);

        id = uint32((idAndStamps >> 224) & 0xFFFFFFFF);
        stakedAt = uint32((idAndStamps >> 192) & 0xFFFFFFFF);
        lockedTill = uint32((idAndStamps >> 160) & 0xFFFFFFFF);
        claimedAt = uint32((idAndStamps >> 128) & 0xFFFFFFFF);

        uint256 dataLength = message.length - 48;
        data = new bytes(dataLength);
        for (uint256 i = 0; i < dataLength; i++) {
            data[i] = message[i + 48];
        }
    }
}
function _unpackStakingActionMsg(bytes memory message)
    internal
    pure
    returns (
        address staker,
        uint96 amount,
        uint32 id,
        uint32 stakedAt,
        uint32 lockedTill,
        uint32 claimedAt,
        bytes memory data
    )
{
    // staker, amount, id and 3 timestamps occupy exactly 48 bytes
    // (`data` may be of zero length)
    require(message.length >= 48, "SMP: unexpected msg length");

    uint256 stakerAndAmount;
    uint256 idAndStamps;
    // solhint-disable-next-line no-inline-assembly
    assembly {
        // the 1st word (32 bytes) contains the `message.length`
        // we need the (entire) 2nd word ..
        stakerAndAmount := mload(add(message, 0x20))
        // .. and (16 bytes of) the 3rd word
        idAndStamps := mload(add(message, 0x40))
    }

    staker = address(uint160(stakerAndAmount >> 96));
    amount = uint96(stakerAndAmount & 0xFFFFFFFFFFFFFFFFFFFFFFFF);

    id = uint32((idAndStamps >> 224) & 0xFFFFFFFF);
    stakedAt = uint32((idAndStamps >> 192) & 0xFFFFFFFF);
    lockedTill = uint32((idAndStamps >> 160) & 0xFFFFFFFF);
    claimedAt = uint32((idAndStamps >> 128) & 0xFFFFFFFF);

    uint256 dataLength = message.length - 48;
    data = new bytes(dataLength);
    for (uint256 i = 0; i < dataLength; i++) {
        data[i] = message[i + 48];
    }
}

// For efficiency we use "packed" (rather than "ABI") encoding.
// It results in shorter data, but requires custom unpack function.