// File: contracts/bridge/DestinationBridge.sol



/**SPDX-License-Identifier: BUSL-1.1

      ▄▄█████████▄
   ╓██▀└ ,╓▄▄▄, '▀██▄
  ██▀ ▄██▀▀╙╙▀▀██▄ └██µ           ,,       ,,      ,     ,,,            ,,,
 ██ ,██¬ ▄████▄  ▀█▄ ╙█▄      ▄███▀▀███▄   ███▄    ██  ███▀▀▀███▄    ▄███▀▀███,
██  ██ ╒█▀'   ╙█▌ ╙█▌ ██     ▐██      ███  █████,  ██  ██▌    └██▌  ██▌     └██▌
██ ▐█▌ ██      ╟█  █▌ ╟█     ██▌      ▐██  ██ └███ ██  ██▌     ╟██ j██       ╟██
╟█  ██ ╙██    ▄█▀ ▐█▌ ██     ╙██      ██▌  ██   ╙████  ██▌    ▄██▀  ██▌     ,██▀
 ██ "██, ╙▀▀███████████⌐      ╙████████▀   ██     ╙██  ███████▀▀     ╙███████▀`
  ██▄ ╙▀██▄▄▄▄▄,,,                ¬─                                    '─¬
   ╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
      ╙▀▀██████R⌐

 */
pragma solidity 0.8.16;

import "contracts/interfaces/IAxelarGateway.sol";
import "contracts/external/axelar/AxelarExecutable.sol";
import "contracts/interfaces/IRWALike.sol";
import "contracts/interfaces/IAllowlist.sol";
import "contracts/external/openzeppelin/contracts/access/Ownable.sol";
import "contracts/external/openzeppelin/contracts/security/Pausable.sol";
import "contracts/bridge/MintRateLimiter.sol";
import "contracts/external/openzeppelin/contracts/token/SafeERC20.sol";

