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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.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 anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing 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/token/ERC20/extensions/draft-IERC20Permit.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

// File: @openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// 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/CvpBridgeLocker.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/IDeBridgeGate.sol";
import "./interfaces/Flags.sol";
import "./interfaces/ICallProxy.sol";

contract CvpBridgeLocker is Ownable {
  using SafeERC20 for IERC20;
  using Flags for uint256;

  IDeBridgeGate public deBridgeGate;
  IERC20 public immutable cvp;

  mapping(uint256 => address) public destinationChainContracts;
  mapping(uint256 => address) public sourceChainsContracts;

  mapping(uint256 => uint256) public chainLimitPerDay;
  mapping(uint256 => mapping(uint256 => uint256)) public transfersPerDay;

  event SendToChain(address sender, uint256 amount, uint256 toChainID, address indexed receipient, uint32 indexed referralCode, bytes32 indexed submissionId);
  event Unlock(address indexed sender, uint256 amount, uint256 indexed fromChainID, address indexed receipient);

  constructor(IDeBridgeGate _deBridgeGate, IERC20 _cvp) Ownable() {
    deBridgeGate = _deBridgeGate;
    cvp = _cvp;
  }

  /**
    Returns the current chain ID, as an integer.
    @return cid uint256 representing the current chain ID.
  */
  function getChainId() public virtual view returns (uint256 cid) {
    assembly {
      cid := chainid()
    }
  }

  /**
    Send CVP Tokens to another blockchain
    @dev Sends CVP tokens to the specified recipient on the specified chain.
    @param _toChainID ID of the destination chain.
    @param _amount Amount of CVP tokens to be sent.
    @param _recipient Address of the recipient on the destination chain.
    @param _referralCode An optional referral code to include in the transaction.
  */
  function sendToChain(uint256 _toChainID, uint256 _amount, address _recipient, uint32 _referralCode) external payable {
    require(destinationChainContracts[_toChainID] != address(0), "Chain contract address not specified");
    _checkChainLimits(_toChainID, _amount);

    cvp.safeTransferFrom(msg.sender, address(this), _amount);

    bytes memory dstTxCall = _encodeUnlockCommand(_amount, _recipient);
    bytes32 submissionId = _send(dstTxCall, _toChainID, _referralCode);
    emit SendToChain(msg.sender, _amount, _toChainID, _recipient, _referralCode, submissionId);
  }

  /**
    Unlock CVP Tokens received from another blockchain
    @dev Unlocks CVP tokens received from the specified chain and sends them to the specified recipient.
    @param _fromChainID ID of the source chain.
    @param _amount Amount of CVP tokens to be unlocked.
    @param _recipient Address of the recipient of the unlocked CVP tokens.
  */
  function unlock(uint256 _fromChainID, uint256 _amount, address _recipient) external {
    require(sourceChainsContracts[_fromChainID] != address(0), "Chain contract address not specified");
    _onlyCrossChain(_fromChainID);
    _checkChainLimits(_fromChainID, _amount);

    cvp.safeTransfer(_recipient, _amount);
    emit Unlock(msg.sender, _amount, _fromChainID, _recipient);
  }

  /**
    Check chain limits for cross-chain transfers
    @dev Checks if the specified amount of tokens can be transferred to the specified chain based on the daily limit set for the chain.
    @param _chainID ID of the chain being checked for the transfer limit.
    @param _amount Amount of tokens to be transferred.
  */
  function _checkChainLimits(uint256 _chainID, uint256 _amount) internal {
    uint256 curEpoch = block.timestamp / 1 days;
    transfersPerDay[_chainID][curEpoch] += _amount;

    require(chainLimitPerDay[_chainID] >= transfersPerDay[_chainID][curEpoch], "Limit reached");
  }

  /**
    Check if function is called by a valid cross-chain contract
    @dev Checks if the function is being called by a valid cross-chain contract for the specified source chain.
    @param _fromChainID ID of the source chain.
  */
  function _onlyCrossChain(uint256 _fromChainID) internal {
    ICallProxy callProxy = ICallProxy(deBridgeGate.callProxy());

    // caller is CallProxy?
    require(address(callProxy) == msg.sender, "Not callProxy");

    uint256 chainIdFrom = callProxy.submissionChainIdFrom();
    require(chainIdFrom == _fromChainID, "Chain id does not match");
    bytes memory nativeSender = callProxy.submissionNativeSender();
    require(keccak256(abi.encodePacked(sourceChainsContracts[chainIdFrom])) == keccak256(nativeSender), "Not valid sender");
  }

  /**
    Encode the unlock command for cross-chain transfer
    @dev Encodes the unlock command with the specified amount and recipient address for the current chain ID.
    @param _amount Amount of tokens to be unlocked.
    @param _recipient Address of the recipient of the unlocked tokens.
    @return The encoded unlock command.
  */
  function _encodeUnlockCommand(uint256 _amount, address _recipient)
    internal
    view
    returns (bytes memory)
  {
    return
    abi.encodeWithSelector(
      CvpBridgeLocker.unlock.selector,
      getChainId(),
      _amount,
      _recipient
    );
  }

  /**
    Send the transaction to the specified chain
    @dev Sends the transaction to the specified chain using the deBridgeGate.
    @param _dstTransactionCall The encoded transaction to be sent to the destination chain.
    @param _toChainId The ID of the destination chain.
    @param _referralCode An optional referral code to include in the transaction.
  */
  function _send(bytes memory _dstTransactionCall, uint256 _toChainId, uint32 _referralCode) internal returns(bytes32 submissionId) {
    uint flags = uint(0)
      .setFlag(Flags.REVERT_IF_EXTERNAL_FAIL, true)
      .setFlag(Flags.PROXY_WITH_SENDER, true);

    return deBridgeGate.sendMessage{value : msg.value}(
      _toChainId, // _chainIdTo
      abi.encodePacked(destinationChainContracts[_toChainId]), // _targetContractAddress
      _dstTransactionCall, // _targetContractCalldata
      flags, // _flags
      _referralCode // _referralCode
    );
  }

  /**
    Allows the owner to set the address of the DeBridgeGate contract.
    @param _deBridgeGate Address of the new DeBridgeGate contract.
  */
  function setDeBridgeGate(IDeBridgeGate _deBridgeGate) external onlyOwner {
    deBridgeGate = _deBridgeGate;
  }

  /**
    @dev Allows the owner to set the address of the destination chain contract for a given chain ID.
    @param _chainId uint256 ID of the chain to set the destination chain contract for.
    @param _contract address Address of the destination chain contract to set.
  */
  function setDestinationChainContract(uint256 _chainId, address _contract) external onlyOwner {
    destinationChainContracts[_chainId] = _contract;
  }

  /**
    @dev Allows the owner to set the address of the source chain contract for a given chain ID.
    @param _chainId uint256 ID of the chain to set the source chain contract for.
    @param _contract address Address of the source chain contract to set.
  */
  function setSourceChainContract(uint256 _chainId, address _contract) external onlyOwner {
    sourceChainsContracts[_chainId] = _contract;
  }

  /**
    @dev Allows the owner to set the maximum transfer limit per day for a specific chain.
    @param _chainId uint256 ID of the chain for which the limit needs to be set.
    @param _amount uint256 Maximum amount that can be transferred per day.
  */
  function setChainLimitPerDay(uint256 _chainId, uint256 _amount) external onlyOwner {
    chainLimitPerDay[_chainId] = _amount;
  }

  /**
    @dev Transfers _amount tokens of _token to the _newBridge address for migration purposes.
    @param _token address of the token to be migrated.
    @param _newBridge address of the new bridge where the tokens will be migrated.
    @param _amount amount of tokens to be migrated.
  */
  function migrate(address _token, address _newBridge, uint256 _amount) external onlyOwner {
    IERC20(_token).safeTransfer(_newBridge, _amount);
  }
}

