// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.19; import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol"; import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Bannable} from "./Bannable.sol"; import { OFTUpgradeable } from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTUpgradeable.sol"; import { SendParam, MessagingFee, MessagingReceipt, OFTReceipt } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; /// @title A Stable Token Contract /// @custom:security-contact security@bitcoinpoint.com /// @dev Extends ERC20Upgradeable, ERC20PausableUpgradeable, /// ERC20PermitUpgradeable, UUPSUpgradeable, and Bannable contract StableTokenV1OFT is ERC20PausableUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable, Bannable, OFTUpgradeable { using SafeERC20 for IERC20; // ------------------------ // EVENTS // ------------------------ event TokenRescued(address indexed token, address indexed to, uint256 amount); // ------------------------ // FUNCTIONS // ------------------------ /// @custom:oz-upgrades-unsafe-allow constructor /// @dev Prevents the implementation contract from being initialized constructor(address _lzEndpoint) OFTUpgradeable(_lzEndpoint) { _disableInitializers(); } /// @notice Initializes the contract with a given owner, name, and symbol. /// @dev Sets up the initial state of the contract. /// @param initialOwner The address of the initial owner. /// @param name_ The name of the token. /// @param symbol_ The symbol of the token. function initialize(address initialOwner, string memory name_, string memory symbol_) external initializer { __OFT_init(name_, symbol_, initialOwner); __Ownable_init(initialOwner); __ERC20Pausable_init(); __ERC20Permit_init(name_); __UUPSUpgradeable_init(); } /// @notice Pauses all token transfers. /// @dev Can only be called by the contract owner. function pause() external onlyOwner { _pause(); } /// @notice Unpauses all token transfers. /// @dev Can only be called by the contract owner. function unpause() external onlyOwner { _unpause(); } /// @notice Mints tokens to a specified address. /// @dev Can only be called by the owner and if the address is not banned. /// @param to The address to mint tokens to. /// @param amount The amount of tokens to mint. function mint(address to, uint256 amount) external onlyOwner notBanned(to) { _mint(to, amount); } /// @notice Burns tokens from a specified address with permission. /// @dev Can only be called by the ContractOwner and if approval given by wallet. /// @param from Location to burn tokens from. /// @param amount The amount of tokens to burn. function burn(address from, uint256 amount) external onlyOwner notBanned(from) { if (from != _msgSender()) { _spendAllowance(from, _msgSender(), amount); } _burn(from, amount); } /// @notice Allows the owner to rescue ERC20 tokens sent to this contract. /// @dev Only callable by the owner. /// @param tokenContract The contract address of the ERC20 token. /// @param to The address to send the rescued tokens to. /// @param amount The amount of tokens to rescue. function rescueERC20(IERC20 tokenContract, address to, uint256 amount) external onlyOwner notBanned(to) { tokenContract.safeTransfer(to, amount); emit TokenRescued(address(tokenContract), to, amount); } // ------------------------ // FUNCTION OVERRIDES // ------------------------ /// @notice Transfer tokens to a specified address. /// @dev Override with additional notBanned checks. /// @param to The address to transfer tokens to. /// @param value The amount of tokens to transfer. /// @return A boolean indicating whether the operation was successful. function transfer(address to, uint256 value) public override notBanned(msg.sender) notBanned(to) returns (bool) { return super.transfer(to, value); } /// @notice Transfers tokens from one address to another. /// @dev Overrides the base transferFrom function with additional notBanned checks. /// @param from The address to transfer tokens from. /// @param to The address to transfer tokens to. /// @param value The amount of tokens to transfer. /// @return A boolean indicating whether the operation was successful. function transferFrom(address from, address to, uint256 value) public override notBanned(msg.sender) notBanned(from) notBanned(to) returns (bool) { return super.transferFrom(from, to, value); } /// @notice Approves another address to spend a specified amount of tokens on behalf of msg.sender. /// @dev Overrides the base approve function with additional notBanned checks. /// @param spender The address which is approved to spend the tokens. /// @param value The amount of tokens spender is approved to use. /// @return A boolean indicating whether the operation was successful. function approve(address spender, uint256 value) public override notBanned(msg.sender) notBanned(spender) returns (bool) { return super.approve(spender, value); } /// @notice Sets approval based on a provided signature, with checks against account bans. /// @dev Overrides the default permit function to include a ban check for both the owner and spender. /// @param owner The address of the token holder granting permission. /// @param spender The address which is being given approval to spend tokens on behalf of the owner. /// @param value The number of tokens the spender is allowed to spend. /// @param deadline The time at which the permit will expire (as a Unix timestamp). /// @param v Recovery byte of the signature. /// @param r Half of the ECDSA signature pair. /// @param s Half of the ECDSA signature pair. function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public override notBanned(msg.sender) notBanned(owner) notBanned(spender) { super.permit(owner, spender, value, deadline, v, r, s); } /// @notice Revoke approval another address to spend a specified amount of tokens on behalf of msg.sender. /// @dev Overrides the base approve function with additional notBanned check on sender, but not on spender. /// @param spender The address which is beoing revoked of approval to spend the tokens. /// @return A boolean indicating whether the operation was successful. function revokeApproval(address spender) public notBanned(msg.sender) returns (bool) { return super.approve(spender, 0); } /// @notice Revokes approval based on a provided signature, with checks against account bans. /// @dev Overrides the default permit function to include a ban check for the owner but not for the spender. /// @param owner The address of the token holder granting permission. /// @param spender The address which is being revoked of approval to spend tokens on behalf of the owner. /// @param deadline The time at which the permit will expire (as a Unix timestamp). /// @param v Recovery byte of the signature. /// @param r Half of the ECDSA signature pair. /// @param s Half of the ECDSA signature pair. function revokePermit(address owner, address spender, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public notBanned(msg.sender) notBanned(owner) { super.permit(owner, spender, 0, deadline, v, r, s); } /** * @dev Override the send operation to include ban check * @param _sendParam The parameters for the send operation. * @param _fee The calculated fee for the send() operation. * - nativeFee: The native fee. * - lzTokenFee: The lzToken fee. * @param _refundAddress The address to receive any excess funds. * @return msgReceipt The receipt for the send operation. * @return oftReceipt The OFT receipt information. * * @dev MessagingReceipt: LayerZero msg receipt * - guid: The unique identifier for the sent message. * - nonce: The nonce of the sent message. * - fee: The LayerZero fee incurred for the message. */ function send( SendParam calldata _sendParam, MessagingFee calldata _fee, address _refundAddress ) external payable virtual override notBanned(msg.sender) returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) { // @dev Applies the token transfers regarding this send() operation. // - amountSentLD is the amount in local decimals that was ACTUALLY sent/debited from the sender. // - amountReceivedLD is the amount in local decimals that will be received/credited to the recipient on the remote OFT instance. (uint256 amountSentLD, uint256 amountReceivedLD) = _debit( msg.sender, _sendParam.amountLD, _sendParam.minAmountLD, _sendParam.dstEid ); // @dev Builds the options and OFT message to quote in the endpoint. (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam, amountReceivedLD); // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt. msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress); // @dev Formulate the OFT receipt. oftReceipt = OFTReceipt(amountSentLD, amountReceivedLD); emit OFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, amountSentLD, amountReceivedLD); } // ------------------------ // ADMIN FUNCTIONS // ------------------------ /// @dev Authorizes an upgrade to a new implementation. /// @param newImplementation The address of the new contract implementation. function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} /// @dev Internal function to handle token update logic. /// @param from The address from which tokens are transferred. /// @param to The address to which tokens are transferred. /// @param value The amount of tokens being transferred. function _update(address from, address to, uint256 value) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) { ERC20PausableUpgradeable._update(from, to, value); } }