// File: contracts/connectors/Connector.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "../libraries/ExecutorLib.sol";
import "../shared/Initializable.sol";

abstract contract Connector is Initializable {
    using ExecutorLib for address;

    address public target;
    address public counterpart;

    /// @dev initialize to keep creation code consistent for create2 deployments
    function initialize(address _target, address _counterpart) public initializer {
        require(_target != address(0), "CNR: Target cannot be zero address");
        require(_counterpart != address(0), "CNR: Counterpart cannot be zero address");

        target = _target;
        counterpart = _counterpart;
    }

    fallback () external payable {
        if (msg.sender == target) {
            _forwardCrossDomainMessage();
        } else {
            _verifyCrossDomainSender();
            target.execute(msg.data, msg.value);
        }
    }

    receive () external payable {
        revert("Do not send ETH to this contract");
    }

    /* ========== Virtual functions ========== */

    function _forwardCrossDomainMessage() internal virtual;

    function _verifyCrossDomainSender() internal virtual;
}
// File: contracts/connectors/PolygonzkConnector.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "../interfaces/polygonzk/messengers/IPolygonZkEVMBridge.sol";
import "./Connector.sol";

contract PolygonzkConnector is Connector {

    uint32 public counterpartNetwork;
    address public messengerAddress;

    function initialize(
        address target,
        address counterpart,
        uint32 _counterpartNetwork,
        address _messengerAddress
    ) external {
        initialize(target, counterpart);

        counterpartNetwork = _counterpartNetwork;
        messengerAddress = _messengerAddress;
    }

    function onMessageReceived(
        address originAddress,
        uint32 originNetwork,
        bytes memory data
    ) external {
        require(originAddress == counterpart, "PLY_ZK_CNR: Origin address does not match counterpart address");
        require(originNetwork == counterpartNetwork, "PLY_ZK_CNR: Origin network does not match counterpart network");
        require(msg.sender == messengerAddress, "PLY_ZK_CNR: Caller is not the messenger");

        uint256 value = 0;
        target.execute(data, value);
    }

    /* ========== Internal functions ========== */

    function _forwardCrossDomainMessage() internal override {
        _forwardCrossDomainMessage(msg.data);
    }

    function _forwardCrossDomainMessage(bytes memory data) internal {
        require(msg.sender == target, "PLY_ZK_CNR: Caller is not the expected sender");

        bool forceUpdateGlobalExitRoot = false;
        IPolygonZkEVMBridge(messengerAddress).bridgeMessage(
            counterpartNetwork,
            counterpart,
            forceUpdateGlobalExitRoot,
            data
        );
    }

    function _verifyCrossDomainSender() internal override {
        revert("BASE_PLY_ZK_CNR: _verifyCrossDomainSender() is handled by onMessageReceived");
    }
}


// File: contracts/interfaces/IMessengerWrapper.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.12 <=0.8.9;
pragma experimental ABIEncoderV2;

interface IMessengerWrapper {
    function sendCrossDomainMessage(bytes memory _calldata) external;
    function verifySender(address l1BridgeCaller, bytes memory _data) external;
    function confirmRoots(
        bytes32[] calldata rootHashes,
        uint256[] calldata destinationChainIds,
        uint256[] calldata totalAmounts,
        uint256[] calldata rootCommittedAts
    ) external;
}

// File: contracts/interfaces/polygonzk/messengers/IPolygonZkEVMBridge.sol



// SPDX-License-Identifier: AGPL-3.0

pragma solidity 0.6.12;

interface IPolygonZkEVMBridge {
    function bridgeMessage(
        uint32 destinationNetwork,
        address destinationAddress,
        bool forceUpdateGlobalExitRoot,
        bytes calldata metadata
    ) external payable;
}
// File: contracts/libraries/ExecutorLib.sol



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

library ExecutorLib {
    function execute(
        address to,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        (bool success, bytes memory res) = payable(to).call{value: value}(data);
        if (!success) {
            // Bubble up error message
            assembly { revert(add(res,0x20), res) }
        }
        return res;
    }
}
// File: contracts/shared/Initializable.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

contract Initializable {
    bool initialized;

    modifier initializer() {
        require(!initialized, "Initializable: contract is already initialized");
        initialized = true;
        _;
    }
}

// File: contracts/wrappers/MessengerWrapper.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.6.12 <=0.8.9;
pragma experimental ABIEncoderV2;

import "../interfaces/IMessengerWrapper.sol";