// File: contracts/interfaces/Flags.sol



// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.7;

library Flags {

  /* ========== FLAGS ========== */

  /// @dev Flag to unwrap ETH
  uint256 public constant UNWRAP_ETH = 0;
  /// @dev Flag to revert if external call fails
  uint256 public constant REVERT_IF_EXTERNAL_FAIL = 1;
  /// @dev Flag to call proxy with a sender contract
  uint256 public constant PROXY_WITH_SENDER = 2;
  /// @dev Data is hash in DeBridgeGate send method
  uint256 public constant SEND_HASHED_DATA = 3;
  /// @dev First 24 bytes from data is gas limit for external call
  uint256 public constant SEND_EXTERNAL_CALL_GAS_LIMIT = 4;
  /// @dev Support multi send for externall call
  uint256 public constant MULTI_SEND = 5;

  /// @dev Get flag
  /// @param _packedFlags Flags packed to uint256
  /// @param _flag Flag to check
  function getFlag(
    uint256 _packedFlags,
    uint256 _flag
  ) internal pure returns (bool) {
    uint256 flag = (_packedFlags >> _flag) & uint256(1);
    return flag == 1;
  }

  /// @dev Set flag
  /// @param _packedFlags Flags packed to uint256
  /// @param _flag Flag to set
  /// @param _value Is set or not set
  function setFlag(
    uint256 _packedFlags,
    uint256 _flag,
    bool _value
  ) internal pure returns (uint256) {
    if (_value)
      return _packedFlags | uint256(1) << _flag;
    else
      return _packedFlags & ~(uint256(1) << _flag);
  }
}

