// File: src/v0.8/dev/BatchBlockhashStore.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

/**
 * @title BatchBlockhashStore
 * @notice The BatchBlockhashStore contract acts as a proxy to write many blockhashes to the
 *   provided BlockhashStore contract efficiently in a single transaction. This results
 *   in plenty of gas savings and higher throughput of blockhash storage, which is desirable
 *   in times of high network congestion.
 */
contract BatchBlockhashStore {
  BlockhashStore public immutable BHS;

  constructor(address blockhashStoreAddr) {
    BHS = BlockhashStore(blockhashStoreAddr);
  }

  /**
   * @notice stores blockhashes of the given block numbers in the configured blockhash store, assuming
   *   they are availble though the blockhash() instruction.
   * @param blockNumbers the block numbers to store the blockhashes of. Must be available via the
   *   blockhash() instruction, otherwise this function call will revert.
   */
  function store(uint256[] memory blockNumbers) public {
    for (uint256 i = 0; i < blockNumbers.length; i++) {
      // skip the block if it's not storeable, the caller will have to check
      // after the transaction is mined to see if the blockhash was truly stored.
      if (!storeableBlock(blockNumbers[i])) {
        continue;
      }
      BHS.store(blockNumbers[i]);
    }
  }

  /**
   * @notice stores blockhashes after verifying blockheader of child/subsequent block
   * @param blockNumbers the block numbers whose blockhashes should be stored, in decreasing order
   * @param headers the rlp-encoded block headers of blockNumbers[i] + 1.
   */
  function storeVerifyHeader(uint256[] memory blockNumbers, bytes[] memory headers) public {
    require(blockNumbers.length == headers.length, "input array arg lengths mismatch");
    for (uint256 i = 0; i < blockNumbers.length; i++) {
      BHS.storeVerifyHeader(blockNumbers[i], headers[i]);
    }
  }

  /**
   * @notice retrieves blockhashes of all the given block numbers from the blockhash store, if available.
   * @param blockNumbers array of block numbers to fetch blockhashes for
   * @return blockhashes array of block hashes corresponding to each block number provided in the `blockNumbers`
   *   param. If the blockhash is not found, 0x0 is returned instead of the real blockhash, indicating
   *   that it is not in the blockhash store.
   */
  function getBlockhashes(uint256[] memory blockNumbers) external view returns (bytes32[] memory) {
    bytes32[] memory blockHashes = new bytes32[](blockNumbers.length);
    for (uint256 i = 0; i < blockNumbers.length; i++) {
      try BHS.getBlockhash(blockNumbers[i]) returns (bytes32 bh) {
        blockHashes[i] = bh;
      } catch Error(
        string memory /* reason */
      ) {
        blockHashes[i] = 0x0;
      }
    }
    return blockHashes;
  }

  /**
   * @notice returns true if and only if the given block number's blockhash can be retrieved
   *   using the blockhash() instruction.
   * @param blockNumber the block number to check if it's storeable with blockhash()
   */
  function storeableBlock(uint256 blockNumber) private view returns (bool) {
    // handle edge case on simulated chains which possibly have < 256 blocks total.
    return block.number <= 256 ? true : blockNumber >= (block.number - 256);
  }
}

interface BlockhashStore {
  function storeVerifyHeader(uint256 n, bytes memory header) external;

  function store(uint256 n) external;

  function getBlockhash(uint256 n) external view returns (bytes32);
}