// File: contracts/integration/mm/interfaces/IMinter.sol



// SPDX-License-Identifier: LZBL-1.1
// Copyright 2023 LayerZero Labs Ltd.
// You may obtain a copy of the License at
// https://github.com/LayerZero-Labs/license/blob/main/LICENSE-LZBL-1.1

pragma solidity ^0.8.0;

import {MessagingFee} from "../../../layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";

interface IMinter {
    struct SwapParam {
        address fromToken;
        uint fromTokenAmount;
        uint64 minUSDVOut;
    }

    error OnlyMinterProxy();
    error SlippageTooHigh();
    error ExceedsUInt64();
    error Blacklisted();
    error TokenNotSupported();

    event ApprovedToken(address toSTBTLp, address token);
    event DisapprovedToken(address toSTBTLp, address token);
    event BlacklistedUser(address user, bool blacklisted);
    event SetRewardToUserBps(uint16 bps);
    event WithdrawToken(address token, address to, uint amount);

    function swapToUSDV(
        address _sender,
        address _toSTBTLp,
        SwapParam calldata _param,
        address _usdvReceiver
    ) external returns (uint usdvOut);

    function swapToUSDVAndSend(
        address _sender,
        address _toSTBTLp,
        SwapParam calldata _param,
        bytes32 _usdvReceiver,
        uint32 _dstEid,
        bytes calldata _extraOptions,
        MessagingFee calldata _msgFee,
        address payable _refundAddress
    ) external payable returns (uint usdvOut);

    function getSwapToUSDVAmountOut(
        address _toSTBTLp,
        address _fromToken,
        uint _fromTokenAmount
    ) external view returns (uint usdvOut);

    function getSwapToUSDVAmountOutVerbose(
        address _toSTBTLp,
        address _fromToken,
        uint _fromTokenAmount
    ) external view returns (uint usdvOut, uint fee, uint reward);

    function getSupportedFromTokens(address _lp) external view returns (address[] memory tokens);

    function color() external view returns (uint32);

    function minterProxy() external view returns (address);
}
// File: contracts/integration/mm/interfaces/IMinterProxy.sol



// SPDX-License-Identifier: LZBL-1.1
// Copyright 2023 LayerZero Labs Ltd.
// You may obtain a copy of the License at
// https://github.com/LayerZero-Labs/license/blob/main/LICENSE-LZBL-1.1

pragma solidity ^0.8.0;

interface IMinterProxy {
    error InvalidMinter();
    error MinterAlreadyRegistered();
    error MinterNotRegistered(uint32 color);
    error HashExists();
    error HashNotExists();

    event RegisteredMinter(address minter, uint32 color);
    event UnregisteredMinter(address minter, uint32 color);
    event SetToSTBTLp(address toSTBTLp);
    event AddedMinterCodeHash(uint hash);
    event RemovedMinterCodeHash(uint hash);

    function isRegistered(address _addr) external view returns (bool);

    function toSTBTLp() external view returns (address);

    function usdv() external view returns (address);
}
// File: contracts/integration/mm/interfaces/IToSTBTLp.sol



// SPDX-License-Identifier: LZBL-1.1
// Copyright 2023 LayerZero Labs Ltd.
// You may obtain a copy of the License at
// https://github.com/LayerZero-Labs/license/blob/main/LICENSE-LZBL-1.1

pragma solidity ^0.8.0;

// lp contract for converting other stable coins to STBT
interface IToSTBTLp {
    function getSupportedTokens() external view returns (address[] memory tokens);

    function swapToSTBT(
        address _fromToken,
        uint _fromTokenAmount,
        uint _minStbtOut
    ) external returns (uint requestedOut, uint rewardOut);

    function getSwapToSTBTAmountOut(
        address _fromToken,
        uint _fromTokenAmount
    ) external view returns (uint requestedOut, uint rewardOut);
}
// File: contracts/integration/mm/Minter.sol



// SPDX-License-Identifier: LZBL-1.1
// Copyright 2023 LayerZero Labs Ltd.
// You may obtain a copy of the License at
// https://github.com/LayerZero-Labs/license/blob/main/LICENSE-LZBL-1.1