contract DestinationBridge is
  AxelarExecutable,
  MintTimeBasedRateLimiter,
  Ownable,
  Pausable
{
  using SafeERC20 for IERC20;
  /// @notice Token contract bridged by this contract
  IRWALike public immutable TOKEN;

  /// @notice Pointer to AxelarGateway contract
  IAxelarGateway public immutable AXELAR_GATEWAY;

  /// @notice Pointer to USDY allowlist
  IAllowlist public immutable ALLOWLIST;

  // Mapping used to track approvers, approved msg src's and spent nonces
  mapping(address => bool) public approvers;
  mapping(string => bytes32) public chainToApprovedSender;
  mapping(bytes32 => mapping(uint256 => bool)) public isSpentNonce;

  /// @notice Versioning for payload, must match SourceBridge version
  bytes32 public constant VERSION = "1.0";

  /// @notice Mappings used to track transaction and thresholds
  mapping(bytes32 => TxnThreshold) public txnToThresholdSet;
  mapping(string => Threshold[]) public chainToThresholds;
  mapping(bytes32 => Transaction) public txnHashToTransaction;

  constructor(
    address _token,
    address _axelarGateway,
    address _allowlist,
    address _ondoApprover,
    address _owner,
    uint256 _mintLimit,
    uint256 _mintDuration
  )
    AxelarExecutable(_axelarGateway)
    MintTimeBasedRateLimiter(_mintDuration, _mintLimit)
  {
    TOKEN = IRWALike(_token);
    AXELAR_GATEWAY = IAxelarGateway(_axelarGateway);
    ALLOWLIST = IAllowlist(_allowlist);
    approvers[_ondoApprover] = true;
    _transferOwnership(_owner);
  }

  /*//////////////////////////////////////////////////////////////
                         Axelar Functions
  //////////////////////////////////////////////////////////////*/

  /**
   * @notice Internal overriden function that is executed when contract is called by Axelar Relayer
   *
   * @param srcChain The string of the source chain eg: arbitrum
   * @param srcAddr  The string of the address of the source contract
   * @param payload  The payload to pass cross chain
   */
  function _execute(
    string calldata srcChain,
    string calldata srcAddr,
    bytes calldata payload
  ) internal override whenNotPaused {
    (bytes32 version, , address srcSender, uint256 amt, uint256 nonce) = abi
      .decode(payload, (bytes32, uint256, address, uint256, uint256));

    if (version != VERSION) {
      revert InvalidVersion();
    }
    if (chainToApprovedSender[srcChain] == bytes32(0)) {
      revert ChainNotSupported();
    }
    if (chainToApprovedSender[srcChain] != keccak256(abi.encode(srcAddr))) {
      revert SourceNotSupported();
    }
    if (isSpentNonce[chainToApprovedSender[srcChain]][nonce]) {
      revert NonceSpent();
    }

    isSpentNonce[chainToApprovedSender[srcChain]][nonce] = true;

    bytes32 txnHash = keccak256(payload);
    txnHashToTransaction[txnHash] = Transaction(srcSender, amt);
    _attachThreshold(amt, txnHash, srcChain);
    _approve(txnHash);
    _mintIfThresholdMet(txnHash);
    emit MessageReceived(txnHash, srcChain, srcSender, amt, nonce);
  }

  /*//////////////////////////////////////////////////////////////
                        Internal Functions
  //////////////////////////////////////////////////////////////*/

  /**
   * @notice Internal function used to attach a specific threshold to a given
   *         `txnHash`.
   *
   * @param amount   The amount of the token being bridged
   * @param txnHash  The transaction hash to associate the threshold with
   * @param srcChain The chain corresponding to the chain that the token
   *                 being bridged originated from.
   */
  function _attachThreshold(
    uint256 amount,
    bytes32 txnHash,
    string memory srcChain
  ) internal {
    Threshold[] memory thresholds = chainToThresholds[srcChain];
    for (uint256 i = 0; i < thresholds.length; ++i) {
      Threshold memory t = thresholds[i];
      if (amount <= t.amount) {
        txnToThresholdSet[txnHash] = TxnThreshold(
          t.numberOfApprovalsNeeded,
          new address[](0)
        );
        break;
      }
    }
    if (txnToThresholdSet[txnHash].numberOfApprovalsNeeded == 0) {
      revert NoThresholdMatch();
    }
  }

  /**
   * @notice Internal function used to approve and conditionally mint for a
   *         given txn. Approval is conditional on this approver having not
   *         previously approved the txn
   *
   * @param txnHash The txnHash to approve and conditionally mint to
   */
  function _approve(bytes32 txnHash) internal {
    // Check that the approver has not already approved
    TxnThreshold storage t = txnToThresholdSet[txnHash];
    uint256 approversLength = t.approvers.length;
    if (approversLength > 0) {
      for (uint256 i = 0; i < approversLength; ++i) {
        if (t.approvers[i] == msg.sender) {
          revert AlreadyApproved();
        }
      }
    }
    t.approvers.push(msg.sender);
    emit TransactionApproved(
      txnHash,
      msg.sender,
      approversLength + 1,
      t.numberOfApprovalsNeeded
    );
  }

  /**
   * @notice Internal function to mint tokens for a user if the transaction has
   *         passed the threshold for number of approvers
   *
   * @param txnHash The hash of the payload we wish to mint
   */
  function _mintIfThresholdMet(bytes32 txnHash) internal {
    bool thresholdMet = _checkThresholdMet(txnHash);
    if (thresholdMet) {
      Transaction memory txn = txnHashToTransaction[txnHash];
      _checkAndUpdateInstantMintLimit(txn.amount);
      if (
        address(ALLOWLIST) != address(0) && !ALLOWLIST.isAllowed(txn.sender)
      ) {
        ALLOWLIST.setAccountStatus(
          txn.sender,
          ALLOWLIST.getValidTermIndexes()[0],
          true
        );
      }
      TOKEN.mint(txn.sender, txn.amount);
      // Clear the approval for this bridge payload
      delete txnHashToTransaction[txnHash];
      emit BridgeCompleted(txn.sender, txn.amount);
    }
  }

  /**
   * @notice Internal function used to check if the approval threshold has been
   *         met for a given transaction.
   *
   * @param txnHash The txnHash to check
   *
   * @dev If an approver has been removed, any previous approvals are still valid
   */
  function _checkThresholdMet(bytes32 txnHash) internal view returns (bool) {
    TxnThreshold storage t = txnToThresholdSet[txnHash];
    return t.approvers.length >= t.numberOfApprovalsNeeded;
  }

  /*//////////////////////////////////////////////////////////////
                        Protected Functions
  //////////////////////////////////////////////////////////////*/

  /**
   * @notice Protected Function used to approve messages passed to the
   *         Receiver contract. This function is able to be called by any
   *         approver that is added and associated with Ondo.
   *
   * @param txnHash The keccak256 hash of the payload
   */
  function approve(bytes32 txnHash) external {
    if (!approvers[msg.sender]) {
      revert NotApprover();
    }
    _approve(txnHash);
    _mintIfThresholdMet(txnHash);
  }

  /**
   * @notice Admin function to add an ondo Signer or Axelar Relayer
   *
   * @param approver  The address we would like to add
   */
  function addApprover(address approver) external onlyOwner {
    approvers[approver] = true;
    emit ApproverAdded(approver);
  }

  /**
   * @notice Admin function to remove an approver
   *
   * @param approver The address of the approver that we would like to remove
   */
  function removeApprover(address approver) external onlyOwner {
    delete approvers[approver];
    emit ApproverRemoved(approver);
  }

  /**
   * @notice Admin function that will allow bridge calls originating from a given address
   *         on a given chain.
   * @notice This will initialize a nested mapping in which spent nonces from this `srcAddress`
   *         are logged and prevented from being reused
   *
   * @param srcChain            The chain to support
   * @param srcContractAddress  The address of the Ondo Bridge on the source chain
   *
   * @dev srcContractAddress: Is case sensitive and must be the checksum address
   * of the srcBridge contract which is allowed to call into this contract.
   */
  function addChainSupport(
    string calldata srcChain,
    string calldata srcContractAddress
  ) external onlyOwner {
    chainToApprovedSender[srcChain] = keccak256(abi.encode(srcContractAddress));
    emit ChainIdSupported(srcChain, srcContractAddress);
  }

  /**
   * @notice Admin function that will remove support for previously supported chains
   *
   * @param srcChain The source chain whose support is being removed
   */
  function removeChainSupport(string calldata srcChain) external onlyOwner {
    delete chainToApprovedSender[srcChain];
    emit ChainSupportRemoved(srcChain);
  }

  /**
   * @notice Admin function used to clear and set thresholds corresponding to a chain
   *
   * @param srcChain       The chain to set the threshold for
   * @param amounts        The ordered array of values corresponding to
   *                       the amount for a given threshold
   * @param numOfApprovers The ordered array of the number of approvals needed
   *                       for a given threshold
   *
   * @dev This function will remove all previously set thresholds for a given chain
   *      and will thresholds corresponding to the params of this function. Passing
   *      in empty arrays will remove all thresholds for a given chain
   */
  function setThresholds(
    string calldata srcChain,
    uint256[] calldata amounts,
    uint256[] calldata numOfApprovers
  ) external onlyOwner {
    if (amounts.length != numOfApprovers.length) {
      revert ArrayLengthMismatch();
    }
    delete chainToThresholds[srcChain];
    for (uint256 i = 0; i < amounts.length; ++i) {
      if (numOfApprovers[i] == 0) {
        revert NumOfApproversCannotBeZero();
      }
      if (i == 0) {
        chainToThresholds[srcChain].push(
          Threshold(amounts[i], numOfApprovers[i])
        );
      } else {
        if (chainToThresholds[srcChain][i - 1].amount > amounts[i]) {
          revert ThresholdsNotInAscendingOrder();
        }
        chainToThresholds[srcChain].push(
          Threshold(amounts[i], numOfApprovers[i])
        );
      }
    }
    emit ThresholdSet(srcChain, amounts, numOfApprovers);
  }

  /**
   * @notice Admin function used to set the mint limit
   *
   * @param mintLimit The new mint limit
   */
  function setMintLimit(uint256 mintLimit) external onlyOwner {
    _setMintLimit(mintLimit);
  }

  /**
   * @notice Admin function used to set the mint duration
   *
   * @param mintDuration The new mint duration
   */
  function setMintLimitDuration(uint256 mintDuration) external onlyOwner {
    _setMintLimitDuration(mintDuration);
  }

  /**
   * @notice Admin function used to pause the contract
   *
   * @dev Only used for bridge functions
   */
  function pause() external onlyOwner {
    _pause();
  }

  /**
   * @notice Admin function used to unpause the contract
   *
   * @dev Only used for bridge functions
   */
  function unpause() external onlyOwner {
    _unpause();
  }

  /**
   * @notice Admin function used to rescue ERC20 Tokens sent to the contract
   *
   * @param _token The address of the token to rescue
   */
  function rescueTokens(address _token) external onlyOwner {
    uint256 balance = IERC20(_token).balanceOf(address(this));
    IERC20(_token).safeTransfer(owner(), balance);
  }

  /*//////////////////////////////////////////////////////////////
                       Helper Functions
  //////////////////////////////////////////////////////////////*/

  /**
   * @notice External view function used to get the number of approvers for a
   *         given txnHash
   *
   * @param txnHash The hash to get the number of approvers for
   */
  function getNumApproved(bytes32 txnHash) external view returns (uint256) {
    return txnToThresholdSet[txnHash].approvers.length;
  }

  /*//////////////////////////////////////////////////////////////
                      Structs, Events, Errors
  //////////////////////////////////////////////////////////////*/

  struct Threshold {
    uint256 amount;
    uint256 numberOfApprovalsNeeded;
  }

  struct TxnThreshold {
    uint256 numberOfApprovalsNeeded;
    address[] approvers;
  }

  struct Transaction {
    address sender;
    uint256 amount;
  }

  /**
   * @notice event emitted when an address is removed as an approver
   *
   * @param approver The address being removed
   */
  event ApproverRemoved(address approver);

  /**
   * @notice event emitted when an address is added as an approver
   *
   * @param approver  The address to add
   */
  event ApproverAdded(address approver);

  /**
   * @notice event emitted when a new contract is whitelisted as an approved
   *         message passer.
   *
   * @param srcChain        The chain for the approved address
   * @param approvedSource  The address corresponding to the source bridge contract
   */
  event ChainIdSupported(string indexed srcChain, string approvedSource);

  /**
   * @notice event emitted when a threshold has been set
   *
   * @param chain           The chain for which the threshold was set
   * @param amounts         The amount of tokens to reach this threshold
   * @param numOfApprovers  The number of approvals needed
   */
  event ThresholdSet(
    string indexed chain,
    uint256[] amounts,
    uint256[] numOfApprovers
  );

  /**
   * @notice event emitted when the user has been minted their tokens on the dst chain
   *
   * @param user    The recipient address of the newly minted tokens
   * @param amount  The amount of tokens that have been minted
   */
  event BridgeCompleted(address indexed user, uint256 amount);

  /**
   * @notice event emitted when this bridge contract receives a cross chain message
   *
   * @param txnHash   The hash of the payload that has been bridged
   * @param srcChain  The chain from which the message is originating
   * @param srcSender The address of the msg.sender on the source chain
   * @param amt       The amount of tokens being bridged
   * @param nonce     The nonce corresponding to the contract which originated the msg
   */
  event MessageReceived(
    bytes32 indexed txnHash,
    string indexed srcChain,
    address indexed srcSender,
    uint256 amt,
    uint256 nonce
  );

  /**
   * @notice event emitted when a transaction has been approved
   *
   * @param txnHash              The hash of the payload that has been approved
   * @param approver             The address of the approver
   * @param numApprovers         The number of approvers for this transaction
   * @param thresholdRequirement The number of approvals needed for this transaction
   */
  event TransactionApproved(
    bytes32 indexed txnHash,
    address approver,
    uint256 numApprovers,
    uint256 thresholdRequirement
  );

  /**
   * @notice event emitted when support for a chain is removed
   *
   * @param srcChain The chain whose support is being removed
   */
  event ChainSupportRemoved(string indexed srcChain);

  error NotApprover();
  error NoThresholdMatch();
  error ThresholdsNotInAscendingOrder();

  error ChainNotSupported();
  error SourceNotSupported();
  error NonceSpent();
  error AlreadyApproved();
  error InvalidVersion();
  error ArrayLengthMismatch();
  error NumOfApproversCannotBeZero();
}

