// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: contracts/interface/IORManager.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {BridgeLib} from "../library/BridgeLib.sol";
import {IORSpvData} from "../interface/IORSpvData.sol";

interface IORManager {
    event ChainInfoUpdated(uint64 indexed id, BridgeLib.ChainInfo chainInfo);
    event ChainTokenUpdated(uint64 indexed id, BridgeLib.TokenInfo tokenInfo);
    event EbcsUpdated(address[] ebcs, bool[] statuses);
    event SubmitterFeeUpdated(address submitter);
    event ProtocolFeeUpdated(uint64 protocolFee);
    event MinChallengeRatioUpdated(uint64 minChallengeRatio);
    event ChallengeUserRatioUpdated(uint64 challengeUserRatio);
    event FeeChallengeSecondUpdated(uint64 feeChallengeSecond);
    event FeeTakeOnChallengeSecondUpdated(uint64 feeTakeOnChallengeSecond);
    event MaxMDCLimitUpdated(uint64 maxMDCLimit);
    event SpvDataContractUpdated(address spvDataContract);
    event ExtraTransferContractsUpdated(uint64[] chainIds, uint256[] extraTransferContracts);

    function registerChains(uint64 enableTime, BridgeLib.ChainInfo[] calldata chains_) external;

    function getPriorityFee() external view returns (uint64);

    function getChallengeGasUsed() external view returns (uint32);

    function getChallengeWithdrawDelay() external view returns (uint32);

    function updatePriorityFee(uint64 priorityFee) external;

    function updateChallengeBasefee(uint32 challengeBasefee) external;

    function updateChallengeWithdrawDelay(uint32 challengeWithdrawDelay) external;

    function updateChainSpvs(uint64 enableTime, uint64 id, address[] calldata spvs, uint256[] calldata indexs) external;

    function getChainInfo(uint64 id) external view returns (BridgeLib.ChainInfo memory);

    function updateChainTokens(
        uint64 enableTime,
        uint64[] calldata ids,
        BridgeLib.TokenInfo[] calldata tokenInfos
    ) external;

    function getChainTokenInfo(uint64 id, uint256 token) external view returns (BridgeLib.TokenInfo memory);

    function ebcIncludes(address ebc) external view returns (bool);

    function updateEbcs(address[] calldata ebcs_, bool[] calldata statuses) external;

    function submitter() external view returns (address);

    function updateSubmitter(uint64 enableTime, address submitter_) external;

    function protocolFee() external view returns (uint64);

    function updateProtocolFee(uint64 enableTime, uint64 protocolFee_) external;

    function minChallengeRatio() external view returns (uint64);

    function updateMinChallengeRatio(uint64 enableTime, uint64 minChallengeRatio_) external;

    function challengeUserRatio() external view returns (uint64);

    function updateChallengeUserRatio(uint64 enableTime, uint64 challengeUserRatio_) external;

    function feeChallengeSecond() external view returns (uint64);

    function updateFeeChallengeSecond(uint64 enableTime, uint64 feeChallengeSecond_) external;

    function feeTakeOnChallengeSecond() external view returns (uint64);

    function updateFeeTakeOnChallengeSecond(uint64 enableTime, uint64 feeTakeOnChallengeSecond_) external;

    function maxMDCLimit() external view returns (uint64);

    function updateMaxMDCLimit(uint64 maxMDCLimit_) external;

    function spvDataContract() external view returns (address);

    function updateSpvDataContract(address spvDataContract_) external;

    function updateSpvBlockInterval(uint64 spvBlockInterval_) external;

    function updateSpvDataInjectOwner(address injectOwner_) external;

    function getExtraTransferContract(uint64 chainId) external view returns (uint256);

    function updateExtraTransferContracts(
        uint64 enableTime,
        uint64[] calldata chainIds,
        uint256[] calldata extraTransferContracts
    ) external;

    function getRulesDecoder() external view returns (address);
}

// File: contracts/interface/IORSpvData.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IORSpvData {
    struct InjectionBlocksRoot {
        uint256 startBlockNumber;
        bytes32 blocksRoot;
    }

    event BlockIntervalUpdated(uint64 blockInterval);
    event InjectOwnerUpdated(address injectOwner);
    event HistoryBlocksRootSaved(uint256 indexed startBlockNumber, bytes32 blocksRoot, uint256 blockInterval);

    function blockInterval() external view returns (uint64);

    function updateBlockInterval(uint64 blockInterval_) external;

    function saveHistoryBlocksRoots() external;

    function getStartBlockNumber(bytes32 blocksRoot) external view returns (uint);

    function injectOwner() external view returns (address);

    function updateInjectOwner(address injectOwner_) external;

    function injectBlocksRoots(
        bytes32 blocksRoot0,
        bytes32 blocksRoot1,
        InjectionBlocksRoot[] calldata injectionBlocksRoots
    ) external;
}

