// File: src/L1MessagesSender.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;

import {IStarknetCore} from "./interfaces/IStarknetCore.sol";
import {Uint256Splitter} from "./lib/Uint256Splitter.sol";

contract L1MessagesSender {
    using Uint256Splitter for uint256;

    IStarknetCore public immutable starknetCore;

    uint256 public immutable l2RecipientAddr;

    /// @dev L2 "receive_commitment" L1 handler selector
    uint256 constant RECEIVE_COMMITMENT_L1_HANDLER_SELECTOR = 0x3fa70707d0e831418fb142ca8fb7483611b84e89c0c42bf1fc2a7a5c40890ad;

    /// @param starknetCore_ a StarknetCore address to send and consume messages on/from L2
    /// @param l2RecipientAddr_ a L2 recipient address that is the recipient contract on L2.
    constructor(IStarknetCore starknetCore_, uint256 l2RecipientAddr_) {
        starknetCore = starknetCore_;
        l2RecipientAddr = l2RecipientAddr_;
    }

    /// @notice Send an exact L1 parent hash to L2
    /// @param blockNumber_ the child block of the requested parent hash
    function sendExactParentHashToL2(uint256 blockNumber_) external payable {
        bytes32 parentHash = blockhash(blockNumber_ - 1);
        require(parentHash != bytes32(0), "ERR_INVALID_BLOCK_NUMBER");

        _sendBlockHashToL2(parentHash, blockNumber_);
    }

    /// @notice Send the L1 latest parent hash to L2
    function sendLatestParentHashToL2() external payable {
        bytes32 parentHash = blockhash(block.number - 1);
        _sendBlockHashToL2(parentHash, block.number);
    }

    function _sendBlockHashToL2(
        bytes32 parentHash_,
        uint256 blockNumber_
    ) internal {
        uint256[] memory message = new uint256[](4);
        (uint256 parentHashLow, uint256 parentHashHigh) = uint256(parentHash_).split128();
        (uint256 blockNumberLow, uint256 blockNumberHigh) = blockNumber_.split128();
        message[0] = parentHashLow;
        message[1] = parentHashHigh;
        message[2] = blockNumberLow;
        message[3] = blockNumberHigh;

        starknetCore.sendMessageToL2{value: msg.value}(
            l2RecipientAddr,
            RECEIVE_COMMITMENT_L1_HANDLER_SELECTOR,
            message
        );
    }
}

// File: src/interfaces/IStarknetCore.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.21;

interface IStarknetCore {
    /**
      Sends a message to an L2 contract.
      This function is payable, the payed amount is the message fee.

      Returns the hash of the message and the nonce of the message.
    */
    function sendMessageToL2(
        uint256 toAddress,
        uint256 selector,
        uint256[] calldata payload
    ) external payable returns (bytes32, uint256);

    /**
      Consumes a message that was sent from an L2 contract.

      Returns the hash of the message.
    */
    function consumeMessageFromL2(
        uint256 fromAddress,
        uint256[] calldata payload
    ) external returns (bytes32);

    /**
      Starts the cancellation of an L1 to L2 message.
      A message can be canceled messageCancellationDelay() seconds after this function is called.

      Note: This function may only be called for a message that is currently pending and the caller
      must be the sender of the that message.
    */
    function startL1ToL2MessageCancellation(
        uint256 toAddress,
        uint256 selector,
        uint256[] calldata payload,
        uint256 nonce
    ) external returns (bytes32);

    /**
      Cancels an L1 to L2 message, this function should be called at least
      messageCancellationDelay() seconds after the call to startL1ToL2MessageCancellation().
      A message may only be cancelled by its sender.
      If the message is missing, the call will revert.

      Note that the message fee is not refunded.
    */
    function cancelL1ToL2Message(
        uint256 toAddress,
        uint256 selector,
        uint256[] calldata payload,
        uint256 nonce
    ) external returns (bytes32);
}

// File: src/lib/Uint256Splitter.sol



// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

library Uint256Splitter {
    uint256 constant _MASK = type(uint128).max;

    /// @notice Splits a uint256 into two uint128s (low, high) represented as uint256s.
    /// @param a The uint256 to split.
    function split128(
        uint256 a
    ) internal pure returns (uint256 lower, uint256 upper) {
        return (a & _MASK, a >> 128);
    }

    /// @notice Merges two uint128s (low, high) into one uint256.
    /// @param lower The lower uint256. The caller is required to pass a value that is less than 2^128 - 1.
    /// @param upper The upper uint256.
    function merge128(
        uint256 lower,
        uint256 upper
    ) internal pure returns (uint256 a) {
        require(lower <= _MASK, "Uint256Splitter: lower exceeds uint128");
        // return (upper << 128) | lower;
        assembly {
            a := or(shl(128, upper), lower)
        }
    }
}