contract IL1Bridge {
    struct TransferBond {
        address bonder;
        uint256 createdAt;
        uint256 totalAmount;
        uint256 challengeStartTime;
        address challenger;
        bool challengeResolved;
    }
    uint256 public challengePeriod;
    mapping(bytes32 => TransferBond) public transferBonds;
    function getIsBonder(address maybeBonder) public view returns (bool) {}
    function getTransferRootId(bytes32 rootHash, uint256 totalAmount) public pure returns (bytes32) {}
    function confirmTransferRoot(
        uint256 originChainId,
        bytes32 rootHash,
        uint256 destinationChainId,
        uint256 totalAmount,
        uint256 rootCommittedAt
    )
        external
    {}
}

abstract contract MessengerWrapper is IMessengerWrapper {
    address public immutable l1BridgeAddress;
    uint256 public immutable l2ChainId;
    bool public isRootConfirmation = false;

    constructor(address _l1BridgeAddress, uint256 _l2ChainId) internal {
        l1BridgeAddress = _l1BridgeAddress;
        l2ChainId = _l2ChainId;
    }

    modifier onlyL1Bridge {
        require(msg.sender == l1BridgeAddress, "MW: Sender must be the L1 Bridge");
        _;
    }

    modifier rootConfirmation {
        isRootConfirmation = true;
        _;
        isRootConfirmation = false;
    }

    /**
     * @dev Confirm roots that have bonded on L1 and passed the challenge period with no challenge
     * @param rootHashes The root hashes to confirm
     * @param destinationChainIds The destinationChainId of the roots to confirm
     * @param totalAmounts The totalAmount of the roots to confirm
     * @param rootCommittedAts The rootCommittedAt of the roots to confirm
     */
    function confirmRoots (
        bytes32[] calldata rootHashes,
        uint256[] calldata destinationChainIds,
        uint256[] calldata totalAmounts,
        uint256[] calldata rootCommittedAts
    ) external override rootConfirmation {
        IL1Bridge l1Bridge = IL1Bridge(l1BridgeAddress);
        require(l1Bridge.getIsBonder(msg.sender), "MW: Sender must be a bonder");
        require(rootHashes.length == totalAmounts.length, "MW: rootHashes and totalAmounts must be the same length");

        uint256 challengePeriod = l1Bridge.challengePeriod();
        for (uint256 i = 0; i < rootHashes.length; i++) {
            bool canConfirm = canConfirmRoot(l1Bridge, rootHashes[i], totalAmounts[i], challengePeriod);
            require(canConfirm, "MW: Root cannot be confirmed");
            l1Bridge.confirmTransferRoot(
                l2ChainId,
                rootHashes[i],
                destinationChainIds[i],
                totalAmounts[i],
                rootCommittedAts[i]
            );
        }
    }
    
    function canConfirmRoot (IL1Bridge l1Bridge, bytes32 rootHash, uint256 totalAmount, uint256 challengePeriod) public view returns (bool) {
        bytes32 transferRootId = l1Bridge.getTransferRootId(rootHash, totalAmount);
        (,uint256 createdAt,,uint256 challengeStartTime,,) = l1Bridge.transferBonds(transferRootId);

        uint256 timeSinceBondCreation = block.timestamp - createdAt;
        if (
            createdAt != 0 &&
            challengeStartTime == 0 &&
            timeSinceBondCreation > challengePeriod
        ) {
            return true;
        }

        return false;
    }
}

// File: contracts/wrappers/PolygonzkMessengerWrapper.sol



// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "./MessengerWrapper.sol";
import "../connectors/PolygonzkConnector.sol";

contract PolygonzkMessengerWrapper is MessengerWrapper, PolygonzkConnector {

    address public immutable l1Messenger;

    constructor(
        address _l1BridgeAddress,
        uint256 _l2ChainId,
        address _l1Messenger
    )
        public
        MessengerWrapper(_l1BridgeAddress, _l2ChainId)
    {
        l1Messenger = _l1Messenger;
    }

    function sendCrossDomainMessage(bytes memory _calldata) public override onlyL1Bridge {
        _forwardCrossDomainMessage(_calldata);
    }

    function verifySender(address l1BridgeCaller, bytes memory /*_data*/) public override {
        if (isRootConfirmation) return;

        require(l1BridgeCaller == address(this), "PLGN_ZK_MSG_WRP: Caller is not the messenger");
        require(msg.sender == l1BridgeAddress, "PLGN_ZK_MSG_WRP: Sender is not the L1 Bridge");
    }
}