// File: contracts/library/BridgeLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

library BridgeLib {
    struct TokenInfo {
        uint256 token; // uint160(address) will overflow in the token used for starknet
        address mainnetToken;
        uint8 decimals;
    }

    struct ChainInfo {
        uint64 id;
        uint192 batchLimit;
        uint64 minVerifyChallengeSourceTxSecond;
        uint64 maxVerifyChallengeSourceTxSecond;
        uint64 minVerifyChallengeDestTxSecond;
        uint64 maxVerifyChallengeDestTxSecond;
        uint256 nativeToken;
        address[] spvs;
    }
}

// File: contracts/library/ConstantsLib.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library ConstantsLib {
    uint256 constant RATIO_MULTIPLE = 1000000;

    uint256 constant MIN_ENABLE_DELAY = 120; // Unit: second

    uint256 constant MAX_ENABLE_DELAY = 1800; // Unit: second

    uint256 constant DEALER_WITHDRAW_DELAY = 3600; // Unit: second

    uint256 constant WITHDRAW_DURATION = 3360; // Unit: second

    uint256 constant LOCK_DURATION = 240; // Unit: second

    uint constant EBC_AMOUNT_PARAMS_MODULUS = 100000;

    uint constant MIN_CHALLENGE_DEPOSIT_AMOUNT = 0.005 ether;
}

// File: contracts/library/HelperLib.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library HelperLib {
    function hash(bytes memory data) internal pure returns (bytes32) {
        return keccak256(data);
    }

    function includes(uint256[] memory arr, uint256 element) internal pure returns (bool) {
        for (uint256 i = 0; i < arr.length; ) {
            if (element == arr[i]) {
                return true;
            }
            unchecked {
                i++;
            }
        }
        return false;
    }

    function arrayIncludes(uint256[] memory arr, uint256[] memory elements) internal pure returns (bool) {
        for (uint256 i = 0; i < elements.length; i++) {
            bool ic = false;
            for (uint256 j = 0; j < arr.length; ) {
                if (elements[i] == arr[j]) {
                    ic = true;
                    break;
                }
                unchecked {
                    j++;
                }
            }

            if (!ic) return false;

            unchecked {
                i++;
            }
        }
        return true;
    }

    function includes(address[] memory arr, address element) internal pure returns (bool) {
        for (uint256 i = 0; i < arr.length; ) {
            if (element == arr[i]) {
                return true;
            }
            unchecked {
                i++;
            }
        }
        return false;
    }

    function arrayIncludes(address[] memory arr, address[] memory elements) internal pure returns (bool) {
        for (uint256 i = 0; i < elements.length; i++) {
            bool ic = false;
            for (uint256 j = 0; j < arr.length; ) {
                if (elements[i] == arr[j]) {
                    ic = true;
                    break;
                }
                unchecked {
                    j++;
                }
            }

            if (!ic) return false;

            unchecked {
                i++;
            }
        }
        return true;
    }

    function calculateChallengeIdentNum(
        uint64 sourceTxTime,
        uint64 sourceChainId,
        uint64 sourceTxBlockNum,
        uint64 sourceTxIndex
    ) internal pure returns (uint256) {
        uint256 challengeIdentNum;

        assembly {
            challengeIdentNum := add(
                shl(192, sourceTxTime),
                add(shl(128, sourceChainId), add(shl(64, sourceTxBlockNum), sourceTxIndex))
            )
        }
        return challengeIdentNum;
    }
}

// File: contracts/ORManager.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import "./interface/IORManager.sol";
import {VersionAndEnableTime} from "./VersionAndEnableTime.sol";
import {HelperLib} from "./library/HelperLib.sol";