// File: contracts/bridge/MintRateLimiter.sol



/**SPDX-License-Identifier: BUSL-1.1

      ▄▄█████████▄
   ╓██▀└ ,╓▄▄▄, '▀██▄
  ██▀ ▄██▀▀╙╙▀▀██▄ └██µ           ,,       ,,      ,     ,,,            ,,,
 ██ ,██¬ ▄████▄  ▀█▄ ╙█▄      ▄███▀▀███▄   ███▄    ██  ███▀▀▀███▄    ▄███▀▀███,
██  ██ ╒█▀'   ╙█▌ ╙█▌ ██     ▐██      ███  █████,  ██  ██▌    └██▌  ██▌     └██▌
██ ▐█▌ ██      ╟█  █▌ ╟█     ██▌      ▐██  ██ └███ ██  ██▌     ╟██ j██       ╟██
╟█  ██ ╙██    ▄█▀ ▐█▌ ██     ╙██      ██▌  ██   ╙████  ██▌    ▄██▀  ██▌     ,██▀
 ██ "██, ╙▀▀███████████⌐      ╙████████▀   ██     ╙██  ███████▀▀     ╙███████▀`
  ██▄ ╙▀██▄▄▄▄▄,,,                ¬─                                    '─¬
   ╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
      ╙▀▀██████R⌐

 */