pragma solidity 0.8.19;

import "../../openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../../openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../../openzeppelin/contracts/access/Ownable.sol";
import "../../layerzerolabs/lz-evm-oapp-v2/contracts/standards/oft/interfaces/IOFT.sol";

import {IUSDV} from "../../usdv/interfaces/IUSDV.sol";
import {IMinter} from "./interfaces/IMinter.sol";
import {IMinterProxy} from "./interfaces/IMinterProxy.sol";
import {IToSTBTLp} from "./interfaces/IToSTBTLp.sol";
import {IVaultManager} from "../../vault/interfaces/IVaultManager.sol";

contract Minter is IMinter, Ownable {
    using SafeERC20 for IERC20;

    uint8 internal constant USDV_DECIMALS = 6;

    IVaultManager public immutable vault;
    address public immutable minterProxy;
    uint public immutable usdv2stbtRate;

    address public immutable stbt;
    address public immutable usdv;

    mapping(address toSTBTLp => mapping(address fromToken => bool)) public approved;
    mapping(address => bool) public blacklisted;

    uint32 public color;
    uint16 public rewardToUserBps;

    constructor(uint32 _color, address _stbt, address _vault, address _minterProxy) {
        stbt = _stbt;
        usdv = IMinterProxy(_minterProxy).usdv();

        IUSDV(usdv).setDefaultColor(address(this), _color);
        color = _color;

        minterProxy = _minterProxy;

        vault = IVaultManager(_vault);
        IERC20(_stbt).safeApprove(_vault, type(uint).max); // to support mint
        (, usdv2stbtRate, ) = vault.assetInfoOf(_stbt);
    }

    // ------------------ modifier ------------------
    modifier onlyMinterProxy() {
        if (msg.sender != minterProxy) revert OnlyMinterProxy();
        _;
    }

    modifier notBlacklisted(address _user) {
        if (blacklisted[_user]) revert Blacklisted();
        _;
    }

    // ------------------ onlyOwner ------------------
    /// @dev to support swapToSTBT, only max approves
    function approve(address _toSTBTLp, address[] calldata _tokens) external onlyOwner {
        for (uint i = 0; i < _tokens.length; i++) {
            address token = _tokens[i];
            if (!approved[_toSTBTLp][token]) {
                approved[_toSTBTLp][token] = true;
                IERC20(token).safeApprove(_toSTBTLp, type(uint).max);
                emit ApprovedToken(_toSTBTLp, token);
            }
        }
    }

    function disapprove(address _toSTBTLp, address[] calldata _tokens) external onlyOwner {
        for (uint i = 0; i < _tokens.length; i++) {
            address token = _tokens[i];
            if (approved[_toSTBTLp][token]) {
                approved[_toSTBTLp][token] = false;
                IERC20(token).safeApprove(_toSTBTLp, 0);
                emit DisapprovedToken(_toSTBTLp, token);
            }
        }
    }

    function blacklist(address _user, bool _isBlacklisted) external onlyOwner {
        blacklisted[_user] = _isBlacklisted;
        emit BlacklistedUser(_user, _isBlacklisted);
    }

    function setRewardToUserBps(uint16 _rewardToUserBps) external onlyOwner {
        rewardToUserBps = _rewardToUserBps;
        emit SetRewardToUserBps(_rewardToUserBps);
    }

    function withdrawToken(address _token, address _to, uint _amount) external onlyOwner {
        IERC20(_token).safeTransfer(_to, _amount);
        emit WithdrawToken(_token, _to, _amount);
    }

    // ------------------ external ------------------
    function swapToUSDV(
        address _sender,
        address _toSTBTLp,
        SwapParam calldata _param,
        address _usdvReceiver
    ) external notBlacklisted(_sender) onlyMinterProxy returns (uint usdvOut) {
        usdvOut = _swapToUSDV(_toSTBTLp, _param.fromToken, _param.fromTokenAmount, _param.minUSDVOut, _usdvReceiver);
    }

    function swapToUSDVAndSend(
        address _sender,
        address _toSTBTLp,
        SwapParam calldata _param,
        bytes32 _usdvReceiver,
        uint32 _dstEid,
        bytes calldata _extraOptions,
        MessagingFee calldata _msgFee,
        address payable _refundAddress
    ) external payable notBlacklisted(_sender) onlyMinterProxy returns (uint usdvOut) {
        usdvOut = _swapToUSDV(_toSTBTLp, _param.fromToken, _param.fromTokenAmount, _param.minUSDVOut, address(this));
        IUSDV(usdv).send{value: msg.value}(
            IOFT.SendParam(_usdvReceiver, usdvOut, usdvOut, _dstEid),
            _extraOptions,
            _msgFee,
            _refundAddress,
            ""
        );
    }

    // ------------------ view ------------------
    /// @dev get supported tokens of lpToSTBT and filter out unapproved tokens
    function getSupportedFromTokens(address _toSTBTLp) external view returns (address[] memory tokens) {
        tokens = IToSTBTLp(_toSTBTLp).getSupportedTokens();

        uint index = 0;
        for (uint i = 0; i < tokens.length; i++) {
            if (approved[_toSTBTLp][tokens[i]]) {
                tokens[index++] = tokens[i]; // index strictly smaller or equals to i, in place update
            }
        }
        assembly {
            mstore(tokens, index)
        }
    }

    function getSwapToUSDVAmountOut(
        address _toSTBTLp,
        address _fromToken,
        uint _fromTokenAmount
    ) external view returns (uint usdvOut) {
        // get requested and reward amount
        (uint stbtOut, uint rewardOut) = IToSTBTLp(_toSTBTLp).getSwapToSTBTAmountOut(_fromToken, _fromTokenAmount);
        // apply reward
        if (rewardOut > 0) {
            uint rewardToUser = (rewardOut * rewardToUserBps) / 10000; // e.g. get 5bps reward. if push 1bps to users, the rewardToUserBps = 2000
            stbtOut += rewardToUser;
        }
        // convert to usdv
        usdvOut = stbtOut / usdv2stbtRate;
        if (usdvOut > type(uint64).max) revert ExceedsUInt64();
    }

    function getSwapToUSDVAmountOutVerbose(
        address _toSTBTLp,
        address _fromToken,
        uint _fromTokenAmount
    ) external view returns (uint usdvOut, uint fee, uint reward) {
        uint8 tokenDecimals = IERC20Metadata(_fromToken).decimals();
        require(tokenDecimals >= USDV_DECIMALS, "BridgeRecolor: token decimals must >= 6");
        uint usdvRequested = _fromTokenAmount / (10 ** (tokenDecimals - USDV_DECIMALS));

        // get swapped and reward amount
        (uint requestedSTBTOut, uint rewardSTBTOut) = IToSTBTLp(_toSTBTLp).getSwapToSTBTAmountOut(
            _fromToken,
            _fromTokenAmount
        );
        usdvOut = requestedSTBTOut / usdv2stbtRate;
        // fee charged by lp
        fee = usdvRequested - usdvOut;

        // apply reward
        if (rewardSTBTOut > 0) {
            reward = (rewardSTBTOut * rewardToUserBps) / 10000 / usdv2stbtRate; // reward to user
            usdvOut += reward; // e.g. get 5bps reward. if push 1bps to users, the rewardToUserBps = 2000
        }
        if (usdvOut > type(uint64).max) revert ExceedsUInt64();
    }

    // ------------------ internal ------------------
    function _swapToUSDV(
        address _toSTBTLp,
        address _fromToken,
        uint _fromTokenAmount,
        uint64 _minUSDVOut,
        address _usdvReceiver
    ) internal returns (uint usdvOut) {
        if (!approved[_toSTBTLp][_fromToken]) revert TokenNotSupported();

        uint minSTBTOut = _minUSDVOut * usdv2stbtRate;
        // get requested and reward amount
        (uint stbtOut, uint rewardOut) = IToSTBTLp(_toSTBTLp).swapToSTBT(_fromToken, _fromTokenAmount, minSTBTOut);
        // apply reward
        if (rewardOut > 0) {
            uint rewardToUser = (rewardOut * rewardToUserBps) / 10000; // e.g. get 5bps reward. if push 1bps to users, the rewardToUserBps = 2000
            stbtOut += rewardToUser;
        }
        // check slippage
        if (stbtOut < minSTBTOut) revert SlippageTooHigh();
        // convert to usdv
        usdvOut = stbtOut / usdv2stbtRate;
        if (usdvOut > type(uint64).max) revert ExceedsUInt64();
        // mint requested to user and leave stbt reward in vault
        vault.mint(stbt, _usdvReceiver, uint64(usdvOut), color, "");
    }
}
// File: contracts/layerzerolabs/lz-evm-oapp-v2/contracts/standards/oft/interfaces/IOFT.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.0;

