contract TBTCSystem is Ownable, ITBTCSystem, DepositLog {

    using SafeMath for uint256;
    using KeepFactorySelection for KeepFactorySelection.Storage;

    event EthBtcPriceFeedAdditionStarted(address _priceFeed, uint256 _timestamp);
    event LotSizesUpdateStarted(uint64[] _lotSizes, uint256 _timestamp);
    event SignerFeeDivisorUpdateStarted(uint16 _signerFeeDivisor, uint256 _timestamp);
    event CollateralizationThresholdsUpdateStarted(
        uint16 _initialCollateralizedPercent,
        uint16 _undercollateralizedThresholdPercent,
        uint16 _severelyUndercollateralizedThresholdPercent,
        uint256 _timestamp
    );
    event KeepFactoriesUpdateStarted(
        address _keepStakedFactory,
        address _fullyBackedFactory,
        address _factorySelector,
        uint256 _timestamp
    );

    event EthBtcPriceFeedAdded(address _priceFeed);
    event LotSizesUpdated(uint64[] _lotSizes);
    event AllowNewDepositsUpdated(bool _allowNewDeposits);
    event SignerFeeDivisorUpdated(uint16 _signerFeeDivisor);
    event CollateralizationThresholdsUpdated(
        uint16 _initialCollateralizedPercent,
        uint16 _undercollateralizedThresholdPercent,
        uint16 _severelyUndercollateralizedThresholdPercent
    );
    event KeepFactoriesUpdated(
        address _keepStakedFactory,
        address _fullyBackedFactory,
        address _factorySelector
    );

    uint256 initializedTimestamp = 0;
    uint256 pausedTimestamp;
    uint256 constant pausedDuration = 10 days;

    ISatWeiPriceFeed public priceFeed;
    IRelay public relay;

    KeepFactorySelection.Storage keepFactorySelection;

    uint16 public keepSize;
    uint16 public keepThreshold;

    // Parameters governed by the TBTCSystem owner
    bool private allowNewDeposits = false;
    uint16 private signerFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005
    uint16 private initialCollateralizedPercent = 150; // percent
    uint16 private undercollateralizedThresholdPercent = 125;  // percent
    uint16 private severelyUndercollateralizedThresholdPercent = 110; // percent
    uint64[] lotSizesSatoshis = [10**6, 10**7, 2 * 10**7, 5 * 10**7, 10**8]; // [0.01, 0.1, 0.2, 0.5, 1.0] BTC

    uint256 constant governanceTimeDelay = 48 hours;
    uint256 constant keepFactoriesUpgradeabilityPeriod = 180 days;

    uint256 private signerFeeDivisorChangeInitiated;
    uint256 private lotSizesChangeInitiated;
    uint256 private collateralizationThresholdsChangeInitiated;
    uint256 private keepFactoriesUpdateInitiated;

    uint16 private newSignerFeeDivisor;
    uint64[] newLotSizesSatoshis;
    uint16 private newInitialCollateralizedPercent;
    uint16 private newUndercollateralizedThresholdPercent;
    uint16 private newSeverelyUndercollateralizedThresholdPercent;
    address private newKeepStakedFactory;
    address private newFullyBackedFactory;
    address private newFactorySelector;

    // price feed
    uint256 constant priceFeedGovernanceTimeDelay = 90 days;
    uint256 ethBtcPriceFeedAdditionInitiated;
    IMedianizer nextEthBtcPriceFeed;

    constructor(address _priceFeed, address _relay) public {
        priceFeed = ISatWeiPriceFeed(_priceFeed);
        relay = IRelay(_relay);
    }

    /// @notice        Initialize contracts
    /// @dev           Only the Deposit factory should call this, and only once.
    /// @param _defaultKeepFactory       ECDSA keep factory backed by KEEP stake.
    /// @param _depositFactory    Deposit Factory. More info in `DepositFactory`.
    /// @param _masterDepositAddress  Master Deposit address. More info in `Deposit`.
    /// @param _tbtcToken         TBTCToken. More info in `TBTCToken`.
    /// @param _tbtcDepositToken  TBTCDepositToken (TDT). More info in `TBTCDepositToken`.
    /// @param _feeRebateToken    FeeRebateToken (FRT). More info in `FeeRebateToken`.
    /// @param _keepThreshold     Signing group honesty threshold.
    /// @param _keepSize          Signing group size.
    function initialize(
        IBondedECDSAKeepFactory _defaultKeepFactory,
        DepositFactory _depositFactory,
        address payable _masterDepositAddress,
        TBTCToken _tbtcToken,
        TBTCDepositToken _tbtcDepositToken,
        FeeRebateToken _feeRebateToken,
        VendingMachine _vendingMachine,
        uint16 _keepThreshold,
        uint16 _keepSize
    ) external onlyOwner {
        require(initializedTimestamp == 0, "already initialized");

        keepFactorySelection.initialize(_defaultKeepFactory);
        keepThreshold = _keepThreshold;
        keepSize = _keepSize;
        initializedTimestamp = block.timestamp;
        allowNewDeposits = true;

        setTbtcDepositToken(_tbtcDepositToken);

        _vendingMachine.setExternalAddresses(
            _tbtcToken,
            _tbtcDepositToken,
            _feeRebateToken
        );
        _depositFactory.setExternalDependencies(
            _masterDepositAddress,
            this,
            _tbtcToken,
            _tbtcDepositToken,
            _feeRebateToken,
            address(_vendingMachine)
        );
    }

    /// @notice Returns whether new deposits should be allowed.
    /// @return True if new deposits should be allowed by the emergency pause button
    function getAllowNewDeposits() external view returns (bool) {
        return allowNewDeposits;
    }

    /// @notice Return the lowest lot size currently enabled for deposits.
    /// @return The lowest lot size, in satoshis.
    function getMinimumLotSize() public view returns (uint256) {
        return lotSizesSatoshis[0];
    }

    /// @notice Return the largest lot size currently enabled for deposits.
    /// @return The largest lot size, in satoshis.
    function getMaximumLotSize() external view returns (uint256) {
        return lotSizesSatoshis[lotSizesSatoshis.length - 1];
    }

    /// @notice One-time-use emergency function to disallow future deposit creation for 10 days.
    function emergencyPauseNewDeposits() external onlyOwner {
        require(pausedTimestamp == 0, "emergencyPauseNewDeposits can only be called once");
        uint256 sinceInit = block.timestamp - initializedTimestamp;
        require(sinceInit < 180 days, "emergencyPauseNewDeposits can only be called within 180 days of initialization");
        pausedTimestamp = block.timestamp;
        allowNewDeposits = false;
        emit AllowNewDepositsUpdated(false);
    }

    /// @notice Anyone can reactivate deposit creations after the pause duration is over.
    function resumeNewDeposits() external {
        require(! allowNewDeposits, "New deposits are currently allowed");
        require(pausedTimestamp != 0, "Deposit has not been paused");
        require(block.timestamp.sub(pausedTimestamp) >= pausedDuration, "Deposits are still paused");
        allowNewDeposits = true;
        emit AllowNewDepositsUpdated(true);
    }

    function getRemainingPauseTerm() external view returns (uint256) {
        require(! allowNewDeposits, "New deposits are currently allowed");
        return (block.timestamp.sub(pausedTimestamp) >= pausedDuration)?
            0:
            pausedDuration.sub(block.timestamp.sub(pausedTimestamp));
    }

    /// @notice Set the system signer fee divisor.
    /// @dev    This can be finalized by calling `finalizeSignerFeeDivisorUpdate`
    ///         Anytime after `governanceTimeDelay` has elapsed.
    /// @param _signerFeeDivisor The signer fee divisor.
    function beginSignerFeeDivisorUpdate(uint16 _signerFeeDivisor)
        external onlyOwner
    {
        require(
            _signerFeeDivisor > 9,
            "Signer fee divisor must be greater than 9, for a signer fee that is <= 10%"
        );
        require(
            _signerFeeDivisor < 5000,
            "Signer fee divisor must be less than 5000, for a signer fee that is > 0.02%"
        );

        newSignerFeeDivisor = _signerFeeDivisor;
        signerFeeDivisorChangeInitiated = block.timestamp;
        emit SignerFeeDivisorUpdateStarted(_signerFeeDivisor, block.timestamp);
    }

    /// @notice Set the allowed deposit lot sizes.
    /// @dev    Lot size array should always contain 10**8 satoshis (1 BTC) and
    ///         cannot contain values less than 50000 satoshis (0.0005 BTC) or
    ///         greater than 10**10 satoshis (100 BTC). Lot size array must not
    ///         have duplicates and it must be sorted.
    ///         This can be finalized by calling `finalizeLotSizesUpdate`
    ///         anytime after `governanceTimeDelay` has elapsed.
    /// @param _lotSizes Array of allowed lot sizes.
    function beginLotSizesUpdate(uint64[] calldata _lotSizes)
        external onlyOwner
    {
        bool hasSingleBitcoin = false;
        for (uint i = 0; i < _lotSizes.length; i++) {
            if (_lotSizes[i] == 10**8) {
                hasSingleBitcoin = true;
            } else if (_lotSizes[i] < 50 * 10**3) {
                // Failed the minimum requirement, break on out.
                revert("Lot sizes less than 0.0005 BTC are not allowed");
            } else if (_lotSizes[i] > 10 * 10**9) {
                // Failed the maximum requirement, break on out.
                revert("Lot sizes greater than 100 BTC are not allowed");
            } else if (i > 0 && _lotSizes[i] == _lotSizes[i-1]) {
                revert("Lot size array must not have duplicates");
            } else if (i > 0 && _lotSizes[i] < _lotSizes[i-1]) {
                revert("Lot size array must be sorted");
            }
        }

        require(hasSingleBitcoin, "Lot size array must always contain 1 BTC");

        emit LotSizesUpdateStarted(_lotSizes, block.timestamp);
        newLotSizesSatoshis = _lotSizes;
        lotSizesChangeInitiated = block.timestamp;
    }

    /// @notice Set the system collateralization levels
    /// @dev    This can be finalized by calling `finalizeCollateralizationThresholdsUpdate`
    ///         Anytime after `governanceTimeDelay` has elapsed.
    /// @param _initialCollateralizedPercent default signing bond percent for new deposits
    /// @param _undercollateralizedThresholdPercent first undercollateralization trigger
    /// @param _severelyUndercollateralizedThresholdPercent second undercollateralization trigger
    function beginCollateralizationThresholdsUpdate(
        uint16 _initialCollateralizedPercent,
        uint16 _undercollateralizedThresholdPercent,
        uint16 _severelyUndercollateralizedThresholdPercent
    ) external onlyOwner {
        require(
            _initialCollateralizedPercent <= 300,
            "Initial collateralized percent must be <= 300%"
        );
        require(
            _initialCollateralizedPercent > 100,
            "Initial collateralized percent must be >= 100%"
        );
        require(
            _initialCollateralizedPercent > _undercollateralizedThresholdPercent,
            "Undercollateralized threshold must be < initial collateralized percent"
        );
        require(
            _undercollateralizedThresholdPercent > _severelyUndercollateralizedThresholdPercent,
            "Severe undercollateralized threshold must be < undercollateralized threshold"
        );

        newInitialCollateralizedPercent = _initialCollateralizedPercent;
        newUndercollateralizedThresholdPercent = _undercollateralizedThresholdPercent;
        newSeverelyUndercollateralizedThresholdPercent = _severelyUndercollateralizedThresholdPercent;
        collateralizationThresholdsChangeInitiated = block.timestamp;
        emit CollateralizationThresholdsUpdateStarted(
            _initialCollateralizedPercent,
            _undercollateralizedThresholdPercent,
            _severelyUndercollateralizedThresholdPercent,
            block.timestamp
        );
    }

    /// @notice Sets the addresses of the KEEP-staked ECDSA keep factory,
    ///         ETH-only-backed ECDSA keep factory and the selection strategy
    ///         that will choose between the two factories for new deposits.
    ///         When the ETH-only-backed factory and strategy are not set TBTCSystem
    ///         will use KEEP-staked factory. When both factories and strategy
    ///         are set, TBTCSystem load balances between two factories based on
    ///         the selection strategy.
    /// @dev It can be finalized by calling `finalizeKeepFactoriesUpdate`
    ///      any time after `governanceTimeDelay` has elapsed. This can be
    ///      called more than once until finalized to reset the values and
    ///      timer. An update can only be initialized before
    ///      `keepFactoriesUpgradeabilityPeriod` elapses after system initialization;
    ///      after that, no further updates can be initialized, though any pending
    ///      update can be finalized. All calls must set all three properties to
    ///      their desired value; leaving a value as 0, even if it was previously
    ///      set, will update that value to be 0. ETH-bond-only factory or the
    ///      strategy are allowed to be set as zero addresses.
    /// @param _keepStakedFactory Address of the KEEP staked based factory.
    /// @param _fullyBackedFactory Address of the ETH-bond-only-based factory.
    /// @param _factorySelector Address of the keep factory selection strategy.
    function beginKeepFactoriesUpdate(
        address _keepStakedFactory,
        address _fullyBackedFactory,
        address _factorySelector
    )
        external onlyOwner
    {
        uint256 sinceInit = block.timestamp - initializedTimestamp;
        require(
            sinceInit < keepFactoriesUpgradeabilityPeriod,
            "beginKeepFactoriesUpdate can only be called within 180 days of initialization"
        );

        // It is required that KEEP staked factory address is configured as this is
        // a default choice factory. Fully backed factory and factory selector
        // are optional for the system to work, hence they don't have to be provided.
        require(
            _keepStakedFactory != address(0),
            "KEEP staked factory must be a nonzero address"
        );

        newKeepStakedFactory = _keepStakedFactory;
        newFullyBackedFactory = _fullyBackedFactory;
        newFactorySelector = _factorySelector;
        keepFactoriesUpdateInitiated = block.timestamp;

        emit KeepFactoriesUpdateStarted(
            _keepStakedFactory,
            _fullyBackedFactory,
            _factorySelector,
            block.timestamp
        );
    }

    /// @notice Add a new ETH/BTC price feed contract to the priecFeed.
    /// @dev This can be finalized by calling `finalizeEthBtcPriceFeedAddition`
    ///      anytime after `priceFeedGovernanceTimeDelay` has elapsed.
    function beginEthBtcPriceFeedAddition(IMedianizer _ethBtcPriceFeed) external onlyOwner {
        bool ethBtcActive;
        (, ethBtcActive) = _ethBtcPriceFeed.peek();
        require(ethBtcActive, "Cannot add inactive feed");

        nextEthBtcPriceFeed = _ethBtcPriceFeed;
        ethBtcPriceFeedAdditionInitiated = block.timestamp;
        emit EthBtcPriceFeedAdditionStarted(address(_ethBtcPriceFeed), block.timestamp);
    }

    modifier onlyAfterGovernanceDelay(
        uint256 _changeInitializedTimestamp,
        uint256 _delay
    ) {
        require(_changeInitializedTimestamp > 0, "Change not initiated");
        require(
            block.timestamp.sub(_changeInitializedTimestamp) >= _delay,
            "Governance delay has not elapsed"
        );
        _;
    }

    /// @notice Finish setting the system signer fee divisor.
    /// @dev `beginSignerFeeDivisorUpdate` must be called first, once `governanceTimeDelay`
    ///       has passed, this function can be called to set the signer fee divisor to the
    ///       value set in `beginSignerFeeDivisorUpdate`
    function finalizeSignerFeeDivisorUpdate()
        external
        onlyOwner
        onlyAfterGovernanceDelay(signerFeeDivisorChangeInitiated, governanceTimeDelay)
    {
        signerFeeDivisor = newSignerFeeDivisor;
        emit SignerFeeDivisorUpdated(newSignerFeeDivisor);
        newSignerFeeDivisor = 0;
        signerFeeDivisorChangeInitiated = 0;
    }
    /// @notice Finish setting the accepted system lot sizes.
    /// @dev `beginLotSizesUpdate` must be called first, once `governanceTimeDelay`
    ///       has passed, this function can be called to set the lot sizes to the
    ///       value set in `beginLotSizesUpdate`
    function finalizeLotSizesUpdate()
        external
        onlyOwner
        onlyAfterGovernanceDelay(lotSizesChangeInitiated, governanceTimeDelay) {

        lotSizesSatoshis = newLotSizesSatoshis;
        emit LotSizesUpdated(newLotSizesSatoshis);
        lotSizesChangeInitiated = 0;
        newLotSizesSatoshis.length = 0;

        refreshMinimumBondableValue();
    }

    /// @notice Finish setting the system collateralization levels
    /// @dev `beginCollateralizationThresholdsUpdate` must be called first, once `governanceTimeDelay`
    ///       has passed, this function can be called to set the collateralization thresholds to the
    ///       value set in `beginCollateralizationThresholdsUpdate`
    function finalizeCollateralizationThresholdsUpdate()
        external
        onlyOwner
        onlyAfterGovernanceDelay(
            collateralizationThresholdsChangeInitiated,
            governanceTimeDelay
        ) {

        initialCollateralizedPercent = newInitialCollateralizedPercent;
        undercollateralizedThresholdPercent = newUndercollateralizedThresholdPercent;
        severelyUndercollateralizedThresholdPercent = newSeverelyUndercollateralizedThresholdPercent;

        emit CollateralizationThresholdsUpdated(
            newInitialCollateralizedPercent,
            newUndercollateralizedThresholdPercent,
            newSeverelyUndercollateralizedThresholdPercent
        );

        newInitialCollateralizedPercent = 0;
        newUndercollateralizedThresholdPercent = 0;
        newSeverelyUndercollateralizedThresholdPercent = 0;
        collateralizationThresholdsChangeInitiated = 0;
    }

    /// @notice Finish setting addresses of the KEEP-staked ECDSA keep factory,
    ///         ETH-only-backed ECDSA keep factory, and the selection strategy
    ///         that will choose between the two factories for new deposits.
    /// @dev `beginKeepFactoriesUpdate` must be called first; once
    ///      `governanceTimeDelay` has passed, this function can be called to
    ///      set factories addresses to the values set in `beginKeepFactoriesUpdate`.
    function finalizeKeepFactoriesUpdate()
        external
        onlyOwner
        onlyAfterGovernanceDelay(
            keepFactoriesUpdateInitiated,
            governanceTimeDelay
        ) {

        keepFactorySelection.setFactories(
            newKeepStakedFactory,
            newFullyBackedFactory,
            newFactorySelector
        );

        emit KeepFactoriesUpdated(
            newKeepStakedFactory,
            newFullyBackedFactory,
            newFactorySelector
        );

        keepFactoriesUpdateInitiated = 0;
        newKeepStakedFactory = address(0);
        newFullyBackedFactory = address(0);
        newFactorySelector = address(0);
    }

    /// @notice Finish adding a new price feed contract to the priceFeed.
    /// @dev `beginEthBtcPriceFeedAddition` must be called first; once
    ///      `ethBtcPriceFeedAdditionInitiated` has passed, this function can be
    ///      called to append a new price feed.
    function finalizeEthBtcPriceFeedAddition()
            external
            onlyOwner
            onlyAfterGovernanceDelay(
                ethBtcPriceFeedAdditionInitiated,
                priceFeedGovernanceTimeDelay
            ) {
        // This process interacts with external contracts, so
        // Checks-Effects-Interactions it.
        IMedianizer _nextEthBtcPriceFeed = nextEthBtcPriceFeed;
        nextEthBtcPriceFeed = IMedianizer(0);
        ethBtcPriceFeedAdditionInitiated = 0;

        emit EthBtcPriceFeedAdded(address(_nextEthBtcPriceFeed));

        priceFeed.addEthBtcFeed(_nextEthBtcPriceFeed);
    }

    /// @notice Gets the system signer fee divisor.
    /// @return The signer fee divisor.
    function getSignerFeeDivisor() external view returns (uint16) { return signerFeeDivisor; }

    /// @notice Gets the allowed lot sizes
    /// @return Uint64 array of allowed lot sizes
    function getAllowedLotSizes() external view returns (uint64[] memory){
        return lotSizesSatoshis;
    }

    /// @notice Get the system undercollateralization level for new deposits
    function getUndercollateralizedThresholdPercent() external view returns (uint16) {
        return undercollateralizedThresholdPercent;
    }

    /// @notice Get the system severe undercollateralization level for new deposits
    function getSeverelyUndercollateralizedThresholdPercent() external view returns (uint16) {
        return severelyUndercollateralizedThresholdPercent;
    }

    /// @notice Get the system initial collateralized level for new deposits.
    function getInitialCollateralizedPercent() external view returns (uint16) {
        return initialCollateralizedPercent;
    }

    /// @notice Get the price of one satoshi in wei.
    /// @dev Reverts if the price of one satoshi is 0 wei, or if the price of
    ///      one satoshi is 1 ether. Can only be called by a deposit with minted
    ///      TDT.
    /// @return The price of one satoshi in wei.
    function fetchBitcoinPrice() external view returns (uint256) {
        require(
            tbtcDepositToken.exists(uint256(msg.sender)),
            "Caller must be a Deposit contract"
        );

        return _fetchBitcoinPrice();
    }

    // Difficulty Oracle
    function fetchRelayCurrentDifficulty() external view returns (uint256) {
        return relay.getCurrentEpochDifficulty();
    }

    function fetchRelayPreviousDifficulty() external view returns (uint256) {
        return relay.getPrevEpochDifficulty();
    }

    /// @notice Get the time remaining until the signer fee divisor can be updated.
    function getRemainingSignerFeeDivisorUpdateTime() external view returns (uint256) {
        return getRemainingChangeTime(
            signerFeeDivisorChangeInitiated,
            governanceTimeDelay
        );
    }

    /// @notice Get the time remaining until the lot sizes can be updated.
    function getRemainingLotSizesUpdateTime() external view returns (uint256) {
        return getRemainingChangeTime(
            lotSizesChangeInitiated,
            governanceTimeDelay
        );
    }

    /// @notice Get the time remaining until the collateralization thresholds can be updated.
    function getRemainingCollateralizationThresholdsUpdateTime() external view returns (uint256) {
        return getRemainingChangeTime(
            collateralizationThresholdsChangeInitiated,
            governanceTimeDelay
        );
    }

    /// @notice Get the time remaining until the Keep ETH-only-backed ECDSA keep
    ///         factory and the selection strategy that will choose between it
    ///         and the KEEP-backed factory can be updated.
    function getRemainingKeepFactoriesUpdateTime() external view returns (uint256) {
        return getRemainingChangeTime(
            keepFactoriesUpdateInitiated,
            governanceTimeDelay
        );
    }

    /// @notice Get the time remaining until the signer fee divisor can be updated.
    function getRemainingEthBtcPriceFeedAdditionTime() external view returns (uint256) {
        return getRemainingChangeTime(
            ethBtcPriceFeedAdditionInitiated,
            priceFeedGovernanceTimeDelay
        );
    }

    /// @notice Get the time remaining until Keep factories can no longer be updated.
    function getRemainingKeepFactoriesUpgradeabilityTime() external view returns (uint256) {
        return getRemainingChangeTime(
            initializedTimestamp,
            keepFactoriesUpgradeabilityPeriod
        );
    }

    /// @notice Refreshes the minimum bondable value required from the operator
    /// to join the sortition pool for tBTC. The minimum bondable value is
    /// equal to the current minimum lot size collateralized 150% multiplied by
    /// the current BTC price.
    /// @dev It is recommended to call this function on tBTC initialization and
    /// after minimum lot size update.
    function refreshMinimumBondableValue() public {
        keepFactorySelection.setMinimumBondableValue(
            calculateBondRequirementWei(getMinimumLotSize()),
            keepSize,
            keepThreshold
        );
    }

    /// @notice Returns the time delay used for governance actions except for
    ///         price feed additions.
    function getGovernanceTimeDelay() external pure returns (uint256) {
        return governanceTimeDelay;
    }

    /// @notice Returns the time period when keep factories upgrades are allowed.
    function getKeepFactoriesUpgradeabilityPeriod() public pure returns (uint256) {
        return keepFactoriesUpgradeabilityPeriod;
    }

    /// @notice Returns the time delay used for price feed addition governance
    ///         actions.
    function getPriceFeedGovernanceTimeDelay() external pure returns (uint256) {
        return priceFeedGovernanceTimeDelay;
    }

    /// @notice Gets a fee estimate for creating a new Deposit.
    /// @return Uint256 estimate.
    function getNewDepositFeeEstimate()
        external
        view
        returns (uint256)
    {
        IBondedECDSAKeepFactory _keepFactory = keepFactorySelection.selectFactory();
        return _keepFactory.openKeepFeeEstimate();
    }

    /// @notice Request a new keep opening.
    /// @param _requestedLotSizeSatoshis Lot size in satoshis.
    /// @param _maxSecuredLifetime Duration of stake lock in seconds.
    /// @return Address of a new keep.
    function requestNewKeep(
        uint64 _requestedLotSizeSatoshis,
        uint256 _maxSecuredLifetime
    )
        external
        payable
        returns (address)
    {
        require(tbtcDepositToken.exists(uint256(msg.sender)), "Caller must be a Deposit contract");
        require(isAllowedLotSize(_requestedLotSizeSatoshis), "provided lot size not supported");

        IBondedECDSAKeepFactory _keepFactory = keepFactorySelection.selectFactoryAndRefresh();
        uint256 bond = calculateBondRequirementWei(_requestedLotSizeSatoshis);
        return _keepFactory.openKeep.value(msg.value)(keepSize, keepThreshold, msg.sender, bond, _maxSecuredLifetime);
    }

    /// @notice Check if a lot size is allowed.
    /// @param _requestedLotSizeSatoshis Lot size to check.
    /// @return True if lot size is allowed, false otherwise.
    function isAllowedLotSize(uint64 _requestedLotSizeSatoshis) public view returns (bool){
        for( uint i = 0; i < lotSizesSatoshis.length; i++){
            if (lotSizesSatoshis[i] == _requestedLotSizeSatoshis){
                return true;
            }
        }
        return false;
    }

    /// @notice Calculates bond requirement in wei for the given lot size in
    ///         satoshis based on the current ETHBTC price.
    /// @param _requestedLotSizeSatoshis Lot size in satoshis.
    /// @return Bond requirement in wei.
    function calculateBondRequirementWei(
        uint256 _requestedLotSizeSatoshis
    ) internal view returns (uint256) {
        uint256 lotSizeInWei = _fetchBitcoinPrice().mul(_requestedLotSizeSatoshis);
        return lotSizeInWei.mul(initialCollateralizedPercent).div(100);
    }

    function _fetchBitcoinPrice() internal view returns (uint256) {
        uint256 price = priceFeed.getPrice();
        if (price == 0 || price > 10 ** 18) {
            // This is if a sat is worth 0 wei, or is worth >1 ether. Revert at
            // once.
            revert("System returned a bad price");
        }
        return price;
    }

    /// @notice Get the time remaining until the function parameter timer value can be updated.
    function getRemainingChangeTime(
        uint256 _changeTimestamp,
        uint256 _delayAmount
    ) internal view returns (uint256){
        require(_changeTimestamp > 0, "Update not initiated");
        uint256 elapsed = block.timestamp.sub(_changeTimestamp);
        if (elapsed >= _delayAmount) {
            return 0;
        } else {
            return _delayAmount.sub(elapsed);
        }
    }
}
/// @title TBTC System.
/// @notice This contract acts as a central point for access control,
///         value governance, and price feed.
/// @dev    Governable values should only affect new deposit creation.
function getSignerFeeDivisor() external view returns (uint16) { return signerFeeDivisor; }

/// @notice Gets the system signer fee divisor.
/// @return The signer fee divisor.