pragma solidity 0.8.16;

/**
 * @title InstantMintTimeBasedRateLimiter
 *
 * @notice This abstract contract implements two rate limiters: one for minting
 *         and one for redeeming. Each limit is completely independent: mints
 *         and redemption don't offset each other. Each limit is associated
 *         with a duration, after which the tracked amount is reset. The
 *         amounts tracked are agnostic to a specific token; the usage is
 *         determined by the inheriting contracts.
 *
 * @dev Although this contract has all of its functions implemented, this
 *      contract is marked abstract to prevent an accidental deployment and to
 *      signify that we would never deploy this contract standalone.
 *
 */
abstract contract MintTimeBasedRateLimiter {
  // `currentMintAmount` resets after this interval (in seconds)
  uint256 public resetMintDuration;
  // timestamp when `currentMintAmount` was last reset
  uint256 public lastResetMintTime;

  // maximum amount that can be minted during a `resetMintDuration` window
  uint256 public mintLimit;
  // amount already minted during the current `resetMintDuration` window
  uint256 public currentMintAmount;

  /**
   * @notice In the constructor, we initialize the variables for the mint and
   *         redemption rate limiters.
   *
   * @param _mintResetDuration   `currentMintAmount` resets after this interval
   *                                    (in seconds)
   * @param _instantMintLimit           maximum amount that can be minted during a
   *                                    `resetMintDuration` window
   *
   * @dev If a duration is zero, the limit resets before each mint/redemption.
   * @dev If a limit is zero, the relevant check always fails.
   */
  constructor(uint256 _mintResetDuration, uint256 _instantMintLimit) {
    resetMintDuration = _mintResetDuration; // can be zero for per-block limit
    mintLimit = _instantMintLimit; // can be zero to disable minting

    lastResetMintTime = block.timestamp;
  }

  /**
   * @notice Checks the requested mint amount against the rate limiter (and
   *         updates the remaining amount)
   *
   * @param amount The requested mint amount
   *
   * @dev Reverts if the requested mint amount exceeds the current limit
   */
  function _checkAndUpdateInstantMintLimit(uint256 amount) internal {
    require(amount > 0, "RateLimit: mint amount can't be zero");

    if (block.timestamp >= lastResetMintTime + resetMintDuration) {
      // time has passed, reset
      currentMintAmount = 0;
      lastResetMintTime = block.timestamp;
    }
    require(
      amount <= mintLimit - currentMintAmount,
      "RateLimit: Mint exceeds rate limit"
    );

    currentMintAmount += amount;
  }

  /**
   * @notice Update the amount of token that can be minted during one duration
   *
   * @param _mintLimit The token amount
   *
   * @dev If a limit is zero, the relevant check always fails.
   */
  function _setMintLimit(uint256 _mintLimit) internal {
    mintLimit = _mintLimit;
    emit MintLimitSet(_mintLimit);
  }

  /**
   * @notice Update the duration for the mint rate limiter
   *
   * @param _mintResetDuration The duration in seconds
   *
   * @dev If a duration is zero, the limit resets before each mint/redemption
   */
  function _setMintLimitDuration(uint256 _mintResetDuration) internal {
    resetMintDuration = _mintResetDuration;
    emit MintLimitDurationSet(_mintResetDuration);
  }

  /**
   * @notice Event emitted when mint limit is set
   *
   * @param mintLimit How much of some token can be minted within
   *                  an interval of length `resetMintDuration`
   *
   * @dev See inheriting contract for representation
   */
  event MintLimitSet(uint256 mintLimit);

  /**
   * @notice Event emitted when mint limit duration is set
   *
   * @param instantMintLimitDuration The time window in which `mintLimit`
   *                          of some token can be minted
   *
   * @dev instantMintLimitDuration is specified in seconds
   */
  event MintLimitDurationSet(uint256 instantMintLimitDuration);
}