import {MessagingFee, MessagingReceipt} from "../../../../../../layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";

// @dev unable to inherit ERC20 because the OFTAdapter needs to use this interface as well
interface IOFT {
    struct SendParam {
        bytes32 to;
        uint amountLD;
        uint minAmountLD;
        uint32 dstEid;
    }

    error LDMinusSD();
    error AmountSlippage(uint _amountLDSend, uint256 _minAmountLD);

    event SetInspector(address _inspector);
    event SendOFT(bytes32 indexed _guid, address indexed _fromAddress, uint _amountLD, bytes _composeMsg);
    event ReceiveOFT(bytes32 indexed _guid, address indexed _toAddress, uint _amountLD);

    function token() external view returns (address);

    function quoteSendFee(
        SendParam calldata _send,
        bytes calldata _options,
        bool _useLZToken,
        bytes calldata _composeMsg
    ) external view returns (uint nativeFee, uint lzTokenFee);

    function send(
        SendParam calldata _send,
        bytes calldata _options,
        MessagingFee calldata _msgFee,
        address payable _refundAddress,
        bytes calldata _composeMsg
    ) external payable returns (MessagingReceipt memory);
}
// File: contracts/layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

import "./IMessageLibManager.sol";
import "./IMessagingComposer.sol";
import "./IMessagingChannel.sol";
import "./IMessagingContext.sol";
import {Origin} from "../MessagingStructs.sol";