// File: contracts/interfaces/ICallProxy.sol



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

interface ICallProxy {

  /// @dev Chain from which the current submission is received
  function submissionChainIdFrom() external returns (uint256);
  /// @dev Native sender of the current submission
  function submissionNativeSender() external returns (bytes memory);

  /// @dev Used for calls where native asset transfer is involved.
  /// @param _reserveAddress Receiver of the tokens if the call to _receiver fails
  /// @param _receiver Contract to be called
  /// @param _data Call data
  /// @param _flags Flags to change certain behavior of this function, see Flags library for more details
  /// @param _nativeSender Native sender
  /// @param _chainIdFrom Id of a chain that originated the request
  function call(
    address _reserveAddress,
    address _receiver,
    bytes memory _data,
    uint256 _flags,
    bytes memory _nativeSender,
    uint256 _chainIdFrom
  ) external payable returns (bool);

  /// @dev Used for calls where ERC20 transfer is involved.
  /// @param _token Asset address
  /// @param _reserveAddress Receiver of the tokens if the call to _receiver fails
  /// @param _receiver Contract to be called
  /// @param _data Call data
  /// @param _flags Flags to change certain behavior of this function, see Flags library for more details
  /// @param _nativeSender Native sender
  /// @param _chainIdFrom Id of a chain that originated the request
  function callERC20(
    address _token,
    address _reserveAddress,
    address _receiver,
    bytes memory _data,
    uint256 _flags,
    bytes memory _nativeSender,
    uint256 _chainIdFrom
  ) external returns (bool);
}

// File: contracts/interfaces/IDeBridgeGate.sol



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

interface IDeBridgeGate {
  /* ========== STRUCTS ========== */

  struct TokenInfo {
    uint256 nativeChainId;
    bytes nativeAddress;
  }

  struct DebridgeInfo {
    uint256 chainId; // native chain id
    uint256 maxAmount; // maximum amount to transfer
    uint256 balance; // total locked assets
    uint256 lockedInStrategies; // total locked assets in strategy (AAVE, Compound, etc)
    address tokenAddress; // asset address on the current chain
    uint16 minReservesBps; // minimal hot reserves in basis points (1/10000)
    bool exist;
  }

  struct DebridgeFeeInfo {
    uint256 collectedFees; // total collected fees
    uint256 withdrawnFees; // fees that already withdrawn
    mapping(uint256 => uint256) getChainFee; // whether the chain for the asset is supported
  }

  struct ChainSupportInfo {
    uint256 fixedNativeFee; // transfer fixed fee
    bool isSupported; // whether the chain for the asset is supported
    uint16 transferFeeBps; // transfer fee rate nominated in basis points (1/10000) of transferred amount
  }

  struct DiscountInfo {
    uint16 discountFixBps; // fix discount in BPS
    uint16 discountTransferBps; // transfer % discount in BPS
  }

  /// @param executionFee Fee paid to the transaction executor.
  /// @param fallbackAddress Receiver of the tokens if the call fails.
  struct SubmissionAutoParamsTo {
    uint256 executionFee;
    uint256 flags;
    bytes fallbackAddress;
    bytes data;
  }

  /// @param executionFee Fee paid to the transaction executor.
  /// @param fallbackAddress Receiver of the tokens if the call fails.
  struct SubmissionAutoParamsFrom {
    uint256 executionFee;
    uint256 flags;
    address fallbackAddress;
    bytes data;
    bytes nativeSender;
  }

  struct FeeParams {
    uint256 receivedAmount;
    uint256 fixFee;
    uint256 transferFee;
    bool useAssetFee;
    bool isNativeToken;
  }