// File: contracts/external/axelar/AxelarExecutable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IAxelarGateway} from "../../interfaces/IAxelarGateway.sol";
import {IAxelarExecutable} from "../../interfaces/IAxelarExecutable.sol";

contract AxelarExecutable is IAxelarExecutable {
  IAxelarGateway public immutable gateway;

  constructor(address gateway_) {
    if (gateway_ == address(0)) revert InvalidAddress();

    gateway = IAxelarGateway(gateway_);
  }

  function execute(
    bytes32 commandId,
    string calldata sourceChain,
    string calldata sourceAddress,
    bytes calldata payload
  ) external {
    bytes32 payloadHash = keccak256(payload);

    if (
      !gateway.validateContractCall(
        commandId,
        sourceChain,
        sourceAddress,
        payloadHash
      )
    ) revert NotApprovedByGateway();

    _execute(sourceChain, sourceAddress, payload);
  }

  function executeWithToken(
    bytes32 commandId,
    string calldata sourceChain,
    string calldata sourceAddress,
    bytes calldata payload,
    string calldata tokenSymbol,
    uint256 amount
  ) external {
    bytes32 payloadHash = keccak256(payload);

    if (
      !gateway.validateContractCallAndMint(
        commandId,
        sourceChain,
        sourceAddress,
        payloadHash,
        tokenSymbol,
        amount
      )
    ) revert NotApprovedByGateway();

    _executeWithToken(sourceChain, sourceAddress, payload, tokenSymbol, amount);
  }

  function _execute(
    string calldata sourceChain,
    string calldata sourceAddress,
    bytes calldata payload
  ) internal virtual {}

  // false detection from slither
  // slither-disable-next-line dead-code
  function _executeWithToken(
    string calldata sourceChain,
    string calldata sourceAddress,
    bytes calldata payload,
    string calldata tokenSymbol,
    uint256 amount
  ) internal virtual {}
}

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "contracts/external/openzeppelin/contracts/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 Returns the address of the current owner.
   */
  function owner() public view virtual returns (address) {
    return _owner;
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    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: contracts/external/openzeppelin/contracts/security/Pausable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)