struct MessagingParams {
    uint32 dstEid;
    bytes32 receiver;
    bytes message;
    bytes options;
}

struct MessagingReceipt {
    bytes32 guid;
    uint64 nonce;
    MessagingFee fee;
}

struct MessagingFee {
    uint nativeFee;
    uint lzTokenFee;
}

interface ILayerZeroEndpointV2 is IMessageLibManager, IMessagingComposer, IMessagingChannel, IMessagingContext {
    event PacketSent(bytes encodedPayload, bytes options, address sendLibrary);

    event PacketDelivered(Origin origin, address receiver, bytes32 payloadHash);

    event PacketReceived(Origin origin, address receiver);

    event LzReceiveFailed(Origin origin, address receiver, bytes reason);

    event LayerZeroTokenSet(address token);

    function quote(
        address _sender,
        uint32 _dstEid,
        bytes calldata _message,
        bool _payInLzToken,
        bytes calldata _options
    ) external view returns (MessagingFee memory);

    function send(
        MessagingParams calldata _params,
        uint _lzTokenFee,
        address payable _refundAddress
    ) external payable returns (MessagingReceipt memory);

    function sendWithAlt(
        MessagingParams calldata _params,
        uint _lzTokenFee,
        uint _altTokenFee
    ) external returns (MessagingReceipt memory);

    function deliver(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external;

    function deliverable(Origin calldata _origin, address _receiveLib, address _receiver) external view returns (bool);

    function lzReceive(
        Origin calldata _origin,
        address _receiver,
        bytes32 _guid,
        bytes calldata _message,
        bytes calldata _extraData
    ) external payable returns (bool, bytes memory);

    // oapp can burn messages partially by calling this function with its own business logic if messages are delivered in order
    function clear(Origin calldata _origin, bytes32 _guid, bytes calldata _message) external;

    function setLayerZeroToken(address _layerZeroToken) external;

    function layerZeroToken() external view returns (address);

    function altFeeToken() external view returns (address);
}
// File: contracts/layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

struct SetConfigParam {
    uint32 configType;
    bytes config;
}

interface IMessageLibManager {
    struct Timeout {
        address lib;
        uint expiry;
    }

