// File: contracts/adapters/HeaderReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { IHeaderReporter } from "../interfaces/IHeaderReporter.sol";
import { HeaderStorage } from "../utils/HeaderStorage.sol";

abstract contract HeaderReporter is IHeaderReporter {
    HeaderStorage public immutable HEADER_STORAGE;
    uint256 public immutable ADAPTER_CHAIN;

    event HeaderReported(address indexed emitter, uint256 indexed blockNumber, bytes32 indexed blockHeader);

    /// @dev Constructs base reporter abstracted from specific message transport
    /// @param headerStorage HeaderStorage contract on this chain to use for block hash obtaining
    /// @param adapterChain Chain ID of the adapter that is served by this reporter
    constructor(address headerStorage, uint256 adapterChain) {
        HEADER_STORAGE = HeaderStorage(headerStorage);
        ADAPTER_CHAIN = adapterChain;
    }

    function reportHeaders(uint256[] memory blockNumbers, address adapter) external payable {
        bytes32[] memory blockHeaders = HEADER_STORAGE.storeBlockHeaders(blockNumbers);
        bytes memory payload = abi.encode(blockNumbers, blockHeaders);
        _sendPayload(payload, adapter);
        for (uint i = 0; i < blockNumbers.length; i++) {
            emit HeaderReported(address(this), blockNumbers[i], blockHeaders[i]);
        }
    }

    function _sendPayload(bytes memory payload, address adapter) internal virtual;
}

// File: contracts/adapters/LayerZero/interfaces/ILayerZeroEndpoint.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

interface ILayerZeroEndpoint {
    // @notice send a LayerZero message to the specified address at a LayerZero endpoint.
    // @param _dstChainId - the destination chain identifier
    // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains
    // @param _payload - a custom bytes payload to send to the destination contract
    // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
    // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
    // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination
    function send(
        uint16 _dstChainId,
        bytes calldata _destination,
        bytes calldata _payload,
        address payable _refundAddress,
        address _zroPaymentAddress,
        bytes calldata _adapterParams
    ) external payable;
}

// File: contracts/adapters/LayerZero/LayerZeroHeaderReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { HeaderReporter } from "../HeaderReporter.sol";
import { LayerZeroReporter } from "./LayerZeroReporter.sol";

contract LayerZeroHeaderReporter is HeaderReporter, LayerZeroReporter {
    constructor(
        address headerStorage,
        uint256 adapterChain,
        address lzEndpoint,
        uint16 lzAdapterChain
    ) HeaderReporter(headerStorage, adapterChain) LayerZeroReporter(lzEndpoint, lzAdapterChain) {} // solhint-disable no-empty-blocks

    function _sendPayload(bytes memory payload, address adapter) internal override {
        _lzSend(payload, adapter);
    }
}

// File: contracts/adapters/LayerZero/LayerZeroReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { ILayerZeroEndpoint } from "./interfaces/ILayerZeroEndpoint.sol";

abstract contract LayerZeroReporter {
    string public constant PROVIDER = "layer-zero";
    ILayerZeroEndpoint public immutable LAYER_ZERO_ENDPOINT;
    uint16 public immutable LAYER_ZERO_ADAPTER_CHAIN;

    constructor(address lzEndpoint, uint16 lzAdapterChain) {
        LAYER_ZERO_ENDPOINT = ILayerZeroEndpoint(lzEndpoint);
        LAYER_ZERO_ADAPTER_CHAIN = lzAdapterChain;
    }

    function _lzSend(bytes memory payload, address adapter) internal {
        bytes memory path = abi.encodePacked(adapter, address(this));
        // solhint-disable-next-line check-send-result
        LAYER_ZERO_ENDPOINT.send{ value: msg.value }(
            LAYER_ZERO_ADAPTER_CHAIN, // _dstChainId: destination LayerZero chainId
            path, // _destination: send to this address on the destination
            payload, // _payload: bytes payload
            payable(msg.sender), // _refundAddress: refund address
            address(0), // _zroPaymentAddress: future parameter
            bytes("") // _adapterParams: adapterParams (see "Advanced Features")
        );
    }
}

// File: contracts/interfaces/IHeaderReporter.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

interface IHeaderReporter {
    /// @dev Reports the given block hash to a different chain according to the reporter configuration.
    /// @param blockNumbers Block numbers to report hashes for.
    /// @param adapter Adapter contract address to report hashes for.
    function reportHeaders(uint256[] memory blockNumbers, address adapter) external payable;
}

// File: contracts/utils/HeaderStorage.sol



// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

contract HeaderStorage {
    mapping(uint256 => bytes32) public headers;

    event HeaderStored(uint256 indexed blockNumber, bytes32 indexed blockHeader);

    error HeaderOutOfRange(address emitter, uint256 blockNumber);

    /// @dev Stores and returns the header for the given block.
    /// @param blockNumber Block number.
    /// @return blockHeader Block header stored.
    /// @notice Reverts if the given block header was not previously stored and is now out of range.
    function storeBlockHeader(uint256 blockNumber) public returns (bytes32 blockHeader) {
        blockHeader = headers[blockNumber];
        if (blockHeader == 0) {
            blockHeader = blockhash(blockNumber);
            if (blockHeader == 0) revert HeaderOutOfRange(address(this), blockNumber);
            headers[blockNumber] = blockHeader;
            emit HeaderStored(blockNumber, blockHeader);
        }
    }

    /// @dev Stores and returns the header for an array of given blocks.
    /// @param blockNumbers Array of block numbers.
    /// @return Array of block headers.
    /// @notice Reverts if the given block header was not previously stored and is now out of range.
    function storeBlockHeaders(uint256[] memory blockNumbers) public returns (bytes32[] memory) {
        bytes32[] memory blockHeaders = new bytes32[](blockNumbers.length);
        for (uint256 i = 0; i < blockNumbers.length; i++) {
            blockHeaders[i] = storeBlockHeader(blockNumbers[i]);
        }
        return blockHeaders;
    }
}