pragma solidity ^0.8.0;

import "contracts/external/openzeppelin/contracts/utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
  /**
   * @dev Emitted when the pause is triggered by `account`.
   */
  event Paused(address account);

  /**
   * @dev Emitted when the pause is lifted by `account`.
   */
  event Unpaused(address account);

  bool private _paused;

  /**
   * @dev Initializes the contract in unpaused state.
   */
  constructor() {
    _paused = false;
  }

  /**
   * @dev Returns true if the contract is paused, and false otherwise.
   */
  function paused() public view virtual returns (bool) {
    return _paused;
  }

  /**
   * @dev Modifier to make a function callable only when the contract is not paused.
   *
   * Requirements:
   *
   * - The contract must not be paused.
   */
  modifier whenNotPaused() {
    require(!paused(), "Pausable: paused");
    _;
  }

  /**
   * @dev Modifier to make a function callable only when the contract is paused.
   *
   * Requirements:
   *
   * - The contract must be paused.
   */
  modifier whenPaused() {
    require(paused(), "Pausable: not paused");
    _;
  }

  /**
   * @dev Triggers stopped state.
   *
   * Requirements:
   *
   * - The contract must not be paused.
   */
  function _pause() internal virtual whenNotPaused {
    _paused = true;
    emit Paused(_msgSender());
  }

  /**
   * @dev Returns to normal state.
   *
   * Requirements:
   *
   * - The contract must be paused.
   */
  function _unpause() internal virtual whenPaused {
    _paused = false;
    emit Unpaused(_msgSender());
  }
}