  /* ========== PUBLIC VARS GETTERS ========== */

  /// @dev Returns whether the transfer with the submissionId was claimed.
  /// submissionId is generated in getSubmissionIdFrom
  function isSubmissionUsed(bytes32 submissionId) view external returns (bool);

  /// @dev Returns native token info by wrapped token address
  function getNativeInfo(address token) view external returns (
    uint256 nativeChainId,
    bytes memory nativeAddress);

  /// @dev Returns address of the proxy to execute user's calls.
  function callProxy() external view returns (address);

  /// @dev Fallback fixed fee in native asset, used if a chain fixed fee is set to 0
  function globalFixedNativeFee() external view returns (uint256);

  /// @dev Fallback transfer fee in BPS, used if a chain transfer fee is set to 0
  function globalTransferFeeBps() external view returns (uint16);

  /* ========== FUNCTIONS ========== */

  /// @dev Submits the message to the deBridge infrastructure to be broadcasted to another supported blockchain (identified by _dstChainId)
  ///      with the instructions to call the _targetContractAddress contract using the given _targetContractCalldata
  /// @notice NO ASSETS ARE BROADCASTED ALONG WITH THIS MESSAGE
  /// @notice DeBridgeGate only accepts submissions with msg.value (native ether) covering a small protocol fee
  ///         (defined in the globalFixedNativeFee property). Any excess amount of ether passed to this function is
  ///         included in the message as the execution fee - the amount deBridgeGate would give as an incentive to
  ///         a third party in return for successful claim transaction execution on the destination chain.
  /// @notice DeBridgeGate accepts a set of flags that control the behaviour of the execution. This simple method
  ///         sets the default set of flags: REVERT_IF_EXTERNAL_FAIL, PROXY_WITH_SENDER
  /// @param _dstChainId ID of the destination chain.
  /// @param _targetContractAddress A contract address to be called on the destination chain
  /// @param _targetContractCalldata Calldata to execute against the target contract on the destination chain
  function sendMessage(
    uint256 _dstChainId,
    bytes memory _targetContractAddress,
    bytes memory _targetContractCalldata
  ) external payable returns (bytes32 submissionId);

  /// @dev Submits the message to the deBridge infrastructure to be broadcasted to another supported blockchain (identified by _dstChainId)
  ///      with the instructions to call the _targetContractAddress contract using the given _targetContractCalldata
  /// @notice NO ASSETS ARE BROADCASTED ALONG WITH THIS MESSAGE
  /// @notice DeBridgeGate only accepts submissions with msg.value (native ether) covering a small protocol fee
  ///         (defined in the globalFixedNativeFee property). Any excess amount of ether passed to this function is
  ///         included in the message as the execution fee - the amount deBridgeGate would give as an incentive to
  ///         a third party in return for successful claim transaction execution on the destination chain.
  /// @notice DeBridgeGate accepts a set of flags that control the behaviour of the execution. This simple method
  ///         sets the default set of flags: REVERT_IF_EXTERNAL_FAIL, PROXY_WITH_SENDER
  /// @param _dstChainId ID of the destination chain.
  /// @param _targetContractAddress A contract address to be called on the destination chain
  /// @param _targetContractCalldata Calldata to execute against the target contract on the destination chain
  /// @param _flags A bitmask of toggles listed in the Flags library
  /// @param _referralCode Referral code to identify this submission
  function sendMessage(
    uint256 _dstChainId,
    bytes memory _targetContractAddress,
    bytes memory _targetContractCalldata,
    uint256 _flags,
    uint32 _referralCode
  ) external payable returns (bytes32 submissionId);

  /// @dev This method is used for the transfer of assets [from the native chain](https://docs.debridge.finance/the-core-protocol/transfers#transfer-from-native-chain).
  /// It locks an asset in the smart contract in the native chain and enables minting of deAsset on the secondary chain.
  /// @param _tokenAddress Asset identifier.
  /// @param _amount Amount to be transferred (note: the fee can be applied).
  /// @param _chainIdTo Chain id of the target chain.
  /// @param _receiver Receiver address.
  /// @param _permitEnvelope Permit for approving the spender by signature. bytes (amount + deadline + signature)
  /// @param _useAssetFee use assets fee for pay protocol fix (work only for specials token)
  /// @param _referralCode Referral code
  /// @param _autoParams Auto params for external call in target network
  function send(
    address _tokenAddress,
    uint256 _amount,
    uint256 _chainIdTo,
    bytes memory _receiver,
    bytes memory _permitEnvelope,
    bool _useAssetFee,
    uint32 _referralCode,
    bytes calldata _autoParams
  ) external payable returns (bytes32 submissionId) ;