contract ORManager is IORManager, Ownable, VersionAndEnableTime {
    using HelperLib for bytes;

    // Ownable._owner use a slot
    // VersionAndEnableTime._version and _enableTime use a slot

    // Warning: the following order and type changes will cause state verification changes
    mapping(uint64 => BridgeLib.ChainInfo) private _chains;
    mapping(bytes32 => BridgeLib.TokenInfo) private _chainTokens; // hash(chainId, token) => TokenInfo
    mapping(address => bool) private _ebcs;

    address private _submitter;
    uint64 private _protocolFee;

    uint64 private _minChallengeRatio = 20000;
    /// @notice Divisor for challenge fee ratio, 1,000,000 means 100%
    uint64 private _challengeUserRatio = 300000;
    uint64 private _feeChallengeSecond;
    uint64 private _feeTakeOnChallengeSecond;

    uint64 private _maxMDCLimit = 2 ** 64 - 1;
    /// @notice uint: wei
    uint64 private _priorityFee = 1 gwei;
    uint32 private _challengeBasefee = 94000; // createchallenge&verify sourceTx calldata 52000 + submit tx 21000 + verify source 21000
    uint32 private _challengeWithdrawDelay = 604800; // Unit: seconds of 7 days: Op Arb
    address private _spvDataContract;

    mapping(uint64 => uint256) private _extraTransferContracts; // Cross-address transfer contracts. chainId => contractAddress

    address private _decoderAddress;

    constructor(address owner_) {
        require(owner_ != address(0), "OZ");
        _transferOwnership(owner_);
    }

    // TODO: setting the same chainId or token affect the protocol?
    function registerChains(uint64 enableTime, BridgeLib.ChainInfo[] calldata chains_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        unchecked {
            for (uint256 i = 0; i < chains_.length; i++) {
                // TODO: There may be some settings that need to restrict modification

                // TODO: ORMakerDeposit.checkChallenge use maxVerifyChallengeSourceTxSecond, maxVerifyChallengeSourceTxSecond cannot be modified.
                //       Or make some adjustments when using

                _chains[chains_[i].id] = chains_[i];
                emit ChainInfoUpdated(chains_[i].id, chains_[i]);
            }
        }
    }

    function getPriorityFee() external view returns (uint64) {
        return _priorityFee;
    }

    function getChallengeGasUsed() external view returns (uint32) {
        return _challengeBasefee;
    }

    function getChallengeWithdrawDelay() external view returns (uint32) {
        return _challengeWithdrawDelay;
    }

    function updatePriorityFee(uint64 priorityFee) external onlyOwner {
        _priorityFee = priorityFee;
    }

    function updateChallengeBasefee(uint32 challengeBasefee) external onlyOwner {
        _challengeBasefee = challengeBasefee;
    }

    function updateChallengeWithdrawDelay(uint32 challengeWithdrawDelay) external onlyOwner {
        _challengeWithdrawDelay = challengeWithdrawDelay;
    }

    function updateChainSpvs(
        uint64 enableTime,
        uint64 id,
        address[] calldata spvs,
        uint256[] calldata indexs
    ) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        unchecked {
            for (uint256 i = 0; i < spvs.length; i++) {
                if (i < indexs.length) {
                    _chains[id].spvs[indexs[i]] = spvs[i];
                } else {
                    _chains[id].spvs.push(spvs[i]);
                }
            }
        }
        emit ChainInfoUpdated(id, _chains[id]);
    }

    function getChainInfo(uint64 id) external view returns (BridgeLib.ChainInfo memory) {
        return _chains[id];
    }

    function updateChainTokens(
        uint64 enableTime,
        uint64[] calldata ids,
        BridgeLib.TokenInfo[] calldata tokenInfos
    ) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        unchecked {
            for (uint256 i = 0; i < ids.length; i++) {
                // TODO: If the token of layer2 changes, how should it be handled here?
                bytes32 key = abi.encode(ids[i], tokenInfos[i].token).hash();
                _chainTokens[key] = tokenInfos[i];
                emit ChainTokenUpdated(ids[i], tokenInfos[i]);
            }
        }
    }

    function getChainTokenInfo(uint64 id, uint256 token) external view returns (BridgeLib.TokenInfo memory) {
        bytes32 key = abi.encode(id, token).hash();
        return _chainTokens[key];
    }

    function ebcIncludes(address ebc) external view returns (bool) {
        return _ebcs[ebc];
    }

    function updateEbcs(address[] calldata ebcs_, bool[] calldata statuses) external onlyOwner {
        unchecked {
            for (uint256 i = 0; i < ebcs_.length; i++) {
                if (i < statuses.length) {
                    _ebcs[ebcs_[i]] = statuses[i];
                } else {
                    _ebcs[ebcs_[i]] = true;
                }
            }
        }
        emit EbcsUpdated(ebcs_, statuses);
    }

    function submitter() external view returns (address) {
        return _submitter;
    }

    function updateSubmitter(uint64 enableTime, address submitter_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _submitter = submitter_;
        emit SubmitterFeeUpdated(_submitter);
    }

    function protocolFee() external view returns (uint64) {
        return _protocolFee;
    }

    function updateProtocolFee(uint64 enableTime, uint64 protocolFee_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _protocolFee = protocolFee_;
        emit ProtocolFeeUpdated(_protocolFee);
    }

    function minChallengeRatio() external view returns (uint64) {
        return _minChallengeRatio;
    }

    function updateMinChallengeRatio(uint64 enableTime, uint64 minChallengeRatio_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _minChallengeRatio = minChallengeRatio_;
        emit MinChallengeRatioUpdated(_minChallengeRatio);
    }

    function challengeUserRatio() external view returns (uint64) {
        return _challengeUserRatio;
    }

    function updateChallengeUserRatio(uint64 enableTime, uint64 challengeUserRatio_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _challengeUserRatio = challengeUserRatio_;
        emit ChallengeUserRatioUpdated(_challengeUserRatio);
    }

    function feeChallengeSecond() external view returns (uint64) {
        return _feeChallengeSecond;
    }

    function updateFeeChallengeSecond(uint64 enableTime, uint64 feeChallengeSecond_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _feeChallengeSecond = feeChallengeSecond_;
        emit FeeChallengeSecondUpdated(_feeChallengeSecond);
    }

    function feeTakeOnChallengeSecond() external view returns (uint64) {
        return _feeTakeOnChallengeSecond;
    }

    function updateFeeTakeOnChallengeSecond(uint64 enableTime, uint64 feeTakeOnChallengeSecond_) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        _feeTakeOnChallengeSecond = feeTakeOnChallengeSecond_;
        emit FeeTakeOnChallengeSecondUpdated(_feeTakeOnChallengeSecond);
    }

    function maxMDCLimit() external view returns (uint64) {
        return _maxMDCLimit;
    }

    function updateMaxMDCLimit(uint64 maxMDCLimit_) external onlyOwner {
        _maxMDCLimit = maxMDCLimit_;
        emit MaxMDCLimitUpdated(_maxMDCLimit);
    }

    function spvDataContract() external view returns (address) {
        return _spvDataContract;
    }

    function updateSpvDataContract(address spvDataContract_) external onlyOwner {
        _spvDataContract = spvDataContract_;
        emit SpvDataContractUpdated(spvDataContract_);
    }

    function updateSpvBlockInterval(uint64 spvBlockInterval_) external onlyOwner {
        IORSpvData(_spvDataContract).updateBlockInterval(spvBlockInterval_);
    }

    function updateSpvDataInjectOwner(address injectOwner_) external onlyOwner {
        IORSpvData(_spvDataContract).updateInjectOwner(injectOwner_);
    }

    function getExtraTransferContract(uint64 chainId) external view returns (uint256) {
        return _extraTransferContracts[chainId];
    }

    function updateExtraTransferContracts(
        uint64 enableTime,
        uint64[] calldata chainIds,
        uint256[] calldata extraTransferContracts
    ) external onlyOwner {
        versionIncreaseAndEnableTime(enableTime);

        require(chainIds.length == extraTransferContracts.length, "CEOF");

        for (uint256 i = 0; i < chainIds.length; i++) {
            _extraTransferContracts[chainIds[i]] = extraTransferContracts[i];
        }
        emit ExtraTransferContractsUpdated(chainIds, extraTransferContracts);
    }

    function updateDecoderAddress(address decoderAddress) external onlyOwner {
        _decoderAddress = decoderAddress;
    }

    function getRulesDecoder() external view returns (address) {
        return _decoderAddress;
    }
}