    event LibraryRegistered(address newLib);
    event DefaultSendLibrarySet(uint32 eid, address newLib);
    event DefaultReceiveLibrarySet(uint32 eid, address oldLib, address newLib);
    event DefaultReceiveLibraryTimeoutSet(uint32 eid, address oldLib, uint expiry);
    event SendLibrarySet(address sender, uint32 eid, address newLib);
    event ReceiveLibrarySet(address receiver, uint32 eid, address oldLib, address newLib);
    event ReceiveLibraryTimoutSet(address receiver, uint32 eid, address oldLib, uint timeout);

    function registerLibrary(address _lib) external;

    function isRegisteredLibrary(address _lib) external view returns (bool);

    function getRegisteredLibraries() external view returns (address[] memory);

    function setDefaultSendLibrary(uint32 _eid, address _newLib) external;

    function defaultSendLibrary(uint32 _eid) external view returns (address);

    function setDefaultReceiveLibrary(uint32 _eid, address _newLib, uint _timeout) external;

    function defaultReceiveLibrary(uint32 _eid) external view returns (address);

    function setDefaultReceiveLibraryTimeout(uint32 _eid, address _lib, uint _expiry) external;

    function defaultReceiveLibraryTimeout(uint32 _eid) external view returns (address lib, uint expiry);

    function defaultConfig(address _lib, uint32 _eid, uint32 _configType) external view returns (bytes memory);

    function isSupportedEid(uint32 _eid) external view returns (bool);

    /// ------------------- OApp interfaces -------------------
    function setSendLibrary(uint32 _eid, address _newLib) external;

    function getSendLibrary(address _sender, uint32 _eid) external view returns (address lib);

    function isDefaultSendLibrary(address _sender, uint32 _eid) external view returns (bool);

    function setReceiveLibrary(uint32 _eid, address _newLib, uint _gracePeriod) external;

    function getReceiveLibrary(address _receiver, uint32 _eid) external view returns (address lib, bool isDefault);

    function setReceiveLibraryTimeout(uint32 _eid, address _lib, uint _gracePeriod) external;

    function receiveLibraryTimeout(address _receiver, uint32 _eid) external view returns (address lib, uint expiry);

    function setConfig(address _lib, uint32 _eid, SetConfigParam[] calldata _params) external;

    function getConfig(
        address _oapp,
        address _lib,
        uint32 _eid,
        uint32 _configType
    ) external view returns (bytes memory config, bool isDefault);

    function snapshotConfig(address _lib, uint32[] calldata _eids) external;

    function resetConfig(address _lib, uint32[] calldata _eids) external;
}
// File: contracts/layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingChannel.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

interface IMessagingChannel {
    event InboundNonceSkipped(uint32 srcEid, bytes32 sender, address receiver, uint64 nonce);

    function eid() external view returns (uint32);

    // this is an emergency function if a message can not be delivered for some reasons
    // required to provide _nextNonce to avoid race condition
    function skip(uint32 _srcEid, bytes32 _sender, uint64 _nonce) external;

    function nextGuid(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (bytes32);

    function inboundNonce(address _receiver, uint32 _srcEid, bytes32 _sender) external view returns (uint64);

    function outboundNonce(address _sender, uint32 _dstEid, bytes32 _receiver) external view returns (uint64);

    function inboundPayloadHash(
        address _receiver,
        uint32 _srcEid,
        bytes32 _sender,
        uint64 _nonce
    ) external view returns (bytes32);

    function hasPayloadHash(
        address _receiver,
        uint32 _srcEid,
        bytes32 _sender,
        uint64 _nonce
    ) external view returns (bool);
}
// File: contracts/layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingComposer.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

interface IMessagingComposer {
    event ComposedMessageDelivered(address receiver, address composer, bytes32 guid, bytes message);
    event ComposedMessageReceived(address receiver, address composer, bytes32 guid);
    event LzComposeFailed(address receiver, address composer, bytes32 guid, bytes reason);