// File: contracts/external/openzeppelin/contracts/token/IERC20.sol



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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
  /**
   * @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);

  /**
   * @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);
}

// File: contracts/external/openzeppelin/contracts/token/SafeERC20.sol



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

pragma solidity ^0.8.0;

import "contracts/external/openzeppelin/contracts/token/IERC20.sol";
import "contracts/external/openzeppelin/contracts/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)
      );
    }
  }

  /**
   * @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: contracts/external/openzeppelin/contracts/utils/Address.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.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 functionCall(target, data, "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"
    );
    require(isContract(target), "Address: call to non-contract");

    (bool success, bytes memory returndata) = target.call{value: value}(data);
    return verifyCallResult(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) {
    require(isContract(target), "Address: static call to non-contract");

    (bool success, bytes memory returndata) = target.staticcall(data);
    return verifyCallResult(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) {
    require(isContract(target), "Address: delegate call to non-contract");

    (bool success, bytes memory returndata) = target.delegatecall(data);
    return verifyCallResult(success, returndata, errorMessage);
  }

  /**
   * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
   * revert reason 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 {
      // 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

        assembly {
          let returndata_size := mload(returndata)
          revert(add(32, returndata), returndata_size)
        }
      } else {
        revert(errorMessage);
      }
    }
  }
}

// File: contracts/external/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/interfaces/IAllowlist.sol



/**SPDX-License-Identifier: BUSL-1.1

      ▄▄█████████▄
   ╓██▀└ ,╓▄▄▄, '▀██▄
  ██▀ ▄██▀▀╙╙▀▀██▄ └██µ           ,,       ,,      ,     ,,,            ,,,
 ██ ,██¬ ▄████▄  ▀█▄ ╙█▄      ▄███▀▀███▄   ███▄    ██  ███▀▀▀███▄    ▄███▀▀███,
██  ██ ╒█▀'   ╙█▌ ╙█▌ ██     ▐██      ███  █████,  ██  ██▌    └██▌  ██▌     └██▌
██ ▐█▌ ██      ╟█  █▌ ╟█     ██▌      ▐██  ██ └███ ██  ██▌     ╟██ j██       ╟██
╟█  ██ ╙██    ▄█▀ ▐█▌ ██     ╙██      ██▌  ██   ╙████  ██▌    ▄██▀  ██▌     ,██▀
 ██ "██, ╙▀▀███████████⌐      ╙████████▀   ██     ╙██  ███████▀▀     ╙███████▀`
  ██▄ ╙▀██▄▄▄▄▄,,,                ¬─                                    '─¬
   ╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
      ╙▀▀██████R⌐

 */
pragma solidity 0.8.16;

interface IAllowlist {
  function addTerm(string calldata term) external;

  function setCurrentTermIndex(uint256 _currentTermIndex) external;

  function setValidTermIndexes(uint256[] calldata indexes) external;

  function isAllowed(address account) external view returns (bool);

  function getCurrentTerm() external view returns (string memory);

  function currentTermIndex() external view returns (uint256);

  function getValidTermIndexes() external view returns (uint256[] memory);

  function addAccountToAllowlist(
    uint256 _currentTermIndex,
    address account,
    uint8 v,
    bytes32 r,
    bytes32 s
  ) external;

  function addSelfToAllowlist(uint256 termIndex) external;

  function setAccountStatus(
    address account,
    uint256 termIndex,
    bool status
  ) external;

  /**
   * @notice Event emitted when a term is added
   *
   * @param hashedMessage The hash of the terms string that was added
   * @param termIndex     The index of the term that was added
   */
  event TermAdded(bytes32 hashedMessage, uint256 termIndex);

  /**
   * @notice Event emitted when the current term index is set
   *
   * @param oldIndex The old current term index
   * @param newIndex The new current term index
   */
  event CurrentTermIndexSet(uint256 oldIndex, uint256 newIndex);

  /**
   * @notice Event emitted when the valid term indexes are set
   *
   * @param oldIndexes The old valid term indexes
   * @param newIndexes The new valid term indexes
   */
  event ValidTermIndexesSet(uint256[] oldIndexes, uint256[] newIndexes);

  /**
   * @notice Event emitted when an accoun status is set by an admin
   *
   * @param account   The account whose status was set
   * @param termIndex The term index of the account whose status that was set
   * @param status    The new status of the account
   */
  event AccountStatusSetByAdmin(
    address indexed account,
    uint256 indexed termIndex,
    bool status
  );

