// File: @openzeppelin/contracts/proxy/Clones.sol



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

pragma solidity ^0.8.0;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 *
 * _Available since v3.4._
 */
library Clones {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(0, 0x09, 0x37)
        }
        require(instance != address(0), "ERC1167: create failed");
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(0, 0x09, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := keccak256(add(ptr, 0x43), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}

// File: contracts/interface/IORChallengeSpv.sol



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

interface IORChallengeSpv {
    struct VerifyInfoSlot {
        address account; // Contract address
        bytes32 key;
        uint value;
    }

    struct VerifyInfo {
        uint[] data;
        VerifyInfoSlot[] slots;
    }

    function verifyChallenge(
        bytes calldata proof,
        bytes32[2] calldata spvBlockHashs,
        bytes32 verifyInfoHash
    ) external view returns (bool);
}

// File: contracts/interface/IORMakerDeposit.sol



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

import {IORChallengeSpv} from "./IORChallengeSpv.sol";
import {BridgeLib} from "../library/BridgeLib.sol";
import {RuleLib} from "../library/RuleLib.sol";

interface IORMakerDeposit {
    struct ChallengeInfo {
        uint sourceTxFrom; // From of the source tx. Uint to support other networks
        uint64 sourceTxTime; // Timestamp of the source tx
        address challenger; // Challenger
        address freezeToken; // Freeze token on L1
        uint64 challengeUserRatio; // Manager's _challengeUserRatio
        uint freezeAmount0; // Owner's freeze amount
        uint freezeAmount1; // Challenger's freeze amount
        uint64 challengeTime; // Time of challenge
        uint64 abortTime; // Time of abort caused by checkChallenge
        uint64 verifiedTime0; // Time of verifyChallengeSource. Greater than 0 means verification passed
        uint64 verifiedTime1; // Time of verifyChallengeDest. Greater than 0 means verification passed
        bytes32 verifiedDataHash0; // Data's hash of verifyChallengeSource
    }

    event ColumnArrayUpdated(
        address indexed impl,
        bytes32 columnArrayHash,
        address[] dealers,
        address[] ebcs,
        uint64[] chainIds
    );
    event SpvUpdated(address indexed impl, uint64 chainId, address spv);
    event ResponseMakersUpdated(address indexed impl, uint[] responseMakers);
    event RulesRootUpdated(address indexed impl, address ebc, RuleLib.RootWithVersion rootWithVersion);
    event ChallengeInfoUpdated(bytes32 indexed challengeId, ChallengeInfo challengeInfo);

    function initialize(address owner_) external;

    function owner() external view returns (address);

    function mdcFactory() external view returns (address);

    function columnArrayHash() external view returns (bytes32);

    function updateColumnArray(
        uint64 enableTime,
        address[] calldata dealers,
        address[] calldata ebcs,
        uint64[] calldata chainIds
    ) external;

    function spv(uint64 chainId) external view returns (address);

    function updateSpvs(uint64 enableTime, address[] calldata spvs, uint64[] calldata chainIds) external;

    function responseMakersHash() external view returns (bytes32);

    function updateResponseMakers(uint64 enableTime, bytes[] calldata responseMakerSignatures) external;

    function freezeAssets(address token) external view returns (uint);

    function deposit(address token, uint amount) external payable;

    function withdraw(address token, uint amount) external;

    function rulesRoot(address ebc) external view returns (RuleLib.RootWithVersion memory);

    function updateRulesRoot(
        uint64 enableTime,
        address ebc,
        RuleLib.Rule[] calldata rules,
        RuleLib.RootWithVersion calldata rootWithVersion,
        uint64[] calldata sourceChainIds,
        uint[] calldata pledgeAmounts
    ) external payable;

    function updateRulesRootERC20(
        uint64 enableTime,
        address ebc,
        RuleLib.Rule[] calldata rules,
        RuleLib.RootWithVersion calldata rootWithVersion,
        uint64[] calldata sourceChainIds,
        uint[] calldata pledgeAmounts,
        address token
    ) external;

    // function challenge(
    //     uint64 sourceChainId,
    //     bytes32 sourceTxHash,
    //     uint64 sourceTxTime,
    //     address freezeToken,
    //     uint freezeAmount1
    // ) external payable;

    // function checkChallenge(uint64 sourceChainId, bytes32 sourceTxHash, uint[] calldata verifiedData0) external;

    // function verifyChallengeSource(
    //     address spvAddress,
    //     bytes calldata proof,
    //     bytes32[2] calldata spvBlockHashs,
    //     IORChallengeSpv.VerifyInfo calldata verifyInfo,
    //     bytes calldata rawDatas
    // ) external;

    // function verifyChallengeDest(
    //     address spvAddress,
    //     bytes calldata proof,
    //     bytes32[2] calldata spvBlockHashs,
    //     IORChallengeSpv.VerifyInfo calldata verifyInfo,
    //     uint[] calldata verifiedData0,
    //     bytes calldata rawDatas
    // ) external;
}

// File: contracts/interface/IORManager.sol



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

import {BridgeLib} from "../library/BridgeLib.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 ExtraTransferContractsUpdated(uint64[] chainIds, uint[] extraTransferContracts);

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

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

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

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

    function getChainTokenInfo(uint64 id, uint 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 getExtraTransferContract(uint64 chainId) external view returns (uint);

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

// File: contracts/interface/IORMDCFactory.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./IORManager.sol";

interface IORMDCFactory {
    event MDCCreated(address maker, address mdc);

    function manager() external view returns (address);

    function implementation() external view returns (address);

    function mdcCreatedTotal() external view returns (uint);

    function createMDC() external;

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

// File: contracts/library/BridgeLib.sol



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

library BridgeLib {
    struct TokenInfo {
        uint 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;
        uint nativeToken;
        address[] spvs;
    }
}

// File: contracts/library/ConstantsLib.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

library ConstantsLib {
    uint constant RATIO_MULTIPLE = 1000000;

    uint constant MIN_ENABLE_DELAY = 120; // Unit: second

    uint constant DEALER_WITHDRAW_DELAY = 3600; // Unit: second

    uint constant WITHDRAW_DURATION = 3360; // Unit: second

    uint constant LOCK_DURATION = 240; // Unit: second
    // uint constant DEALER_WITHDRAW_DELAY = 30; // Unit: second for test off-chain

    // uint constant WITHDRAW_DURATION = 300; // Unit: second for test off-chain

    // uint constant LOCK_DURATION = 180; // Unit: second for test off-chain

    uint constant EBC_AMOUNT_PARAMS_MODULUS = 10000;
}

// 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(uint[] memory arr, uint element) internal pure returns (bool) {
        for (uint i = 0; i < arr.length; ) {
            if (element == arr[i]) {
                return true;
            }
            unchecked {
                i++;
            }
        }
        return false;
    }

    function arrayIncludes(uint[] memory arr, uint[] memory elements) internal pure returns (bool) {
        for (uint i = 0; i < elements.length; i++) {
            bool ic = false;
            for (uint 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 (uint 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 (uint i = 0; i < elements.length; i++) {
            bool ic = false;
            for (uint j = 0; j < arr.length; ) {
                if (elements[i] == arr[j]) {
                    ic = true;
                    break;
                }
                unchecked {
                    j++;
                }
            }

            if (!ic) return false;

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

// File: contracts/library/RuleLib.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

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

library RuleLib {
    struct Rule {
        uint64 chainId0; // 59144
        uint64 chainId1; // 10
        uint8 status0;
        uint8 status1;
        uint token0;
        uint token1;
        uint128 minPrice0;
        uint128 minPrice1;
        uint128 maxPrice0;
        uint128 maxPrice1;
        uint128 withholdingFee0;
        uint128 withholdingFee1;
        uint32 tradingFee0;
        uint32 tradingFee1;
        uint32 responseTime0;
        uint32 responseTime1;
        uint32 compensationRatio0;
        uint32 compensationRatio1;
    }

    struct RootWithVersion {
        bytes32 root;
        uint32 version;
    }

    struct RuleOneway {
        uint64 sourceChainId;
        uint64 destChainId;
        uint8 status;
        uint sourceToken;
        uint destToken;
        uint128 minPrice;
        uint128 maxPrice;
        uint128 withholdingFee;
        uint32 tradingFee;
        uint32 responseTime;
        uint32 compensationRatio;
    }

    function checkChainIds(uint64 chainId0, uint64 chainId1) internal pure {
        require(chainId0 < chainId1, "C0LC1");
    }

    function checkWithholdingFees(uint128 withholdingFee0, uint128 withholdingFee1) internal pure {
        require(withholdingFee0 % ConstantsLib.EBC_AMOUNT_PARAMS_MODULUS == 0, "WFI0");
        require(withholdingFee1 % ConstantsLib.EBC_AMOUNT_PARAMS_MODULUS == 0, "WFI1");
    }

    function convertToOneway(
        Rule memory rule,
        uint64 sourceChainId
    ) internal pure returns (RuleOneway memory ruleOneway) {
        require(sourceChainId == rule.chainId0 || sourceChainId == rule.chainId1, "SCI");

        if (sourceChainId == rule.chainId0) {
            return
                RuleOneway(
                    rule.chainId0,
                    rule.chainId1,
                    rule.status0,
                    rule.token0,
                    rule.token1,
                    rule.minPrice0,
                    rule.maxPrice0,
                    rule.withholdingFee0,
                    rule.tradingFee0,
                    rule.responseTime0,
                    rule.compensationRatio0
                );
        } else {
            return
                RuleOneway(
                    rule.chainId1,
                    rule.chainId0,
                    rule.status1,
                    rule.token1,
                    rule.token0,
                    rule.minPrice1,
                    rule.maxPrice1,
                    rule.withholdingFee1,
                    rule.tradingFee1,
                    rule.responseTime1,
                    rule.compensationRatio1
                );
        }
    }
}

// File: contracts/ORMDCFactory.sol



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

import "@openzeppelin/contracts/proxy/Clones.sol";
import "./interface/IORMDCFactory.sol";
import "./interface/IORMakerDeposit.sol";
import "./interface/IORManager.sol";
import {HelperLib} from "./library/HelperLib.sol";

contract ORMDCFactory is IORMDCFactory {
    using HelperLib for bytes;

    IORManager private _manager;
    address private _implementation;
    uint256 private _mdcCreatedTotal;

    constructor(address manager_, address implementation_) {
        require(manager_ != address(0), "MZ");
        require(implementation_ != address(0), "IZ");

        _manager = IORManager(manager_);
        _implementation = implementation_;
    }

    function manager() external view returns (address) {
        return address(_manager);
    }

    function implementation() external view returns (address) {
        return _implementation;
    }

    function mdcCreatedTotal() external view returns (uint) {
        return _mdcCreatedTotal;
    }

    function createMDC() external {
        require(_mdcCreatedTotal < _manager.maxMDCLimit(), "MML");
        address mdcAddress = Clones.cloneDeterministic(
            _implementation,
            abi.encodePacked(address(this), msg.sender).hash()
        );

        unchecked {
            ++_mdcCreatedTotal;
        }

        IORMakerDeposit(mdcAddress).initialize(msg.sender);

        emit MDCCreated(msg.sender, mdcAddress);
    }

    function predictMDCAddress() external view returns (address) {
        address mdcAddress = Clones.predictDeterministicAddress(
            _implementation,
            abi.encodePacked(address(this), msg.sender).hash()
        );
        return mdcAddress;
    }
}