    function deliverComposedMessage(address _composer, bytes32 _guid, bytes calldata _message) external;

    function lzCompose(
        address _receiver,
        address _composer,
        bytes32 _guid,
        bytes calldata _message,
        bytes calldata _extraData
    ) external payable returns (bool, bytes memory);
}
// File: contracts/layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessagingContext.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

interface IMessagingContext {
    function isSendingMessage() external view returns (bool);

    function getSendContext() external view returns (uint32, address);
}
// File: contracts/layerzerolabs/lz-evm-protocol-v2/contracts/MessagingStructs.sol



// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.8.0;

struct PacketForQuote {
    address sender;
    uint32 dstEid;
    bytes message;
}

struct Packet {
    uint64 nonce;
    uint32 srcEid;
    address sender;
    uint32 dstEid;
    bytes32 receiver;
    bytes32 guid;
    bytes message;
}

struct Origin {
    uint32 srcEid;
    bytes32 sender;
    uint64 nonce;
}
// File: contracts/openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @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: contracts/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: contracts/openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol



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

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
// File: contracts/openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/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: contracts/openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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: contracts/openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol



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

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/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;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    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));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    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");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    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");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation 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).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // 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 cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}
// File: contracts/openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [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://consensys.net/diligence/blog/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.8.0/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: contracts/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/usdv/interfaces/IUSDV.sol



// SPDX-License-Identifier: LZBL-1.1
// Copyright 2023 LayerZero Labs Ltd.
// You may obtain a copy of the License at
// https://github.com/LayerZero-Labs/license/blob/main/LICENSE-LZBL-1.1

pragma solidity ^0.8.0;

import "../../layerzerolabs/lz-evm-oapp-v2/contracts/standards/oft/interfaces/IOFT.sol";
import "../../openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";

struct Delta {
    uint32 color;
    int64 amount;
}

struct State {
    uint32 color;
    uint64 balance;
    // config
    bool blacklisted;
    uint32 defaultColor;
}

enum Role {
    OWNER,
    OPERATOR,
    VAULT,
    MESSAGING,
    FOUNDATION
}

interface IUSDV is IOFT, IERC20Upgradeable {
    error Unauthorized();
    error InvalidUser();
    error InvalidArgument();
    error NotImplemented();
    error InsufficientBalance();
    error Paused();
    error Blacklisted();
    error FeeTooHigh();

    // role assigment
    event SetRole(Role role, address addr);
    event SetColorer(address indexed caller, address indexed user, address colorer);
    event SetDefaultColor(address indexed caller, address indexed user, uint32 defaultColor);
    // governance state
    event SetBlacklist(address indexed user, bool isBlacklisted);
    event SetPause(bool paused);
    // cross-chain events
    event Synced(bytes32 guid, Delta[] deltas);

    function mint(address _receiver, uint64 _amount, uint32 _color) external;

    function burn(address _from, uint64 _amount, uint32[] calldata _delta) external returns (Delta[] memory deltas);

    /// -------- coloring interfaces --------
    function setColorer(address _user, address _colorer) external;

    function setDefaultColor(address _user, uint32 _defaultColor) external;

    /// -------- governance interfaces --------
    function setPause(bool _pause) external;

    function setRole(Role _role, address _addr) external;

    /// -------- cross-chain interfaces (non-OFT) --------
    function sendAck(bytes32 _guid, address _receiver, uint32 _color, uint64 _amount, uint64 _theta) external;

    function syncDelta(
        uint32 _dstEid,
        uint64 _theta,
        uint32[] calldata _deficits,
        uint64 _feeCap,
        bytes calldata _extraOptions,
        MessagingFee calldata _msgFee,
        address payable _refundAddress
    ) external payable returns (MessagingReceipt memory msgReceipt);

    function syncDeltaAck(Delta[] calldata _deltas) external;