  /**
   * @notice Event emitted when an account adds itself added to the allowlist
   *
   * @param account   The account that was added
   * @param termIndex The term index for which the account was added
   */
  event AccountAddedSelf(address indexed account, uint256 indexed termIndex);

  /**
   * @notice Event emitted when an account is added to the allowlist by a signature
   *
   * @param account   The account that was added
   * @param termIndex The term index for which the account was added
   * @param v         The v value of the signature
   * @param r         The r value of the signature
   * @param s         The s value of the signature
   */
  event AccountAddedFromSignature(
    address indexed account,
    uint256 indexed termIndex,
    uint8 v,
    bytes32 r,
    bytes32 s
  );

  /**
   * @notice Event emitted when an account status is set
   *
   * @param account   The account whose status was set
   * @param termIndex The term index of the account whose status was set
   * @param status    The new status of the account
   */
  event AccountStatusSet(
    address indexed account,
    uint256 indexed termIndex,
    bool status
  );

  /// ERRORS ///
  error InvalidTermIndex();
  error InvalidVSignature();
  error AlreadyVerified();
  error InvalidSigner();
}

// File: contracts/interfaces/IAxelarExecutable.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

interface IAxelarExecutable {
  error InvalidAddress();
  error NotApprovedByGateway();

  function gateway() external view returns (IAxelarGateway);

  function execute(
    bytes32 commandId,
    string calldata sourceChain,
    string calldata sourceAddress,
    bytes calldata payload
  ) external;

  function executeWithToken(
    bytes32 commandId,
    string calldata sourceChain,
    string calldata sourceAddress,
    bytes calldata payload,
    string calldata tokenSymbol,
    uint256 amount
  ) external;
}

// File: contracts/interfaces/IAxelarGateway.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IAxelarGateway {
  function callContract(
    string calldata destinationChain,
    string calldata contractAddress,
    bytes calldata payload
  ) external;

  function validateContractCall(
    bytes32 commandId,
    string calldata sourceChain,
    string calldata sourceAddress,
    bytes32 payloadHash
  ) external returns (bool);

  function validateContractCallAndMint(
    bytes32 commandId,
    string calldata sourceChain,
    string calldata sourceAddress,
    bytes32 payloadHash,
    string calldata symbol,
    uint256 amount
  ) external returns (bool);
}

// File: contracts/interfaces/IRWALike.sol



/**SPDX-License-Identifier: BUSL-1.1

      ▄▄█████████▄
   ╓██▀└ ,╓▄▄▄, '▀██▄
  ██▀ ▄██▀▀╙╙▀▀██▄ └██µ           ,,       ,,      ,     ,,,            ,,,
 ██ ,██¬ ▄████▄  ▀█▄ ╙█▄      ▄███▀▀███▄   ███▄    ██  ███▀▀▀███▄    ▄███▀▀███,
██  ██ ╒█▀'   ╙█▌ ╙█▌ ██     ▐██      ███  █████,  ██  ██▌    └██▌  ██▌     └██▌
██ ▐█▌ ██      ╟█  █▌ ╟█     ██▌      ▐██  ██ └███ ██  ██▌     ╟██ j██       ╟██
╟█  ██ ╙██    ▄█▀ ▐█▌ ██     ╙██      ██▌  ██   ╙████  ██▌    ▄██▀  ██▌     ,██▀
 ██ "██, ╙▀▀███████████⌐      ╙████████▀   ██     ╙██  ███████▀▀     ╙███████▀`
  ██▄ ╙▀██▄▄▄▄▄,,,                ¬─                                    '─¬
   ╙▀██▄ '╙╙╙▀▀▀▀▀▀▀▀
      ╙▀▀██████R⌐
 */
pragma solidity 0.8.16;

// This interface is not inherited directly by RWA, instead, it is a
// subset of functions provided by all RWA tokens that the RWA Hub
// Client uses.
import "contracts/external/openzeppelin/contracts/token/IERC20.sol";

interface IRWALike is IERC20 {
  function mint(address to, uint256 amount) external;

  function burn(uint256 amount) external;

  function burnFrom(address from, uint256 amount) external;
}