// File: contracts/VersionAndEnableTime.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {ConstantsLib} from "./library/ConstantsLib.sol";

contract VersionAndEnableTime {
    // uint192 private _version;
    uint128 private _version = 1;
    uint64 private _blockNumber = 1;
    uint64 private _enableTime = 100;

    // TODO: modify requires more gas
    // modifier versionIncreaseAndEnableTime(uint64 enableTime) {
    //     require(enableTime - block.timestamp >= ConstantsLib.MIN_ENABLE_DELAY, "OFET");

    //     _;
    //     _version += 1;

    //     _enableTime = enableTime;
    // }

    function versionIncreaseAndEnableTime(uint64 enableTime) public {
        require(
            (enableTime >= ConstantsLib.MIN_ENABLE_DELAY + block.timestamp) &&
                (enableTime <= ConstantsLib.MAX_ENABLE_DELAY + block.timestamp) &&
                (enableTime > _enableTime),
            "OFET"
        );
        uint64 blockNumberCurrent = uint64(block.number);
        require(blockNumberCurrent > _blockNumber, "BNE");

        _version += 1;
        _blockNumber = blockNumberCurrent;
        _enableTime = enableTime;
    }

    function getVersionAndEnableTime() external view returns (uint128 version, uint64 blockNumber, uint64 enableTime) {
        return (_version, _blockNumber, _enableTime);
    }
}