    function quoteSyncDeltaFee(
        uint32 _dstEid,
        uint32 _numDeficits,
        bytes calldata _extraOptions,
        bool _useLzToken
    ) external view returns (uint nativeFee, uint lzTokenFee);

    function userStates(
        address _user
    ) external view returns (uint32 color, uint64 balance, bool blacklisted, uint32 defaultColor);
}
// File: contracts/vault/interfaces/IVaultManager.sol



// SPDX-License-Identifier: LZBL-1.1
// Copyright 2023 LayerZero Labs Ltd.
// You may obtain a copy of the License at
// https://github.com/LayerZero-Labs/license/blob/main/LICENSE-LZBL-1.1

pragma solidity ^0.8.0;

import {Delta} from "../../usdv/interfaces/IUSDV.sol";
import "../libs/Asset.sol";

enum Role {
    OWNER,
    OPERATOR,
    FOUNDATION,
    LIQUIDITY_PROVIDER,
    DONOR
}

interface IVaultManager {
    error InvalidAmount();
    error NotDeltaZero();
    error WrongSign();
    error InvalidArgument();
    error InvalidColor(uint32 color);
    error ColorPaused();
    error Unauthorized();

    event DistributedReward(address[] token, uint[] amounts);
    event Minted(address indexed receiver, uint32 indexed color, uint64 amount, bytes32 memo);
    event Redeemed(address indexed redeemer, uint64 amount);
    event PendingRemint(Delta[] deltas);
    event WithdrewReward(address caller, address receiver, uint32 color, uint64 rewards);
    event WithdrewFees(address caller, address receiver, uint64 fees);
    event RegisteredAsset(address asset);
    event EnabledAsset(bool enabled);
    event SetRole(Role role, address addr);
    event SetFeeBpsCap(Role role, uint16 cap);
    event SetFeeBps(Role role, uint16 bps);
    event SetRateLimiter(address limiter);
    event PausedColor(uint32 color, bool paused);
    event SetMinter(address minter, uint32 color);

    function assetInfoOf(
        address _token
    ) external view returns (bool enabled, uint usdvToTokenRate, uint collateralized);

    function mint(address _token, address _receiver, uint64 _amount, uint32 _color, bytes32 _memo) external;

    function remint(Delta[] calldata _deltas, uint64 _remintFee) external;

    function redeem(
        address _token,
        address _receiver,
        uint64 _amount,
        uint64 _minAmount,
        uint32[] calldata _deficits
    ) external returns (uint amountAfterFee);

    function redeemOut(address _token, uint64 _amount) external view returns (uint);
}
// File: contracts/vault/libs/Asset.sol



// SPDX-License-Identifier: LZBL-1.1
// Copyright 2023 LayerZero Labs Ltd.
// You may obtain a copy of the License at
// https://github.com/LayerZero-Labs/license/blob/main/LICENSE-LZBL-1.1

pragma solidity ^0.8.0;

import "../../openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../../openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Governance} from "./Governance.sol";

