contract StateCommitmentChain is IStateCommitmentChain, Lib_AddressResolver {
  /*************
   * Constants *
   *************/

  uint256 public FRAUD_PROOF_WINDOW;
  uint256 public SEQUENCER_PUBLISH_WINDOW;

  /***************
   * Constructor *
   ***************/

  /**
   * @param _libAddressManager Address of the Address Manager.
   */
  constructor(
    address _libAddressManager,
    uint256 _fraudProofWindow,
    uint256 _sequencerPublishWindow
  ) Lib_AddressResolver(_libAddressManager) {
    FRAUD_PROOF_WINDOW = _fraudProofWindow;
    SEQUENCER_PUBLISH_WINDOW = _sequencerPublishWindow;
  }

  /********************
   * Public Functions *
   ********************/

  /**
   * Accesses the batch storage container.
   * @return Reference to the batch storage container.
   */
  function batches() public view returns (IChainStorageContainer) {
    return IChainStorageContainer(resolve('ChainStorageContainer-SCC-batches'));
  }

  /**
   * @inheritdoc IStateCommitmentChain
   */
  function getTotalElements() public view returns (uint256 _totalElements) {
    (uint40 totalElements, ) = _getBatchExtraData();
    return uint256(totalElements);
  }

  /**
   * @inheritdoc IStateCommitmentChain
   */
  function getTotalBatches() public view returns (uint256 _totalBatches) {
    return batches().length();
  }

  /**
   * @inheritdoc IStateCommitmentChain
   */
  function getLastSequencerTimestamp()
    public
    view
    returns (uint256 _lastSequencerTimestamp)
  {
    (, uint40 lastSequencerTimestamp) = _getBatchExtraData();
    return uint256(lastSequencerTimestamp);
  }

  /**
   * @inheritdoc IStateCommitmentChain
   */
  function appendStateBatch(
    bytes32[] memory _batch,
    uint256 _shouldStartAtElement
  ) public {
    // Fail fast in to make sure our batch roots aren't accidentally made fraudulent by the
    // publication of batches by some other user.
    require(
      _shouldStartAtElement == getTotalElements(),
      'Actual batch start index does not match expected start index.'
    );

    // Proposers must have previously staked at the BondManager
    require(
      IBondManager(resolve('BondManager')).isCollateralized(msg.sender),
      'Proposer does not have enough collateral posted'
    );

    require(_batch.length > 0, 'Cannot submit an empty state batch.');

    require(
      getTotalElements() + _batch.length <=
        ICanonicalTransactionChain(resolve('CanonicalTransactionChain'))
          .getTotalElements(),
      'Number of state roots cannot exceed the number of canonical transactions.'
    );

    // Pass the block's timestamp and the publisher of the data
    // to be used in the fraud proofs
    _appendBatch(_batch, abi.encode(block.timestamp, msg.sender));
  }

  /**
   * @inheritdoc IStateCommitmentChain
   */
  function deleteStateBatch(Lib_OVMCodec.ChainBatchHeader memory _batchHeader)
    public
  {
    require(
      msg.sender == resolve('OVM_FraudVerifier'),
      'State batches can only be deleted by the OVM_FraudVerifier.'
    );

    require(_isValidBatchHeader(_batchHeader), 'Invalid batch header.');

    require(
      insideFraudProofWindow(_batchHeader),
      'State batches can only be deleted within the fraud proof window.'
    );

    _deleteBatch(_batchHeader);
  }

  /**
   * @inheritdoc IStateCommitmentChain
   */
  function verifyStateCommitment(
    bytes32 _element,
    Lib_OVMCodec.ChainBatchHeader memory _batchHeader,
    Lib_OVMCodec.ChainInclusionProof memory _proof
  ) public view returns (bool) {
    require(_isValidBatchHeader(_batchHeader), 'Invalid batch header.');

    require(
      Lib_MerkleTree.verify(
        _batchHeader.batchRoot,
        _element,
        _proof.index,
        _proof.siblings,
        _batchHeader.batchSize
      ),
      'Invalid inclusion proof.'
    );

    return true;
  }

  /**
   * @inheritdoc IStateCommitmentChain
   */
  function insideFraudProofWindow(
    Lib_OVMCodec.ChainBatchHeader memory _batchHeader
  ) public view returns (bool _inside) {
    (uint256 timestamp, ) = abi.decode(
      _batchHeader.extraData,
      (uint256, address)
    );

    require(timestamp != 0, 'Batch header timestamp cannot be zero');
    return (timestamp + FRAUD_PROOF_WINDOW) > block.timestamp;
  }

  /**********************
   * Internal Functions *
   **********************/

  /**
   * Parses the batch context from the extra data.
   * @return Total number of elements submitted.
   * @return Timestamp of the last batch submitted by the sequencer.
   */
  function _getBatchExtraData() internal view returns (uint40, uint40) {
    bytes27 extraData = batches().getGlobalMetadata();

    // solhint-disable max-line-length
    uint40 totalElements;
    uint40 lastSequencerTimestamp;
    assembly {
      extraData := shr(40, extraData)
      totalElements := and(
        extraData,
        0x000000000000000000000000000000000000000000000000000000FFFFFFFFFF
      )
      lastSequencerTimestamp := shr(
        40,
        and(
          extraData,
          0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000
        )
      )
    }
    // solhint-enable max-line-length

    return (totalElements, lastSequencerTimestamp);
  }

  /**
   * Encodes the batch context for the extra data.
   * @param _totalElements Total number of elements submitted.
   * @param _lastSequencerTimestamp Timestamp of the last batch submitted by the sequencer.
   * @return Encoded batch context.
   */
  function _makeBatchExtraData(
    uint40 _totalElements,
    uint40 _lastSequencerTimestamp
  ) internal pure returns (bytes27) {
    bytes27 extraData;
    assembly {
      extraData := _totalElements
      extraData := or(extraData, shl(40, _lastSequencerTimestamp))
      extraData := shl(40, extraData)
    }

    return extraData;
  }

  /**
   * Appends a batch to the chain.
   * @param _batch Elements within the batch.
   * @param _extraData Any extra data to append to the batch.
   */
  function _appendBatch(bytes32[] memory _batch, bytes memory _extraData)
    internal
  {
    address sequencer = resolve('OVM_Proposer');
    (
      uint40 totalElements,
      uint40 lastSequencerTimestamp
    ) = _getBatchExtraData();

    if (msg.sender == sequencer) {
      lastSequencerTimestamp = uint40(block.timestamp);
    } else {
      // We keep track of the last batch submitted by the sequencer so there's a window in
      // which only the sequencer can publish state roots. A window like this just reduces
      // the chance of "system breaking" state roots being published while we're still in
      // testing mode. This window should be removed or significantly reduced in the future.
      require(
        lastSequencerTimestamp + SEQUENCER_PUBLISH_WINDOW < block.timestamp,
        'Cannot publish state roots within the sequencer publication window.'
      );
    }

    // For efficiency reasons getMerkleRoot modifies the `_batch` argument in place
    // while calculating the root hash therefore any arguments passed to it must not
    // be used again afterwards
    Lib_OVMCodec.ChainBatchHeader memory batchHeader = Lib_OVMCodec
      .ChainBatchHeader({
        batchIndex: getTotalBatches(),
        batchRoot: Lib_MerkleTree.getMerkleRoot(_batch),
        batchSize: _batch.length,
        prevTotalElements: totalElements,
        extraData: _extraData
      });

    emit StateBatchAppended(
      batchHeader.batchIndex,
      batchHeader.batchRoot,
      batchHeader.batchSize,
      batchHeader.prevTotalElements,
      batchHeader.extraData
    );

    batches().push(
      Lib_OVMCodec.hashBatchHeader(batchHeader),
      _makeBatchExtraData(
        uint40(batchHeader.prevTotalElements + batchHeader.batchSize),
        lastSequencerTimestamp
      )
    );
  }

  /**
   * Removes a batch and all subsequent batches from the chain.
   * @param _batchHeader Header of the batch to remove.
   */
  function _deleteBatch(Lib_OVMCodec.ChainBatchHeader memory _batchHeader)
    internal
  {
    require(
      _batchHeader.batchIndex < batches().length(),
      'Invalid batch index.'
    );

    require(_isValidBatchHeader(_batchHeader), 'Invalid batch header.');

    batches().deleteElementsAfterInclusive(
      _batchHeader.batchIndex,
      _makeBatchExtraData(uint40(_batchHeader.prevTotalElements), 0)
    );

    emit StateBatchDeleted(_batchHeader.batchIndex, _batchHeader.batchRoot);
  }

  /**
   * Checks that a batch header matches the stored hash for the given index.
   * @param _batchHeader Batch header to validate.
   * @return Whether or not the header matches the stored one.
   */
  function _isValidBatchHeader(
    Lib_OVMCodec.ChainBatchHeader memory _batchHeader
  ) internal view returns (bool) {
    return
      Lib_OVMCodec.hashBatchHeader(_batchHeader) ==
      batches().get(_batchHeader.batchIndex);
  }
}
/**
 * @title StateCommitmentChain
 * @dev The State Commitment Chain (SCC) contract contains a list of proposed state roots which
 * Proposers assert to be a result of each transaction in the Canonical Transaction Chain (CTC).
 * Elements here have a 1:1 correspondence with transactions in the CTC, and should be the unique
 * state root calculated off-chain by applying the canonical transactions one by one.
 *
 * Runtime target: EVM
 */
function batches() public view returns (IChainStorageContainer) {
  return IChainStorageContainer(resolve('ChainStorageContainer-SCC-batches'));
}

/**
 * Accesses the batch storage container.
 * @return Reference to the batch storage container.
 */