  /// @dev Is used for transfers [into the native chain](https://docs.debridge.finance/the-core-protocol/transfers#transfer-from-secondary-chain-to-native-chain)
  /// to unlock the designated amount of asset from collateral and transfer it to the receiver.
  /// @param _debridgeId Asset identifier.
  /// @param _amount Amount of the transferred asset (note: the fee can be applied).
  /// @param _chainIdFrom Chain where submission was sent
  /// @param _receiver Receiver address.
  /// @param _nonce Submission id.
  /// @param _signatures Validators signatures to confirm
  /// @param _autoParams Auto params for external call
  function claim(
    bytes32 _debridgeId,
    uint256 _amount,
    uint256 _chainIdFrom,
    address _receiver,
    uint256 _nonce,
    bytes calldata _signatures,
    bytes calldata _autoParams
  ) external;

  /// @dev Withdraw collected fees to feeProxy
  /// @param _debridgeId Asset identifier.
  function withdrawFee(bytes32 _debridgeId) external;

  /// @dev Returns asset fixed fee value for specified debridge and chainId.
  /// @param _debridgeId Asset identifier.
  /// @param _chainId Chain id.
  function getDebridgeChainAssetFixedFee(
    bytes32 _debridgeId,
    uint256 _chainId
  ) external view returns (uint256);

  /* ========== EVENTS ========== */

  /// @dev Emitted once the tokens are sent from the original(native) chain to the other chain; the transfer tokens
  /// are expected to be claimed by the users.
  event Sent(
    bytes32 submissionId,
    bytes32 indexed debridgeId,
    uint256 amount,
    bytes receiver,
    uint256 nonce,
    uint256 indexed chainIdTo,
    uint32 referralCode,
    FeeParams feeParams,
    bytes autoParams,
    address nativeSender
  // bool isNativeToken //added to feeParams
  );

  /// @dev Emitted once the tokens are transferred and withdrawn on a target chain
  event Claimed(
    bytes32 submissionId,
    bytes32 indexed debridgeId,
    uint256 amount,
    address indexed receiver,
    uint256 nonce,
    uint256 indexed chainIdFrom,
    bytes autoParams,
    bool isNativeToken
  );

  /// @dev Emitted when new asset support is added.
  event PairAdded(
    bytes32 debridgeId,
    address tokenAddress,
    bytes nativeAddress,
    uint256 indexed nativeChainId,
    uint256 maxAmount,
    uint16 minReservesBps
  );

  event MonitoringSendEvent(
    bytes32 submissionId,
    uint256 nonce,
    uint256 lockedOrMintedAmount,
    uint256 totalSupply
  );

  event MonitoringClaimEvent(
    bytes32 submissionId,
    uint256 lockedOrMintedAmount,
    uint256 totalSupply
  );

  /// @dev Emitted when the asset is allowed/disallowed to be transferred to the chain.
  event ChainSupportUpdated(uint256 chainId, bool isSupported, bool isChainFrom);
  /// @dev Emitted when the supported chains are updated.
  event ChainsSupportUpdated(
    uint256 chainIds,
    ChainSupportInfo chainSupportInfo,
    bool isChainFrom);

  /// @dev Emitted when the new call proxy is set.
  event CallProxyUpdated(address callProxy);
  /// @dev Emitted when the transfer request is executed.
  event AutoRequestExecuted(
    bytes32 submissionId,
    bool indexed success,
    address callProxy
  );

  /// @dev Emitted when a submission is blocked.
  event Blocked(bytes32 submissionId);
  /// @dev Emitted when a submission is unblocked.
  event Unblocked(bytes32 submissionId);

  /// @dev Emitted when fee is withdrawn.
  event WithdrawnFee(bytes32 debridgeId, uint256 fee);

  /// @dev Emitted when globalFixedNativeFee and globalTransferFeeBps are updated.
  event FixedNativeFeeUpdated(
    uint256 globalFixedNativeFee,
    uint256 globalTransferFeeBps);

  /// @dev Emitted when globalFixedNativeFee is updated by feeContractUpdater
  event FixedNativeFeeAutoUpdated(uint256 globalFixedNativeFee);
}