library Asset {
    using SafeERC20 for IERC20;
    using Governance for Governance.Info;

    struct Info {
        IERC20 token;
        bool registered;
        bool enabled; // to allow minting
        uint usdvToTokenRate;
        // all the following values are in token amount
        uint collateralized;
    }

    error TokenDecimalInvalid(uint provided, uint min);
    error Disabled();
    error AlreadyRegistered();
    error SlippageTooHigh();
    error NotRegistered();

    modifier onlyRegistered(Info storage _self) {
        if (!_self.registered) revert NotRegistered();
        _;
    }

    function initialize(Info storage _self, address _token, uint8 _shareDecimals) internal {
        if (_self.registered) revert AlreadyRegistered();

        // set token
        _self.token = IERC20(_token);
        _self.enabled = true;
        _self.registered = true;

        // set conversion rate
        uint8 tokenDecimals = IERC20Metadata(_token).decimals();
        if (_shareDecimals > tokenDecimals) {
            revert TokenDecimalInvalid(tokenDecimals, _shareDecimals);
        }
        _self.usdvToTokenRate = 10 ** (tokenDecimals - _shareDecimals);
    }

    // @dev if _newFund is true, _from must approve the token transfer
    // @dev if internal mint such as turning yield into collateral, _newFund should be false
    function credit(Info storage _self, address _from, uint64 _usdvAmount, bool _newFund) internal {
        // assert the asset is enabled
        if (!_self.enabled) revert Disabled();

        uint tokenAmount = _usdv2Token(_self, _usdvAmount);
        _self.collateralized += tokenAmount;

        if (_newFund) {
            _self.token.safeTransferFrom(_from, address(this), tokenAmount);
        }
    }

    function redeem(
        Info storage _self,
        Governance.Info storage _govInfo,
        address _receiver,
        uint64 _usdvAmount,
        uint64 _minUsdvAmount
    ) internal onlyRegistered(_self) returns (uint amountAfterFee) {
        uint tokenAmount = _usdv2Token(_self, _usdvAmount);
        _self.collateralized -= tokenAmount;
        // pay redemption fee
        amountAfterFee = _govInfo.payRedemptionFee(_self.token, tokenAmount);
        if (amountAfterFee < _usdv2Token(_self, _minUsdvAmount)) revert SlippageTooHigh();

        // transfer collateral to receiver
        _self.token.safeTransfer(_receiver, amountAfterFee);
    }

    function setEnabled(Info storage _self, bool _enabled) internal onlyRegistered(_self) {
        _self.enabled = _enabled;
    }

    // ========================= View =========================
    /// @return reward in usdv
    function distributable(Info storage _self) internal view returns (uint) {
        uint rebasedFunds = _self.token.balanceOf(address(this)) - _self.collateralized;
        return rebasedFunds / _self.usdvToTokenRate;
    }

    function redeemOutput(
        Info storage _self,
        Governance.Info storage _govInfo,
        uint64 _shares
    ) internal view returns (uint) {
        uint tokenAmount = _usdv2Token(_self, _shares);
        return tokenAmount - _govInfo.getRedemptionFee(tokenAmount);
    }

    // ========================= Internal =========================
    function _usdv2Token(Info storage _self, uint64 _shares) private view returns (uint) {
        return _shares * _self.usdvToTokenRate;
    }
}
// File: contracts/vault/libs/Governance.sol



// SPDX-License-Identifier: LZBL-1.1
// Copyright 2023 LayerZero Labs Ltd.
// You may obtain a copy of the License at
// https://github.com/LayerZero-Labs/license/blob/main/LICENSE-LZBL-1.1

pragma solidity ^0.8.0;

import "../../openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Role} from "../interfaces/IVaultManager.sol";
import "../../openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

library Governance {
    using SafeERC20 for IERC20;

    uint16 internal constant ONE_HUNDRED_PERCENT = 10000; // 100%
    uint16 internal constant RESERVE_FEE_BPS_CAP = 300; // 3%
    // A % fee is applicable on yield distribution for the vault service.
    uint16 internal constant REDEMPTION_FEE_BPS_CAP_MIN = 10; //0.1%

    struct Fee {
        uint16 bps;
        uint16 cap;
    }

    struct Info {
        mapping(Role => Fee) fees;
        mapping(Role => address) roles;
        uint operatorLastPing; // timestamp
    }

    event PaidRedemptionFee(address indexed to, uint amount);

    // ========================= Operator =========================
    function ping(Info storage _self) internal {
        _self.operatorLastPing = block.timestamp;
    }

    // ========================= Fees =========================
    function getRedemptionFee(Info storage _self, uint _amount) internal view returns (uint fee) {
        return (_amount * _self.fees[Role.FOUNDATION].bps) / ONE_HUNDRED_PERCENT;
    }

    function payRedemptionFee(Info storage _self, IERC20 _token, uint _amount) internal returns (uint) {
        uint fee = getRedemptionFee(_self, _amount);
        // pay redemption fee to operator
        address _to = _self.roles[Role.OPERATOR];
        _token.safeTransfer(_to, fee);
        emit PaidRedemptionFee(_to, fee);

        return _amount - fee;
    }
}