ETH Price: $2,051.04 (-1.92%)

Transaction Decoder

Block:
20270423 at Jul-09-2024 05:27:35 PM +UTC
Transaction Fee:
0.000924006587085526 ETH $1.90
Gas Used:
121,457 Gas / 7.607684918 Gwei

Emitted Events:

435 AxelarGasServiceProxy.0x617332c1832058df6ee45fcbdf471251474c9945a8e5d229287a21a5f67ccf0a( 0x617332c1832058df6ee45fcbdf471251474c9945a8e5d229287a21a5f67ccf0a, 0x0000000000000000000000004f49b53928a71e553bb1b0f66a5bcb54fd4e8932, 0x146ee416a0baa0a2199ab359939613dbbd78aea820bfc4a3793a8d7cd4470209, 0000000000000000000000000000000000000000000000000000000000000080, 00000000000000000000000000000000000000000000000000000000000000c0, 00000000000000000000000000000000000000000000000000002fe64200ab09, 00000000000000000000000011487f9e7e06b592896e9732d410de3064c49f3d, 0000000000000000000000000000000000000000000000000000000000000009, 696d6d757461626c650000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000000000000000000002a, 3078346634394235333932384137314535353362423142304636366135426342, 3534466434453839333200000000000000000000000000000000000000000000 )
436 0x4f4495243837681061c4743b74b3eedf548d56a5.0x30ae6cc78c27e651745bf2ad08a11de83910ac1e347a52f7ac898c0fbef94dae( 0x30ae6cc78c27e651745bf2ad08a11de83910ac1e347a52f7ac898c0fbef94dae, 0x0000000000000000000000004f49b53928a71e553bb1b0f66a5bcb54fd4e8932, 0x146ee416a0baa0a2199ab359939613dbbd78aea820bfc4a3793a8d7cd4470209, 0000000000000000000000000000000000000000000000000000000000000060, 00000000000000000000000000000000000000000000000000000000000000a0, 0000000000000000000000000000000000000000000000000000000000000100, 0000000000000000000000000000000000000000000000000000000000000009, 696d6d757461626c650000000000000000000000000000000000000000000000, 000000000000000000000000000000000000000000000000000000000000002a, 3078346634394235333932384137314535353362423142304636366135426342, 3534466434453839333200000000000000000000000000000000000000000000, 00000000000000000000000000000000000000000000000000000000000000a0, 87a7811f4bfedea3d341ad165680ae306b01aaeacc205d227629cf157dd9f821, 0000000000000000000000000000000000000000000000000000000000000eee, 00000000000000000000000011487f9e7e06b592896e9732d410de3064c49f3d, 000000000000000000000000329898a33227bc862c97366d51e7cd6fc9b4cb2e, 00000000000000000000000000000000000000000000000000d529ae9e860000 )
437 TransparentUpgradeableProxy.0x4106fee90ed8efa9447954d90d963ce701bd49555bb6ac0878b19c1bb3d8207b( 0x4106fee90ed8efa9447954d90d963ce701bd49555bb6ac0878b19c1bb3d8207b, 0x1579d012ead512dcb248c91571fa46aaa0f3399f8d3e1098fac22575db2ed693, 0x818ca94c43b1fb176f112f95a3f55baaa93c55623d666c8c7241e5a0775c4282, 0x146ee416a0baa0a2199ab359939613dbbd78aea820bfc4a3793a8d7cd4470209 )
438 TransparentUpgradeableProxy.0xd1b4e24e81f1c901551420568a9447fd105593af143ae8b096c6385e4fb7ec86( 0xd1b4e24e81f1c901551420568a9447fd105593af143ae8b096c6385e4fb7ec86, 0x0000000000000000000000000000000000000000000000000000000000000eee, 0x00000000000000000000000052a6c53869ce09a731cd772f245b97a4401d3348, 0x000000000000000000000000329898a33227bc862c97366d51e7cd6fc9b4cb2e, 00000000000000000000000011487f9e7e06b592896e9732d410de3064c49f3d, 00000000000000000000000000000000000000000000000000d529ae9e860000 )

Account State Difference:

  Address   Before After State Difference Code
0x11487f9E...064c49f3d
0.080149189915821437 Eth
Nonce: 212
0.019172517332412318 Eth
Nonce: 213
0.060976672583409119
0x2d5d7d31...09a082712
(Axelar: Gas Service)
0.119466316937593603 Eth0.119518982933917196 Eth0.000052665996323593
(beaverbuild)
9.958204645689854112 Eth9.958368672574632617 Eth0.000164026884778505
0xBa5E35E2...De2d13eB6 1,182.25424 Eth1,182.31424 Eth0.06

Execution Trace

ETH 0.060052665996323593 TransparentUpgradeableProxy.e0410432( )
  • ETH 0.060052665996323593 RootERC20BridgeFlowRate.depositToETH( receiver=0x329898a33227Bc862c97366D51E7cd6Fc9b4cb2e, amount=60000000000000000 )
    • ETH 0.000052665996323593 TransparentUpgradeableProxy.f20755ba( )
      • ETH 0.000052665996323593 RootAxelarBridgeAdaptor.sendMessage( payload=0x87A7811F4BFEDEA3D341AD165680AE306B01AAEACC205D227629CF157DD9F8210000000000000000000000000000000000000000000000000000000000000EEE00000000000000000000000011487F9E7E06B592896E9732D410DE3064C49F3D000000000000000000000000329898A33227BC862C97366D51E7CD6FC9B4CB2E00000000000000000000000000000000000000000000000000D529AE9E860000, refundRecipient=0x11487f9E7E06b592896e9732d410dE3064c49f3d )
        • ETH 0.000052665996323593 AxelarGasServiceProxy.0c93e3bb( )
          • ETH 0.000052665996323593 AxelarGasService.payNativeGasForContractCall( sender=0x4f49B53928A71E553bB1B0F66a5BcB54Fd4E8932, destinationChain=immutable, destinationAddress=0x4f49B53928A71E553bB1B0F66a5BcB54Fd4E8932, payload=0x87A7811F4BFEDEA3D341AD165680AE306B01AAEACC205D227629CF157DD9F8210000000000000000000000000000000000000000000000000000000000000EEE00000000000000000000000011487F9E7E06B592896E9732D410DE3064C49F3D000000000000000000000000329898A33227BC862C97366D51E7CD6FC9B4CB2E00000000000000000000000000000000000000000000000000D529AE9E860000, refundAddress=0x11487f9E7E06b592896e9732d410dE3064c49f3d )
          • Axelar: Gateway.1c92115f( )
            • AxelarGateway.callContract( destinationChain=immutable, destinationContractAddress=0x4f49B53928A71E553bB1B0F66a5BcB54Fd4E8932, payload=0x87A7811F4BFEDEA3D341AD165680AE306B01AAEACC205D227629CF157DD9F8210000000000000000000000000000000000000000000000000000000000000EEE00000000000000000000000011487F9E7E06B592896E9732D410DE3064C49F3D000000000000000000000000329898A33227BC862C97366D51E7CD6FC9B4CB2E00000000000000000000000000000000000000000000000000D529AE9E860000 )
              File 1 of 7: TransparentUpgradeableProxy
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
              pragma solidity ^0.8.0;
              import "../ERC1967/ERC1967Proxy.sol";
              /**
               * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
               * does not implement this interface directly, and some of its functions are implemented by an internal dispatch
               * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
               * include them in the ABI so this interface must be used to interact with it.
               */
              interface ITransparentUpgradeableProxy is IERC1967 {
                  function admin() external view returns (address);
                  function implementation() external view returns (address);
                  function changeAdmin(address) external;
                  function upgradeTo(address) external;
                  function upgradeToAndCall(address, bytes memory) external payable;
              }
              /**
               * @dev This contract implements a proxy that is upgradeable by an admin.
               *
               * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
               * clashing], which can potentially be used in an attack, this contract uses the
               * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
               * things that go hand in hand:
               *
               * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
               * that call matches one of the admin functions exposed by the proxy itself.
               * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
               * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
               * "admin cannot fallback to proxy target".
               *
               * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
               * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
               * to sudden errors when trying to call a function from the proxy implementation.
               *
               * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
               * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
               *
               * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
               * inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch
               * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
               * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
               * implementation.
               *
               * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler
               * will not check that there are no selector conflicts, due to the note above. A selector clash between any new function
               * and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could
               * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised.
               */
              contract TransparentUpgradeableProxy is ERC1967Proxy {
                  /**
                   * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
                   * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
                   */
                  constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
                      _changeAdmin(admin_);
                  }
                  /**
                   * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
                   *
                   * CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the
                   * implementation provides a function with the same selector.
                   */
                  modifier ifAdmin() {
                      if (msg.sender == _getAdmin()) {
                          _;
                      } else {
                          _fallback();
                      }
                  }
                  /**
                   * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior
                   */
                  function _fallback() internal virtual override {
                      if (msg.sender == _getAdmin()) {
                          bytes memory ret;
                          bytes4 selector = msg.sig;
                          if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) {
                              ret = _dispatchUpgradeTo();
                          } else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
                              ret = _dispatchUpgradeToAndCall();
                          } else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) {
                              ret = _dispatchChangeAdmin();
                          } else if (selector == ITransparentUpgradeableProxy.admin.selector) {
                              ret = _dispatchAdmin();
                          } else if (selector == ITransparentUpgradeableProxy.implementation.selector) {
                              ret = _dispatchImplementation();
                          } else {
                              revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                          }
                          assembly {
                              return(add(ret, 0x20), mload(ret))
                          }
                      } else {
                          super._fallback();
                      }
                  }
                  /**
                   * @dev Returns the current admin.
                   *
                   * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                   * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                   * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                   */
                  function _dispatchAdmin() private returns (bytes memory) {
                      _requireZeroValue();
                      address admin = _getAdmin();
                      return abi.encode(admin);
                  }
                  /**
                   * @dev Returns the current implementation.
                   *
                   * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                   * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                   * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                   */
                  function _dispatchImplementation() private returns (bytes memory) {
                      _requireZeroValue();
                      address implementation = _implementation();
                      return abi.encode(implementation);
                  }
                  /**
                   * @dev Changes the admin of the proxy.
                   *
                   * Emits an {AdminChanged} event.
                   */
                  function _dispatchChangeAdmin() private returns (bytes memory) {
                      _requireZeroValue();
                      address newAdmin = abi.decode(msg.data[4:], (address));
                      _changeAdmin(newAdmin);
                      return "";
                  }
                  /**
                   * @dev Upgrade the implementation of the proxy.
                   */
                  function _dispatchUpgradeTo() private returns (bytes memory) {
                      _requireZeroValue();
                      address newImplementation = abi.decode(msg.data[4:], (address));
                      _upgradeToAndCall(newImplementation, bytes(""), false);
                      return "";
                  }
                  /**
                   * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
                   * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
                   * proxied contract.
                   */
                  function _dispatchUpgradeToAndCall() private returns (bytes memory) {
                      (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
                      _upgradeToAndCall(newImplementation, data, true);
                      return "";
                  }
                  /**
                   * @dev Returns the current admin.
                   *
                   * CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead.
                   */
                  function _admin() internal view virtual returns (address) {
                      return _getAdmin();
                  }
                  /**
                   * @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to
                   * emulate some proxy functions being non-payable while still allowing value to pass through.
                   */
                  function _requireZeroValue() private {
                      require(msg.value == 0);
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol)
              pragma solidity ^0.8.0;
              import "../Proxy.sol";
              import "./ERC1967Upgrade.sol";
              /**
               * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
               * implementation address that can be changed. This address is stored in storage in the location specified by
               * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
               * implementation behind the proxy.
               */
              contract ERC1967Proxy is Proxy, ERC1967Upgrade {
                  /**
                   * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
                   *
                   * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
                   * function call, and allows initializing the storage of the proxy like a Solidity constructor.
                   */
                  constructor(address _logic, bytes memory _data) payable {
                      _upgradeToAndCall(_logic, _data, false);
                  }
                  /**
                   * @dev Returns the current implementation address.
                   */
                  function _implementation() internal view virtual override returns (address impl) {
                      return ERC1967Upgrade._getImplementation();
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
               * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
               * be specified by overriding the virtual {_implementation} function.
               *
               * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
               * different contract through the {_delegate} function.
               *
               * The success and return data of the delegated call will be returned back to the caller of the proxy.
               */
              abstract contract Proxy {
                  /**
                   * @dev Delegates the current call to `implementation`.
                   *
                   * This function does not return to its internal call site, it will return directly to the external caller.
                   */
                  function _delegate(address implementation) internal virtual {
                      assembly {
                          // Copy msg.data. We take full control of memory in this inline assembly
                          // block because it will not return to Solidity code. We overwrite the
                          // Solidity scratch pad at memory position 0.
                          calldatacopy(0, 0, calldatasize())
                          // Call the implementation.
                          // out and outsize are 0 because we don't know the size yet.
                          let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                          // Copy the returned data.
                          returndatacopy(0, 0, returndatasize())
                          switch result
                          // delegatecall returns 0 on error.
                          case 0 {
                              revert(0, returndatasize())
                          }
                          default {
                              return(0, returndatasize())
                          }
                      }
                  }
                  /**
                   * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
                   * and {_fallback} should delegate.
                   */
                  function _implementation() internal view virtual returns (address);
                  /**
                   * @dev Delegates the current call to the address returned by `_implementation()`.
                   *
                   * This function does not return to its internal call site, it will return directly to the external caller.
                   */
                  function _fallback() internal virtual {
                      _beforeFallback();
                      _delegate(_implementation());
                  }
                  /**
                   * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
                   * function in the contract matches the call data.
                   */
                  fallback() external payable virtual {
                      _fallback();
                  }
                  /**
                   * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
                   * is empty.
                   */
                  receive() external payable virtual {
                      _fallback();
                  }
                  /**
                   * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
                   * call, or as part of the Solidity `fallback` or `receive` functions.
                   *
                   * If overridden should call `super._beforeFallback()`.
                   */
                  function _beforeFallback() internal virtual {}
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
              pragma solidity ^0.8.2;
              import "../beacon/IBeacon.sol";
              import "../../interfaces/IERC1967.sol";
              import "../../interfaces/draft-IERC1822.sol";
              import "../../utils/Address.sol";
              import "../../utils/StorageSlot.sol";
              /**
               * @dev This abstract contract provides getters and event emitting update functions for
               * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
               *
               * _Available since v4.1._
               */
              abstract contract ERC1967Upgrade is IERC1967 {
                  // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
                  bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
                  /**
                   * @dev Storage slot with the address of the current implementation.
                   * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                   * validated in the constructor.
                   */
                  bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                  /**
                   * @dev Returns the current implementation address.
                   */
                  function _getImplementation() internal view returns (address) {
                      return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                  }
                  /**
                   * @dev Stores a new address in the EIP1967 implementation slot.
                   */
                  function _setImplementation(address newImplementation) private {
                      require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                      StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                  }
                  /**
                   * @dev Perform implementation upgrade
                   *
                   * Emits an {Upgraded} event.
                   */
                  function _upgradeTo(address newImplementation) internal {
                      _setImplementation(newImplementation);
                      emit Upgraded(newImplementation);
                  }
                  /**
                   * @dev Perform implementation upgrade with additional setup call.
                   *
                   * Emits an {Upgraded} event.
                   */
                  function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
                      _upgradeTo(newImplementation);
                      if (data.length > 0 || forceCall) {
                          Address.functionDelegateCall(newImplementation, data);
                      }
                  }
                  /**
                   * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
                   *
                   * Emits an {Upgraded} event.
                   */
                  function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
                      // Upgrades from old implementations will perform a rollback test. This test requires the new
                      // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
                      // this special case will break upgrade paths from old UUPS implementation to new ones.
                      if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
                          _setImplementation(newImplementation);
                      } else {
                          try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                              require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
                          } catch {
                              revert("ERC1967Upgrade: new implementation is not UUPS");
                          }
                          _upgradeToAndCall(newImplementation, data, forceCall);
                      }
                  }
                  /**
                   * @dev Storage slot with the admin of the contract.
                   * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                   * validated in the constructor.
                   */
                  bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                  /**
                   * @dev Returns the current admin.
                   */
                  function _getAdmin() internal view returns (address) {
                      return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
                  }
                  /**
                   * @dev Stores a new address in the EIP1967 admin slot.
                   */
                  function _setAdmin(address newAdmin) private {
                      require(newAdmin != address(0), "ERC1967: new admin is the zero address");
                      StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
                  }
                  /**
                   * @dev Changes the admin of the proxy.
                   *
                   * Emits an {AdminChanged} event.
                   */
                  function _changeAdmin(address newAdmin) internal {
                      emit AdminChanged(_getAdmin(), newAdmin);
                      _setAdmin(newAdmin);
                  }
                  /**
                   * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
                   * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
                   */
                  bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
                  /**
                   * @dev Returns the current beacon.
                   */
                  function _getBeacon() internal view returns (address) {
                      return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
                  }
                  /**
                   * @dev Stores a new beacon in the EIP1967 beacon slot.
                   */
                  function _setBeacon(address newBeacon) private {
                      require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
                      require(
                          Address.isContract(IBeacon(newBeacon).implementation()),
                          "ERC1967: beacon implementation is not a contract"
                      );
                      StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
                  }
                  /**
                   * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
                   * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
                   *
                   * Emits a {BeaconUpgraded} event.
                   */
                  function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
                      _setBeacon(newBeacon);
                      emit BeaconUpgraded(newBeacon);
                      if (data.length > 0 || forceCall) {
                          Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev This is the interface that {BeaconProxy} expects of its beacon.
               */
              interface IBeacon {
                  /**
                   * @dev Must return an address that can be used as a delegate call target.
                   *
                   * {BeaconProxy} will check that this address is a contract.
                   */
                  function implementation() external view returns (address);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
               *
               * _Available since v4.8.3._
               */
              interface IERC1967 {
                  /**
                   * @dev Emitted when the implementation is upgraded.
                   */
                  event Upgraded(address indexed implementation);
                  /**
                   * @dev Emitted when the admin account has changed.
                   */
                  event AdminChanged(address previousAdmin, address newAdmin);
                  /**
                   * @dev Emitted when the beacon is changed.
                   */
                  event BeaconUpgraded(address indexed beacon);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
               * proxy whose upgrades are fully controlled by the current implementation.
               */
              interface IERC1822Proxiable {
                  /**
                   * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
                   * address.
                   *
                   * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
                   * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
                   * function revert if invoked through a proxy.
                   */
                  function proxiableUUID() external view returns (bytes32);
              }
              // 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);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
              // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
              pragma solidity ^0.8.0;
              /**
               * @dev Library for reading and writing primitive types to specific storage slots.
               *
               * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
               * This library helps with reading and writing to such slots without the need for inline assembly.
               *
               * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
               *
               * Example usage to set ERC1967 implementation slot:
               * ```solidity
               * contract ERC1967 {
               *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
               *
               *     function _getImplementation() internal view returns (address) {
               *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
               *     }
               *
               *     function _setImplementation(address newImplementation) internal {
               *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
               *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
               *     }
               * }
               * ```
               *
               * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
               * _Available since v4.9 for `string`, `bytes`._
               */
              library StorageSlot {
                  struct AddressSlot {
                      address value;
                  }
                  struct BooleanSlot {
                      bool value;
                  }
                  struct Bytes32Slot {
                      bytes32 value;
                  }
                  struct Uint256Slot {
                      uint256 value;
                  }
                  struct StringSlot {
                      string value;
                  }
                  struct BytesSlot {
                      bytes value;
                  }
                  /**
                   * @dev Returns an `AddressSlot` with member `value` located at `slot`.
                   */
                  function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
                   */
                  function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
                   */
                  function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
                   */
                  function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `StringSlot` with member `value` located at `slot`.
                   */
                  function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
                   */
                  function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := store.slot
                      }
                  }
                  /**
                   * @dev Returns an `BytesSlot` with member `value` located at `slot`.
                   */
                  function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
                   */
                  function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := store.slot
                      }
                  }
              }
              

              File 2 of 7: AxelarGasServiceProxy
              // Sources flattened with hardhat v2.9.9 https://hardhat.org
              
              // File contracts/interfaces/IUpgradable.sol
              
              // SPDX-License-Identifier: MIT
              
              pragma solidity 0.8.9;
              
              // General interface for upgradable contracts
              interface IUpgradable {
                  error NotOwner();
                  error InvalidOwner();
                  error InvalidCodeHash();
                  error InvalidImplementation();
                  error SetupFailed();
                  error NotProxy();
              
                  event Upgraded(address indexed newImplementation);
                  event OwnershipTransferred(address indexed newOwner);
              
                  // Get current owner
                  function owner() external view returns (address);
              
                  function contractId() external pure returns (bytes32);
              
                  function implementation() external view returns (address);
              
                  function upgrade(
                      address newImplementation,
                      bytes32 newImplementationCodeHash,
                      bytes calldata params
                  ) external;
              
                  function setup(bytes calldata data) external;
              }
              
              
              // File contracts/util/Proxy.sol
              
              contract Proxy {
                  error InvalidImplementation();
                  error SetupFailed();
                  error EtherNotAccepted();
                  error NotOwner();
                  error AlreadyInitialized();
              
                  // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
                  bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                  // keccak256('owner')
                  bytes32 internal constant _OWNER_SLOT = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0;
              
                  constructor() {
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          sstore(_OWNER_SLOT, caller())
                      }
                  }
              
                  function init(
                      address implementationAddress,
                      address newOwner,
                      bytes memory params
                  ) external {
                      address owner;
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          owner := sload(_OWNER_SLOT)
                      }
                      if (msg.sender != owner) revert NotOwner();
                      if (implementation() != address(0)) revert AlreadyInitialized();
                      if (IUpgradable(implementationAddress).contractId() != contractId()) revert InvalidImplementation();
              
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          sstore(_IMPLEMENTATION_SLOT, implementationAddress)
                          sstore(_OWNER_SLOT, newOwner)
                      }
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, ) = implementationAddress.delegatecall(
                          // keccak('setup(bytes)') selector
                          abi.encodeWithSelector(0x9ded06df, params)
                      );
                      if (!success) revert SetupFailed();
                  }
              
                  // solhint-disable-next-line no-empty-blocks
                  function contractId() internal pure virtual returns (bytes32) {}
              
                  function implementation() public view returns (address implementation_) {
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          implementation_ := sload(_IMPLEMENTATION_SLOT)
                      }
                  }
              
                  // solhint-disable-next-line no-empty-blocks
                  function setup(bytes calldata data) public {}
              
                  // solhint-disable-next-line no-complex-fallback
                  fallback() external payable {
                      address implementaion_ = implementation();
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          calldatacopy(0, 0, calldatasize())
              
                          let result := delegatecall(gas(), implementaion_, 0, calldatasize(), 0, 0)
                          returndatacopy(0, 0, returndatasize())
              
                          switch result
                          case 0 {
                              revert(0, returndatasize())
                          }
                          default {
                              return(0, returndatasize())
                          }
                      }
                  }
              
                  receive() external payable virtual {
                      revert EtherNotAccepted();
                  }
              }
              
              
              // File contracts/gas-service/AxelarGasServiceProxy.sol
              
              contract AxelarGasServiceProxy is Proxy {
                  function contractId() internal pure override returns (bytes32) {
                      return keccak256('axelar-gas-service');
                  }
              }

              File 3 of 7: TransparentUpgradeableProxy
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
              pragma solidity ^0.8.0;
              import "../ERC1967/ERC1967Proxy.sol";
              /**
               * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
               * does not implement this interface directly, and some of its functions are implemented by an internal dispatch
               * mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
               * include them in the ABI so this interface must be used to interact with it.
               */
              interface ITransparentUpgradeableProxy is IERC1967 {
                  function admin() external view returns (address);
                  function implementation() external view returns (address);
                  function changeAdmin(address) external;
                  function upgradeTo(address) external;
                  function upgradeToAndCall(address, bytes memory) external payable;
              }
              /**
               * @dev This contract implements a proxy that is upgradeable by an admin.
               *
               * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
               * clashing], which can potentially be used in an attack, this contract uses the
               * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
               * things that go hand in hand:
               *
               * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
               * that call matches one of the admin functions exposed by the proxy itself.
               * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
               * implementation. If the admin tries to call a function on the implementation it will fail with an error that says
               * "admin cannot fallback to proxy target".
               *
               * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
               * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
               * to sudden errors when trying to call a function from the proxy implementation.
               *
               * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
               * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
               *
               * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
               * inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch
               * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
               * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
               * implementation.
               *
               * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler
               * will not check that there are no selector conflicts, due to the note above. A selector clash between any new function
               * and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could
               * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised.
               */
              contract TransparentUpgradeableProxy is ERC1967Proxy {
                  /**
                   * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
                   * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
                   */
                  constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
                      _changeAdmin(admin_);
                  }
                  /**
                   * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
                   *
                   * CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the
                   * implementation provides a function with the same selector.
                   */
                  modifier ifAdmin() {
                      if (msg.sender == _getAdmin()) {
                          _;
                      } else {
                          _fallback();
                      }
                  }
                  /**
                   * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior
                   */
                  function _fallback() internal virtual override {
                      if (msg.sender == _getAdmin()) {
                          bytes memory ret;
                          bytes4 selector = msg.sig;
                          if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) {
                              ret = _dispatchUpgradeTo();
                          } else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
                              ret = _dispatchUpgradeToAndCall();
                          } else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) {
                              ret = _dispatchChangeAdmin();
                          } else if (selector == ITransparentUpgradeableProxy.admin.selector) {
                              ret = _dispatchAdmin();
                          } else if (selector == ITransparentUpgradeableProxy.implementation.selector) {
                              ret = _dispatchImplementation();
                          } else {
                              revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
                          }
                          assembly {
                              return(add(ret, 0x20), mload(ret))
                          }
                      } else {
                          super._fallback();
                      }
                  }
                  /**
                   * @dev Returns the current admin.
                   *
                   * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                   * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                   * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
                   */
                  function _dispatchAdmin() private returns (bytes memory) {
                      _requireZeroValue();
                      address admin = _getAdmin();
                      return abi.encode(admin);
                  }
                  /**
                   * @dev Returns the current implementation.
                   *
                   * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
                   * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
                   * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
                   */
                  function _dispatchImplementation() private returns (bytes memory) {
                      _requireZeroValue();
                      address implementation = _implementation();
                      return abi.encode(implementation);
                  }
                  /**
                   * @dev Changes the admin of the proxy.
                   *
                   * Emits an {AdminChanged} event.
                   */
                  function _dispatchChangeAdmin() private returns (bytes memory) {
                      _requireZeroValue();
                      address newAdmin = abi.decode(msg.data[4:], (address));
                      _changeAdmin(newAdmin);
                      return "";
                  }
                  /**
                   * @dev Upgrade the implementation of the proxy.
                   */
                  function _dispatchUpgradeTo() private returns (bytes memory) {
                      _requireZeroValue();
                      address newImplementation = abi.decode(msg.data[4:], (address));
                      _upgradeToAndCall(newImplementation, bytes(""), false);
                      return "";
                  }
                  /**
                   * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
                   * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
                   * proxied contract.
                   */
                  function _dispatchUpgradeToAndCall() private returns (bytes memory) {
                      (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
                      _upgradeToAndCall(newImplementation, data, true);
                      return "";
                  }
                  /**
                   * @dev Returns the current admin.
                   *
                   * CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead.
                   */
                  function _admin() internal view virtual returns (address) {
                      return _getAdmin();
                  }
                  /**
                   * @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to
                   * emulate some proxy functions being non-payable while still allowing value to pass through.
                   */
                  function _requireZeroValue() private {
                      require(msg.value == 0);
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol)
              pragma solidity ^0.8.0;
              import "../Proxy.sol";
              import "./ERC1967Upgrade.sol";
              /**
               * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
               * implementation address that can be changed. This address is stored in storage in the location specified by
               * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
               * implementation behind the proxy.
               */
              contract ERC1967Proxy is Proxy, ERC1967Upgrade {
                  /**
                   * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
                   *
                   * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
                   * function call, and allows initializing the storage of the proxy like a Solidity constructor.
                   */
                  constructor(address _logic, bytes memory _data) payable {
                      _upgradeToAndCall(_logic, _data, false);
                  }
                  /**
                   * @dev Returns the current implementation address.
                   */
                  function _implementation() internal view virtual override returns (address impl) {
                      return ERC1967Upgrade._getImplementation();
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
               * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
               * be specified by overriding the virtual {_implementation} function.
               *
               * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
               * different contract through the {_delegate} function.
               *
               * The success and return data of the delegated call will be returned back to the caller of the proxy.
               */
              abstract contract Proxy {
                  /**
                   * @dev Delegates the current call to `implementation`.
                   *
                   * This function does not return to its internal call site, it will return directly to the external caller.
                   */
                  function _delegate(address implementation) internal virtual {
                      assembly {
                          // Copy msg.data. We take full control of memory in this inline assembly
                          // block because it will not return to Solidity code. We overwrite the
                          // Solidity scratch pad at memory position 0.
                          calldatacopy(0, 0, calldatasize())
                          // Call the implementation.
                          // out and outsize are 0 because we don't know the size yet.
                          let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
                          // Copy the returned data.
                          returndatacopy(0, 0, returndatasize())
                          switch result
                          // delegatecall returns 0 on error.
                          case 0 {
                              revert(0, returndatasize())
                          }
                          default {
                              return(0, returndatasize())
                          }
                      }
                  }
                  /**
                   * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
                   * and {_fallback} should delegate.
                   */
                  function _implementation() internal view virtual returns (address);
                  /**
                   * @dev Delegates the current call to the address returned by `_implementation()`.
                   *
                   * This function does not return to its internal call site, it will return directly to the external caller.
                   */
                  function _fallback() internal virtual {
                      _beforeFallback();
                      _delegate(_implementation());
                  }
                  /**
                   * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
                   * function in the contract matches the call data.
                   */
                  fallback() external payable virtual {
                      _fallback();
                  }
                  /**
                   * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
                   * is empty.
                   */
                  receive() external payable virtual {
                      _fallback();
                  }
                  /**
                   * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
                   * call, or as part of the Solidity `fallback` or `receive` functions.
                   *
                   * If overridden should call `super._beforeFallback()`.
                   */
                  function _beforeFallback() internal virtual {}
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
              pragma solidity ^0.8.2;
              import "../beacon/IBeacon.sol";
              import "../../interfaces/IERC1967.sol";
              import "../../interfaces/draft-IERC1822.sol";
              import "../../utils/Address.sol";
              import "../../utils/StorageSlot.sol";
              /**
               * @dev This abstract contract provides getters and event emitting update functions for
               * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
               *
               * _Available since v4.1._
               */
              abstract contract ERC1967Upgrade is IERC1967 {
                  // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
                  bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
                  /**
                   * @dev Storage slot with the address of the current implementation.
                   * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                   * validated in the constructor.
                   */
                  bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                  /**
                   * @dev Returns the current implementation address.
                   */
                  function _getImplementation() internal view returns (address) {
                      return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
                  }
                  /**
                   * @dev Stores a new address in the EIP1967 implementation slot.
                   */
                  function _setImplementation(address newImplementation) private {
                      require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
                      StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
                  }
                  /**
                   * @dev Perform implementation upgrade
                   *
                   * Emits an {Upgraded} event.
                   */
                  function _upgradeTo(address newImplementation) internal {
                      _setImplementation(newImplementation);
                      emit Upgraded(newImplementation);
                  }
                  /**
                   * @dev Perform implementation upgrade with additional setup call.
                   *
                   * Emits an {Upgraded} event.
                   */
                  function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
                      _upgradeTo(newImplementation);
                      if (data.length > 0 || forceCall) {
                          Address.functionDelegateCall(newImplementation, data);
                      }
                  }
                  /**
                   * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
                   *
                   * Emits an {Upgraded} event.
                   */
                  function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
                      // Upgrades from old implementations will perform a rollback test. This test requires the new
                      // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
                      // this special case will break upgrade paths from old UUPS implementation to new ones.
                      if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
                          _setImplementation(newImplementation);
                      } else {
                          try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
                              require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
                          } catch {
                              revert("ERC1967Upgrade: new implementation is not UUPS");
                          }
                          _upgradeToAndCall(newImplementation, data, forceCall);
                      }
                  }
                  /**
                   * @dev Storage slot with the admin of the contract.
                   * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                   * validated in the constructor.
                   */
                  bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                  /**
                   * @dev Returns the current admin.
                   */
                  function _getAdmin() internal view returns (address) {
                      return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
                  }
                  /**
                   * @dev Stores a new address in the EIP1967 admin slot.
                   */
                  function _setAdmin(address newAdmin) private {
                      require(newAdmin != address(0), "ERC1967: new admin is the zero address");
                      StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
                  }
                  /**
                   * @dev Changes the admin of the proxy.
                   *
                   * Emits an {AdminChanged} event.
                   */
                  function _changeAdmin(address newAdmin) internal {
                      emit AdminChanged(_getAdmin(), newAdmin);
                      _setAdmin(newAdmin);
                  }
                  /**
                   * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
                   * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
                   */
                  bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
                  /**
                   * @dev Returns the current beacon.
                   */
                  function _getBeacon() internal view returns (address) {
                      return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
                  }
                  /**
                   * @dev Stores a new beacon in the EIP1967 beacon slot.
                   */
                  function _setBeacon(address newBeacon) private {
                      require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
                      require(
                          Address.isContract(IBeacon(newBeacon).implementation()),
                          "ERC1967: beacon implementation is not a contract"
                      );
                      StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
                  }
                  /**
                   * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
                   * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
                   *
                   * Emits a {BeaconUpgraded} event.
                   */
                  function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
                      _setBeacon(newBeacon);
                      emit BeaconUpgraded(newBeacon);
                      if (data.length > 0 || forceCall) {
                          Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev This is the interface that {BeaconProxy} expects of its beacon.
               */
              interface IBeacon {
                  /**
                   * @dev Must return an address that can be used as a delegate call target.
                   *
                   * {BeaconProxy} will check that this address is a contract.
                   */
                  function implementation() external view returns (address);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
               *
               * _Available since v4.8.3._
               */
              interface IERC1967 {
                  /**
                   * @dev Emitted when the implementation is upgraded.
                   */
                  event Upgraded(address indexed implementation);
                  /**
                   * @dev Emitted when the admin account has changed.
                   */
                  event AdminChanged(address previousAdmin, address newAdmin);
                  /**
                   * @dev Emitted when the beacon is changed.
                   */
                  event BeaconUpgraded(address indexed beacon);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
               * proxy whose upgrades are fully controlled by the current implementation.
               */
              interface IERC1822Proxiable {
                  /**
                   * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
                   * address.
                   *
                   * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
                   * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
                   * function revert if invoked through a proxy.
                   */
                  function proxiableUUID() external view returns (bytes32);
              }
              // 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);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
              // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
              pragma solidity ^0.8.0;
              /**
               * @dev Library for reading and writing primitive types to specific storage slots.
               *
               * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
               * This library helps with reading and writing to such slots without the need for inline assembly.
               *
               * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
               *
               * Example usage to set ERC1967 implementation slot:
               * ```solidity
               * contract ERC1967 {
               *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
               *
               *     function _getImplementation() internal view returns (address) {
               *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
               *     }
               *
               *     function _setImplementation(address newImplementation) internal {
               *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
               *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
               *     }
               * }
               * ```
               *
               * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
               * _Available since v4.9 for `string`, `bytes`._
               */
              library StorageSlot {
                  struct AddressSlot {
                      address value;
                  }
                  struct BooleanSlot {
                      bool value;
                  }
                  struct Bytes32Slot {
                      bytes32 value;
                  }
                  struct Uint256Slot {
                      uint256 value;
                  }
                  struct StringSlot {
                      string value;
                  }
                  struct BytesSlot {
                      bytes value;
                  }
                  /**
                   * @dev Returns an `AddressSlot` with member `value` located at `slot`.
                   */
                  function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
                   */
                  function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
                   */
                  function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
                   */
                  function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `StringSlot` with member `value` located at `slot`.
                   */
                  function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
                   */
                  function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := store.slot
                      }
                  }
                  /**
                   * @dev Returns an `BytesSlot` with member `value` located at `slot`.
                   */
                  function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := slot
                      }
                  }
                  /**
                   * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
                   */
                  function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          r.slot := store.slot
                      }
                  }
              }
              

              File 4 of 7: RootERC20BridgeFlowRate
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import "./FlowRateDetection.sol";
              import "./FlowRateWithdrawalQueue.sol";
              import "../RootERC20Bridge.sol";
              import {
                  IRootERC20BridgeFlowRateEvents,
                  IRootERC20BridgeFlowRateErrors
              } from "../../interfaces/root/flowrate/IRootERC20BridgeFlowRate.sol";
              /**
               * @title  Root ERC 20 Bridge Flow Rate
               * @author Immutable Pty Ltd (Peter Robinson @drinkcoffee, Craig MacGregor @proletesseract)
               * @notice Adds security features to the `RootERC20Bridge` implementation help prevent or reduce the scope of attacks.
               * @dev    Features: In addition the features of the `RootERC20Bridge`, this contract adds the following capabilities:
               *         - A withdrawal queue is defined. In certain situations, a crosschain transfer results
               *           in a withdrawal being put in a "queue". Users can withdraw the amount after a delay.
               *           The default delay is one day. The queue is implemented as an array. Users can choose
               *           which queued withdrawals they want to process.
               *         - An account with `RATE` role can enable or disable the withdrawal queue, and configure
               *           parameters for each token related to the withdrawal queue.
               *         - Withdrawals of tokens whose amount is greater than a token specific threshold are
               *           put into the withdrawal queue. This just affects an individual withdrawal. It does
               *           not affect other withdrawals by that user or by any other user.
               *         - Withdrawals are put into the withdrawal queue when no thresholds have been defined
               *           for the token being withdrawn. This just affects an individual withdrawal. It does
               *           not affect other withdrawals by that user or by any other user.
               *         - If the rate of withdrawal of any token is over a token specific threshold, then all
               *           withdrwals are put into the withdrawal queue.
               *
               *         Almost all users will be unaffected by these changes, and for them the bridge will
               *         appear to operate as if `RootERC20Bridge` contract had been deployed. That is,
               *         almost all withdrawals will not go into a withdrawal queue if the
               *         RootERC20BridgeFlowRate contract has been configured for all tokens
               *         that are likely to be used on the bridge with appropriate thresholds.
               *
               *         Note: This is an upgradeable contract that should be operated behind OpenZeppelin's
               *               TransparentUpgradeableProxy.sol.
               *         Note: The initialize function is susceptible to front running. To prevent this,
               *               call the function when TransparentUpgradeableProxy.sol is deployed by passing
               *               in the function call data in TransparentUpgradeableProxy's constructor.
               *               Alternatively, deploy TransparentUpgradeableProxy, call initialize, and then
               *               check the contract's configuration. If the initialize call has been front run,
               *               deploy a new instance of TransparentUpgradeableProxy and repeat the process.
               *         Note: Administrators should take care when configuring flow rate values for tokens.
               *               The configuration should be reviewed from time to time. For example, an
               *               attacker could cheaply trigger the withdrawal queue if one token has a flow rate
               *               configuration (capacity and refill rate) that has a substantially lower value
               *               than other tokens. The attacker could trigger the withdrawal queue, hope that
               *               administrators will reduce the withdrawal delay, to expedite the clearance of
               *               legitimate transactions in the withdrawal queue, and then execute their main
               *               attack, withdrawing a large sum via some other unrelated bridge attack. The
               *               way to protect against this is to have flow rate configurations for all
               *               configured tokens to be for similar amounts and be wary of reducing the withdrawal
               *               delay.
               *               Another consideration is that an attacker could observe pending withdrawals,
               *               and grief a legitimate user's withdrawal. They could do this by withdrawing a
               *               sum, which when combined with the users, is just about the flow rate that will
               *               trigger the withdrawal queue, immediately before the legitimate user. The
               *               legitimate user's withdrawal would then trigger the withdrawal queue. The
               *               mitigation for this is to make the flow rate configurations such that an
               *               attacker would need to invest substantial amounts to attack the system in
               *               this way. This requires monitoring what typical bridge flows are, and
               *               understanding how they change with time.
               */
              contract RootERC20BridgeFlowRate is
                  RootERC20Bridge,
                  FlowRateDetection,
                  FlowRateWithdrawalQueue,
                  IRootERC20BridgeFlowRateEvents,
                  IRootERC20BridgeFlowRateErrors
              {
                  // Constants used for access control
                  bytes32 private constant RATE_CONTROL_ROLE = keccak256("RATE");
                  // Threshold for large transfers
                  // Map ERC 20 token address to threshold
                  mapping(address => uint256) public largeTransferThresholds;
                  constructor(address _initializerAddress) RootERC20Bridge(_initializerAddress) {}
                  function initialize(
                      InitializationRoles memory newRoles,
                      address newRootBridgeAdaptor,
                      address newChildERC20Bridge,
                      address newChildTokenTemplate,
                      address newRootIMXToken,
                      address newRootWETHToken,
                      uint256 newImxCumulativeDepositLimit,
                      address rateAdmin
                  ) external initializer {
                      if (rateAdmin == address(0)) {
                          revert ZeroAddress();
                      }
                      __RootERC20Bridge_init(
                          newRoles,
                          newRootBridgeAdaptor,
                          newChildERC20Bridge,
                          newChildTokenTemplate,
                          newRootIMXToken,
                          newRootWETHToken,
                          newImxCumulativeDepositLimit
                      );
                      __FlowRateWithdrawalQueue_init();
                      _grantRole(RATE_CONTROL_ROLE, rateAdmin);
                  }
                  // Ensure initialize from RootERC20Bridge can not be called.
                  function initialize(InitializationRoles memory, address, address, address, address, address, uint256)
                      external
                      pure
                      override
                  {
                      revert WrongInitializer();
                  }
                  /**
                   * @notice Activate the withdrawal queue for all tokens.
                   * @dev This function manually activates the withdrawal queue. However the
                   *      queue is automatically activated when the flow rate detection code
                   *      determines that there is a large outflow of any token.
                   *      Only RATE role.
                   */
                  function activateWithdrawalQueue() external onlyRole(RATE_CONTROL_ROLE) {
                      _activateWithdrawalQueue();
                  }
                  /**
                   * @notice Deactivate the withdrawal queue for all tokens.
                   * @dev This function manually deactivates the withdrawal queue.
                   *      Only RATE role.
                   */
                  function deactivateWithdrawalQueue() external onlyRole(RATE_CONTROL_ROLE) {
                      _deactivateWithdrawalQueue();
                  }
                  /**
                   * @notice Set the time in the queue for queued withdrawals.
                   * @param delay The number of seconds between when the AxelarAdapter is called to
                   *         complete a crosschain transfer.
                   * @dev Only RATE role.
                   * NOTE: There is no range checking on delay. Delay could be inadvertently be set to
                   *       a very large value, representing a large delay. If this is done, the withdrawal
                   *       delay should be set again.
                   *       Another possible scenario is that the withdrawal queue has been enabled
                   *       inadvertently. In this case, the withdrawal queue could be disabled,
                   *       and the withdrawal delay could be set to 0, the queued withdrawals could
                   *       all be finalised, and then the withdrawal delay could be set to the desired
                   *       value.
                   */
                  function setWithdrawalDelay(uint256 delay) external onlyRole(RATE_CONTROL_ROLE) {
                      _setWithdrawalDelay(delay);
                  }
                  /**
                   * @notice Set the thresholds to use for a certain token.
                   * @param token The token to apply the thresholds to.
                   * @param capacity The size of the bucket in tokens.
                   * @param refillRate How quickly the bucket refills in tokens per second.
                   * @param largeTransferThreshold Threshold over which a withdrawal is deemed to be large,
                   *         and will be put in the withdrawal queue.
                   * @dev Only RATE role.
                   *
                   * Example parameter values:
                   *  Assume the desired configuration is:
                   *  - large transfer threshold is 100,000 IMX.
                   *  - high flow rate threshold is 1,000,000 IMX per hour.
                   *  Further assume the ERC 20 contract has been configured with 18 decimals. This is true
                   *  for IMX and MATIC.
                   *
                   *  The capacity should be set to the flow rate number. In this example, 1,000,000 IMX.
                   *  The refill rate should be the capacity divided by the flow rate period in seconds.
                   *   In this example, 1,000,000 IMX divided by 3600 seconds in an hour.
                   *
                   *  Hence, the configuration should be set to:
                   *  - capacity = 1,000,000,000,000,000,000,000,000
                   *  - refillRate =     277,777,777,777,777,777,777
                   *  - largeTransferThreshold = 100,000,000,000,000,000,000,000
                   *
                   * NOTE: Beyond checking that capacity and refillRate are not zero, no range checking is
                   *       undertaken on capacity, refillRate, or largeTransferThreshold. Setting
                   *       largeTransferThreshold to zero (or a very low value) would cause all (or most)
                   *       withdrawals to be put the with withdrawal queue. Having a very large value for
                   *       largeTransferThreshold or capacity will either reduce the effectiveness of
                   *       the flow rate detection mechanism or prevent it from working altogether. As such,
                   *       the values of capacity, refillRate and largeTransferThreshold should be chosen
                   *       carefully.
                   */
                  function setRateControlThreshold(
                      address token,
                      uint256 capacity,
                      uint256 refillRate,
                      uint256 largeTransferThreshold
                  ) external onlyRole(RATE_CONTROL_ROLE) {
                      uint256 previousCapacity = flowRateBuckets[token].capacity;
                      uint256 previousRefillRate = flowRateBuckets[token].refillRate;
                      uint256 previousLargeTransferThreshold = largeTransferThresholds[token];
                      _setFlowRateThreshold(token, capacity, refillRate);
                      largeTransferThresholds[token] = largeTransferThreshold;
                      emit RateControlThresholdSet(
                          token,
                          capacity,
                          refillRate,
                          largeTransferThreshold,
                          previousCapacity,
                          previousRefillRate,
                          previousLargeTransferThreshold
                      );
                  }
                  /**
                   * @notice Complete crosschain transfer of funds.
                   * @param data Contains the crosschain transfer information:
                   *         - token: Token address on the root chain.
                   *         - withdrawer: Account that initiated the transfer on the child chain.
                   *         - receiver: Account to transfer tokens to.
                   *         - amount: The number of tokens to transfer.
                   * @dev Called by the AxelarAdapter.
                   *      Only when not paused.
                   */
                  function _withdraw(bytes memory data) internal override {
                      (address rootToken, address childToken, address withdrawer, address receiver, uint256 amount) =
                          _decodeAndValidateWithdrawal(data);
                      // Update the flow rate checking. Delay the withdrawal if the request was
                      // for a token that has not been configured.
                      bool delayWithdrawalUnknownToken = _updateFlowRateBucket(rootToken, amount);
                      bool delayWithdrawalLargeAmount = false;
                      // Delay the withdrawal if the amount is greater than the threshold.
                      if (!delayWithdrawalUnknownToken) {
                          delayWithdrawalLargeAmount = (amount >= largeTransferThresholds[rootToken]);
                      }
                      // Ensure storage variable is cached on the stack.
                      bool queueActivated = withdrawalQueueActivated;
                      if (delayWithdrawalLargeAmount || delayWithdrawalUnknownToken || queueActivated) {
                          _enqueueWithdrawal(receiver, withdrawer, rootToken, amount);
                          emit QueuedWithdrawal(
                              rootToken,
                              withdrawer,
                              receiver,
                              amount,
                              delayWithdrawalLargeAmount,
                              delayWithdrawalUnknownToken,
                              queueActivated
                          );
                      } else {
                          _executeTransfer(rootToken, childToken, withdrawer, receiver, amount);
                      }
                  }
                  /**
                   * @notice Withdraw a queued withdrawal.
                   * @param receiver Address to withdraw value for.
                   * @param index Offset into array of queued withdrawals.
                   * @dev Only when not paused.
                   */
                  function finaliseQueuedWithdrawal(address receiver, uint256 index) external nonReentrant {
                      (address withdrawer, address token, uint256 amount) = _processWithdrawal(receiver, index);
                      address childToken = rootTokenToChildToken[token];
                      _executeTransfer(token, childToken, withdrawer, receiver, amount);
                  }
                  /**
                   * @notice Withdraw one or more queued withdrawals for a specific token, aggregating the amounts.
                   * @param receiver Address to withdraw value for.
                   * @param token Token to do the aggregated withdrawal for.
                   * @param indices Offsets into array of queued withdrawals.
                   * @dev Only when not paused.
                   *   Note that withdrawer in the ERC20Withdraw event emitted in the _executeTransfer function
                   *   will represent the withdrawer of the last bridge transfer.
                   */
                  function finaliseQueuedWithdrawalsAggregated(address receiver, address token, uint256[] calldata indices)
                      external
                      nonReentrant
                  {
                      if (indices.length == 0) {
                          revert ProvideAtLeastOneIndex();
                      }
                      uint256 total = 0;
                      address withdrawer = address(0);
                      for (uint256 i = 0; i < indices.length; i++) {
                          address actualToken;
                          uint256 amount;
                          (withdrawer, actualToken, amount) = _processWithdrawal(receiver, indices[i]);
                          if (actualToken != token) {
                              revert MixedTokens(token, actualToken);
                          }
                          total += amount;
                      }
                      address childToken = rootTokenToChildToken[token];
                      _executeTransfer(token, childToken, withdrawer, receiver, total);
                  }
                  // slither-disable-next-line unused-state,naming-convention
                  uint256[50] private __gapRootERC20BridgeFlowRate;
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              /**
               * @title  Flow Rate Detection
               * @author Immutable Pty Ltd (Peter Robinson @drinkcoffee and Zhenyang Shi @wcgcyx)
               * @notice Detects large flows of tokens using a bucket system.
               * @dev    Each token has a bucket. The bucket is filled at a constant rate: a number of
               *         tokens per second. The bucket empties each time there is a withdrawal. Withdrawal
               *         requests for tokens that don't have a configured bucket are delayed.
               *         Note: This code is part of RootERC20BridgeFlowRate. It has been separated out
               *         to make it easier to understand and test the functionality.
               *         Note that this contract is upgradeable.
               */
              abstract contract FlowRateDetection {
                  // Holds flow rate information for a single token.
                  struct Bucket {
                      // The number of tokens that can fit in the bucket.
                      // A capacity of zero indicates that flow rate detection is not configured for the token.
                      uint256 capacity;
                      // The number of tokens in the bucket.
                      uint256 depth;
                      // The last time the bucket was updated.
                      uint256 refillTime;
                      // The number of tokens added per second.
                      uint256 refillRate;
                  }
                  // Map ERC 20 token address to buckets
                  mapping(address => Bucket) public flowRateBuckets;
                  // True if all tokens should be put in the withdrawal queue.
                  bool public withdrawalQueueActivated;
                  // Emitted when there is a withdrawal request for a token for which there is no bucket.
                  event WithdrawalForNonFlowRatedToken(address indexed token, uint256 amount);
                  // Emitted when queue activated or deactivated
                  event AutoActivatedWithdrawalQueue();
                  event ActivatedWithdrawalQueue(address who);
                  event DeactivatedWithdrawalQueue(address who);
                  error InvalidToken();
                  error InvalidCapacity();
                  error InvalidRefillRate();
                  /**
                   * @notice Activate the withdrawal queue for all tokens.
                   */
                  function _activateWithdrawalQueue() internal {
                      withdrawalQueueActivated = true;
                      emit ActivatedWithdrawalQueue(msg.sender);
                  }
                  /**
                   * @notice Deactivate the withdrawal queue for all tokens.
                   * @dev This does not affect withdrawals already in the queue.
                   */
                  function _deactivateWithdrawalQueue() internal {
                      withdrawalQueueActivated = false;
                      emit DeactivatedWithdrawalQueue(msg.sender);
                  }
                  /**
                   * @notice Initialise or update a bucket for a token.
                   * @param token Address of the token to configure the bucket for.
                   * @param capacity The number of tokens before the bucket overflows.
                   * @param refillRate The number of tokens added to the bucket each second.
                   * @dev If this is a new bucket, then the depth is the capacity. If the bucket is existing, then
                   *      the depth is not altered.
                   */
                  function _setFlowRateThreshold(address token, uint256 capacity, uint256 refillRate) internal {
                      if (token == address(0)) {
                          revert InvalidToken();
                      }
                      if (capacity == 0) {
                          revert InvalidCapacity();
                      }
                      if (refillRate == 0) {
                          revert InvalidRefillRate();
                      }
                      Bucket storage bucket = flowRateBuckets[token];
                      if (bucket.capacity == 0) {
                          bucket.depth = capacity;
                      }
                      bucket.capacity = capacity;
                      bucket.refillRate = refillRate;
                  }
                  /**
                   * @notice Update the flow rate measurement for a token.
                   * @param token Address of token being withdrawn.
                   * @param amount The number of tokens being withdrawn.
                   * @return delayWithdrawal Delay this withdrawal because it is for an unconfigured token.
                   */
                  function _updateFlowRateBucket(address token, uint256 amount) internal returns (bool delayWithdrawal) {
                      Bucket storage bucket = flowRateBuckets[token];
                      uint256 capacity = bucket.capacity;
                      if (capacity == 0) {
                          emit WithdrawalForNonFlowRatedToken(token, amount);
                          return true;
                      }
                      // Calculate the depth assuming no withdrawal.
                      // slither-disable-next-line timestamp
                      uint256 depth = bucket.depth + (block.timestamp - bucket.refillTime) * bucket.refillRate;
                      // slither-disable-next-line timestamp
                      bucket.refillTime = block.timestamp;
                      // slither-disable-next-line timestamp
                      if (depth > capacity) {
                          depth = capacity;
                      }
                      // slither-disable-next-line timestamp
                      if (amount >= depth) {
                          // The bucket is empty indicating the flow rate is high. Automatically
                          // enable the withdrawal queue.
                          emit AutoActivatedWithdrawalQueue();
                          withdrawalQueueActivated = true;
                          bucket.depth = 0;
                      } else {
                          bucket.depth = depth - amount;
                      }
                      return false;
                  }
                  // slither-disable-next-line unused-state,naming-convention
                  uint256[50] private __gapFlowRateDetection;
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import {
                  IFlowRateWithdrawalQueueEvents,
                  IFlowRateWithdrawalQueueErrors
              } from "../../interfaces/root/flowrate/IFlowRateWithdrawalQueue.sol";
              /**
               * @title  Flow Rate Withdrawal Queue
               * @author Immutable Pty Ltd (Peter Robinson @drinkcoffee)
               * @notice Queue for withdrawals from the RootERC20BridgeFlowRate.
               * @dev    When withdrawals are delayed, they are put in the queue defined in this contract.
               *         The "queue" is actually a per-user array that gets bigger each time a withdrawal
               *         is added. Users can choose which withdrawals they are interested in and process
               *         just those withdrawals.
               *         Note: This code is part of RootERC20BridgeFlowRate. It has been separated out
               *         to make it easier to understand the functionality.
               *         Note that this contract is upgradeable.
               */
              abstract contract FlowRateWithdrawalQueue is IFlowRateWithdrawalQueueEvents, IFlowRateWithdrawalQueueErrors {
                  // One day in seconds.
                  uint256 private constant DEFAULT_WITHDRAW_DELAY = 1 days;
                  // A single token withdrawal for a user.
                  struct PendingWithdrawal {
                      // The account that initiated the crosschain transfer on the child chain.
                      address withdrawer;
                      // The token being withdrawn.
                      address token;
                      // The number of tokens.
                      uint256 amount;
                      // The time when the withdraw was requested. The pending withdrawal can be
                      // withdrawn at time timestamp + withdrawalDelay. Note that it is possible
                      // that the withdrawalDelay is updated while the withdrawal is still pending.
                      uint256 timestamp;
                  }
                  // Mapping of user addresses to withdrawal queue.
                  mapping(address => PendingWithdrawal[]) private pendingWithdrawals;
                  // Information found in a search.
                  struct FindPendingWithdrawal {
                      // Index into array.
                      uint256 index;
                      // The number of tokens.
                      uint256 amount;
                      // The time when the withdraw was requested. The pending withdrawal can be
                      // withdrawn at time timestamp + withdrawalDelay. Note that it is possible
                      // that the withdrawalDelay is updated while the withdrawal is still pending.
                      uint256 timestamp;
                  }
                  // The amount of time between a withdrawal request and a user being allowed to withdraw.
                  uint256 public withdrawalDelay;
                  /**
                   * @notice Initialization function for FlowRateWithdrawalQueue
                   */
                  // slither-disable-next-line naming-convention
                  function __FlowRateWithdrawalQueue_init() internal {
                      _setWithdrawalDelay(DEFAULT_WITHDRAW_DELAY);
                  }
                  /**
                   * @notice Set the delay in seconds between when a withdrawal is requested and
                   *         when it can be withdrawn.
                   * @param delay Withdrawal delay in seconds.
                   */
                  function _setWithdrawalDelay(uint256 delay) internal {
                      uint256 previousDelay = withdrawalDelay;
                      withdrawalDelay = delay;
                      emit WithdrawalDelayUpdated(delay, previousDelay);
                  }
                  /**
                   * @notice Add a withdrawal request to the queue.
                   * @param receiver The account that the tokens should be transferred to.
                   * @param withdrawer The account that initiated the crosschain transfer on the child chain.
                   * @param token The token to withdraw.
                   * @param amount The amount to withdraw.
                   */
                  function _enqueueWithdrawal(address receiver, address withdrawer, address token, uint256 amount) internal {
                      // @TODO look at using a mapping instead of an array to make the withdraw function simpler
                      if (token == address(0)) {
                          revert TokenIsZero(receiver);
                      }
                      // solhint-disable-next-line not-rely-on-time
                      PendingWithdrawal memory newPendingWithdrawal = PendingWithdrawal(withdrawer, token, amount, block.timestamp);
                      uint256 index = pendingWithdrawals[receiver].length;
                      pendingWithdrawals[receiver].push(newPendingWithdrawal);
                      // solhint-disable-next-line not-rely-on-time
                      emit EnQueuedWithdrawal(token, withdrawer, receiver, amount, block.timestamp, index);
                  }
                  /**
                   * @notice Fetch a withdrawal request from the queue / array.
                   * @param receiver The account that the tokens should be transferred to.
                   * @return withdrawer The account on the child chain that initiated the crosschain transfer.
                   * @return token The token to transfer to the receiver.
                   * @return amount The number of tokens to transfer to the receiver.
                   */
                  function _processWithdrawal(address receiver, uint256 index)
                      internal
                      returns (address withdrawer, address token, uint256 amount)
                  {
                      PendingWithdrawal[] storage withdrawals = pendingWithdrawals[receiver];
                      // Check if the request is beyond the end of the array.
                      uint256 length = pendingWithdrawals[receiver].length;
                      if (index >= pendingWithdrawals[receiver].length) {
                          revert IndexOutsideWithdrawalQueue(length, index);
                      }
                      PendingWithdrawal storage withdrawal = withdrawals[index];
                      withdrawer = withdrawal.withdrawer;
                      token = withdrawal.token;
                      amount = withdrawal.amount;
                      if (token == address(0)) {
                          revert WithdrawalAlreadyProcessed(receiver, index);
                      }
                      // Note: Add the withdrawal delay here, and not when enqueuing to allow changes
                      // to withdrawal delay to have effect on in progress withdrawals.
                      uint256 withdrawalTime = withdrawal.timestamp + withdrawalDelay;
                      // slither-disable-next-line timestamp
                      if (block.timestamp < withdrawalTime) {
                          // solhint-disable-next-line not-rely-on-time
                          revert WithdrawalRequestTooEarly(block.timestamp, withdrawalTime);
                      }
                      // Zeroize the old queue item to save some gas.
                      delete withdrawals[index];
                      emit ProcessedWithdrawal(token, withdrawer, receiver, amount, block.timestamp, index);
                  }
                  /**
                   * @notice Fetch the length of the pending withdrawals array for an address.
                   * @param receiver The account to fetch the queue for.
                   * @return length Length of array of pending withdrawals array.
                   */
                  function getPendingWithdrawalsLength(address receiver) external view returns (uint256 length) {
                      length = pendingWithdrawals[receiver].length;
                  }
                  /**
                   * @notice Fetch the queue of pending withdrawals for an address.
                   * @param receiver The account to fetch the queue for.
                   * @param indices Offsets into withdrawal queue to fetch information for.
                   * @return pending Array of pending withdrawals. Zero fill are returned if the index is beyond the end of the queue.
                   */
                  function getPendingWithdrawals(address receiver, uint256[] calldata indices)
                      external
                      view
                      returns (PendingWithdrawal[] memory pending)
                  {
                      PendingWithdrawal[] storage withdrawals = pendingWithdrawals[receiver];
                      uint256 withdrawalsLength = withdrawals.length;
                      pending = new PendingWithdrawal[](indices.length);
                      for (uint256 i = 0; i < pending.length; i++) {
                          if (indices[i] >= withdrawalsLength) {
                              pending[i] = PendingWithdrawal(address(0), address(0), 0, 0);
                          } else {
                              pending[i] = withdrawals[indices[i]];
                          }
                      }
                  }
                  /**
                   * @notice Fetch the queue of pending withdrawals for an address for a token type.
                   * @param receiver The account to fetch the queue for.
                   * @param token The token to locate withdrawals for.
                   * @param startIndex Offset into withdrawal array to start search.
                   * @param stopIndex Offset into withdrawal array to stop search.
                   * @param maxFind Maximum number of items to locate.
                   * @return found Array of information about pending withdrawals for the token.
                   */
                  function findPendingWithdrawals(
                      address receiver,
                      address token,
                      uint256 startIndex,
                      uint256 stopIndex,
                      uint256 maxFind
                  ) external view returns (FindPendingWithdrawal[] memory found) {
                      PendingWithdrawal[] storage withdrawals = pendingWithdrawals[receiver];
                      found = new FindPendingWithdrawal[](maxFind);
                      uint256 foundIndex = 0;
                      uint256 stop = stopIndex > withdrawals.length ? withdrawals.length : stopIndex;
                      for (uint256 i = startIndex; i < stop && foundIndex < maxFind; i++) {
                          if (withdrawals[i].token == token) {
                              found[foundIndex] = FindPendingWithdrawal(i, withdrawals[i].amount, withdrawals[i].timestamp);
                              foundIndex++;
                          }
                      }
                      if (foundIndex != maxFind) {
                          FindPendingWithdrawal[] memory temp = new FindPendingWithdrawal[](foundIndex);
                          for (uint256 i = 0; i < foundIndex; i++) {
                              temp[i] = found[i];
                          }
                          found = temp;
                      }
                  }
                  // slither-disable-next-line unused-state,naming-convention
                  uint256[50] private __gapFlowRateWithdrawalQueue;
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
              import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
              import {Address} from "@openzeppelin/contracts/utils/Address.sol";
              import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
              import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
              import {
                  IRootERC20Bridge,
                  IERC20Metadata,
                  IRootERC20BridgeEvents,
                  IRootERC20BridgeErrors
              } from "../interfaces/root/IRootERC20Bridge.sol";
              import {IRootBridgeAdaptor} from "../interfaces/root/IRootBridgeAdaptor.sol";
              import {IWETH} from "../interfaces/root/IWETH.sol";
              import {BridgeRoles} from "../common/BridgeRoles.sol";
              import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
              /**
               * @title Root ERC20 Bridge
               * @notice An ERC20 bridge contract for the root chain, which enables bridging of standard ERC20 tokens, ETH, wETH, IMX and wIMX from the root chain to the child chain and back.
               * @dev Features:
               *      - Map ERC20 tokens from the root chain to the child chain, so as to enable subsequent bridging of the token.
               *      - Deposit ERC20 tokens, native ETH, wrapped ETH and IMX from the root chain to the child chain.
               *      - Withdraw ERC20 tokens, ETH, and IMX from the child chain to the root chain.
               *      - Set and manage a limit to the amount of IMX that can be deposited into the bridge.
               *      - Manage Role Based Access Control
               *
               * @dev Design:
               *      This contract follows a pattern of using a bridge adaptor to communicate with the child chain. This is because the underlying communication protocol may change,
               *      and also allows us to decouple vendor-specific messaging logic from the bridge logic.
               *      Because of this pattern, any checks or logic that is agnostic to the messaging protocol should be done in this contract.
               *      Any checks or logic that is specific to the underlying messaging protocol should be done in the bridge adaptor.
               *
               * @dev Roles:
               *      - An account with a PAUSER_ROLE can pause the contract.
               *      - An account with an UNPAUSER_ROLE can unpause the contract.
               *      - An account with a VARIABLE_MANAGER_ROLE can update the cumulative IMX deposit limit.
               *      - An account with an ADAPTOR_MANAGER_ROLE can update the root bridge adaptor address.
               *      - An account with a DEFAULT_ADMIN_ROLE can grant and revoke roles.
               *
               * @dev Caution:
               *      - When depositing IMX (L1 -> L2) it's crucial to make sure that the receiving address on the child chain,
               *        if it's a contract, has a receive or fallback function that allows it to accept native IMX on the child chain.
               *        If this isn't the case, the transaction on the child chain could revert, potentially locking the user's funds indefinitely.
               *      - There is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care.
               *      - The initialize function is susceptible to front running, so precautions should be taken to account for this scenario.
               *
               * @dev Note:
               *      - This is an upgradeable contract that should be operated behind OpenZeppelin's TransparentUpgradeableProxy.
               */
              contract RootERC20Bridge is
                  BridgeRoles,
                  ReentrancyGuardUpgradeable,
                  IRootERC20Bridge,
                  IRootERC20BridgeEvents,
                  IRootERC20BridgeErrors
              {
                  using SafeERC20 for IERC20Metadata;
                  /// @dev leave this as the first param for the integration tests.
                  mapping(address => address) public rootTokenToChildToken;
                  /// @notice Role identifier those who can update the cumulative IMX deposit limit.
                  bytes32 public constant VARIABLE_MANAGER_ROLE = keccak256("VARIABLE_MANAGER");
                  uint256 public constant UNLIMITED_DEPOSIT = 0;
                  bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN");
                  bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT");
                  bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW");
                  address public constant NATIVE_ETH = address(0xeee);
                  address public constant NATIVE_IMX = address(0xfff);
                  /// @dev The address of the bridge adapter used to send and receive messages to and from the child chain.
                  IRootBridgeAdaptor public rootBridgeAdaptor;
                  /// @dev The address that will be minting tokens on the child chain.
                  address public childERC20Bridge;
                  /// @dev The address of the token template that will be cloned to create tokens on the child chain.
                  address public childTokenTemplate;
                  /// @dev The address of the IMX ERC20 token on L1.
                  address public rootIMXToken;
                  /// @dev The address of the ETH ERC20 token on L2.
                  address public childETHToken;
                  /// @dev The address of the wETH ERC20 token on L1.
                  address public rootWETHToken;
                  /// @dev The maximum cumulative amount of IMX that can be deposited into the bridge.
                  /// @dev A limit of zero indicates unlimited.
                  uint256 public imxCumulativeDepositLimit;
                  /// @dev Address of the authorized initializer.
                  address public immutable initializerAddress;
                  /**
                   * @notice Modifier to ensure that the caller is the registered root bridge adaptor.
                   */
                  modifier onlyBridgeAdaptor() {
                      if (msg.sender != address(rootBridgeAdaptor)) {
                          revert NotBridgeAdaptor();
                      }
                      _;
                  }
                  /**
                   * @notice Constructs the RootERC20Bridge contract.
                   * @param _initializerAddress The address of the authorized initializer.
                   */
                  constructor(address _initializerAddress) {
                      if (_initializerAddress == address(0)) {
                          revert ZeroAddress();
                      }
                      initializerAddress = _initializerAddress;
                  }
                  /**
                   * @notice Initialization function for RootERC20Bridge.
                   * @param newRoles Struct containing addresses of roles.
                   * @param newRootBridgeAdaptor Address of adaptor to send bridge messages to, and receive messages from.
                   * @param newChildERC20Bridge Address of child ERC20 bridge to communicate with.
                   * @param newChildTokenTemplate Address of child token template to clone.
                   * @param newRootIMXToken Address of ERC20 IMX on the root chain.
                   * @param newRootWETHToken Address of ERC20 WETH on the root chain.
                   * @param newImxCumulativeDepositLimit The cumulative IMX deposit limit.
                   * @dev Can only be called once.
                   */
                  function initialize(
                      InitializationRoles memory newRoles,
                      address newRootBridgeAdaptor,
                      address newChildERC20Bridge,
                      address newChildTokenTemplate,
                      address newRootIMXToken,
                      address newRootWETHToken,
                      uint256 newImxCumulativeDepositLimit
                  ) external virtual initializer {
                      __RootERC20Bridge_init(
                          newRoles,
                          newRootBridgeAdaptor,
                          newChildERC20Bridge,
                          newChildTokenTemplate,
                          newRootIMXToken,
                          newRootWETHToken,
                          newImxCumulativeDepositLimit
                      );
                  }
                  /**
                   * @notice Initialization function for RootERC20Bridge.
                   * @param newRoles Struct containing addresses of roles.
                   * @param newRootBridgeAdaptor Address of StateSender to send bridge messages to, and receive messages from.
                   * @param newChildERC20Bridge Address of child ERC20 bridge to communicate with.
                   * @param newChildTokenTemplate Address of child token template to clone.
                   * @param newRootIMXToken Address of ERC20 IMX on the root chain.
                   * @param newRootWETHToken Address of ERC20 WETH on the root chain.
                   * @param newImxCumulativeDepositLimit The cumulative IMX deposit limit.
                   */
                  function __RootERC20Bridge_init(
                      InitializationRoles memory newRoles,
                      address newRootBridgeAdaptor,
                      address newChildERC20Bridge,
                      address newChildTokenTemplate,
                      address newRootIMXToken,
                      address newRootWETHToken,
                      uint256 newImxCumulativeDepositLimit
                  ) internal {
                      if (msg.sender != initializerAddress) {
                          revert UnauthorizedInitializer();
                      }
                      if (
                          newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0)
                              || newChildTokenTemplate == address(0) || newRootIMXToken == address(0) || newRootWETHToken == address(0)
                              || newRoles.defaultAdmin == address(0) || newRoles.pauser == address(0) || newRoles.unpauser == address(0)
                              || newRoles.variableManager == address(0) || newRoles.adaptorManager == address(0)
                      ) {
                          revert ZeroAddress();
                      }
                      __AccessControl_init();
                      __Pausable_init();
                      __ReentrancyGuard_init();
                      _grantRole(DEFAULT_ADMIN_ROLE, newRoles.defaultAdmin);
                      _grantRole(PAUSER_ROLE, newRoles.pauser);
                      _grantRole(UNPAUSER_ROLE, newRoles.unpauser);
                      _grantRole(VARIABLE_MANAGER_ROLE, newRoles.variableManager);
                      _grantRole(ADAPTOR_MANAGER_ROLE, newRoles.adaptorManager);
                      childERC20Bridge = newChildERC20Bridge;
                      childTokenTemplate = newChildTokenTemplate;
                      rootIMXToken = newRootIMXToken;
                      rootWETHToken = newRootWETHToken;
                      childETHToken = Clones.predictDeterministicAddress(
                          childTokenTemplate, keccak256(abi.encodePacked(NATIVE_ETH)), childERC20Bridge
                      );
                      rootBridgeAdaptor = IRootBridgeAdaptor(newRootBridgeAdaptor);
                      imxCumulativeDepositLimit = newImxCumulativeDepositLimit;
                      // Map the supported tokens by default
                      rootTokenToChildToken[rootIMXToken] = rootIMXToken;
                      rootTokenToChildToken[NATIVE_ETH] = NATIVE_ETH;
                      rootTokenToChildToken[rootWETHToken] = NATIVE_ETH;
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   */
                  function revokeVariableManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      revokeRole(VARIABLE_MANAGER_ROLE, account);
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   */
                  function grantVariableManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      grantRole(VARIABLE_MANAGER_ROLE, account);
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   */
                  function updateRootBridgeAdaptor(address newRootBridgeAdaptor) external onlyRole(ADAPTOR_MANAGER_ROLE) {
                      if (newRootBridgeAdaptor == address(0)) {
                          revert ZeroAddress();
                      }
                      emit RootBridgeAdaptorUpdated(address(rootBridgeAdaptor), newRootBridgeAdaptor);
                      rootBridgeAdaptor = IRootBridgeAdaptor(newRootBridgeAdaptor);
                  }
                  /**
                   * @notice Updates the IMX deposit limit.
                   * @param newImxCumulativeDepositLimit The new cumulative IMX deposit limit.
                   * @dev Can only be called by VARIABLE_MANAGER_ROLE.
                   * @dev The limit can decrease, but it can never decrease to below the contract's IMX balance.
                   */
                  function updateImxCumulativeDepositLimit(uint256 newImxCumulativeDepositLimit)
                      external
                      onlyRole(VARIABLE_MANAGER_ROLE)
                  {
                      if (
                          newImxCumulativeDepositLimit != UNLIMITED_DEPOSIT
                              && newImxCumulativeDepositLimit < IERC20Metadata(rootIMXToken).balanceOf(address(this))
                      ) {
                          revert ImxDepositLimitTooLow();
                      }
                      emit NewImxDepositLimit(imxCumulativeDepositLimit, newImxCumulativeDepositLimit);
                      imxCumulativeDepositLimit = newImxCumulativeDepositLimit;
                  }
                  /**
                   * @notice Method to receive ETH back from the WETH contract when it is unwrapped
                   * @dev When a user deposits wETH, it must first be unwrapped.
                   *      This allows the bridge to store the underlying native ETH, rather than the wrapped version.
                   *      The unwrapping is done through the WETH contract's `withdraw()` function, which sends the native ETH to this bridge contract.
                   *      The only reason this `receive()` function is needed is for this process, hence the validation ensures that the sender is the WETH contract.
                   */
                  receive() external payable {
                      // Revert if sender is not the WETH token address
                      if (msg.sender != rootWETHToken) {
                          revert NonWrappedNativeTransfer();
                      }
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   * @dev This is only callable by the root chain bridge adaptor.
                   *      This method assumes that the adaptor will have performed all
                   *     validations relating to the source of the message, prior to calling this method.
                   */
                  function onMessageReceive(bytes calldata data) external override whenNotPaused onlyBridgeAdaptor {
                      if (data.length <= 32) {
                          // Data must always be greater than 32.
                          // 32 bytes for the signature, and at least some information for the payload
                          revert InvalidData("Data too short");
                      }
                      if (bytes32(data[:32]) == WITHDRAW_SIG) {
                          _withdraw(data[32:]);
                      } else {
                          revert InvalidData("Unsupported action signature");
                      }
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   * @dev Note that there is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care.
                   */
                  function mapToken(IERC20Metadata rootToken) external payable override whenNotPaused returns (address) {
                      return _mapToken(rootToken);
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   */
                  function depositETH(uint256 amount) external payable {
                      _depositETH(msg.sender, amount);
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   */
                  function depositToETH(address receiver, uint256 amount) external payable {
                      _depositETH(receiver, amount);
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   * @dev Caution:
                   *      - When depositing IMX, it's crucial to make sure that the receiving address (`msg.sender`) on the child chain,
                   *        if it's a contract, has a receive or fallback function that allows it to accept native IMX.
                   *        If this isn't the case, the transaction on the child chain could revert, potentially locking the user's funds indefinitely.
                   *      - Note that there is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care.
                   */
                  function deposit(IERC20Metadata rootToken, uint256 amount) external payable override {
                      _depositToken(rootToken, msg.sender, amount);
                  }
                  /**
                   * @inheritdoc IRootERC20Bridge
                   * @dev Caution:
                   *      - When depositing IMX, it's crucial to make sure that the receiving address (`receiver`) on the child chain,
                   *        if it's a contract, has a receive or fallback function that allows it to accept native IMX.
                   *        If this isn't the case, the transaction on the child chain could revert, potentially locking the user's funds indefinitely.
                   *      - Note that there is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care.
                   */
                  function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external payable override {
                      _depositToken(rootToken, receiver, amount);
                  }
                  function _depositETH(address receiver, uint256 amount) private {
                      if (msg.value < amount) {
                          revert InsufficientValue();
                      }
                      uint256 expectedBalance = address(this).balance - (msg.value - amount);
                      _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount);
                      // invariant check to ensure that the root native balance has increased by the amount deposited
                      if (address(this).balance != expectedBalance) {
                          revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance);
                      }
                  }
                  function _depositToken(IERC20Metadata rootToken, address receiver, uint256 amount) private {
                      if (address(rootToken) == rootWETHToken) {
                          _depositWrappedETH(receiver, amount);
                      } else {
                          _depositERC20(rootToken, receiver, amount);
                      }
                  }
                  function _depositWrappedETH(address receiver, uint256 amount) private {
                      uint256 expectedBalance = address(this).balance + amount;
                      IERC20Metadata erc20WETH = IERC20Metadata(rootWETHToken);
                      erc20WETH.safeTransferFrom(msg.sender, address(this), amount);
                      IWETH(rootWETHToken).withdraw(amount);
                      // invariant check to ensure that the root native balance has increased by the amount deposited
                      if (address(this).balance != expectedBalance) {
                          revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance);
                      }
                      _deposit(IERC20Metadata(rootWETHToken), receiver, amount);
                  }
                  function _depositERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private {
                      uint256 expectedBalance = rootToken.balanceOf(address(this)) + amount;
                      _deposit(rootToken, receiver, amount);
                      // invariant check to ensure that the root token balance has increased by the amount deposited
                      // slither-disable-next-line incorrect-equality
                      if (rootToken.balanceOf(address(this)) != expectedBalance) {
                          revert BalanceInvariantCheckFailed(rootToken.balanceOf(address(this)), expectedBalance);
                      }
                  }
                  function _mapToken(IERC20Metadata rootToken) private returns (address) {
                      if (msg.value == 0) {
                          revert NoGas();
                      }
                      if (address(rootToken) == address(0)) {
                          revert ZeroAddress();
                      }
                      if (address(rootToken) == rootIMXToken) {
                          revert CantMapIMX();
                      }
                      if (address(rootToken) == NATIVE_ETH) {
                          revert CantMapETH();
                      }
                      if (address(rootToken) == rootWETHToken) {
                          revert CantMapWETH();
                      }
                      if (rootTokenToChildToken[address(rootToken)] != address(0)) {
                          revert AlreadyMapped();
                      }
                      address childBridge = childERC20Bridge;
                      address childToken =
                          Clones.predictDeterministicAddress(childTokenTemplate, keccak256(abi.encodePacked(rootToken)), childBridge);
                      rootTokenToChildToken[address(rootToken)] = childToken;
                      (string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals) = _getTokenDetails(rootToken);
                      bytes memory payload = abi.encode(MAP_TOKEN_SIG, rootToken, tokenName, tokenSymbol, tokenDecimals);
                      rootBridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender);
                      emit L1TokenMapped(address(rootToken), childToken);
                      return childToken;
                  }
                  function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount)
                      private
                      nonReentrant
                      whenNotPaused
                      wontIMXOverflow(address(rootToken), amount)
                  {
                      if (receiver == address(0) || address(rootToken) == address(0)) {
                          revert ZeroAddress();
                      }
                      if (amount == 0) {
                          revert ZeroAmount();
                      }
                      if (msg.value == 0) {
                          revert NoGas();
                      }
                      // ETH, WETH and IMX do not need to be mapped since it should have been mapped on initialization
                      if (rootTokenToChildToken[address(rootToken)] == address(0)) {
                          revert NotMapped();
                      }
                      // We can call _mapToken here, but ordering in the GMP is not guaranteed.
                      // Therefore, we need to decide how to handle this and it may be a UI decision to wait until map token message is executed on child chain.
                      // Discuss this, and add this decision to the design doc.
                      address payloadToken = (address(rootToken) == rootWETHToken) ? NATIVE_ETH : address(rootToken);
                      // Deposit sig, root token address, depositor, receiver, amount
                      bytes memory payload = abi.encode(DEPOSIT_SIG, payloadToken, msg.sender, receiver, amount);
                      // Adjust for fee amount on native transfers
                      uint256 feeAmount = (address(rootToken) == NATIVE_ETH) ? msg.value - amount : msg.value;
                      // Send message to child chain
                      rootBridgeAdaptor.sendMessage{value: feeAmount}(payload, msg.sender);
                      // Emit the appropriate deposit event
                      _transferTokensAndEmitEvent(address(rootToken), receiver, amount);
                  }
                  /**
                   * @notice Private helper function to emit the appropriate deposit event and execute transfer if rootIMX or rootERC20
                   */
                  function _transferTokensAndEmitEvent(address rootToken, address receiver, uint256 amount) private {
                      // ETH also cannot be transferred since it was received in the payable function call
                      if (rootToken == NATIVE_ETH) {
                          emit NativeEthDeposit(rootToken, childETHToken, msg.sender, receiver, amount);
                          // WETH is also not transferred here since it was earlier unwrapped to ETH
                      } else if (rootToken == rootWETHToken) {
                          emit WETHDeposit(rootToken, childETHToken, msg.sender, receiver, amount);
                      } else if (rootToken == rootIMXToken) {
                          emit IMXDeposit(rootToken, msg.sender, receiver, amount);
                          IERC20Metadata(rootToken).safeTransferFrom(msg.sender, address(this), amount);
                      } else {
                          emit ChildChainERC20Deposit(rootToken, rootTokenToChildToken[rootToken], msg.sender, receiver, amount);
                          IERC20Metadata(rootToken).safeTransferFrom(msg.sender, address(this), amount);
                      }
                  }
                  function _withdraw(bytes memory data) internal virtual {
                      (address rootToken, address childToken, address withdrawer, address receiver, uint256 amount) =
                          _decodeAndValidateWithdrawal(data);
                      _executeTransfer(rootToken, childToken, withdrawer, receiver, amount);
                  }
                  function _decodeAndValidateWithdrawal(bytes memory data)
                      internal
                      view
                      returns (address rootToken, address childToken, address withdrawer, address receiver, uint256 amount)
                  {
                      (rootToken, withdrawer, receiver, amount) = abi.decode(data, (address, address, address, uint256));
                      if (address(rootToken) == address(0)) {
                          revert ZeroAddress();
                      }
                      if (rootToken == rootIMXToken) {
                          childToken = NATIVE_IMX;
                      } else if (rootToken == NATIVE_ETH) {
                          childToken = childETHToken;
                      } else {
                          childToken = rootTokenToChildToken[rootToken];
                          if (childToken == address(0)) {
                              revert NotMapped();
                          }
                      }
                  }
                  function _executeTransfer(
                      address rootToken,
                      address childToken,
                      address withdrawer,
                      address receiver,
                      uint256 amount
                  ) internal whenNotPaused {
                      if (rootToken == NATIVE_ETH) {
                          Address.sendValue(payable(receiver), amount);
                          emit RootChainETHWithdraw(NATIVE_ETH, childToken, withdrawer, receiver, amount);
                      } else {
                          IERC20Metadata(rootToken).safeTransfer(receiver, amount);
                          emit RootChainERC20Withdraw(rootToken, childToken, withdrawer, receiver, amount);
                      }
                  }
                  function _getTokenDetails(IERC20Metadata token) private view returns (string memory, string memory, uint8) {
                      string memory tokenName;
                      try token.name() returns (string memory name) {
                          tokenName = name;
                      } catch {
                          revert TokenNotSupported();
                      }
                      string memory tokenSymbol;
                      try token.symbol() returns (string memory symbol) {
                          tokenSymbol = symbol;
                      } catch {
                          revert TokenNotSupported();
                      }
                      uint8 tokenDecimals;
                      try token.decimals() returns (uint8 decimals) {
                          tokenDecimals = decimals;
                      } catch {
                          revert TokenNotSupported();
                      }
                      return (tokenName, tokenSymbol, tokenDecimals);
                  }
                  modifier wontIMXOverflow(address rootToken, uint256 amount) {
                      // Assert whether the deposit is root IMX
                      address imxToken = rootIMXToken;
                      uint256 depositLimit = imxCumulativeDepositLimit;
                      if (rootToken == imxToken && depositLimit != UNLIMITED_DEPOSIT) {
                          // Based on the balance of this contract, check if the deposit will exceed the cumulative limit
                          if (IERC20Metadata(imxToken).balanceOf(address(this)) + amount > depositLimit) {
                              revert ImxDepositLimitExceeded();
                          }
                      }
                      _;
                  }
                  // slither-disable-next-line unused-state,naming-convention
                  uint256[50] private __gapRootERC20Bridge;
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              /**
               * @title Root ERC20 Bridge Flow Rate Events
               * @notice Defines event types emitted by a Root ERC20 Bridge implementation with flow rate control capabilities.
               */
              interface IRootERC20BridgeFlowRateEvents {
                  /**
                   * @notice Indicates rate control thresholds have been set for a certain token.
                   * @param token The token thresholds applied to.
                   * @param capacity The size of the bucket in tokens.
                   * @param refillRate How quickly the bucket refills in tokens per second.
                   * @param largeTransferThreshold Threshold over which a withdrawal is deemed to be large,
                   *         and will be put in the withdrawal queue.
                   */
                  event RateControlThresholdSet(
                      address indexed token,
                      uint256 capacity,
                      uint256 refillRate,
                      uint256 largeTransferThreshold,
                      uint256 previousCapacity,
                      uint256 previousRefillRate,
                      uint256 previousLargeTransferThreshold
                  );
                  /**
                   * @notice Indicates a withdrawal was queued.
                   * @param token Address of token that is being withdrawn.
                   * @param withdrawer child chain sender of tokens.
                   * @param receiver Recipient of tokens.
                   * @param amount The number of tokens.
                   * @param delayWithdrawalLargeAmount is true if the reason for queuing was a large transfer.
                   * @param delayWithdrawalUnknownToken is true if the reason for queuing was that the
                   *         token had not been configured using the setRateControlThreshold function.
                   * @param withdrawalQueueActivated is true if the withdrawal queue has been activated.
                   */
                  event QueuedWithdrawal(
                      address indexed token,
                      address indexed withdrawer,
                      address indexed receiver,
                      uint256 amount,
                      bool delayWithdrawalLargeAmount,
                      bool delayWithdrawalUnknownToken,
                      bool withdrawalQueueActivated
                  );
              }
              /**
               * @title Root ERC20 Bridge Flow Rate Errors
               * @notice Defines error types emitted by a Root ERC20 Bridge implementation with flow rate control capabilities.
               */
              interface IRootERC20BridgeFlowRateErrors {
                  // Error if the RootERC20Bridge initializer is called, and not the one for this contract.
                  error WrongInitializer();
                  // finaliseQueuedWithdrawalsAggregated was called with a zero length indices array.
                  error ProvideAtLeastOneIndex();
                  // The expected and actual token did not match for an aggregated withdrawal.
                  error MixedTokens(address token, address actualToken);
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              /**
               * @title Withdrawal Queue Events
               * @notice Defines event types emitted by the an implementation of the flow rate withdrawal queue.
               */
              interface IFlowRateWithdrawalQueueEvents {
                  // Indicates a withdrawal has been queued.
                  event EnQueuedWithdrawal(
                      address indexed token,
                      address indexed withdrawer,
                      address indexed receiver,
                      uint256 amount,
                      uint256 timestamp,
                      uint256 index
                  );
                  // Indicates a withdrawal has been processed.
                  event ProcessedWithdrawal(
                      address indexed token,
                      address indexed withdrawer,
                      address indexed receiver,
                      uint256 amount,
                      uint256 timestamp,
                      uint256 index
                  );
                  // Indicates the new withdrawal delay.
                  event WithdrawalDelayUpdated(uint256 delay, uint256 previousDelay);
              }
              /**
               * @title Withdrawal Queue Errors
               * @notice Defines the error types that can be thrown by an implementation of the flow rate withdrawal queue.
               */
              interface IFlowRateWithdrawalQueueErrors {
                  // A withdrawal was being processed, but the index is outside of the array.
                  error IndexOutsideWithdrawalQueue(uint256 lengthOfQueue, uint256 requestedIndex);
                  // A withdrawal was being processed, but the withdrawal is not yet available.
                  error WithdrawalRequestTooEarly(uint256 timeNow, uint256 currentWithdrawalTime);
                  // A withdrawal was being processed, but the token is zero. This indicates that the
                  // withdrawal has already been processed.
                  error WithdrawalAlreadyProcessed(address receiver, uint256 index);
                  // Attempting to enqueue a token with token address = 0.
                  error TokenIsZero(address receiver);
              }
              // 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));
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (proxy/Clones.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
               * deploying minimal proxy contracts, also known as "clones".
               *
               * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
               * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
               *
               * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
               * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
               * deterministic method.
               *
               * _Available since v3.4._
               */
              library Clones {
                  /**
                   * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
                   *
                   * This function uses the create opcode, which should never revert.
                   */
                  function clone(address implementation) internal returns (address instance) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
                          // of the `implementation` address with the bytecode before the address.
                          mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
                          // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
                          mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
                          instance := create(0, 0x09, 0x37)
                      }
                      require(instance != address(0), "ERC1167: create failed");
                  }
                  /**
                   * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
                   *
                   * This function uses the create2 opcode and a `salt` to deterministically deploy
                   * the clone. Using the same `implementation` and `salt` multiple time will revert, since
                   * the clones cannot be deployed twice at the same address.
                   */
                  function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
                          // of the `implementation` address with the bytecode before the address.
                          mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
                          // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
                          mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
                          instance := create2(0, 0x09, 0x37, salt)
                      }
                      require(instance != address(0), "ERC1167: create2 failed");
                  }
                  /**
                   * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
                   */
                  function predictDeterministicAddress(
                      address implementation,
                      bytes32 salt,
                      address deployer
                  ) internal pure returns (address predicted) {
                      /// @solidity memory-safe-assembly
                      assembly {
                          let ptr := mload(0x40)
                          mstore(add(ptr, 0x38), deployer)
                          mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
                          mstore(add(ptr, 0x14), implementation)
                          mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
                          mstore(add(ptr, 0x58), salt)
                          mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
                          predicted := keccak256(add(ptr, 0x43), 0x55)
                      }
                  }
                  /**
                   * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
                   */
                  function predictDeterministicAddress(
                      address implementation,
                      bytes32 salt
                  ) internal view returns (address predicted) {
                      return predictDeterministicAddress(implementation, salt, address(this));
                  }
              }
              // 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);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
              pragma solidity ^0.8.0;
              import "../proxy/utils/Initializable.sol";
              /**
               * @dev Contract module that helps prevent reentrant calls to a function.
               *
               * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
               * available, which can be applied to functions to make sure there are no nested
               * (reentrant) calls to them.
               *
               * Note that because there is a single `nonReentrant` guard, functions marked as
               * `nonReentrant` may not call one another. This can be worked around by making
               * those functions `private`, and then adding `external` `nonReentrant` entry
               * points to them.
               *
               * TIP: If you would like to learn more about reentrancy and alternative ways
               * to protect against it, check out our blog post
               * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
               */
              abstract contract ReentrancyGuardUpgradeable is Initializable {
                  // Booleans are more expensive than uint256 or any type that takes up a full
                  // word because each write operation emits an extra SLOAD to first read the
                  // slot's contents, replace the bits taken up by the boolean, and then write
                  // back. This is the compiler's defense against contract upgrades and
                  // pointer aliasing, and it cannot be disabled.
                  // The values being non-zero value makes deployment a bit more expensive,
                  // but in exchange the refund on every call to nonReentrant will be lower in
                  // amount. Since refunds are capped to a percentage of the total
                  // transaction's gas, it is best to keep them low in cases like this one, to
                  // increase the likelihood of the full refund coming into effect.
                  uint256 private constant _NOT_ENTERED = 1;
                  uint256 private constant _ENTERED = 2;
                  uint256 private _status;
                  function __ReentrancyGuard_init() internal onlyInitializing {
                      __ReentrancyGuard_init_unchained();
                  }
                  function __ReentrancyGuard_init_unchained() internal onlyInitializing {
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Prevents a contract from calling itself, directly or indirectly.
                   * Calling a `nonReentrant` function from another `nonReentrant`
                   * function is not supported. It is possible to prevent this from happening
                   * by making the `nonReentrant` function external, and making it call a
                   * `private` function that does the actual work.
                   */
                  modifier nonReentrant() {
                      _nonReentrantBefore();
                      _;
                      _nonReentrantAfter();
                  }
                  function _nonReentrantBefore() private {
                      // On the first call to nonReentrant, _status will be _NOT_ENTERED
                      require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                      // Any calls to nonReentrant after this point will fail
                      _status = _ENTERED;
                  }
                  function _nonReentrantAfter() private {
                      // By storing the original value once again, a refund is triggered (see
                      // https://eips.ethereum.org/EIPS/eip-2200)
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
                   * `nonReentrant` function in the call stack.
                   */
                  function _reentrancyGuardEntered() internal view returns (bool) {
                      return _status == _ENTERED;
                  }
                  /**
                   * @dev This empty reserved space is put in place to allow future versions to add new
                   * variables without shifting down storage in the inheritance chain.
                   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                   */
                  uint256[49] private __gap;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
              pragma solidity ^0.8.0;
              import "./IAccessControlUpgradeable.sol";
              import "../utils/ContextUpgradeable.sol";
              import "../utils/StringsUpgradeable.sol";
              import "../utils/introspection/ERC165Upgradeable.sol";
              import "../proxy/utils/Initializable.sol";
              /**
               * @dev Contract module that allows children to implement role-based access
               * control mechanisms. This is a lightweight version that doesn't allow enumerating role
               * members except through off-chain means by accessing the contract event logs. Some
               * applications may benefit from on-chain enumerability, for those cases see
               * {AccessControlEnumerable}.
               *
               * Roles are referred to by their `bytes32` identifier. These should be exposed
               * in the external API and be unique. The best way to achieve this is by
               * using `public constant` hash digests:
               *
               * ```solidity
               * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
               * ```
               *
               * Roles can be used to represent a set of permissions. To restrict access to a
               * function call, use {hasRole}:
               *
               * ```solidity
               * function foo() public {
               *     require(hasRole(MY_ROLE, msg.sender));
               *     ...
               * }
               * ```
               *
               * Roles can be granted and revoked dynamically via the {grantRole} and
               * {revokeRole} functions. Each role has an associated admin role, and only
               * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
               *
               * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
               * that only accounts with this role will be able to grant or revoke other
               * roles. More complex role relationships can be created by using
               * {_setRoleAdmin}.
               *
               * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
               * grant and revoke this role. Extra precautions should be taken to secure
               * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
               * to enforce additional security measures for this role.
               */
              abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
                  function __AccessControl_init() internal onlyInitializing {
                  }
                  function __AccessControl_init_unchained() internal onlyInitializing {
                  }
                  struct RoleData {
                      mapping(address => bool) members;
                      bytes32 adminRole;
                  }
                  mapping(bytes32 => RoleData) private _roles;
                  bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
                  /**
                   * @dev Modifier that checks that an account has a specific role. Reverts
                   * with a standardized message including the required role.
                   *
                   * The format of the revert reason is given by the following regular expression:
                   *
                   *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
                   *
                   * _Available since v4.1._
                   */
                  modifier onlyRole(bytes32 role) {
                      _checkRole(role);
                      _;
                  }
                  /**
                   * @dev See {IERC165-supportsInterface}.
                   */
                  function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                      return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
                  }
                  /**
                   * @dev Returns `true` if `account` has been granted `role`.
                   */
                  function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
                      return _roles[role].members[account];
                  }
                  /**
                   * @dev Revert with a standard message if `_msgSender()` is missing `role`.
                   * Overriding this function changes the behavior of the {onlyRole} modifier.
                   *
                   * Format of the revert message is described in {_checkRole}.
                   *
                   * _Available since v4.6._
                   */
                  function _checkRole(bytes32 role) internal view virtual {
                      _checkRole(role, _msgSender());
                  }
                  /**
                   * @dev Revert with a standard message if `account` is missing `role`.
                   *
                   * The format of the revert reason is given by the following regular expression:
                   *
                   *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
                   */
                  function _checkRole(bytes32 role, address account) internal view virtual {
                      if (!hasRole(role, account)) {
                          revert(
                              string(
                                  abi.encodePacked(
                                      "AccessControl: account ",
                                      StringsUpgradeable.toHexString(account),
                                      " is missing role ",
                                      StringsUpgradeable.toHexString(uint256(role), 32)
                                  )
                              )
                          );
                      }
                  }
                  /**
                   * @dev Returns the admin role that controls `role`. See {grantRole} and
                   * {revokeRole}.
                   *
                   * To change a role's admin, use {_setRoleAdmin}.
                   */
                  function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
                      return _roles[role].adminRole;
                  }
                  /**
                   * @dev Grants `role` to `account`.
                   *
                   * If `account` had not been already granted `role`, emits a {RoleGranted}
                   * event.
                   *
                   * Requirements:
                   *
                   * - the caller must have ``role``'s admin role.
                   *
                   * May emit a {RoleGranted} event.
                   */
                  function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                      _grantRole(role, account);
                  }
                  /**
                   * @dev Revokes `role` from `account`.
                   *
                   * If `account` had been granted `role`, emits a {RoleRevoked} event.
                   *
                   * Requirements:
                   *
                   * - the caller must have ``role``'s admin role.
                   *
                   * May emit a {RoleRevoked} event.
                   */
                  function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                      _revokeRole(role, account);
                  }
                  /**
                   * @dev Revokes `role` from the calling account.
                   *
                   * Roles are often managed via {grantRole} and {revokeRole}: this function's
                   * purpose is to provide a mechanism for accounts to lose their privileges
                   * if they are compromised (such as when a trusted device is misplaced).
                   *
                   * If the calling account had been revoked `role`, emits a {RoleRevoked}
                   * event.
                   *
                   * Requirements:
                   *
                   * - the caller must be `account`.
                   *
                   * May emit a {RoleRevoked} event.
                   */
                  function renounceRole(bytes32 role, address account) public virtual override {
                      require(account == _msgSender(), "AccessControl: can only renounce roles for self");
                      _revokeRole(role, account);
                  }
                  /**
                   * @dev Grants `role` to `account`.
                   *
                   * If `account` had not been already granted `role`, emits a {RoleGranted}
                   * event. Note that unlike {grantRole}, this function doesn't perform any
                   * checks on the calling account.
                   *
                   * May emit a {RoleGranted} event.
                   *
                   * [WARNING]
                   * ====
                   * This function should only be called from the constructor when setting
                   * up the initial roles for the system.
                   *
                   * Using this function in any other way is effectively circumventing the admin
                   * system imposed by {AccessControl}.
                   * ====
                   *
                   * NOTE: This function is deprecated in favor of {_grantRole}.
                   */
                  function _setupRole(bytes32 role, address account) internal virtual {
                      _grantRole(role, account);
                  }
                  /**
                   * @dev Sets `adminRole` as ``role``'s admin role.
                   *
                   * Emits a {RoleAdminChanged} event.
                   */
                  function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
                      bytes32 previousAdminRole = getRoleAdmin(role);
                      _roles[role].adminRole = adminRole;
                      emit RoleAdminChanged(role, previousAdminRole, adminRole);
                  }
                  /**
                   * @dev Grants `role` to `account`.
                   *
                   * Internal function without access restriction.
                   *
                   * May emit a {RoleGranted} event.
                   */
                  function _grantRole(bytes32 role, address account) internal virtual {
                      if (!hasRole(role, account)) {
                          _roles[role].members[account] = true;
                          emit RoleGranted(role, account, _msgSender());
                      }
                  }
                  /**
                   * @dev Revokes `role` from `account`.
                   *
                   * Internal function without access restriction.
                   *
                   * May emit a {RoleRevoked} event.
                   */
                  function _revokeRole(bytes32 role, address account) internal virtual {
                      if (hasRole(role, account)) {
                          _roles[role].members[account] = false;
                          emit RoleRevoked(role, account, _msgSender());
                      }
                  }
                  /**
                   * @dev This empty reserved space is put in place to allow future versions to add new
                   * variables without shifting down storage in the inheritance chain.
                   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                   */
                  uint256[49] private __gap;
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
              /**
               * @title Root ERC20 Bridge Interface
               * @notice Defines the key functions of an ERC20 bridge on the root chain, which enables bridging of standard ERC20 tokens, ETH, wETH, IMX and wIMX from the root chain to the child chain and back.
               * @dev Features:
               *     - Maps tokens from the root chain to the child chain.
               *     - Deposits tokens from the root chain to the child chain.
               *     - Deposits native ETH from the root chain to the child chain.
               */
              interface IRootERC20Bridge {
                  /**
                   * @notice Holds the addresses of accounts that should be assigned different roles in the bridge, during initialization.
                   */
                  struct InitializationRoles {
                      address defaultAdmin; // The address which will inherit `DEFAULT_ADMIN_ROLE`.
                      address pauser; // The address which will inherit `PAUSER_ROLE`.
                      address unpauser; // The address which will inherit `UNPAUSER_ROLE`.
                      address variableManager; // The address which will inherit `VARIABLE_MANAGER_ROLE`.
                      address adaptorManager; // The address which will inherit `ADAPTOR_MANAGER_ROLE`.
                  }
                  /**
                   * @notice Function to revoke `VARIABLE_MANAGER_ROLE` role from an address
                   */
                  function revokeVariableManagerRole(address account) external;
                  /**
                   * @notice Function to grant `VARIABLE_MANAGER_ROLE` role to an address
                   */
                  function grantVariableManagerRole(address account) external;
                  /**
                   * @notice Updates the root bridge adaptor.
                   * @param newRootBridgeAdaptor Address of new root bridge adaptor.
                   * @dev Can only be called by ADAPTOR_MANAGER_ROLE.
                   */
                  function updateRootBridgeAdaptor(address newRootBridgeAdaptor) external;
                  /**
                   * @notice Receives a bridge message from the child chain.
                   * @param data The data payload of the message.
                   * @dev This function is called by the underlying bridge adaptor on the root chain, when it receives a validated message from the GMP.
                   *         It assumes that the underlying adaptor has already validated the sender chain and sender address.
                   */
                  function onMessageReceive(bytes calldata data) external;
                  /**
                   * @notice Initiates sending a mapToken message to the child chain, if the token hasn't been mapped before.
                   *         This operation requires the `rootToken` to have the following public getter functions: `name()`, `symbol()`, and `decimals()`.
                   *         These functions are optional in the ERC20 standard. If the token does not provide these functions,
                   *         the mapping operation will fail and return a `TokenNotSupported` error.
                   *
                   * @dev The function:
                   *      - fails with a `AlreadyMapped` error if the token has already been mapped.
                   *      - populates a root token => child token mapping on the root chain before
                   *        sending a message telling the child chain to do the same.
                   *      - is `payable` because the message passing protocol requires a fee to be paid.
                   *
                   * @dev The address of the child chain token is deterministic using CREATE2.
                   *
                   * @param rootToken The address of the token on the root chain.
                   * @return childToken The address of the token to be deployed on the child chain.
                   */
                  function mapToken(IERC20Metadata rootToken) external payable returns (address);
                  /**
                   * @notice Deposit tokens to the bridge and issue corresponding tokens to `msg.sender` on the child chain.
                   * @custom:requires `rootToken` should already have been mapped with `mapToken()`.
                   * @param rootToken The address of the token on the root chain.
                   * @param amount The amount of tokens to deposit.
                   * @dev The function is `payable` because the message passing protocol requires a fee to be paid.
                   */
                  function deposit(IERC20Metadata rootToken, uint256 amount) external payable;
                  /**
                   * @notice Deposit tokens to the bridge and issue corresponding tokens to `receiver` address on the child chain.
                   * @custom:requires `rootToken` should already have been mapped with `mapToken()`.
                   * @param rootToken The address of the token on the root chain.
                   * @param receiver The address of the receiver on the child chain, to credit tokens to.
                   * @param amount The amount of tokens to deposit.
                   * @dev The function is `payable` because the message passing protocol requires a fee to be paid.
                   */
                  function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external payable;
                  /**
                   * @notice Deposit ETH to the bridge and issue corresponding wrapped ETH to `msg.sender` on the child chain.
                   * @param amount The amount of tokens to deposit.
                   * @dev The function is `payable` because the message passing protocol requires a fee to be paid.
                   * @dev the `msg.value` provided should cover the amount to send as well as the bridge fee.
                   */
                  function depositETH(uint256 amount) external payable;
                  /**
                   * @notice Deposit ETH to the bridge and issue corresponding wrapped ETH to `receiver` address on the child chain.
                   * @param receiver The address of the receiver on the child chain.
                   * @param amount The amount of tokens to deposit.
                   * @dev The function is `payable` because the message passing protocol requires a fee to be paid.
                   * @dev the `msg.value` provided should cover the amount to send as well as the bridge fee.
                   */
                  function depositToETH(address receiver, uint256 amount) external payable;
              }
              /**
               * @title Root ERC20 Bridge Events
               * @notice Defines event types emitted by a Root ERC20 Bridge implementation.
               */
              interface IRootERC20BridgeEvents {
                  /// @notice Emitted when the root chain bridge adaptor is updated.
                  event RootBridgeAdaptorUpdated(address oldRootBridgeAdaptor, address newRootBridgeAdaptor);
                  /// @notice Emitted when the IMX deposit limit is updated.
                  event NewImxDepositLimit(uint256 oldImxDepositLimit, uint256 newImxDepositLimit);
                  /// @notice Emitted when a map token message is sent to the child chain.
                  event L1TokenMapped(address indexed rootToken, address indexed childToken);
                  /// @notice Emitted when an ERC20 deposit message is sent to the child chain.
                  event ChildChainERC20Deposit(
                      address indexed rootToken,
                      address indexed childToken,
                      address depositor,
                      address indexed receiver,
                      uint256 amount
                  );
                  /// @notice Emitted when an IMX deposit is initated on the root chain.
                  event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount);
                  /// @notice Emitted when a WETH deposit is initiated on the root chain.
                  event WETHDeposit(
                      address indexed rootToken,
                      address indexed childToken,
                      address depositor,
                      address indexed receiver,
                      uint256 amount
                  );
                  /// @notice Emitted when an ETH deposit initiated on the root chain.
                  event NativeEthDeposit(
                      address indexed rootToken,
                      address indexed childToken,
                      address depositor,
                      address indexed receiver,
                      uint256 amount
                  );
                  /// @notice Emitted when an ERC20 withdrawal is executed on the root chain.
                  event RootChainERC20Withdraw(
                      address indexed rootToken,
                      address indexed childToken,
                      address withdrawer,
                      address indexed receiver,
                      uint256 amount
                  );
                  /// @notice Emitted when an ETH withdrawal is executed on the root chain.
                  event RootChainETHWithdraw(
                      address indexed rootToken,
                      address indexed childToken,
                      address withdrawer,
                      address indexed receiver,
                      uint256 amount
                  );
              }
              /**
               * @notice Root ERC20 Bridge Errors
               * @notice Defines error types emitted by a Root ERC20 Bridge implementation.
               */
              interface IRootERC20BridgeErrors {
                  /// @notice Error when the amount requested is less than the value sent.
                  error InsufficientValue();
                  /// @notice Error when there is no gas payment received.
                  error ZeroAmount();
                  /// @notice Error when a zero address is given when not valid.
                  error ZeroAddress();
                  /// @notice Error when a message is sent with no gas payment.
                  error NoGas();
                  /// @notice Error when the child chain name is invalid.
                  error InvalidChildChain();
                  /// @notice Error when a token is already mapped.
                  error AlreadyMapped();
                  /// @notice Error when a token is not mapped when it should be.
                  error NotMapped();
                  /// @notice Error when attempting to map IMX.
                  error CantMapIMX();
                  /// @notice Error when attempting to map ETH.
                  error CantMapETH();
                  /// @notice Error when attempting to map wETH.
                  error CantMapWETH();
                  /// @notice Error when token balance invariant check fails.
                  error BalanceInvariantCheckFailed(uint256 actualBalance, uint256 expectedBalance);
                  /// @notice Error when a message received has invalid data.
                  error InvalidData(string reason);
                  /// @notice Error when caller is not the root bridge adaptor but should be.
                  error NotBridgeAdaptor();
                  /// @notice Error when the total IMX deposit limit is exceeded
                  error ImxDepositLimitExceeded();
                  /// @notice Error when the IMX deposit limit is set below the amount of IMX already deposited
                  error ImxDepositLimitTooLow();
                  /// @notice Error when native transfer is sent to contract from non wrapped-token address.
                  error NonWrappedNativeTransfer();
                  /// @notice Error when the an unauthorized initializer tries to initialize the contract.
                  error UnauthorizedInitializer();
                  /// @notice Error when attempt to map a ERC20 token that doesn't support name(), symbol() or decimals().
                  error TokenNotSupported();
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              /**
               * @title Root ERC20 Bridge Adaptor Interface
               * @notice Defines the functions that can be used be used by a Root ERC20 Bridge to send messages through an underlying GMP
               * @dev This interface abstracts the details of the underlying General Purpose Message Passing protocol.
               *      This minimizes changes to the interface consumer if the underlying GMP is changed in the future.
               *      In addition, this interface is not specific to an ERC20 bridge, and will likely eventually be renamed to be more generic.
               */
              interface IRootBridgeAdaptor {
                  /**
                   * @notice Send an arbitrary message to the child chain via the message passing protocol.
                   * @param payload The message to send, encoded in a `bytes` array.
                   * @param refundRecipient Used if the message passing protocol requires fees & pays back excess to a refund recipient.
                   * @dev The function is `payable` because the message passing protocol may require a fee to be paid.
                   */
                  function sendMessage(bytes calldata payload, address refundRecipient) external payable;
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
              /**
               * @notice Interface for the Wrapped ETH (wETH) contract.
               * @dev Interface for the standard wrapped ETH contract deployed on Ethereum: https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
               */
              interface IWETH is IERC20 {
                  /**
                   * @notice Emitted when native ETH is deposited to the contract, and a corresponding amount of wETH are minted
                   * @param account The address of the account that deposited the tokens.
                   * @param value The amount of tokens that were deposited.
                   */
                  event Deposit(address indexed account, uint256 value);
                  /**
                   * @notice Emitted when wETH is withdrawn from the contract, and a corresponding amount of wETH are burnt.
                   * @param account The address of the account that withdrew the tokens.
                   * @param value The amount of tokens that were withdrawn.
                   */
                  event Withdrawal(address indexed account, uint256 value);
                  /**
                   * @notice Deposit native ETH to the contract and mint an equal amount of wrapped ETH to msg.sender.
                   */
                  function deposit() external payable;
                  /**
                   * @notice Withdraw given amount of native ETH to msg.sender after burning an equal amount of wrapped ETH.
                   * @param value The amount to withdraw.
                   */
                  function withdraw(uint256 value) external;
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
              import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
              /**
               * @title BridgeRoles.sol
               * @notice BridgeRoles.sol is an abstract contract that defines the roles and permissions and pausable functionality across the root and child chain bridge contracts.
               * @dev This contract uses OpenZeppelin's AccessControl and Pausable contracts. This contract is abstract and is intended to be inherited by the root and child chain bridge contracts.
               */
              abstract contract BridgeRoles is AccessControlUpgradeable, PausableUpgradeable {
                  // Roles
                  /// @notice Role identifier for those who can pause functionanlity.
                  bytes32 public constant PAUSER_ROLE = keccak256("PAUSER");
                  /// @notice Role identifier for those who can unpause functionality.
                  bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER");
                  /// @notice Role identifier for those who can update the bridge adaptor.
                  bytes32 public constant ADAPTOR_MANAGER_ROLE = keccak256("ADAPTOR_MANAGER");
                  // Role granting functions
                  /**
                   * @notice Function to grant pauser role to an address
                   */
                  function grantPauserRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      grantRole(PAUSER_ROLE, account);
                  }
                  /**
                   * @notice Function to grant unpauser role to an address
                   */
                  function grantUnpauserRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      grantRole(UNPAUSER_ROLE, account);
                  }
                  /**
                   * @notice Function to grant adaptor manager role to an address
                   */
                  function grantAdaptorManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      grantRole(ADAPTOR_MANAGER_ROLE, account);
                  }
                  // Role revoking functions
                  /**
                   * @notice Function to revoke pauser role from an address
                   */
                  function revokePauserRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      revokeRole(PAUSER_ROLE, account);
                  }
                  /**
                   * @notice Function to revoke unpauser role from an address
                   */
                  function revokeUnpauserRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      revokeRole(UNPAUSER_ROLE, account);
                  }
                  /**
                   * @notice Function to revoke adaptor manager role from an address
                   */
                  function revokeAdaptorManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      revokeRole(ADAPTOR_MANAGER_ROLE, account);
                  }
                  // Pausable functions
                  /**
                   * @notice Function to pause the contract
                   * @dev Only PAUSER_ROLE can call this function
                   */
                  function pause() external onlyRole(PAUSER_ROLE) {
                      _pause();
                  }
                  /**
                   * @notice Function to pause the contract
                   * @dev Only UNPAUSER_ROLE can call this function
                   */
                  function unpause() external onlyRole(UNPAUSER_ROLE) {
                      _unpause();
                  }
                  // slither-disable-next-line unused-state,naming-convention
                  uint256[50] private __gapBridgeRoles;
              }
              // 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);
              }
              // 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);
              }
              // 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);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
              pragma solidity ^0.8.2;
              import "../../utils/AddressUpgradeable.sol";
              /**
               * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
               * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
               * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
               * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
               *
               * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
               * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
               * case an upgrade adds a module that needs to be initialized.
               *
               * For example:
               *
               * [.hljs-theme-light.nopadding]
               * ```solidity
               * contract MyToken is ERC20Upgradeable {
               *     function initialize() initializer public {
               *         __ERC20_init("MyToken", "MTK");
               *     }
               * }
               *
               * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
               *     function initializeV2() reinitializer(2) public {
               *         __ERC20Permit_init("MyToken");
               *     }
               * }
               * ```
               *
               * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
               * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
               *
               * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
               * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
               *
               * [CAUTION]
               * ====
               * Avoid leaving a contract uninitialized.
               *
               * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
               * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
               * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
               *
               * [.hljs-theme-light.nopadding]
               * ```
               * /// @custom:oz-upgrades-unsafe-allow constructor
               * constructor() {
               *     _disableInitializers();
               * }
               * ```
               * ====
               */
              abstract contract Initializable {
                  /**
                   * @dev Indicates that the contract has been initialized.
                   * @custom:oz-retyped-from bool
                   */
                  uint8 private _initialized;
                  /**
                   * @dev Indicates that the contract is in the process of being initialized.
                   */
                  bool private _initializing;
                  /**
                   * @dev Triggered when the contract has been initialized or reinitialized.
                   */
                  event Initialized(uint8 version);
                  /**
                   * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
                   * `onlyInitializing` functions can be used to initialize parent contracts.
                   *
                   * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
                   * constructor.
                   *
                   * Emits an {Initialized} event.
                   */
                  modifier initializer() {
                      bool isTopLevelCall = !_initializing;
                      require(
                          (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
                          "Initializable: contract is already initialized"
                      );
                      _initialized = 1;
                      if (isTopLevelCall) {
                          _initializing = true;
                      }
                      _;
                      if (isTopLevelCall) {
                          _initializing = false;
                          emit Initialized(1);
                      }
                  }
                  /**
                   * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
                   * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
                   * used to initialize parent contracts.
                   *
                   * A reinitializer may be used after the original initialization step. This is essential to configure modules that
                   * are added through upgrades and that require initialization.
                   *
                   * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
                   * cannot be nested. If one is invoked in the context of another, execution will revert.
                   *
                   * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
                   * a contract, executing them in the right order is up to the developer or operator.
                   *
                   * WARNING: setting the version to 255 will prevent any future reinitialization.
                   *
                   * Emits an {Initialized} event.
                   */
                  modifier reinitializer(uint8 version) {
                      require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
                      _initialized = version;
                      _initializing = true;
                      _;
                      _initializing = false;
                      emit Initialized(version);
                  }
                  /**
                   * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
                   * {initializer} and {reinitializer} modifiers, directly or indirectly.
                   */
                  modifier onlyInitializing() {
                      require(_initializing, "Initializable: contract is not initializing");
                      _;
                  }
                  /**
                   * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
                   * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
                   * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
                   * through proxies.
                   *
                   * Emits an {Initialized} event the first time it is successfully executed.
                   */
                  function _disableInitializers() internal virtual {
                      require(!_initializing, "Initializable: contract is initializing");
                      if (_initialized != type(uint8).max) {
                          _initialized = type(uint8).max;
                          emit Initialized(type(uint8).max);
                      }
                  }
                  /**
                   * @dev Returns the highest version that has been initialized. See {reinitializer}.
                   */
                  function _getInitializedVersion() internal view returns (uint8) {
                      return _initialized;
                  }
                  /**
                   * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
                   */
                  function _isInitializing() internal view returns (bool) {
                      return _initializing;
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev External interface of AccessControl declared to support ERC165 detection.
               */
              interface IAccessControlUpgradeable {
                  /**
                   * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
                   *
                   * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
                   * {RoleAdminChanged} not being emitted signaling this.
                   *
                   * _Available since v3.1._
                   */
                  event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
                  /**
                   * @dev Emitted when `account` is granted `role`.
                   *
                   * `sender` is the account that originated the contract call, an admin role
                   * bearer except when using {AccessControl-_setupRole}.
                   */
                  event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
                  /**
                   * @dev Emitted when `account` is revoked `role`.
                   *
                   * `sender` is the account that originated the contract call:
                   *   - if using `revokeRole`, it is the admin role bearer
                   *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
                   */
                  event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
                  /**
                   * @dev Returns `true` if `account` has been granted `role`.
                   */
                  function hasRole(bytes32 role, address account) external view returns (bool);
                  /**
                   * @dev Returns the admin role that controls `role`. See {grantRole} and
                   * {revokeRole}.
                   *
                   * To change a role's admin, use {AccessControl-_setRoleAdmin}.
                   */
                  function getRoleAdmin(bytes32 role) external view returns (bytes32);
                  /**
                   * @dev Grants `role` to `account`.
                   *
                   * If `account` had not been already granted `role`, emits a {RoleGranted}
                   * event.
                   *
                   * Requirements:
                   *
                   * - the caller must have ``role``'s admin role.
                   */
                  function grantRole(bytes32 role, address account) external;
                  /**
                   * @dev Revokes `role` from `account`.
                   *
                   * If `account` had been granted `role`, emits a {RoleRevoked} event.
                   *
                   * Requirements:
                   *
                   * - the caller must have ``role``'s admin role.
                   */
                  function revokeRole(bytes32 role, address account) external;
                  /**
                   * @dev Revokes `role` from the calling account.
                   *
                   * Roles are often managed via {grantRole} and {revokeRole}: this function's
                   * purpose is to provide a mechanism for accounts to lose their privileges
                   * if they are compromised (such as when a trusted device is misplaced).
                   *
                   * If the calling account had been granted `role`, emits a {RoleRevoked}
                   * event.
                   *
                   * Requirements:
                   *
                   * - the caller must be `account`.
                   */
                  function renounceRole(bytes32 role, address account) external;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
              pragma solidity ^0.8.0;
              import "../proxy/utils/Initializable.sol";
              /**
               * @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 ContextUpgradeable is Initializable {
                  function __Context_init() internal onlyInitializing {
                  }
                  function __Context_init_unchained() internal onlyInitializing {
                  }
                  function _msgSender() internal view virtual returns (address) {
                      return msg.sender;
                  }
                  function _msgData() internal view virtual returns (bytes calldata) {
                      return msg.data;
                  }
                  /**
                   * @dev This empty reserved space is put in place to allow future versions to add new
                   * variables without shifting down storage in the inheritance chain.
                   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                   */
                  uint256[50] private __gap;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
              pragma solidity ^0.8.0;
              import "./math/MathUpgradeable.sol";
              import "./math/SignedMathUpgradeable.sol";
              /**
               * @dev String operations.
               */
              library StringsUpgradeable {
                  bytes16 private constant _SYMBOLS = "0123456789abcdef";
                  uint8 private constant _ADDRESS_LENGTH = 20;
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                   */
                  function toString(uint256 value) internal pure returns (string memory) {
                      unchecked {
                          uint256 length = MathUpgradeable.log10(value) + 1;
                          string memory buffer = new string(length);
                          uint256 ptr;
                          /// @solidity memory-safe-assembly
                          assembly {
                              ptr := add(buffer, add(32, length))
                          }
                          while (true) {
                              ptr--;
                              /// @solidity memory-safe-assembly
                              assembly {
                                  mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                              }
                              value /= 10;
                              if (value == 0) break;
                          }
                          return buffer;
                      }
                  }
                  /**
                   * @dev Converts a `int256` to its ASCII `string` decimal representation.
                   */
                  function toString(int256 value) internal pure returns (string memory) {
                      return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMathUpgradeable.abs(value))));
                  }
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                   */
                  function toHexString(uint256 value) internal pure returns (string memory) {
                      unchecked {
                          return toHexString(value, MathUpgradeable.log256(value) + 1);
                      }
                  }
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                   */
                  function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                      bytes memory buffer = new bytes(2 * length + 2);
                      buffer[0] = "0";
                      buffer[1] = "x";
                      for (uint256 i = 2 * length + 1; i > 1; --i) {
                          buffer[i] = _SYMBOLS[value & 0xf];
                          value >>= 4;
                      }
                      require(value == 0, "Strings: hex length insufficient");
                      return string(buffer);
                  }
                  /**
                   * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                   */
                  function toHexString(address addr) internal pure returns (string memory) {
                      return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                  }
                  /**
                   * @dev Returns true if the two strings are equal.
                   */
                  function equal(string memory a, string memory b) internal pure returns (bool) {
                      return keccak256(bytes(a)) == keccak256(bytes(b));
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
              pragma solidity ^0.8.0;
              import "./IERC165Upgradeable.sol";
              import "../../proxy/utils/Initializable.sol";
              /**
               * @dev Implementation of the {IERC165} interface.
               *
               * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
               * for the additional interface id that will be supported. For example:
               *
               * ```solidity
               * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
               *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
               * }
               * ```
               *
               * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
               */
              abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
                  function __ERC165_init() internal onlyInitializing {
                  }
                  function __ERC165_init_unchained() internal onlyInitializing {
                  }
                  /**
                   * @dev See {IERC165-supportsInterface}.
                   */
                  function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                      return interfaceId == type(IERC165Upgradeable).interfaceId;
                  }
                  /**
                   * @dev This empty reserved space is put in place to allow future versions to add new
                   * variables without shifting down storage in the inheritance chain.
                   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                   */
                  uint256[50] private __gap;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
              pragma solidity ^0.8.0;
              import "../utils/ContextUpgradeable.sol";
              import "../proxy/utils/Initializable.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 PausableUpgradeable is Initializable, ContextUpgradeable {
                  /**
                   * @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.
                   */
                  function __Pausable_init() internal onlyInitializing {
                      __Pausable_init_unchained();
                  }
                  function __Pausable_init_unchained() internal onlyInitializing {
                      _paused = false;
                  }
                  /**
                   * @dev Modifier to make a function callable only when the contract is not paused.
                   *
                   * Requirements:
                   *
                   * - The contract must not be paused.
                   */
                  modifier whenNotPaused() {
                      _requireNotPaused();
                      _;
                  }
                  /**
                   * @dev Modifier to make a function callable only when the contract is paused.
                   *
                   * Requirements:
                   *
                   * - The contract must be paused.
                   */
                  modifier whenPaused() {
                      _requirePaused();
                      _;
                  }
                  /**
                   * @dev Returns true if the contract is paused, and false otherwise.
                   */
                  function paused() public view virtual returns (bool) {
                      return _paused;
                  }
                  /**
                   * @dev Throws if the contract is paused.
                   */
                  function _requireNotPaused() internal view virtual {
                      require(!paused(), "Pausable: paused");
                  }
                  /**
                   * @dev Throws if the contract is not paused.
                   */
                  function _requirePaused() internal view virtual {
                      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());
                  }
                  /**
                   * @dev This empty reserved space is put in place to allow future versions to add new
                   * variables without shifting down storage in the inheritance chain.
                   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                   */
                  uint256[49] private __gap;
              }
              // 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 AddressUpgradeable {
                  /**
                   * @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);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Standard math utilities missing in the Solidity language.
               */
              library MathUpgradeable {
                  enum Rounding {
                      Down, // Toward negative infinity
                      Up, // Toward infinity
                      Zero // Toward zero
                  }
                  /**
                   * @dev Returns the largest of two numbers.
                   */
                  function max(uint256 a, uint256 b) internal pure returns (uint256) {
                      return a > b ? a : b;
                  }
                  /**
                   * @dev Returns the smallest of two numbers.
                   */
                  function min(uint256 a, uint256 b) internal pure returns (uint256) {
                      return a < b ? a : b;
                  }
                  /**
                   * @dev Returns the average of two numbers. The result is rounded towards
                   * zero.
                   */
                  function average(uint256 a, uint256 b) internal pure returns (uint256) {
                      // (a + b) / 2 can overflow.
                      return (a & b) + (a ^ b) / 2;
                  }
                  /**
                   * @dev Returns the ceiling of the division of two numbers.
                   *
                   * This differs from standard division with `/` in that it rounds up instead
                   * of rounding down.
                   */
                  function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                      // (a + b - 1) / b can overflow on addition, so we distribute.
                      return a == 0 ? 0 : (a - 1) / b + 1;
                  }
                  /**
                   * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
                   * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
                   * with further edits by Uniswap Labs also under MIT license.
                   */
                  function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
                      unchecked {
                          // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                          // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                          // variables such that product = prod1 * 2^256 + prod0.
                          uint256 prod0; // Least significant 256 bits of the product
                          uint256 prod1; // Most significant 256 bits of the product
                          assembly {
                              let mm := mulmod(x, y, not(0))
                              prod0 := mul(x, y)
                              prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                          }
                          // Handle non-overflow cases, 256 by 256 division.
                          if (prod1 == 0) {
                              // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                              // The surrounding unchecked block does not change this fact.
                              // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                              return prod0 / denominator;
                          }
                          // Make sure the result is less than 2^256. Also prevents denominator == 0.
                          require(denominator > prod1, "Math: mulDiv overflow");
                          ///////////////////////////////////////////////
                          // 512 by 256 division.
                          ///////////////////////////////////////////////
                          // Make division exact by subtracting the remainder from [prod1 prod0].
                          uint256 remainder;
                          assembly {
                              // Compute remainder using mulmod.
                              remainder := mulmod(x, y, denominator)
                              // Subtract 256 bit number from 512 bit number.
                              prod1 := sub(prod1, gt(remainder, prod0))
                              prod0 := sub(prod0, remainder)
                          }
                          // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                          // See https://cs.stackexchange.com/q/138556/92363.
                          // Does not overflow because the denominator cannot be zero at this stage in the function.
                          uint256 twos = denominator & (~denominator + 1);
                          assembly {
                              // Divide denominator by twos.
                              denominator := div(denominator, twos)
                              // Divide [prod1 prod0] by twos.
                              prod0 := div(prod0, twos)
                              // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                              twos := add(div(sub(0, twos), twos), 1)
                          }
                          // Shift in bits from prod1 into prod0.
                          prod0 |= prod1 * twos;
                          // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                          // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                          // four bits. That is, denominator * inv = 1 mod 2^4.
                          uint256 inverse = (3 * denominator) ^ 2;
                          // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                          // in modular arithmetic, doubling the correct bits in each step.
                          inverse *= 2 - denominator * inverse; // inverse mod 2^8
                          inverse *= 2 - denominator * inverse; // inverse mod 2^16
                          inverse *= 2 - denominator * inverse; // inverse mod 2^32
                          inverse *= 2 - denominator * inverse; // inverse mod 2^64
                          inverse *= 2 - denominator * inverse; // inverse mod 2^128
                          inverse *= 2 - denominator * inverse; // inverse mod 2^256
                          // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                          // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                          // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                          // is no longer required.
                          result = prod0 * inverse;
                          return result;
                      }
                  }
                  /**
                   * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
                   */
                  function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
                      uint256 result = mulDiv(x, y, denominator);
                      if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                          result += 1;
                      }
                      return result;
                  }
                  /**
                   * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
                   *
                   * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
                   */
                  function sqrt(uint256 a) internal pure returns (uint256) {
                      if (a == 0) {
                          return 0;
                      }
                      // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                      //
                      // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                      // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                      //
                      // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                      // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                      // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                      //
                      // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                      uint256 result = 1 << (log2(a) >> 1);
                      // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                      // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                      // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                      // into the expected uint128 result.
                      unchecked {
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          return min(result, a / result);
                      }
                  }
                  /**
                   * @notice Calculates sqrt(a), following the selected rounding direction.
                   */
                  function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = sqrt(a);
                          return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 2, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log2(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >> 128 > 0) {
                              value >>= 128;
                              result += 128;
                          }
                          if (value >> 64 > 0) {
                              value >>= 64;
                              result += 64;
                          }
                          if (value >> 32 > 0) {
                              value >>= 32;
                              result += 32;
                          }
                          if (value >> 16 > 0) {
                              value >>= 16;
                              result += 16;
                          }
                          if (value >> 8 > 0) {
                              value >>= 8;
                              result += 8;
                          }
                          if (value >> 4 > 0) {
                              value >>= 4;
                              result += 4;
                          }
                          if (value >> 2 > 0) {
                              value >>= 2;
                              result += 2;
                          }
                          if (value >> 1 > 0) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log2(value);
                          return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 10, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log10(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >= 10 ** 64) {
                              value /= 10 ** 64;
                              result += 64;
                          }
                          if (value >= 10 ** 32) {
                              value /= 10 ** 32;
                              result += 32;
                          }
                          if (value >= 10 ** 16) {
                              value /= 10 ** 16;
                              result += 16;
                          }
                          if (value >= 10 ** 8) {
                              value /= 10 ** 8;
                              result += 8;
                          }
                          if (value >= 10 ** 4) {
                              value /= 10 ** 4;
                              result += 4;
                          }
                          if (value >= 10 ** 2) {
                              value /= 10 ** 2;
                              result += 2;
                          }
                          if (value >= 10 ** 1) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log10(value);
                          return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 256, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   *
                   * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
                   */
                  function log256(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >> 128 > 0) {
                              value >>= 128;
                              result += 16;
                          }
                          if (value >> 64 > 0) {
                              value >>= 64;
                              result += 8;
                          }
                          if (value >> 32 > 0) {
                              value >>= 32;
                              result += 4;
                          }
                          if (value >> 16 > 0) {
                              value >>= 16;
                              result += 2;
                          }
                          if (value >> 8 > 0) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log256(value);
                          return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Standard signed math utilities missing in the Solidity language.
               */
              library SignedMathUpgradeable {
                  /**
                   * @dev Returns the largest of two signed numbers.
                   */
                  function max(int256 a, int256 b) internal pure returns (int256) {
                      return a > b ? a : b;
                  }
                  /**
                   * @dev Returns the smallest of two signed numbers.
                   */
                  function min(int256 a, int256 b) internal pure returns (int256) {
                      return a < b ? a : b;
                  }
                  /**
                   * @dev Returns the average of two signed numbers without overflow.
                   * The result is rounded towards zero.
                   */
                  function average(int256 a, int256 b) internal pure returns (int256) {
                      // Formula from the book "Hacker's Delight"
                      int256 x = (a & b) + ((a ^ b) >> 1);
                      return x + (int256(uint256(x) >> 255) & (a ^ b));
                  }
                  /**
                   * @dev Returns the absolute unsigned value of a signed value.
                   */
                  function abs(int256 n) internal pure returns (uint256) {
                      unchecked {
                          // must be unchecked in order to support `n = type(int256).min`
                          return uint256(n >= 0 ? n : -n);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Interface of the ERC165 standard, as defined in the
               * https://eips.ethereum.org/EIPS/eip-165[EIP].
               *
               * Implementers can declare support of contract interfaces, which can then be
               * queried by others ({ERC165Checker}).
               *
               * For an implementation, see {ERC165}.
               */
              interface IERC165Upgradeable {
                  /**
                   * @dev Returns true if this contract implements the interface defined by
                   * `interfaceId`. See the corresponding
                   * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
                   * to learn more about how these ids are created.
                   *
                   * This function call must use less than 30 000 gas.
                   */
                  function supportsInterface(bytes4 interfaceId) external view returns (bool);
              }
              

              File 5 of 7: RootAxelarBridgeAdaptor
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
              import {AxelarExecutable} from "@axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
              import {IAxelarGasService} from "@axelar-cgp-solidity/contracts/interfaces/IAxelarGasService.sol";
              import {IRootBridgeAdaptor} from "../interfaces/root/IRootBridgeAdaptor.sol";
              import {
                  IRootAxelarBridgeAdaptorEvents,
                  IRootAxelarBridgeAdaptorErrors,
                  IRootAxelarBridgeAdaptor
              } from "../interfaces/root/IRootAxelarBridgeAdaptor.sol";
              import {IRootERC20Bridge} from "../interfaces/root/IRootERC20Bridge.sol";
              import {AdaptorRoles} from "../common/AdaptorRoles.sol";
              /**
               * @notice Facilitates communication between the RootERC20Bridge and the Axelar core contracts, to send and receive messages to and from the child chain.
               * @dev The contract ensures that any delivered message originated from the registered child chain and bridge adapter contract on the child chain. It will reject all other messages.
               * @dev Features:
               *      - Send messages to the child chain via the Axelar Gateway.
               *      - Receive and validate messages from the child chain via the Axelar Gateway.
               *      - Manage Role Based Access Control
               * @dev Roles:
               *      - An account with a BRIDGE_MANAGER_ROLE can update the root bridge address.
               *      - An account with a TARGET_MANAGER_ROLE can update the child chain name and the child bridge adaptor address.
               *      - An account with a GAS_SERVICE_MANAGER_ROLE can update the gas service address.
               *      - An account with a DEFAULT_ADMIN_ROLE can grant and revoke roles.
               * @dev Note:
               *      - This is an upgradeable contract that should be operated behind OpenZeppelin's TransparentUpgradeableProxy.
               *      - The initialize function is susceptible to front running, so precautions should be taken to account for this scenario.
               */
              contract RootAxelarBridgeAdaptor is
                  AdaptorRoles,
                  AxelarExecutable,
                  IRootBridgeAdaptor,
                  IRootAxelarBridgeAdaptorEvents,
                  IRootAxelarBridgeAdaptorErrors,
                  IRootAxelarBridgeAdaptor
              {
                  /// @notice Address of the bridge contract on the root chain.
                  IRootERC20Bridge public rootBridge;
                  /// @notice Axelar's ID for the child chain. Axelar uses the chain name as the chain ID.
                  string public childChainId;
                  /// @notice Address of the bridge adaptor on the child chain, which this contract will communicate with.
                  string public childBridgeAdaptor;
                  /// @notice Address of the Axelar Gas Service contract.
                  IAxelarGasService public gasService;
                  /// @notice Address of the authorized initializer.
                  address public immutable initializerAddress;
                  /**
                   * @notice Constructs the RootAxelarBridgeAdaptor contract.
                   * @param _gateway The address of the Axelar gateway contract.
                   * @param _initializerAddress The address of the authorized initializer.
                   */
                  constructor(address _gateway, address _initializerAddress) AxelarExecutable(_gateway) {
                      if (_initializerAddress == address(0)) {
                          revert ZeroAddresses();
                      }
                      initializerAddress = _initializerAddress;
                  }
                  /**
                   * @notice Initialization function for RootAxelarBridgeAdaptor.
                   * @param _adaptorRoles Struct containing addresses of roles.
                   * @param _rootBridge Address of root bridge contract.
                   * @param _childChainId Axelar's ID for the child chain.
                   * @param _childBridgeAdaptor Address of the bridge adaptor on the child chain.
                   * @param _gasService Address of Axelar Gas Service contract.
                   */
                  function initialize(
                      InitializationRoles memory _adaptorRoles,
                      address _rootBridge,
                      string memory _childChainId,
                      string memory _childBridgeAdaptor,
                      address _gasService
                  ) public initializer {
                      if (msg.sender != initializerAddress) {
                          revert UnauthorizedInitializer();
                      }
                      if (
                          _rootBridge == address(0) || _gasService == address(0) || _adaptorRoles.defaultAdmin == address(0)
                              || _adaptorRoles.bridgeManager == address(0) || _adaptorRoles.gasServiceManager == address(0)
                              || _adaptorRoles.targetManager == address(0)
                      ) {
                          revert ZeroAddresses();
                      }
                      if (bytes(_childChainId).length == 0) {
                          revert InvalidChildChain();
                      }
                      if (bytes(_childBridgeAdaptor).length == 0) {
                          revert InvalidChildBridgeAdaptor();
                      }
                      __AccessControl_init();
                      _grantRole(DEFAULT_ADMIN_ROLE, _adaptorRoles.defaultAdmin);
                      _grantRole(BRIDGE_MANAGER_ROLE, _adaptorRoles.bridgeManager);
                      _grantRole(GAS_SERVICE_MANAGER_ROLE, _adaptorRoles.gasServiceManager);
                      _grantRole(TARGET_MANAGER_ROLE, _adaptorRoles.targetManager);
                      rootBridge = IRootERC20Bridge(_rootBridge);
                      childChainId = _childChainId;
                      childBridgeAdaptor = _childBridgeAdaptor;
                      gasService = IAxelarGasService(_gasService);
                  }
                  /**
                   * @inheritdoc IRootAxelarBridgeAdaptor
                   */
                  function updateRootBridge(address newRootBridge) external override onlyRole(BRIDGE_MANAGER_ROLE) {
                      if (newRootBridge == address(0)) {
                          revert ZeroAddresses();
                      }
                      emit RootBridgeUpdated(address(rootBridge), newRootBridge);
                      rootBridge = IRootERC20Bridge(newRootBridge);
                  }
                  /**
                   * @inheritdoc IRootAxelarBridgeAdaptor
                   */
                  function updateChildChain(string memory newChildChain) external override onlyRole(TARGET_MANAGER_ROLE) {
                      if (bytes(newChildChain).length == 0) {
                          revert InvalidChildChain();
                      }
                      emit ChildChainUpdated(childChainId, newChildChain);
                      childChainId = newChildChain;
                  }
                  /**
                   * @inheritdoc IRootAxelarBridgeAdaptor
                   */
                  function updateChildBridgeAdaptor(string memory newChildBridgeAdaptor) external onlyRole(TARGET_MANAGER_ROLE) {
                      if (bytes(newChildBridgeAdaptor).length == 0) {
                          revert InvalidChildBridgeAdaptor();
                      }
                      emit ChildBridgeAdaptorUpdated(childBridgeAdaptor, newChildBridgeAdaptor);
                      childBridgeAdaptor = newChildBridgeAdaptor;
                  }
                  /**
                   * @inheritdoc IRootAxelarBridgeAdaptor
                   */
                  function updateGasService(address newGasService) external override onlyRole(GAS_SERVICE_MANAGER_ROLE) {
                      if (newGasService == address(0)) {
                          revert ZeroAddresses();
                      }
                      emit GasServiceUpdated(address(gasService), newGasService);
                      gasService = IAxelarGasService(newGasService);
                  }
                  /**
                   * @inheritdoc IRootBridgeAdaptor
                   */
                  function sendMessage(bytes calldata payload, address refundRecipient) external payable override {
                      if (msg.value == 0) {
                          revert NoGas();
                      }
                      if (msg.sender != address(rootBridge)) {
                          revert CallerNotBridge();
                      }
                      // Load from storage.
                      string memory _childBridgeAdaptor = childBridgeAdaptor;
                      string memory _childChain = childChainId;
                      gasService.payNativeGasForContractCall{value: msg.value}(
                          address(this), _childChain, _childBridgeAdaptor, payload, refundRecipient
                      );
                      gateway.callContract(_childChain, _childBridgeAdaptor, payload);
                      emit AxelarMessageSent(_childChain, _childBridgeAdaptor, payload);
                  }
                  /**
                   * @dev This function is called by the parent `AxelarExecutable` contract to execute a message payload sent from the child chain.
                   *      It is only called after the message has been validated by the Axelar core contracts.
                   *      Validations include, ensuring that the Axelar validator set has signed the message and that the message has not been executed before.
                   *      For more details see:
                   *        - [AxelarExecutable.sol](https://github.com/axelarnetwork/axelar-cgp-solidity/blob/d4536599321774927bf9716178a9e360f8e0efac/contracts/AxelarGateway.sol#L233),
                   *        - [AxelarGateway.sol](https://github.com/axelarnetwork/axelar-cgp-solidity/blob/d4536599321774927bf9716178a9e360f8e0efac/contracts/AxelarGateway.sol#L233)
                   *
                   * @dev The function first validates the message by checking that it originated from the registered
                   *      child chain and bridge adaptor contract on the child chain. If not, the message is rejected.
                   *      If a message is valid, it calls the root bridge contract's `onMessageReceive` function.
                   * @param _sourceChain The chain id that the message originated from.
                   * @param _sourceAddress The contract address that sent the message on the source chain.
                   * @param _payload The message payload.
                   * @custom:assumes `_sourceAddress` is a 20 byte address.
                   */
                  function _execute(string calldata _sourceChain, string calldata _sourceAddress, bytes calldata _payload)
                      internal
                      override
                  {
                      if (!Strings.equal(_sourceChain, childChainId)) {
                          revert InvalidSourceChain();
                      }
                      if (!Strings.equal(_sourceAddress, childBridgeAdaptor)) {
                          revert InvalidSourceAddress();
                      }
                      emit AdaptorExecute(_sourceChain, _sourceAddress, _payload);
                      rootBridge.onMessageReceive(_payload);
                  }
                  /**
                   * @inheritdoc AxelarExecutable
                   * @dev This function is called by the parent `AxelarExecutable` contract's `executeWithToken()` function.
                   *      However, this function is not required for the bridge, and thus reverts with an `UnsupportedOperation` error.
                   */
                  function _executeWithToken(string calldata, string calldata, bytes calldata, string calldata, uint256)
                      internal
                      pure
                      override
                  {
                      revert UnsupportedOperation();
                  }
                  // slither-disable-next-line unused-state,naming-convention
                  uint256[50] private __gapRootAxelarBridgeAdaptor;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
              pragma solidity ^0.8.0;
              import "./math/Math.sol";
              import "./math/SignedMath.sol";
              /**
               * @dev String operations.
               */
              library Strings {
                  bytes16 private constant _SYMBOLS = "0123456789abcdef";
                  uint8 private constant _ADDRESS_LENGTH = 20;
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                   */
                  function toString(uint256 value) internal pure returns (string memory) {
                      unchecked {
                          uint256 length = Math.log10(value) + 1;
                          string memory buffer = new string(length);
                          uint256 ptr;
                          /// @solidity memory-safe-assembly
                          assembly {
                              ptr := add(buffer, add(32, length))
                          }
                          while (true) {
                              ptr--;
                              /// @solidity memory-safe-assembly
                              assembly {
                                  mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                              }
                              value /= 10;
                              if (value == 0) break;
                          }
                          return buffer;
                      }
                  }
                  /**
                   * @dev Converts a `int256` to its ASCII `string` decimal representation.
                   */
                  function toString(int256 value) internal pure returns (string memory) {
                      return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
                  }
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                   */
                  function toHexString(uint256 value) internal pure returns (string memory) {
                      unchecked {
                          return toHexString(value, Math.log256(value) + 1);
                      }
                  }
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                   */
                  function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                      bytes memory buffer = new bytes(2 * length + 2);
                      buffer[0] = "0";
                      buffer[1] = "x";
                      for (uint256 i = 2 * length + 1; i > 1; --i) {
                          buffer[i] = _SYMBOLS[value & 0xf];
                          value >>= 4;
                      }
                      require(value == 0, "Strings: hex length insufficient");
                      return string(buffer);
                  }
                  /**
                   * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                   */
                  function toHexString(address addr) internal pure returns (string memory) {
                      return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                  }
                  /**
                   * @dev Returns true if the two strings are equal.
                   */
                  function equal(string memory a, string memory b) internal pure returns (bool) {
                      return keccak256(bytes(a)) == keccak256(bytes(b));
                  }
              }
              // 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 {}
                  function _executeWithToken(
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      bytes calldata payload,
                      string calldata tokenSymbol,
                      uint256 amount
                  ) internal virtual {}
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IUpgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IUpgradable.sol';
              /**
               * @title IAxelarGasService Interface
               * @notice This is an interface for the AxelarGasService contract which manages gas payments
               * and refunds for cross-chain communication on the Axelar network.
               * @dev This interface inherits IUpgradable
               */
              interface IAxelarGasService is IUpgradable {
                  error InvalidAddress();
                  error NotCollector();
                  error InvalidAmounts();
                  event GasPaidForContractCall(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event GasPaidForContractCallWithToken(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasPaidForContractCall(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasPaidForContractCallWithToken(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event GasPaidForExpressCall(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event GasPaidForExpressCallWithToken(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasPaidForExpressCall(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasPaidForExpressCallWithToken(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event GasAdded(bytes32 indexed txHash, uint256 indexed logIndex, address gasToken, uint256 gasFeeAmount, address refundAddress);
                  event NativeGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress);
                  event ExpressGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, address gasToken, uint256 gasFeeAmount, address refundAddress);
                  event NativeExpressGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress);
                  event Refunded(bytes32 indexed txHash, uint256 indexed logIndex, address payable receiver, address token, uint256 amount);
                  /**
                   * @notice Pay for gas using ERC20 tokens for a contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForContractCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Pay for gas using ERC20 tokens for a contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForContractCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Pay for gas using native currency for a contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForContractCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Pay for gas using native currency for a contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForContractCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Pay for gas using ERC20 tokens for an express contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForExpressCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Pay for gas using ERC20 tokens for an express contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForExpressCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Pay for gas using native currency for an express contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForExpressCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Pay for gas using native currency for an express contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForExpressCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Add additional gas payment using ERC20 tokens after initiating a cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param gasToken The ERC20 token address used to add gas
                   * @param gasFeeAmount The amount of tokens to add as gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Add additional gas payment using native currency after initiating a cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addNativeGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Add additional gas payment using ERC20 tokens after initiating an express cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param gasToken The ERC20 token address used to add gas
                   * @param gasFeeAmount The amount of tokens to add as gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addExpressGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Add additional gas payment using native currency after initiating an express cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addNativeExpressGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Allows the gasCollector to collect accumulated fees from the contract.
                   * @dev Use address(0) as the token address for native currency.
                   * @param receiver The address to receive the collected fees
                   * @param tokens Array of token addresses to be collected
                   * @param amounts Array of amounts to be collected for each respective token address
                   */
                  function collectFees(
                      address payable receiver,
                      address[] calldata tokens,
                      uint256[] calldata amounts
                  ) external;
                  /**
                   * @notice Refunds gas payment to the receiver in relation to a specific cross-chain transaction.
                   * @dev Only callable by the gasCollector.
                   * @dev Use address(0) as the token address to refund native currency.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param receiver The address to receive the refund
                   * @param token The token address to be refunded
                   * @param amount The amount to refund
                   */
                  function refund(
                      bytes32 txHash,
                      uint256 logIndex,
                      address payable receiver,
                      address token,
                      uint256 amount
                  ) external;
                  /**
                   * @notice Returns the address of the designated gas collector.
                   * @return address of the gas collector
                   */
                  function gasCollector() external returns (address);
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              /**
               * @title Root ERC20 Bridge Adaptor Interface
               * @notice Defines the functions that can be used be used by a Root ERC20 Bridge to send messages through an underlying GMP
               * @dev This interface abstracts the details of the underlying General Purpose Message Passing protocol.
               *      This minimizes changes to the interface consumer if the underlying GMP is changed in the future.
               *      In addition, this interface is not specific to an ERC20 bridge, and will likely eventually be renamed to be more generic.
               */
              interface IRootBridgeAdaptor {
                  /**
                   * @notice Send an arbitrary message to the child chain via the message passing protocol.
                   * @param payload The message to send, encoded in a `bytes` array.
                   * @param refundRecipient Used if the message passing protocol requires fees & pays back excess to a refund recipient.
                   * @dev The function is `payable` because the message passing protocol may require a fee to be paid.
                   */
                  function sendMessage(bytes calldata payload, address refundRecipient) external payable;
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              /**
               * @title Root Axelar Bridge Adaptor interface
               * @notice Defines the functions and types of a root chain bridge adaptor.
               */
              interface IRootAxelarBridgeAdaptor {
                  /// @notice Initialization roles used by the adaptor.
                  struct InitializationRoles {
                      address defaultAdmin; // The address which will inherit `DEFAULT_ADMIN_ROLE`.
                      address bridgeManager; // The address which will inherit `BRIDGE_MANAGER_ROLE`.
                      address gasServiceManager; // The address which will inherit `GAS_SERVICE_MANAGER_ROLE`.
                      address targetManager; // The address which will inherit `TARGET_MANAGER_ROLE`.
                  }
                  /**
                   * @notice Update the root bridge
                   * @param newRootBridge Address of new root bridge.
                   * @dev Can only be called by BRIDGE_MANAGER_ROLE.
                   */
                  function updateRootBridge(address newRootBridge) external;
                  /**
                   * @notice Update the child chain name.
                   * @param newChildChain Name of new child chain.
                   * @dev Can only be called by TARGET_MANAGER_ROLE.
                   */
                  function updateChildChain(string memory newChildChain) external;
                  /**
                   * @notice Update the child chain bridge adaptor address
                   * @param newChildBridgeAdaptor address of the new child bridge adaptor.
                   * @dev Can only be called by TARGET_MANAGER_ROLE.
                   */
                  function updateChildBridgeAdaptor(string memory newChildBridgeAdaptor) external;
                  /**
                   * @notice Update the gas service.
                   * @param newGasService Address of new gas service.
                   * @dev Can only be called by GAS_SERVICE_MANAGER_ROLE.
                   */
                  function updateGasService(address newGasService) external;
                  /**
                   * @notice Get the child chain id
                   * @return Axelar's string id of the child chain.
                   */
                  function childChainId() external view returns (string memory);
                  /**
                   * @notice Get the child bridge adaptor address.
                   * @return String representation of the check sum address of the bridge adaptor on the child chain
                   */
                  function childBridgeAdaptor() external view returns (string memory);
              }
              /**
               * @title Root Bridge Adaptor Errors
               * @notice Contains the different error types that can be thrown by a bridge adaptor
               */
              interface IRootAxelarBridgeAdaptorErrors {
                  /// @notice Error when a zero address is given when not valid.
                  error ZeroAddresses();
                  /// @notice Error when a message is to be sent to a child chain that isn't valid.
                  error InvalidChildChain();
                  /// @notice Error when no gas (in the form of `msg.value`) is given to the transaction to pay for Axelar message passing.
                  error NoGas();
                  /// @notice Error when the contract calling the adaptor is not the bridge.
                  error CallerNotBridge();
                  /// @notice Error when the given child chain bridge adaptor is invalid.
                  error InvalidChildBridgeAdaptor();
                  /// @notice Error when a message received has invalid source address.
                  error InvalidSourceAddress();
                  /// @notice Error when a message received has invalid source chain.
                  error InvalidSourceChain();
                  /// @notice Error when the an unauthorized initializer tries to initialize the contract.
                  error UnauthorizedInitializer();
                  /// @notice Error when a function that isn't supported by the adaptor is called.
                  error UnsupportedOperation();
              }
              /**
               * @title Root Bridge Adaptor Events
               * @notice Contains the event types that can be emitted by a bridge adaptor
               */
              interface IRootAxelarBridgeAdaptorEvents {
                  /// @notice Emitted when the child chain bridge adaptor is updated.
                  event ChildBridgeAdaptorUpdated(string oldChildBridgeAdaptor, string newChildBridgeAdaptor);
                  /// @notice Emitted when an Axelar message is sent to the child chain.
                  event AxelarMessageSent(string indexed childChain, string indexed childBridgeAdaptor, bytes indexed payload);
                  /// @notice Emitted when an Axelar message is received from the child chain.
                  event AdaptorExecute(string sourceChain, string sourceAddress_, bytes payload_);
                  /// @notice Emitted when the root bridge is updated.
                  event RootBridgeUpdated(address indexed oldRootBridge, address indexed newRootBridge);
                  /// @notice Emitted when the child chain name is updated.
                  event ChildChainUpdated(string indexed oldChildChain, string indexed newChildChain);
                  /// @notice Emitted when the gas service is updated.
                  event GasServiceUpdated(address indexed oldGasService, address indexed newGasService);
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
              /**
               * @title Root ERC20 Bridge Interface
               * @notice Defines the key functions of an ERC20 bridge on the root chain, which enables bridging of standard ERC20 tokens, ETH, wETH, IMX and wIMX from the root chain to the child chain and back.
               * @dev Features:
               *     - Maps tokens from the root chain to the child chain.
               *     - Deposits tokens from the root chain to the child chain.
               *     - Deposits native ETH from the root chain to the child chain.
               */
              interface IRootERC20Bridge {
                  /**
                   * @notice Holds the addresses of accounts that should be assigned different roles in the bridge, during initialization.
                   */
                  struct InitializationRoles {
                      address defaultAdmin; // The address which will inherit `DEFAULT_ADMIN_ROLE`.
                      address pauser; // The address which will inherit `PAUSER_ROLE`.
                      address unpauser; // The address which will inherit `UNPAUSER_ROLE`.
                      address variableManager; // The address which will inherit `VARIABLE_MANAGER_ROLE`.
                      address adaptorManager; // The address which will inherit `ADAPTOR_MANAGER_ROLE`.
                  }
                  /**
                   * @notice Function to revoke `VARIABLE_MANAGER_ROLE` role from an address
                   */
                  function revokeVariableManagerRole(address account) external;
                  /**
                   * @notice Function to grant `VARIABLE_MANAGER_ROLE` role to an address
                   */
                  function grantVariableManagerRole(address account) external;
                  /**
                   * @notice Updates the root bridge adaptor.
                   * @param newRootBridgeAdaptor Address of new root bridge adaptor.
                   * @dev Can only be called by ADAPTOR_MANAGER_ROLE.
                   */
                  function updateRootBridgeAdaptor(address newRootBridgeAdaptor) external;
                  /**
                   * @notice Receives a bridge message from the child chain.
                   * @param data The data payload of the message.
                   * @dev This function is called by the underlying bridge adaptor on the root chain, when it receives a validated message from the GMP.
                   *         It assumes that the underlying adaptor has already validated the sender chain and sender address.
                   */
                  function onMessageReceive(bytes calldata data) external;
                  /**
                   * @notice Initiates sending a mapToken message to the child chain, if the token hasn't been mapped before.
                   *         This operation requires the `rootToken` to have the following public getter functions: `name()`, `symbol()`, and `decimals()`.
                   *         These functions are optional in the ERC20 standard. If the token does not provide these functions,
                   *         the mapping operation will fail and return a `TokenNotSupported` error.
                   *
                   * @dev The function:
                   *      - fails with a `AlreadyMapped` error if the token has already been mapped.
                   *      - populates a root token => child token mapping on the root chain before
                   *        sending a message telling the child chain to do the same.
                   *      - is `payable` because the message passing protocol requires a fee to be paid.
                   *
                   * @dev The address of the child chain token is deterministic using CREATE2.
                   *
                   * @param rootToken The address of the token on the root chain.
                   * @return childToken The address of the token to be deployed on the child chain.
                   */
                  function mapToken(IERC20Metadata rootToken) external payable returns (address);
                  /**
                   * @notice Deposit tokens to the bridge and issue corresponding tokens to `msg.sender` on the child chain.
                   * @custom:requires `rootToken` should already have been mapped with `mapToken()`.
                   * @param rootToken The address of the token on the root chain.
                   * @param amount The amount of tokens to deposit.
                   * @dev The function is `payable` because the message passing protocol requires a fee to be paid.
                   */
                  function deposit(IERC20Metadata rootToken, uint256 amount) external payable;
                  /**
                   * @notice Deposit tokens to the bridge and issue corresponding tokens to `receiver` address on the child chain.
                   * @custom:requires `rootToken` should already have been mapped with `mapToken()`.
                   * @param rootToken The address of the token on the root chain.
                   * @param receiver The address of the receiver on the child chain, to credit tokens to.
                   * @param amount The amount of tokens to deposit.
                   * @dev The function is `payable` because the message passing protocol requires a fee to be paid.
                   */
                  function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external payable;
                  /**
                   * @notice Deposit ETH to the bridge and issue corresponding wrapped ETH to `msg.sender` on the child chain.
                   * @param amount The amount of tokens to deposit.
                   * @dev The function is `payable` because the message passing protocol requires a fee to be paid.
                   * @dev the `msg.value` provided should cover the amount to send as well as the bridge fee.
                   */
                  function depositETH(uint256 amount) external payable;
                  /**
                   * @notice Deposit ETH to the bridge and issue corresponding wrapped ETH to `receiver` address on the child chain.
                   * @param receiver The address of the receiver on the child chain.
                   * @param amount The amount of tokens to deposit.
                   * @dev The function is `payable` because the message passing protocol requires a fee to be paid.
                   * @dev the `msg.value` provided should cover the amount to send as well as the bridge fee.
                   */
                  function depositToETH(address receiver, uint256 amount) external payable;
              }
              /**
               * @title Root ERC20 Bridge Events
               * @notice Defines event types emitted by a Root ERC20 Bridge implementation.
               */
              interface IRootERC20BridgeEvents {
                  /// @notice Emitted when the root chain bridge adaptor is updated.
                  event RootBridgeAdaptorUpdated(address oldRootBridgeAdaptor, address newRootBridgeAdaptor);
                  /// @notice Emitted when the IMX deposit limit is updated.
                  event NewImxDepositLimit(uint256 oldImxDepositLimit, uint256 newImxDepositLimit);
                  /// @notice Emitted when a map token message is sent to the child chain.
                  event L1TokenMapped(address indexed rootToken, address indexed childToken);
                  /// @notice Emitted when an ERC20 deposit message is sent to the child chain.
                  event ChildChainERC20Deposit(
                      address indexed rootToken,
                      address indexed childToken,
                      address depositor,
                      address indexed receiver,
                      uint256 amount
                  );
                  /// @notice Emitted when an IMX deposit is initated on the root chain.
                  event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount);
                  /// @notice Emitted when a WETH deposit is initiated on the root chain.
                  event WETHDeposit(
                      address indexed rootToken,
                      address indexed childToken,
                      address depositor,
                      address indexed receiver,
                      uint256 amount
                  );
                  /// @notice Emitted when an ETH deposit initiated on the root chain.
                  event NativeEthDeposit(
                      address indexed rootToken,
                      address indexed childToken,
                      address depositor,
                      address indexed receiver,
                      uint256 amount
                  );
                  /// @notice Emitted when an ERC20 withdrawal is executed on the root chain.
                  event RootChainERC20Withdraw(
                      address indexed rootToken,
                      address indexed childToken,
                      address withdrawer,
                      address indexed receiver,
                      uint256 amount
                  );
                  /// @notice Emitted when an ETH withdrawal is executed on the root chain.
                  event RootChainETHWithdraw(
                      address indexed rootToken,
                      address indexed childToken,
                      address withdrawer,
                      address indexed receiver,
                      uint256 amount
                  );
              }
              /**
               * @notice Root ERC20 Bridge Errors
               * @notice Defines error types emitted by a Root ERC20 Bridge implementation.
               */
              interface IRootERC20BridgeErrors {
                  /// @notice Error when the amount requested is less than the value sent.
                  error InsufficientValue();
                  /// @notice Error when there is no gas payment received.
                  error ZeroAmount();
                  /// @notice Error when a zero address is given when not valid.
                  error ZeroAddress();
                  /// @notice Error when a message is sent with no gas payment.
                  error NoGas();
                  /// @notice Error when the child chain name is invalid.
                  error InvalidChildChain();
                  /// @notice Error when a token is already mapped.
                  error AlreadyMapped();
                  /// @notice Error when a token is not mapped when it should be.
                  error NotMapped();
                  /// @notice Error when attempting to map IMX.
                  error CantMapIMX();
                  /// @notice Error when attempting to map ETH.
                  error CantMapETH();
                  /// @notice Error when attempting to map wETH.
                  error CantMapWETH();
                  /// @notice Error when token balance invariant check fails.
                  error BalanceInvariantCheckFailed(uint256 actualBalance, uint256 expectedBalance);
                  /// @notice Error when a message received has invalid data.
                  error InvalidData(string reason);
                  /// @notice Error when caller is not the root bridge adaptor but should be.
                  error NotBridgeAdaptor();
                  /// @notice Error when the total IMX deposit limit is exceeded
                  error ImxDepositLimitExceeded();
                  /// @notice Error when the IMX deposit limit is set below the amount of IMX already deposited
                  error ImxDepositLimitTooLow();
                  /// @notice Error when native transfer is sent to contract from non wrapped-token address.
                  error NonWrappedNativeTransfer();
                  /// @notice Error when the an unauthorized initializer tries to initialize the contract.
                  error UnauthorizedInitializer();
                  /// @notice Error when attempt to map a ERC20 token that doesn't support name(), symbol() or decimals().
                  error TokenNotSupported();
              }
              // Copyright Immutable Pty Ltd 2018 - 2023
              // SPDX-License-Identifier: Apache 2.0
              pragma solidity 0.8.19;
              import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
              /**
               * @title AdaptorRoles.sol
               * @notice AdaptorRoles.sol is an abstract contract that defines the roles and permissions across the root and child chain adaptor contracts.
               * @dev This contract uses OpenZeppelin's AccessControl contract. This contract is abstract and is intended to be inherited by the root and child chain adaptor contracts.
               */
              abstract contract AdaptorRoles is AccessControlUpgradeable {
                  // Roles
                  /// @notice Role identifier for those who can update the bridge used by the adaptor.
                  bytes32 public constant BRIDGE_MANAGER_ROLE = keccak256("BRIDGE_MANAGER");
                  /// @notice Role identifier for those who can update the gas service used by the adaptor.
                  bytes32 public constant GAS_SERVICE_MANAGER_ROLE = keccak256("GAS_SERVICE_MANAGER");
                  /// @notice Role identifier for those who can update targeted bridge used by the adaptor (e.g. target is child chain on root adaptors).
                  bytes32 public constant TARGET_MANAGER_ROLE = keccak256("TARGET_MANAGER");
                  // Role granting functions
                  /**
                   * @notice Function to grant bridge manager role to an address
                   */
                  function grantBridgeManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      grantRole(BRIDGE_MANAGER_ROLE, account);
                  }
                  /**
                   * @notice Function to grant gas service manager role to an address
                   */
                  function grantGasServiceManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      grantRole(GAS_SERVICE_MANAGER_ROLE, account);
                  }
                  /**
                   * @notice Function to grant target manager role to an address
                   */
                  function grantTargetManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      grantRole(TARGET_MANAGER_ROLE, account);
                  }
                  // Role revoking functions
                  /**
                   * @notice Function to revoke bridge manager role from an address
                   */
                  function revokeBridgeManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      revokeRole(BRIDGE_MANAGER_ROLE, account);
                  }
                  /**
                   * @notice Function to revoke gas service manager role from an address
                   */
                  function revokeGasServiceManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      revokeRole(GAS_SERVICE_MANAGER_ROLE, account);
                  }
                  /**
                   * @notice Function to target manager role from an address
                   */
                  function revokeTargetManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
                      revokeRole(TARGET_MANAGER_ROLE, account);
                  }
                  // slither-disable-next-line unused-state,naming-convention
                  uint256[50] private __gapAdaptorRoles;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Standard math utilities missing in the Solidity language.
               */
              library Math {
                  enum Rounding {
                      Down, // Toward negative infinity
                      Up, // Toward infinity
                      Zero // Toward zero
                  }
                  /**
                   * @dev Returns the largest of two numbers.
                   */
                  function max(uint256 a, uint256 b) internal pure returns (uint256) {
                      return a > b ? a : b;
                  }
                  /**
                   * @dev Returns the smallest of two numbers.
                   */
                  function min(uint256 a, uint256 b) internal pure returns (uint256) {
                      return a < b ? a : b;
                  }
                  /**
                   * @dev Returns the average of two numbers. The result is rounded towards
                   * zero.
                   */
                  function average(uint256 a, uint256 b) internal pure returns (uint256) {
                      // (a + b) / 2 can overflow.
                      return (a & b) + (a ^ b) / 2;
                  }
                  /**
                   * @dev Returns the ceiling of the division of two numbers.
                   *
                   * This differs from standard division with `/` in that it rounds up instead
                   * of rounding down.
                   */
                  function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                      // (a + b - 1) / b can overflow on addition, so we distribute.
                      return a == 0 ? 0 : (a - 1) / b + 1;
                  }
                  /**
                   * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
                   * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
                   * with further edits by Uniswap Labs also under MIT license.
                   */
                  function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
                      unchecked {
                          // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                          // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                          // variables such that product = prod1 * 2^256 + prod0.
                          uint256 prod0; // Least significant 256 bits of the product
                          uint256 prod1; // Most significant 256 bits of the product
                          assembly {
                              let mm := mulmod(x, y, not(0))
                              prod0 := mul(x, y)
                              prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                          }
                          // Handle non-overflow cases, 256 by 256 division.
                          if (prod1 == 0) {
                              // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                              // The surrounding unchecked block does not change this fact.
                              // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                              return prod0 / denominator;
                          }
                          // Make sure the result is less than 2^256. Also prevents denominator == 0.
                          require(denominator > prod1, "Math: mulDiv overflow");
                          ///////////////////////////////////////////////
                          // 512 by 256 division.
                          ///////////////////////////////////////////////
                          // Make division exact by subtracting the remainder from [prod1 prod0].
                          uint256 remainder;
                          assembly {
                              // Compute remainder using mulmod.
                              remainder := mulmod(x, y, denominator)
                              // Subtract 256 bit number from 512 bit number.
                              prod1 := sub(prod1, gt(remainder, prod0))
                              prod0 := sub(prod0, remainder)
                          }
                          // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                          // See https://cs.stackexchange.com/q/138556/92363.
                          // Does not overflow because the denominator cannot be zero at this stage in the function.
                          uint256 twos = denominator & (~denominator + 1);
                          assembly {
                              // Divide denominator by twos.
                              denominator := div(denominator, twos)
                              // Divide [prod1 prod0] by twos.
                              prod0 := div(prod0, twos)
                              // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                              twos := add(div(sub(0, twos), twos), 1)
                          }
                          // Shift in bits from prod1 into prod0.
                          prod0 |= prod1 * twos;
                          // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                          // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                          // four bits. That is, denominator * inv = 1 mod 2^4.
                          uint256 inverse = (3 * denominator) ^ 2;
                          // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                          // in modular arithmetic, doubling the correct bits in each step.
                          inverse *= 2 - denominator * inverse; // inverse mod 2^8
                          inverse *= 2 - denominator * inverse; // inverse mod 2^16
                          inverse *= 2 - denominator * inverse; // inverse mod 2^32
                          inverse *= 2 - denominator * inverse; // inverse mod 2^64
                          inverse *= 2 - denominator * inverse; // inverse mod 2^128
                          inverse *= 2 - denominator * inverse; // inverse mod 2^256
                          // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                          // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                          // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                          // is no longer required.
                          result = prod0 * inverse;
                          return result;
                      }
                  }
                  /**
                   * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
                   */
                  function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
                      uint256 result = mulDiv(x, y, denominator);
                      if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                          result += 1;
                      }
                      return result;
                  }
                  /**
                   * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
                   *
                   * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
                   */
                  function sqrt(uint256 a) internal pure returns (uint256) {
                      if (a == 0) {
                          return 0;
                      }
                      // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                      //
                      // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                      // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                      //
                      // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                      // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                      // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                      //
                      // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                      uint256 result = 1 << (log2(a) >> 1);
                      // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                      // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                      // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                      // into the expected uint128 result.
                      unchecked {
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          return min(result, a / result);
                      }
                  }
                  /**
                   * @notice Calculates sqrt(a), following the selected rounding direction.
                   */
                  function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = sqrt(a);
                          return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 2, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log2(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >> 128 > 0) {
                              value >>= 128;
                              result += 128;
                          }
                          if (value >> 64 > 0) {
                              value >>= 64;
                              result += 64;
                          }
                          if (value >> 32 > 0) {
                              value >>= 32;
                              result += 32;
                          }
                          if (value >> 16 > 0) {
                              value >>= 16;
                              result += 16;
                          }
                          if (value >> 8 > 0) {
                              value >>= 8;
                              result += 8;
                          }
                          if (value >> 4 > 0) {
                              value >>= 4;
                              result += 4;
                          }
                          if (value >> 2 > 0) {
                              value >>= 2;
                              result += 2;
                          }
                          if (value >> 1 > 0) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log2(value);
                          return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 10, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log10(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >= 10 ** 64) {
                              value /= 10 ** 64;
                              result += 64;
                          }
                          if (value >= 10 ** 32) {
                              value /= 10 ** 32;
                              result += 32;
                          }
                          if (value >= 10 ** 16) {
                              value /= 10 ** 16;
                              result += 16;
                          }
                          if (value >= 10 ** 8) {
                              value /= 10 ** 8;
                              result += 8;
                          }
                          if (value >= 10 ** 4) {
                              value /= 10 ** 4;
                              result += 4;
                          }
                          if (value >= 10 ** 2) {
                              value /= 10 ** 2;
                              result += 2;
                          }
                          if (value >= 10 ** 1) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log10(value);
                          return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 256, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   *
                   * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
                   */
                  function log256(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >> 128 > 0) {
                              value >>= 128;
                              result += 16;
                          }
                          if (value >> 64 > 0) {
                              value >>= 64;
                              result += 8;
                          }
                          if (value >> 32 > 0) {
                              value >>= 32;
                              result += 4;
                          }
                          if (value >> 16 > 0) {
                              value >>= 16;
                              result += 2;
                          }
                          if (value >> 8 > 0) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log256(value);
                          return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Standard signed math utilities missing in the Solidity language.
               */
              library SignedMath {
                  /**
                   * @dev Returns the largest of two signed numbers.
                   */
                  function max(int256 a, int256 b) internal pure returns (int256) {
                      return a > b ? a : b;
                  }
                  /**
                   * @dev Returns the smallest of two signed numbers.
                   */
                  function min(int256 a, int256 b) internal pure returns (int256) {
                      return a < b ? a : b;
                  }
                  /**
                   * @dev Returns the average of two signed numbers without overflow.
                   * The result is rounded towards zero.
                   */
                  function average(int256 a, int256 b) internal pure returns (int256) {
                      // Formula from the book "Hacker's Delight"
                      int256 x = (a & b) + ((a ^ b) >> 1);
                      return x + (int256(uint256(x) >> 255) & (a ^ b));
                  }
                  /**
                   * @dev Returns the absolute unsigned value of a signed value.
                   */
                  function abs(int256 n) internal pure returns (uint256) {
                      unchecked {
                          // must be unchecked in order to support `n = type(int256).min`
                          return uint256(n >= 0 ? n : -n);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IGovernable } from './IGovernable.sol';
              interface IAxelarGateway is IGovernable {
                  /**********\\
                  |* Errors *|
                  \\**********/
                  error NotSelf();
                  error NotProxy();
                  error InvalidCodeHash();
                  error SetupFailed();
                  error InvalidAuthModule();
                  error InvalidTokenDeployer();
                  error InvalidAmount();
                  error InvalidChainId();
                  error InvalidCommands();
                  error TokenDoesNotExist(string symbol);
                  error TokenAlreadyExists(string symbol);
                  error TokenDeployFailed(string symbol);
                  error TokenContractDoesNotExist(address token);
                  error BurnFailed(string symbol);
                  error MintFailed(string symbol);
                  error InvalidSetMintLimitsParams();
                  error ExceedMintLimit(string symbol);
                  /**********\\
                  |* Events *|
                  \\**********/
                  event TokenSent(
                      address indexed sender,
                      string destinationChain,
                      string destinationAddress,
                      string symbol,
                      uint256 amount
                  );
                  event ContractCall(
                      address indexed sender,
                      string destinationChain,
                      string destinationContractAddress,
                      bytes32 indexed payloadHash,
                      bytes payload
                  );
                  event ContractCallWithToken(
                      address indexed sender,
                      string destinationChain,
                      string destinationContractAddress,
                      bytes32 indexed payloadHash,
                      bytes payload,
                      string symbol,
                      uint256 amount
                  );
                  event Executed(bytes32 indexed commandId);
                  event TokenDeployed(string symbol, address tokenAddresses);
                  event ContractCallApproved(
                      bytes32 indexed commandId,
                      string sourceChain,
                      string sourceAddress,
                      address indexed contractAddress,
                      bytes32 indexed payloadHash,
                      bytes32 sourceTxHash,
                      uint256 sourceEventIndex
                  );
                  event ContractCallApprovedWithMint(
                      bytes32 indexed commandId,
                      string sourceChain,
                      string sourceAddress,
                      address indexed contractAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      bytes32 sourceTxHash,
                      uint256 sourceEventIndex
                  );
                  event TokenMintLimitUpdated(string symbol, uint256 limit);
                  event OperatorshipTransferred(bytes newOperatorsData);
                  event Upgraded(address indexed implementation);
                  /********************\\
                  |* Public Functions *|
                  \\********************/
                  function sendToken(
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      string calldata symbol,
                      uint256 amount
                  ) external;
                  function callContract(
                      string calldata destinationChain,
                      string calldata contractAddress,
                      bytes calldata payload
                  ) external;
                  function callContractWithToken(
                      string calldata destinationChain,
                      string calldata contractAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount
                  ) external;
                  function isContractCallApproved(
                      bytes32 commandId,
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash
                  ) external view returns (bool);
                  function isContractCallAndMintApproved(
                      bytes32 commandId,
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash,
                      string calldata symbol,
                      uint256 amount
                  ) external view returns (bool);
                  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);
                  /***********\\
                  |* Getters *|
                  \\***********/
                  function authModule() external view returns (address);
                  function tokenDeployer() external view returns (address);
                  function tokenMintLimit(string memory symbol) external view returns (uint256);
                  function tokenMintAmount(string memory symbol) external view returns (uint256);
                  function allTokensFrozen() external view returns (bool);
                  function implementation() external view returns (address);
                  function tokenAddresses(string memory symbol) external view returns (address);
                  function tokenFrozen(string memory symbol) external view returns (bool);
                  function isCommandExecuted(bytes32 commandId) external view returns (bool);
                  function adminEpoch() external view returns (uint256);
                  function adminThreshold(uint256 epoch) external view returns (uint256);
                  function admins(uint256 epoch) external view returns (address[] memory);
                  /*******************\\
                  |* Admin Functions *|
                  \\*******************/
                  function setTokenMintLimits(string[] calldata symbols, uint256[] calldata limits) external;
                  function upgrade(
                      address newImplementation,
                      bytes32 newImplementationCodeHash,
                      bytes calldata setupParams
                  ) external;
                  /**********************\\
                  |* External Functions *|
                  \\**********************/
                  function setup(bytes calldata params) external;
                  function execute(bytes calldata input) external;
              }
              // 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;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IOwnable } from './IOwnable.sol';
              import { IContractIdentifier } from './IContractIdentifier.sol';
              // General interface for upgradable contracts
              interface IUpgradable is IOwnable, IContractIdentifier {
                  error InvalidCodeHash();
                  error InvalidImplementation();
                  error SetupFailed();
                  error NotProxy();
                  event Upgraded(address indexed newImplementation);
                  function implementation() external view returns (address);
                  function upgrade(
                      address newImplementation,
                      bytes32 newImplementationCodeHash,
                      bytes calldata params
                  ) external;
                  function setup(bytes calldata data) external;
              }
              // 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);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (access/AccessControl.sol)
              pragma solidity ^0.8.0;
              import "./IAccessControlUpgradeable.sol";
              import "../utils/ContextUpgradeable.sol";
              import "../utils/StringsUpgradeable.sol";
              import "../utils/introspection/ERC165Upgradeable.sol";
              import "../proxy/utils/Initializable.sol";
              /**
               * @dev Contract module that allows children to implement role-based access
               * control mechanisms. This is a lightweight version that doesn't allow enumerating role
               * members except through off-chain means by accessing the contract event logs. Some
               * applications may benefit from on-chain enumerability, for those cases see
               * {AccessControlEnumerable}.
               *
               * Roles are referred to by their `bytes32` identifier. These should be exposed
               * in the external API and be unique. The best way to achieve this is by
               * using `public constant` hash digests:
               *
               * ```solidity
               * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
               * ```
               *
               * Roles can be used to represent a set of permissions. To restrict access to a
               * function call, use {hasRole}:
               *
               * ```solidity
               * function foo() public {
               *     require(hasRole(MY_ROLE, msg.sender));
               *     ...
               * }
               * ```
               *
               * Roles can be granted and revoked dynamically via the {grantRole} and
               * {revokeRole} functions. Each role has an associated admin role, and only
               * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
               *
               * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
               * that only accounts with this role will be able to grant or revoke other
               * roles. More complex role relationships can be created by using
               * {_setRoleAdmin}.
               *
               * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
               * grant and revoke this role. Extra precautions should be taken to secure
               * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
               * to enforce additional security measures for this role.
               */
              abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
                  function __AccessControl_init() internal onlyInitializing {
                  }
                  function __AccessControl_init_unchained() internal onlyInitializing {
                  }
                  struct RoleData {
                      mapping(address => bool) members;
                      bytes32 adminRole;
                  }
                  mapping(bytes32 => RoleData) private _roles;
                  bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
                  /**
                   * @dev Modifier that checks that an account has a specific role. Reverts
                   * with a standardized message including the required role.
                   *
                   * The format of the revert reason is given by the following regular expression:
                   *
                   *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
                   *
                   * _Available since v4.1._
                   */
                  modifier onlyRole(bytes32 role) {
                      _checkRole(role);
                      _;
                  }
                  /**
                   * @dev See {IERC165-supportsInterface}.
                   */
                  function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                      return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId);
                  }
                  /**
                   * @dev Returns `true` if `account` has been granted `role`.
                   */
                  function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
                      return _roles[role].members[account];
                  }
                  /**
                   * @dev Revert with a standard message if `_msgSender()` is missing `role`.
                   * Overriding this function changes the behavior of the {onlyRole} modifier.
                   *
                   * Format of the revert message is described in {_checkRole}.
                   *
                   * _Available since v4.6._
                   */
                  function _checkRole(bytes32 role) internal view virtual {
                      _checkRole(role, _msgSender());
                  }
                  /**
                   * @dev Revert with a standard message if `account` is missing `role`.
                   *
                   * The format of the revert reason is given by the following regular expression:
                   *
                   *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
                   */
                  function _checkRole(bytes32 role, address account) internal view virtual {
                      if (!hasRole(role, account)) {
                          revert(
                              string(
                                  abi.encodePacked(
                                      "AccessControl: account ",
                                      StringsUpgradeable.toHexString(account),
                                      " is missing role ",
                                      StringsUpgradeable.toHexString(uint256(role), 32)
                                  )
                              )
                          );
                      }
                  }
                  /**
                   * @dev Returns the admin role that controls `role`. See {grantRole} and
                   * {revokeRole}.
                   *
                   * To change a role's admin, use {_setRoleAdmin}.
                   */
                  function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
                      return _roles[role].adminRole;
                  }
                  /**
                   * @dev Grants `role` to `account`.
                   *
                   * If `account` had not been already granted `role`, emits a {RoleGranted}
                   * event.
                   *
                   * Requirements:
                   *
                   * - the caller must have ``role``'s admin role.
                   *
                   * May emit a {RoleGranted} event.
                   */
                  function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                      _grantRole(role, account);
                  }
                  /**
                   * @dev Revokes `role` from `account`.
                   *
                   * If `account` had been granted `role`, emits a {RoleRevoked} event.
                   *
                   * Requirements:
                   *
                   * - the caller must have ``role``'s admin role.
                   *
                   * May emit a {RoleRevoked} event.
                   */
                  function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
                      _revokeRole(role, account);
                  }
                  /**
                   * @dev Revokes `role` from the calling account.
                   *
                   * Roles are often managed via {grantRole} and {revokeRole}: this function's
                   * purpose is to provide a mechanism for accounts to lose their privileges
                   * if they are compromised (such as when a trusted device is misplaced).
                   *
                   * If the calling account had been revoked `role`, emits a {RoleRevoked}
                   * event.
                   *
                   * Requirements:
                   *
                   * - the caller must be `account`.
                   *
                   * May emit a {RoleRevoked} event.
                   */
                  function renounceRole(bytes32 role, address account) public virtual override {
                      require(account == _msgSender(), "AccessControl: can only renounce roles for self");
                      _revokeRole(role, account);
                  }
                  /**
                   * @dev Grants `role` to `account`.
                   *
                   * If `account` had not been already granted `role`, emits a {RoleGranted}
                   * event. Note that unlike {grantRole}, this function doesn't perform any
                   * checks on the calling account.
                   *
                   * May emit a {RoleGranted} event.
                   *
                   * [WARNING]
                   * ====
                   * This function should only be called from the constructor when setting
                   * up the initial roles for the system.
                   *
                   * Using this function in any other way is effectively circumventing the admin
                   * system imposed by {AccessControl}.
                   * ====
                   *
                   * NOTE: This function is deprecated in favor of {_grantRole}.
                   */
                  function _setupRole(bytes32 role, address account) internal virtual {
                      _grantRole(role, account);
                  }
                  /**
                   * @dev Sets `adminRole` as ``role``'s admin role.
                   *
                   * Emits a {RoleAdminChanged} event.
                   */
                  function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
                      bytes32 previousAdminRole = getRoleAdmin(role);
                      _roles[role].adminRole = adminRole;
                      emit RoleAdminChanged(role, previousAdminRole, adminRole);
                  }
                  /**
                   * @dev Grants `role` to `account`.
                   *
                   * Internal function without access restriction.
                   *
                   * May emit a {RoleGranted} event.
                   */
                  function _grantRole(bytes32 role, address account) internal virtual {
                      if (!hasRole(role, account)) {
                          _roles[role].members[account] = true;
                          emit RoleGranted(role, account, _msgSender());
                      }
                  }
                  /**
                   * @dev Revokes `role` from `account`.
                   *
                   * Internal function without access restriction.
                   *
                   * May emit a {RoleRevoked} event.
                   */
                  function _revokeRole(bytes32 role, address account) internal virtual {
                      if (hasRole(role, account)) {
                          _roles[role].members[account] = false;
                          emit RoleRevoked(role, account, _msgSender());
                      }
                  }
                  /**
                   * @dev This empty reserved space is put in place to allow future versions to add new
                   * variables without shifting down storage in the inheritance chain.
                   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                   */
                  uint256[49] private __gap;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /**
               * @title IGovernable Interface
               * @notice This is an interface used by the AxelarGateway contract to manage governance and mint limiter roles.
               */
              interface IGovernable {
                  error NotGovernance();
                  error NotMintLimiter();
                  error InvalidGovernance();
                  error InvalidMintLimiter();
                  event GovernanceTransferred(address indexed previousGovernance, address indexed newGovernance);
                  event MintLimiterTransferred(address indexed previousGovernance, address indexed newGovernance);
                  /**
                   * @notice Returns the governance address.
                   * @return address of the governance
                   */
                  function governance() external view returns (address);
                  /**
                   * @notice Returns the mint limiter address.
                   * @return address of the mint limiter
                   */
                  function mintLimiter() external view returns (address);
                  /**
                   * @notice Transfer the governance role to another address.
                   * @param newGovernance The new governance address
                   */
                  function transferGovernance(address newGovernance) external;
                  /**
                   * @notice Transfer the mint limiter role to another address.
                   * @param newGovernance The new mint limiter address
                   */
                  function transferMintLimiter(address newGovernance) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /**
               * @title IOwnable Interface
               * @notice IOwnable is an interface that abstracts the implementation of a
               * contract with ownership control features. It's commonly used in upgradable
               * contracts and includes the functionality to get current owner, transfer
               * ownership, and propose and accept ownership.
               */
              interface IOwnable {
                  error NotOwner();
                  error InvalidOwner();
                  error InvalidOwnerAddress();
                  event OwnershipTransferStarted(address indexed newOwner);
                  event OwnershipTransferred(address indexed newOwner);
                  /**
                   * @notice Returns the current owner of the contract.
                   * @return address The address of the current owner
                   */
                  function owner() external view returns (address);
                  /**
                   * @notice Returns the address of the pending owner of the contract.
                   * @return address The address of the pending owner
                   */
                  function pendingOwner() external view returns (address);
                  /**
                   * @notice Transfers ownership of the contract to a new address
                   * @param newOwner The address to transfer ownership to
                   */
                  function transferOwnership(address newOwner) external;
                  /**
                   * @notice Proposes to transfer the contract's ownership to a new address.
                   * The new owner needs to accept the ownership explicitly.
                   * @param newOwner The address to transfer ownership to
                   */
                  function proposeOwnership(address newOwner) external;
                  /**
                   * @notice Transfers ownership to the pending owner.
                   * @dev Can only be called by the pending owner
                   */
                  function acceptOwnership() external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              // General interface for upgradable contracts
              interface IContractIdentifier {
                  /**
                   * @notice Returns the contract ID. It can be used as a check during upgrades.
                   * @dev Meant to be overridden in derived contracts.
                   * @return bytes32 The contract ID
                   */
                  function contractId() external pure returns (bytes32);
              }
              // 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);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev External interface of AccessControl declared to support ERC165 detection.
               */
              interface IAccessControlUpgradeable {
                  /**
                   * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
                   *
                   * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
                   * {RoleAdminChanged} not being emitted signaling this.
                   *
                   * _Available since v3.1._
                   */
                  event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
                  /**
                   * @dev Emitted when `account` is granted `role`.
                   *
                   * `sender` is the account that originated the contract call, an admin role
                   * bearer except when using {AccessControl-_setupRole}.
                   */
                  event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
                  /**
                   * @dev Emitted when `account` is revoked `role`.
                   *
                   * `sender` is the account that originated the contract call:
                   *   - if using `revokeRole`, it is the admin role bearer
                   *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
                   */
                  event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
                  /**
                   * @dev Returns `true` if `account` has been granted `role`.
                   */
                  function hasRole(bytes32 role, address account) external view returns (bool);
                  /**
                   * @dev Returns the admin role that controls `role`. See {grantRole} and
                   * {revokeRole}.
                   *
                   * To change a role's admin, use {AccessControl-_setRoleAdmin}.
                   */
                  function getRoleAdmin(bytes32 role) external view returns (bytes32);
                  /**
                   * @dev Grants `role` to `account`.
                   *
                   * If `account` had not been already granted `role`, emits a {RoleGranted}
                   * event.
                   *
                   * Requirements:
                   *
                   * - the caller must have ``role``'s admin role.
                   */
                  function grantRole(bytes32 role, address account) external;
                  /**
                   * @dev Revokes `role` from `account`.
                   *
                   * If `account` had been granted `role`, emits a {RoleRevoked} event.
                   *
                   * Requirements:
                   *
                   * - the caller must have ``role``'s admin role.
                   */
                  function revokeRole(bytes32 role, address account) external;
                  /**
                   * @dev Revokes `role` from the calling account.
                   *
                   * Roles are often managed via {grantRole} and {revokeRole}: this function's
                   * purpose is to provide a mechanism for accounts to lose their privileges
                   * if they are compromised (such as when a trusted device is misplaced).
                   *
                   * If the calling account had been granted `role`, emits a {RoleRevoked}
                   * event.
                   *
                   * Requirements:
                   *
                   * - the caller must be `account`.
                   */
                  function renounceRole(bytes32 role, address account) external;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
              pragma solidity ^0.8.0;
              import "../proxy/utils/Initializable.sol";
              /**
               * @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 ContextUpgradeable is Initializable {
                  function __Context_init() internal onlyInitializing {
                  }
                  function __Context_init_unchained() internal onlyInitializing {
                  }
                  function _msgSender() internal view virtual returns (address) {
                      return msg.sender;
                  }
                  function _msgData() internal view virtual returns (bytes calldata) {
                      return msg.data;
                  }
                  /**
                   * @dev This empty reserved space is put in place to allow future versions to add new
                   * variables without shifting down storage in the inheritance chain.
                   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                   */
                  uint256[50] private __gap;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
              pragma solidity ^0.8.0;
              import "./math/MathUpgradeable.sol";
              import "./math/SignedMathUpgradeable.sol";
              /**
               * @dev String operations.
               */
              library StringsUpgradeable {
                  bytes16 private constant _SYMBOLS = "0123456789abcdef";
                  uint8 private constant _ADDRESS_LENGTH = 20;
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` decimal representation.
                   */
                  function toString(uint256 value) internal pure returns (string memory) {
                      unchecked {
                          uint256 length = MathUpgradeable.log10(value) + 1;
                          string memory buffer = new string(length);
                          uint256 ptr;
                          /// @solidity memory-safe-assembly
                          assembly {
                              ptr := add(buffer, add(32, length))
                          }
                          while (true) {
                              ptr--;
                              /// @solidity memory-safe-assembly
                              assembly {
                                  mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                              }
                              value /= 10;
                              if (value == 0) break;
                          }
                          return buffer;
                      }
                  }
                  /**
                   * @dev Converts a `int256` to its ASCII `string` decimal representation.
                   */
                  function toString(int256 value) internal pure returns (string memory) {
                      return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMathUpgradeable.abs(value))));
                  }
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
                   */
                  function toHexString(uint256 value) internal pure returns (string memory) {
                      unchecked {
                          return toHexString(value, MathUpgradeable.log256(value) + 1);
                      }
                  }
                  /**
                   * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
                   */
                  function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                      bytes memory buffer = new bytes(2 * length + 2);
                      buffer[0] = "0";
                      buffer[1] = "x";
                      for (uint256 i = 2 * length + 1; i > 1; --i) {
                          buffer[i] = _SYMBOLS[value & 0xf];
                          value >>= 4;
                      }
                      require(value == 0, "Strings: hex length insufficient");
                      return string(buffer);
                  }
                  /**
                   * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
                   */
                  function toHexString(address addr) internal pure returns (string memory) {
                      return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
                  }
                  /**
                   * @dev Returns true if the two strings are equal.
                   */
                  function equal(string memory a, string memory b) internal pure returns (bool) {
                      return keccak256(bytes(a)) == keccak256(bytes(b));
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
              pragma solidity ^0.8.0;
              import "./IERC165Upgradeable.sol";
              import "../../proxy/utils/Initializable.sol";
              /**
               * @dev Implementation of the {IERC165} interface.
               *
               * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
               * for the additional interface id that will be supported. For example:
               *
               * ```solidity
               * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
               *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
               * }
               * ```
               *
               * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
               */
              abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable {
                  function __ERC165_init() internal onlyInitializing {
                  }
                  function __ERC165_init_unchained() internal onlyInitializing {
                  }
                  /**
                   * @dev See {IERC165-supportsInterface}.
                   */
                  function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
                      return interfaceId == type(IERC165Upgradeable).interfaceId;
                  }
                  /**
                   * @dev This empty reserved space is put in place to allow future versions to add new
                   * variables without shifting down storage in the inheritance chain.
                   * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
                   */
                  uint256[50] private __gap;
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)
              pragma solidity ^0.8.2;
              import "../../utils/AddressUpgradeable.sol";
              /**
               * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
               * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
               * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
               * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
               *
               * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
               * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
               * case an upgrade adds a module that needs to be initialized.
               *
               * For example:
               *
               * [.hljs-theme-light.nopadding]
               * ```solidity
               * contract MyToken is ERC20Upgradeable {
               *     function initialize() initializer public {
               *         __ERC20_init("MyToken", "MTK");
               *     }
               * }
               *
               * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
               *     function initializeV2() reinitializer(2) public {
               *         __ERC20Permit_init("MyToken");
               *     }
               * }
               * ```
               *
               * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
               * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
               *
               * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
               * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
               *
               * [CAUTION]
               * ====
               * Avoid leaving a contract uninitialized.
               *
               * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
               * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
               * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
               *
               * [.hljs-theme-light.nopadding]
               * ```
               * /// @custom:oz-upgrades-unsafe-allow constructor
               * constructor() {
               *     _disableInitializers();
               * }
               * ```
               * ====
               */
              abstract contract Initializable {
                  /**
                   * @dev Indicates that the contract has been initialized.
                   * @custom:oz-retyped-from bool
                   */
                  uint8 private _initialized;
                  /**
                   * @dev Indicates that the contract is in the process of being initialized.
                   */
                  bool private _initializing;
                  /**
                   * @dev Triggered when the contract has been initialized or reinitialized.
                   */
                  event Initialized(uint8 version);
                  /**
                   * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
                   * `onlyInitializing` functions can be used to initialize parent contracts.
                   *
                   * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
                   * constructor.
                   *
                   * Emits an {Initialized} event.
                   */
                  modifier initializer() {
                      bool isTopLevelCall = !_initializing;
                      require(
                          (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
                          "Initializable: contract is already initialized"
                      );
                      _initialized = 1;
                      if (isTopLevelCall) {
                          _initializing = true;
                      }
                      _;
                      if (isTopLevelCall) {
                          _initializing = false;
                          emit Initialized(1);
                      }
                  }
                  /**
                   * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
                   * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
                   * used to initialize parent contracts.
                   *
                   * A reinitializer may be used after the original initialization step. This is essential to configure modules that
                   * are added through upgrades and that require initialization.
                   *
                   * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
                   * cannot be nested. If one is invoked in the context of another, execution will revert.
                   *
                   * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
                   * a contract, executing them in the right order is up to the developer or operator.
                   *
                   * WARNING: setting the version to 255 will prevent any future reinitialization.
                   *
                   * Emits an {Initialized} event.
                   */
                  modifier reinitializer(uint8 version) {
                      require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
                      _initialized = version;
                      _initializing = true;
                      _;
                      _initializing = false;
                      emit Initialized(version);
                  }
                  /**
                   * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
                   * {initializer} and {reinitializer} modifiers, directly or indirectly.
                   */
                  modifier onlyInitializing() {
                      require(_initializing, "Initializable: contract is not initializing");
                      _;
                  }
                  /**
                   * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
                   * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
                   * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
                   * through proxies.
                   *
                   * Emits an {Initialized} event the first time it is successfully executed.
                   */
                  function _disableInitializers() internal virtual {
                      require(!_initializing, "Initializable: contract is initializing");
                      if (_initialized != type(uint8).max) {
                          _initialized = type(uint8).max;
                          emit Initialized(type(uint8).max);
                      }
                  }
                  /**
                   * @dev Returns the highest version that has been initialized. See {reinitializer}.
                   */
                  function _getInitializedVersion() internal view returns (uint8) {
                      return _initialized;
                  }
                  /**
                   * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
                   */
                  function _isInitializing() internal view returns (bool) {
                      return _initializing;
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Standard math utilities missing in the Solidity language.
               */
              library MathUpgradeable {
                  enum Rounding {
                      Down, // Toward negative infinity
                      Up, // Toward infinity
                      Zero // Toward zero
                  }
                  /**
                   * @dev Returns the largest of two numbers.
                   */
                  function max(uint256 a, uint256 b) internal pure returns (uint256) {
                      return a > b ? a : b;
                  }
                  /**
                   * @dev Returns the smallest of two numbers.
                   */
                  function min(uint256 a, uint256 b) internal pure returns (uint256) {
                      return a < b ? a : b;
                  }
                  /**
                   * @dev Returns the average of two numbers. The result is rounded towards
                   * zero.
                   */
                  function average(uint256 a, uint256 b) internal pure returns (uint256) {
                      // (a + b) / 2 can overflow.
                      return (a & b) + (a ^ b) / 2;
                  }
                  /**
                   * @dev Returns the ceiling of the division of two numbers.
                   *
                   * This differs from standard division with `/` in that it rounds up instead
                   * of rounding down.
                   */
                  function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                      // (a + b - 1) / b can overflow on addition, so we distribute.
                      return a == 0 ? 0 : (a - 1) / b + 1;
                  }
                  /**
                   * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
                   * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
                   * with further edits by Uniswap Labs also under MIT license.
                   */
                  function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
                      unchecked {
                          // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                          // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                          // variables such that product = prod1 * 2^256 + prod0.
                          uint256 prod0; // Least significant 256 bits of the product
                          uint256 prod1; // Most significant 256 bits of the product
                          assembly {
                              let mm := mulmod(x, y, not(0))
                              prod0 := mul(x, y)
                              prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                          }
                          // Handle non-overflow cases, 256 by 256 division.
                          if (prod1 == 0) {
                              // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                              // The surrounding unchecked block does not change this fact.
                              // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                              return prod0 / denominator;
                          }
                          // Make sure the result is less than 2^256. Also prevents denominator == 0.
                          require(denominator > prod1, "Math: mulDiv overflow");
                          ///////////////////////////////////////////////
                          // 512 by 256 division.
                          ///////////////////////////////////////////////
                          // Make division exact by subtracting the remainder from [prod1 prod0].
                          uint256 remainder;
                          assembly {
                              // Compute remainder using mulmod.
                              remainder := mulmod(x, y, denominator)
                              // Subtract 256 bit number from 512 bit number.
                              prod1 := sub(prod1, gt(remainder, prod0))
                              prod0 := sub(prod0, remainder)
                          }
                          // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                          // See https://cs.stackexchange.com/q/138556/92363.
                          // Does not overflow because the denominator cannot be zero at this stage in the function.
                          uint256 twos = denominator & (~denominator + 1);
                          assembly {
                              // Divide denominator by twos.
                              denominator := div(denominator, twos)
                              // Divide [prod1 prod0] by twos.
                              prod0 := div(prod0, twos)
                              // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                              twos := add(div(sub(0, twos), twos), 1)
                          }
                          // Shift in bits from prod1 into prod0.
                          prod0 |= prod1 * twos;
                          // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                          // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                          // four bits. That is, denominator * inv = 1 mod 2^4.
                          uint256 inverse = (3 * denominator) ^ 2;
                          // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                          // in modular arithmetic, doubling the correct bits in each step.
                          inverse *= 2 - denominator * inverse; // inverse mod 2^8
                          inverse *= 2 - denominator * inverse; // inverse mod 2^16
                          inverse *= 2 - denominator * inverse; // inverse mod 2^32
                          inverse *= 2 - denominator * inverse; // inverse mod 2^64
                          inverse *= 2 - denominator * inverse; // inverse mod 2^128
                          inverse *= 2 - denominator * inverse; // inverse mod 2^256
                          // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                          // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                          // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                          // is no longer required.
                          result = prod0 * inverse;
                          return result;
                      }
                  }
                  /**
                   * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
                   */
                  function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
                      uint256 result = mulDiv(x, y, denominator);
                      if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                          result += 1;
                      }
                      return result;
                  }
                  /**
                   * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
                   *
                   * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
                   */
                  function sqrt(uint256 a) internal pure returns (uint256) {
                      if (a == 0) {
                          return 0;
                      }
                      // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                      //
                      // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                      // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                      //
                      // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                      // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                      // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                      //
                      // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                      uint256 result = 1 << (log2(a) >> 1);
                      // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                      // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                      // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                      // into the expected uint128 result.
                      unchecked {
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          result = (result + a / result) >> 1;
                          return min(result, a / result);
                      }
                  }
                  /**
                   * @notice Calculates sqrt(a), following the selected rounding direction.
                   */
                  function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = sqrt(a);
                          return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 2, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log2(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >> 128 > 0) {
                              value >>= 128;
                              result += 128;
                          }
                          if (value >> 64 > 0) {
                              value >>= 64;
                              result += 64;
                          }
                          if (value >> 32 > 0) {
                              value >>= 32;
                              result += 32;
                          }
                          if (value >> 16 > 0) {
                              value >>= 16;
                              result += 16;
                          }
                          if (value >> 8 > 0) {
                              value >>= 8;
                              result += 8;
                          }
                          if (value >> 4 > 0) {
                              value >>= 4;
                              result += 4;
                          }
                          if (value >> 2 > 0) {
                              value >>= 2;
                              result += 2;
                          }
                          if (value >> 1 > 0) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log2(value);
                          return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 10, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log10(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >= 10 ** 64) {
                              value /= 10 ** 64;
                              result += 64;
                          }
                          if (value >= 10 ** 32) {
                              value /= 10 ** 32;
                              result += 32;
                          }
                          if (value >= 10 ** 16) {
                              value /= 10 ** 16;
                              result += 16;
                          }
                          if (value >= 10 ** 8) {
                              value /= 10 ** 8;
                              result += 8;
                          }
                          if (value >= 10 ** 4) {
                              value /= 10 ** 4;
                              result += 4;
                          }
                          if (value >= 10 ** 2) {
                              value /= 10 ** 2;
                              result += 2;
                          }
                          if (value >= 10 ** 1) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log10(value);
                          return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
                      }
                  }
                  /**
                   * @dev Return the log in base 256, rounded down, of a positive value.
                   * Returns 0 if given 0.
                   *
                   * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
                   */
                  function log256(uint256 value) internal pure returns (uint256) {
                      uint256 result = 0;
                      unchecked {
                          if (value >> 128 > 0) {
                              value >>= 128;
                              result += 16;
                          }
                          if (value >> 64 > 0) {
                              value >>= 64;
                              result += 8;
                          }
                          if (value >> 32 > 0) {
                              value >>= 32;
                              result += 4;
                          }
                          if (value >> 16 > 0) {
                              value >>= 16;
                              result += 2;
                          }
                          if (value >> 8 > 0) {
                              result += 1;
                          }
                      }
                      return result;
                  }
                  /**
                   * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
                   * Returns 0 if given 0.
                   */
                  function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                      unchecked {
                          uint256 result = log256(value);
                          return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Standard signed math utilities missing in the Solidity language.
               */
              library SignedMathUpgradeable {
                  /**
                   * @dev Returns the largest of two signed numbers.
                   */
                  function max(int256 a, int256 b) internal pure returns (int256) {
                      return a > b ? a : b;
                  }
                  /**
                   * @dev Returns the smallest of two signed numbers.
                   */
                  function min(int256 a, int256 b) internal pure returns (int256) {
                      return a < b ? a : b;
                  }
                  /**
                   * @dev Returns the average of two signed numbers without overflow.
                   * The result is rounded towards zero.
                   */
                  function average(int256 a, int256 b) internal pure returns (int256) {
                      // Formula from the book "Hacker's Delight"
                      int256 x = (a & b) + ((a ^ b) >> 1);
                      return x + (int256(uint256(x) >> 255) & (a ^ b));
                  }
                  /**
                   * @dev Returns the absolute unsigned value of a signed value.
                   */
                  function abs(int256 n) internal pure returns (uint256) {
                      unchecked {
                          // must be unchecked in order to support `n = type(int256).min`
                          return uint256(n >= 0 ? n : -n);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Interface of the ERC165 standard, as defined in the
               * https://eips.ethereum.org/EIPS/eip-165[EIP].
               *
               * Implementers can declare support of contract interfaces, which can then be
               * queried by others ({ERC165Checker}).
               *
               * For an implementation, see {ERC165}.
               */
              interface IERC165Upgradeable {
                  /**
                   * @dev Returns true if this contract implements the interface defined by
                   * `interfaceId`. See the corresponding
                   * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
                   * to learn more about how these ids are created.
                   *
                   * This function call must use less than 30 000 gas.
                   */
                  function supportsInterface(bytes4 interfaceId) external view returns (bool);
              }
              // 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 AddressUpgradeable {
                  /**
                   * @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 6 of 7: AxelarGasService
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { GasEstimationType, GasInfo } from '../types/GasEstimationTypes.sol';
              import { IInterchainGasEstimation } from '../interfaces/IInterchainGasEstimation.sol';
              /**
               * @title InterchainGasEstimation
               * @notice This is an abstract contract that allows for estimating gas fees for cross-chain communication on the Axelar network.
               */
              abstract contract InterchainGasEstimation is IInterchainGasEstimation {
                  // keccak256('GasEstimate.Slot') - 1
                  bytes32 internal constant GAS_SERVICE_SLOT = 0x2fa150da4c9f4c3a28593398c65313dd42f63d0530ec6db4a2b46e6d837a3902;
                  // 68 bytes for the TX RLP encoding overhead
                  uint256 internal constant TX_ENCODING_OVERHEAD = 68;
                  // GMP executeWithToken call parameters
                  // 4 bytes for method selector, 32 bytes for the commandId, 96 bytes for the sourceChain, 128 bytes for the sourceAddress, 96 bytes for token symbol, 32 bytes for amount
                  // Expecting most of the calldata bytes to be zeroes. So multiplying by 8 as a weighted average of 4 and 16
                  uint256 internal constant GMP_CALLDATA_SIZE = 4 + 32 + 96 + 128 + 96 + 32; // 388 bytes
                  struct GasServiceStorage {
                      mapping(string => GasInfo) gasPrices;
                  }
                  /**
                   * @notice Returns the gas price for a specific chain.
                   * @param chain The name of the chain
                   * @return gasInfo The gas info for the chain
                   */
                  function getGasInfo(string calldata chain) external view returns (GasInfo memory) {
                      return _storage().gasPrices[chain];
                  }
                  /**
                   * @notice Sets the gas price for a specific chain.
                   * @dev This function is called by the gas oracle to update the gas price for a specific chain.
                   * @param chain The name of the chain
                   * @param gasInfo The gas info for the chain
                   */
                  function _setGasInfo(string calldata chain, GasInfo calldata gasInfo) internal {
                      emit GasInfoUpdated(chain, gasInfo);
                      _storage().gasPrices[chain] = gasInfo;
                  }
                  /**
                   * @notice Estimates the gas fee for a contract call on a destination chain.
                   * @param destinationChain Axelar registered name of the destination chain
                   * param destinationAddress Destination contract address being called
                   * @param executionGasLimit The gas limit to be used for the destination contract execution,
                   *        e.g. pass in 200k if your app consumes needs upto 200k for this contract call
                   * param params Additional parameters for the gas estimation
                   * @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service.
                   */
                  function estimateGasFee(
                      string calldata destinationChain,
                      string calldata, /* destinationAddress */
                      bytes calldata payload,
                      uint256 executionGasLimit,
                      bytes calldata /* params */
                  ) public view returns (uint256 gasEstimate) {
                      GasInfo storage gasInfo = _storage().gasPrices[destinationChain];
                      GasEstimationType gasEstimationType = GasEstimationType(gasInfo.gasEstimationType);
                      gasEstimate = gasInfo.axelarBaseFee + (executionGasLimit * gasInfo.relativeGasPrice);
                      // if chain is L2, compute L1 data fee using L1 gas price info
                      if (gasEstimationType != GasEstimationType.Default) {
                          GasInfo storage l1GasInfo = _storage().gasPrices['ethereum'];
                          gasEstimate += computeL1DataFee(gasEstimationType, payload, gasInfo, l1GasInfo);
                      }
                  }
                  /**
                   * @notice Computes the additional L1 data fee for an L2 destination chain.
                   * @param gasEstimationType The gas estimation type
                   * @param payload The payload of the contract call
                   * @param l1GasInfo The L1 gas info
                   * @return l1DataFee The L1 to L2 data fee
                   */
                  function computeL1DataFee(
                      GasEstimationType gasEstimationType,
                      bytes calldata payload,
                      GasInfo storage gasInfo,
                      GasInfo storage l1GasInfo
                  ) internal view returns (uint256) {
                      if (gasEstimationType == GasEstimationType.OptimismEcotone) {
                          return optimismEcotoneL1Fee(payload, gasInfo, l1GasInfo);
                      }
                      if (gasEstimationType == GasEstimationType.OptimismBedrock) {
                          return optimismBedrockL1Fee(payload, gasInfo, l1GasInfo);
                      }
                      if (gasEstimationType == GasEstimationType.Arbitrum) {
                          return arbitrumL1Fee(payload, gasInfo, l1GasInfo);
                      }
                      if (gasEstimationType == GasEstimationType.Scroll) {
                          return scrollL1Fee(payload, gasInfo, l1GasInfo);
                      }
                      revert UnsupportedEstimationType(gasEstimationType);
                  }
                  /**
                   * @notice Computes the L1 to L2 fee for an OP chain with Ecotone gas model.
                   * @param payload The payload of the contract call
                   * @param gasInfo Destination chain gas info
                   * @param l1GasInfo The L1 gas info
                   * @return l1DataFee The L1 to L2 data fee
                   */
                  function optimismEcotoneL1Fee(
                      bytes calldata payload,
                      GasInfo storage gasInfo,
                      GasInfo storage l1GasInfo
                  ) internal view returns (uint256 l1DataFee) {
                      /* Optimism Ecotone gas model https://docs.optimism.io/stack/transactions/fees#ecotone
                           tx_compressed_size = ((count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16)) / 16
                           weighted_gas_price = 16 * base_fee_scalar*base_fee + blob_base_fee_scalar * blob_base_fee
                           l1_data_fee = tx_compressed_size * weighted_gas_price
                         Reference implementation:
                           https://github.com/ethereum-optimism/optimism/blob/876e16ad04968f0bb641eb76f98eb77e7e1a3e16/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L138
                      */
                      // The new base_fee_scalar is currently set to 0.001368
                      // We are setting it to un upper bound of 0.0015 to account for possible fluctuations
                      uint256 scalarPrecision = 10**6;
                      // The blob_base_fee_scalar is currently set to 0.810949. Setting it to 0.9 as an upper bound
                      // https://eips.ethereum.org/EIPS/eip-4844
                      uint256 blobBaseFeeScalar = 9 * 10**5; // 0.9 multiplied by scalarPrecision
                      // Calculating transaction size in bytes that will later be divided by 16 to compress the size
                      uint256 txSize = _l1TxSize(payload);
                      uint256 weightedGasPrice = 16 *
                          gasInfo.l1FeeScalar *
                          l1GasInfo.relativeGasPrice +
                          blobBaseFeeScalar *
                          l1GasInfo.relativeBlobBaseFee;
                      l1DataFee = (weightedGasPrice * txSize) / (16 * scalarPrecision); // 16 for txSize compression and scalar precision conversion
                  }
                  /**
                   * @notice Computes the L1 to L2 fee for an OP chain with Bedrock gas model.
                   * @param payload The payload of the contract call
                   * @param gasInfo Destination chain gas info
                   * @param l1GasInfo The L1 gas info
                   * @return l1DataFee The L1 to L2 data fee
                   */
                  function optimismBedrockL1Fee(
                      bytes calldata payload,
                      GasInfo storage gasInfo,
                      GasInfo storage l1GasInfo
                  ) internal view returns (uint256 l1DataFee) {
                      // Resembling OP Bedrock gas price model
                      // https://docs.optimism.io/stack/transactions/fees#bedrock
                      // https://docs-v2.mantle.xyz/devs/concepts/tx-fee/ef
                      // Reference https://github.com/mantlenetworkio/mantle-v2/blob/a29f01045191344b0ba89542215e6a02bd5e7fcc/packages/contracts-bedrock/contracts/L2/GasPriceOracle.sol#L98-L105
                      uint256 overhead = 188;
                      uint256 precision = 1e6;
                      uint256 txSize = _l1TxSize(payload) + overhead;
                      return (l1GasInfo.relativeGasPrice * txSize * gasInfo.l1FeeScalar) / precision;
                  }
                  /**
                   * @notice Computes the L1 to L2 fee for a contract call on the Arbitrum chain.
                   * @param payload The payload of the contract call
                   * param gasInfo Destination chain gas info
                   * @param l1GasInfo The L1 gas info
                   * @return l1DataFee The L1 to L2 data fee
                   */
                  function arbitrumL1Fee(
                      bytes calldata payload,
                      GasInfo storage, /* gasInfo */
                      GasInfo storage l1GasInfo
                  ) internal view returns (uint256 l1DataFee) {
                      // https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas
                      // https://docs.arbitrum.io/arbos/l1-pricing
                      // Reference https://github.com/OffchainLabs/nitro/blob/master/arbos/l1pricing/l1pricing.go#L565-L578
                      uint256 oneInBips = 10000;
                      uint256 txDataNonZeroGasEIP2028 = 16;
                      uint256 estimationPaddingUnits = 16 * txDataNonZeroGasEIP2028;
                      uint256 estimationPaddingBasisPoints = 100;
                      uint256 l1Bytes = TX_ENCODING_OVERHEAD + GMP_CALLDATA_SIZE + payload.length;
                      // Brotli baseline compression rate as 2x
                      uint256 units = (txDataNonZeroGasEIP2028 * l1Bytes) / 2;
                      return
                          (l1GasInfo.relativeGasPrice *
                              (units + estimationPaddingUnits) *
                              (oneInBips + estimationPaddingBasisPoints)) / oneInBips;
                  }
                  /**
                   * @notice Computes the L1 to L2 fee for a contract call on the Scroll chain.
                   * @param payload The payload of the contract call
                   * @param gasInfo Destination chain gas info
                   * @param l1GasInfo The L1 gas info
                   * @return l1DataFee The L1 to L2 data fee
                   */
                  function scrollL1Fee(
                      bytes calldata payload,
                      GasInfo storage gasInfo,
                      GasInfo storage l1GasInfo
                  ) internal view returns (uint256 l1DataFee) {
                      // https://docs.scroll.io/en/developers/guides/estimating-gas-and-tx-fees/
                      // Reference https://github.com/scroll-tech/scroll/blob/af2913903b181f3492af1c62b4da4c1c99cc552d/contracts/src/L2/predeploys/L1GasPriceOracle.sol#L63-L86
                      uint256 overhead = 2500;
                      uint256 precision = 1e9;
                      uint256 txSize = _l1TxSize(payload) + overhead + (4 * 16);
                      return (l1GasInfo.relativeGasPrice * txSize * gasInfo.l1FeeScalar) / precision;
                  }
                  /**
                   * @notice Computes the transaction size for an L1 transaction
                   * @param payload The payload of the contract call
                   * @return txSize The transaction size
                   */
                  function _l1TxSize(bytes calldata payload) private pure returns (uint256 txSize) {
                      txSize = TX_ENCODING_OVERHEAD * 16;
                      // GMP executeWithToken call parameters
                      // Expecting most of the calldata bytes to be zeroes. So multiplying by 8 as a weighted average of 4 and 16
                      txSize += GMP_CALLDATA_SIZE * 8;
                      uint256 length = payload.length;
                      for (uint256 i; i < length; ++i) {
                          if (payload[i] == 0) {
                              txSize += 4; // 4 for each zero byte
                          } else {
                              txSize += 16; // 16 for each non-zero byte
                          }
                      }
                  }
                  /**
                   * @notice Get the storage slot for the GasServiceStorage struct
                   */
                  function _storage() private pure returns (GasServiceStorage storage slot) {
                      assembly {
                          slot.slot := GAS_SERVICE_SLOT
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { GasInfo } from '../types/GasEstimationTypes.sol';
              import { IInterchainGasEstimation } from './IInterchainGasEstimation.sol';
              import { IUpgradable } from './IUpgradable.sol';
              /**
               * @title IAxelarGasService Interface
               * @notice This is an interface for the AxelarGasService contract which manages gas payments
               * and refunds for cross-chain communication on the Axelar network.
               * @dev This interface inherits IUpgradable
               */
              interface IAxelarGasService is IInterchainGasEstimation, IUpgradable {
                  error InvalidAddress();
                  error NotCollector();
                  error InvalidAmounts();
                  error InvalidGasUpdates();
                  error InvalidParams();
                  error InsufficientGasPayment(uint256 required, uint256 provided);
                  event GasPaidForContractCall(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event GasPaidForContractCallWithToken(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasPaidForContractCall(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasPaidForContractCallWithToken(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event GasPaidForExpressCall(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event GasPaidForExpressCallWithToken(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasPaidForExpressCall(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasPaidForExpressCallWithToken(
                      address indexed sourceAddress,
                      string destinationChain,
                      string destinationAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event GasAdded(
                      bytes32 indexed txHash,
                      uint256 indexed logIndex,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeGasAdded(bytes32 indexed txHash, uint256 indexed logIndex, uint256 gasFeeAmount, address refundAddress);
                  event ExpressGasAdded(
                      bytes32 indexed txHash,
                      uint256 indexed logIndex,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event NativeExpressGasAdded(
                      bytes32 indexed txHash,
                      uint256 indexed logIndex,
                      uint256 gasFeeAmount,
                      address refundAddress
                  );
                  event Refunded(
                      bytes32 indexed txHash,
                      uint256 indexed logIndex,
                      address payable receiver,
                      address token,
                      uint256 amount
                  );
                  /**
                   * @notice Pay for gas for any type of contract execution on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @dev If estimateOnChain is true, the function will estimate the gas cost and revert if the payment is insufficient.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param executionGasLimit The gas limit for the contract call
                   * @param estimateOnChain Flag to enable on-chain gas estimation
                   * @param refundAddress The address where refunds, if any, should be sent
                   * @param params Additional parameters for gas payment. This can be left empty for normal contract call payments.
                   */
                  function payGas(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      uint256 executionGasLimit,
                      bool estimateOnChain,
                      address refundAddress,
                      bytes calldata params
                  ) external payable;
                  /**
                   * @notice Pay for gas using ERC20 tokens for a contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForContractCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Pay for gas using ERC20 tokens for a contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForContractCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Pay for gas using native currency for a contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForContractCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Pay for gas using native currency for a contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForContractCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Pay for gas using ERC20 tokens for an express contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForExpressCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Pay for gas using ERC20 tokens for an express contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForExpressCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Pay for gas using native currency for an express contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForExpressCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Pay for gas using native currency for an express contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForExpressCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Add additional gas payment using ERC20 tokens after initiating a cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param gasToken The ERC20 token address used to add gas
                   * @param gasFeeAmount The amount of tokens to add as gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Add additional gas payment using native currency after initiating a cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addNativeGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Add additional gas payment using ERC20 tokens after initiating an express cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param gasToken The ERC20 token address used to add gas
                   * @param gasFeeAmount The amount of tokens to add as gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addExpressGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external;
                  /**
                   * @notice Add additional gas payment using native currency after initiating an express cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addNativeExpressGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address refundAddress
                  ) external payable;
                  /**
                   * @notice Updates the gas price for a specific chain.
                   * @dev This function is called by the gas oracle to update the gas prices for a specific chains.
                   * @param chains Array of chain names
                   * @param gasUpdates Array of gas updates
                   */
                  function updateGasInfo(string[] calldata chains, GasInfo[] calldata gasUpdates) external;
                  /**
                   * @notice Allows the gasCollector to collect accumulated fees from the contract.
                   * @dev Use address(0) as the token address for native currency.
                   * @param receiver The address to receive the collected fees
                   * @param tokens Array of token addresses to be collected
                   * @param amounts Array of amounts to be collected for each respective token address
                   */
                  function collectFees(
                      address payable receiver,
                      address[] calldata tokens,
                      uint256[] calldata amounts
                  ) external;
                  /**
                   * @notice Refunds gas payment to the receiver in relation to a specific cross-chain transaction.
                   * @dev Only callable by the gasCollector.
                   * @dev Use address(0) as the token address to refund native currency.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param receiver The address to receive the refund
                   * @param token The token address to be refunded
                   * @param amount The amount to refund
                   */
                  function refund(
                      bytes32 txHash,
                      uint256 logIndex,
                      address payable receiver,
                      address token,
                      uint256 amount
                  ) external;
                  /**
                   * @notice Returns the address of the designated gas collector.
                   * @return address of the gas collector
                   */
                  function gasCollector() external returns (address);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              // General interface for upgradable contracts
              interface IContractIdentifier {
                  /**
                   * @notice Returns the contract ID. It can be used as a check during upgrades.
                   * @dev Meant to be overridden in derived contracts.
                   * @return bytes32 The contract ID
                   */
                  function contractId() external pure returns (bytes32);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  error InvalidAccount();
                  /**
                   * @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 `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
                      address recipient,
                      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);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IContractIdentifier } from './IContractIdentifier.sol';
              interface IImplementation is IContractIdentifier {
                  error NotProxy();
                  function setup(bytes calldata data) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { GasEstimationType, GasInfo } from '../types/GasEstimationTypes.sol';
              /**
               * @title IInterchainGasEstimation Interface
               * @notice This is an interface for the InterchainGasEstimation contract
               * which allows for estimating gas fees for cross-chain communication on the Axelar network.
               */
              interface IInterchainGasEstimation {
                  error UnsupportedEstimationType(GasEstimationType gasEstimationType);
                  /**
                   * @notice Event emitted when the gas price for a specific chain is updated.
                   * @param chain The name of the chain
                   * @param info The gas info for the chain
                   */
                  event GasInfoUpdated(string chain, GasInfo info);
                  /**
                   * @notice Returns the gas price for a specific chain.
                   * @param chain The name of the chain
                   * @return gasInfo The gas info for the chain
                   */
                  function getGasInfo(string calldata chain) external view returns (GasInfo memory);
                  /**
                   * @notice Estimates the gas fee for a cross-chain contract call.
                   * @param destinationChain Axelar registered name of the destination chain
                   * @param destinationAddress Destination contract address being called
                   * @param executionGasLimit The gas limit to be used for the destination contract execution,
                   *        e.g. pass in 200k if your app consumes needs upto 200k for this contract call
                   * @param params Additional parameters for the gas estimation
                   * @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be forwarded to the gas service.
                   */
                  function estimateGasFee(
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      uint256 executionGasLimit,
                      bytes calldata params
                  ) external view returns (uint256 gasEstimate);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /**
               * @title IOwnable Interface
               * @notice IOwnable is an interface that abstracts the implementation of a
               * contract with ownership control features. It's commonly used in upgradable
               * contracts and includes the functionality to get current owner, transfer
               * ownership, and propose and accept ownership.
               */
              interface IOwnable {
                  error NotOwner();
                  error InvalidOwner();
                  error InvalidOwnerAddress();
                  event OwnershipTransferStarted(address indexed newOwner);
                  event OwnershipTransferred(address indexed newOwner);
                  /**
                   * @notice Returns the current owner of the contract.
                   * @return address The address of the current owner
                   */
                  function owner() external view returns (address);
                  /**
                   * @notice Returns the address of the pending owner of the contract.
                   * @return address The address of the pending owner
                   */
                  function pendingOwner() external view returns (address);
                  /**
                   * @notice Transfers ownership of the contract to a new address
                   * @param newOwner The address to transfer ownership to
                   */
                  function transferOwnership(address newOwner) external;
                  /**
                   * @notice Proposes to transfer the contract's ownership to a new address.
                   * The new owner needs to accept the ownership explicitly.
                   * @param newOwner The address to transfer ownership to
                   */
                  function proposeOwnership(address newOwner) external;
                  /**
                   * @notice Transfers ownership to the pending owner.
                   * @dev Can only be called by the pending owner
                   */
                  function acceptOwnership() external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IOwnable } from './IOwnable.sol';
              import { IImplementation } from './IImplementation.sol';
              // General interface for upgradable contracts
              interface IUpgradable is IOwnable, IImplementation {
                  error InvalidCodeHash();
                  error InvalidImplementation();
                  error SetupFailed();
                  event Upgraded(address indexed newImplementation);
                  function implementation() external view returns (address);
                  function upgrade(
                      address newImplementation,
                      bytes32 newImplementationCodeHash,
                      bytes calldata params
                  ) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              error NativeTransferFailed();
              /*
               * @title SafeNativeTransfer
               * @dev This library is used for performing safe native value transfers in Solidity by utilizing inline assembly.
               */
              library SafeNativeTransfer {
                  /*
                   * @notice Perform a native transfer to a given address.
                   * @param receiver The recipient address to which the amount will be sent.
                   * @param amount The amount of native value to send.
                   * @throws NativeTransferFailed error if transfer is not successful.
                   */
                  function safeNativeTransfer(address receiver, uint256 amount) internal {
                      bool success;
                      assembly {
                          success := call(gas(), receiver, amount, 0, 0, 0, 0)
                      }
                      if (!success) revert NativeTransferFailed();
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IERC20 } from '../interfaces/IERC20.sol';
              error TokenTransferFailed();
              /*
               * @title SafeTokenCall
               * @dev This library is used for performing safe token transfers.
               */
              library SafeTokenCall {
                  /*
                   * @notice Make a safe call to a token contract.
                   * @param token The token contract to interact with.
                   * @param callData The function call data.
                   * @throws TokenTransferFailed error if transfer of token is not successful.
                   */
                  function safeCall(IERC20 token, bytes memory callData) internal {
                      (bool success, bytes memory returnData) = address(token).call(callData);
                      bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));
                      if (!transferred || address(token).code.length == 0) revert TokenTransferFailed();
                  }
              }
              /*
               * @title SafeTokenTransfer
               * @dev This library safely transfers tokens from the contract to a recipient.
               */
              library SafeTokenTransfer {
                  /*
                   * @notice Transfer tokens to a recipient.
                   * @param token The token contract.
                   * @param receiver The recipient of the tokens.
                   * @param amount The amount of tokens to transfer.
                   */
                  function safeTransfer(
                      IERC20 token,
                      address receiver,
                      uint256 amount
                  ) internal {
                      SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20.transfer.selector, receiver, amount));
                  }
              }
              /*
               * @title SafeTokenTransferFrom
               * @dev This library helps to safely transfer tokens on behalf of a token holder.
               */
              library SafeTokenTransferFrom {
                  /*
                   * @notice Transfer tokens on behalf of a token holder.
                   * @param token The token contract.
                   * @param from The address of the token holder.
                   * @param to The address the tokens are to be sent to.
                   * @param amount The amount of tokens to be transferred.
                   */
                  function safeTransferFrom(
                      IERC20 token,
                      address from,
                      address to,
                      uint256 amount
                  ) internal {
                      SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, amount));
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /**
               * @title GasEstimationType
               * @notice This enum represents the gas estimation types for different chains.
               */
              enum GasEstimationType {
                  Default,
                  OptimismEcotone,
                  OptimismBedrock,
                  Arbitrum,
                  Scroll
              }
              /**
               * @title GasInfo
               * @notice This struct represents the gas pricing information for a specific chain.
               * @dev Smaller uint types are used for efficient struct packing to save storage costs.
               */
              struct GasInfo {
                  /// @dev Custom gas pricing rule, such as L1 data fee on L2s
                  uint64 gasEstimationType;
                  /// @dev Scalar value needed for specific gas estimation types, expected to be less than 1e10
                  uint64 l1FeeScalar;
                  /// @dev Axelar base fee for cross-chain message approval on destination, in terms of source native gas token
                  uint128 axelarBaseFee;
                  /// @dev Gas price of destination chain, in terms of the source chain token, i.e dest_gas_price * dest_token_market_price / src_token_market_price
                  uint128 relativeGasPrice;
                  /// @dev Needed for specific gas estimation types. Blob base fee of destination chain, in terms of the source chain token, i.e dest_blob_base_fee * dest_token_market_price / src_token_market_price
                  uint128 relativeBlobBaseFee;
                  /// @dev Axelar express fee for express execution, in terms of source chain token
                  uint128 expressFee;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IImplementation } from '../interfaces/IImplementation.sol';
              /**
               * @title Implementation
               * @notice This contract serves as a base for other contracts and enforces a proxy-first access restriction.
               * @dev Derived contracts must implement the setup function.
               */
              abstract contract Implementation is IImplementation {
                  address private immutable implementationAddress;
                  /**
                   * @dev Contract constructor that sets the implementation address to the address of this contract.
                   */
                  constructor() {
                      implementationAddress = address(this);
                  }
                  /**
                   * @dev Modifier to require the caller to be the proxy contract.
                   * Reverts if the caller is the current contract (i.e., the implementation contract itself).
                   */
                  modifier onlyProxy() {
                      if (implementationAddress == address(this)) revert NotProxy();
                      _;
                  }
                  /**
                   * @notice Initializes contract parameters.
                   * This function is intended to be overridden by derived contracts.
                   * The overriding function must have the onlyProxy modifier.
                   * @param params The parameters to be used for initialization
                   */
                  function setup(bytes calldata params) external virtual;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IImplementation } from '../interfaces/IImplementation.sol';
              import { IUpgradable } from '../interfaces/IUpgradable.sol';
              import { Ownable } from '../utils/Ownable.sol';
              import { Implementation } from './Implementation.sol';
              /**
               * @title Upgradable Contract
               * @notice This contract provides an interface for upgradable smart contracts and includes the functionality to perform upgrades.
               */
              abstract contract Upgradable is Ownable, Implementation, IUpgradable {
                  // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
                  bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                  /**
                   * @notice Constructor sets the implementation address to the address of the contract itself
                   * @dev This is used in the onlyProxy modifier to prevent certain functions from being called directly
                   * on the implementation contract itself.
                   * @dev The owner is initially set as address(1) because the actual owner is set within the proxy. It is not
                   * set as the zero address because Ownable is designed to throw an error for ownership transfers to the zero address.
                   */
                  constructor() Ownable(address(1)) {}
                  /**
                   * @notice Returns the address of the current implementation
                   * @return implementation_ Address of the current implementation
                   */
                  function implementation() public view returns (address implementation_) {
                      assembly {
                          implementation_ := sload(_IMPLEMENTATION_SLOT)
                      }
                  }
                  /**
                   * @notice Upgrades the contract to a new implementation
                   * @param newImplementation The address of the new implementation contract
                   * @param newImplementationCodeHash The codehash of the new implementation contract
                   * @param params Optional setup parameters for the new implementation contract
                   * @dev This function is only callable by the owner.
                   */
                  function upgrade(
                      address newImplementation,
                      bytes32 newImplementationCodeHash,
                      bytes calldata params
                  ) external override onlyOwner {
                      if (IUpgradable(newImplementation).contractId() != IUpgradable(implementation()).contractId())
                          revert InvalidImplementation();
                      if (newImplementationCodeHash != newImplementation.codehash) revert InvalidCodeHash();
                      assembly {
                          sstore(_IMPLEMENTATION_SLOT, newImplementation)
                      }
                      emit Upgraded(newImplementation);
                      if (params.length > 0) {
                          // slither-disable-next-line controlled-delegatecall
                          (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(this.setup.selector, params));
                          if (!success) revert SetupFailed();
                      }
                  }
                  /**
                   * @notice Sets up the contract with initial data
                   * @param data Initialization data for the contract
                   * @dev This function is only callable by the proxy contract.
                   */
                  function setup(bytes calldata data) external override(IImplementation, Implementation) onlyProxy {
                      _setup(data);
                  }
                  /**
                   * @notice Internal function to set up the contract with initial data
                   * @param data Initialization data for the contract
                   * @dev This function should be implemented in derived contracts.
                   */
                  function _setup(bytes calldata data) internal virtual {}
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IOwnable } from '../interfaces/IOwnable.sol';
              /**
               * @title Ownable
               * @notice A 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.
               *
               * The owner account is set through ownership transfer. This module makes
               * it possible to transfer the ownership of the contract to a new account in one
               * step, as well as to an interim pending owner. In the second flow the ownership does not
               * change until the pending owner accepts the ownership transfer.
               */
              abstract contract Ownable is IOwnable {
                  // keccak256('owner')
                  bytes32 internal constant _OWNER_SLOT = 0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0;
                  // keccak256('ownership-transfer')
                  bytes32 internal constant _OWNERSHIP_TRANSFER_SLOT =
                      0x9855384122b55936fbfb8ca5120e63c6537a1ac40caf6ae33502b3c5da8c87d1;
                  /**
                   * @notice Initializes the contract by transferring ownership to the owner parameter.
                   * @param _owner Address to set as the initial owner of the contract
                   */
                  constructor(address _owner) {
                      _transferOwnership(_owner);
                  }
                  /**
                   * @notice Modifier that throws an error if called by any account other than the owner.
                   */
                  modifier onlyOwner() {
                      if (owner() != msg.sender) revert NotOwner();
                      _;
                  }
                  /**
                   * @notice Returns the current owner of the contract.
                   * @return owner_ The current owner of the contract
                   */
                  function owner() public view returns (address owner_) {
                      assembly {
                          owner_ := sload(_OWNER_SLOT)
                      }
                  }
                  /**
                   * @notice Returns the pending owner of the contract.
                   * @return owner_ The pending owner of the contract
                   */
                  function pendingOwner() public view returns (address owner_) {
                      assembly {
                          owner_ := sload(_OWNERSHIP_TRANSFER_SLOT)
                      }
                  }
                  /**
                   * @notice Transfers ownership of the contract to a new account `newOwner`.
                   * @dev Can only be called by the current owner.
                   * @param newOwner The address to transfer ownership to
                   */
                  function transferOwnership(address newOwner) external virtual onlyOwner {
                      _transferOwnership(newOwner);
                  }
                  /**
                   * @notice Propose to transfer ownership of the contract to a new account `newOwner`.
                   * @dev Can only be called by the current owner. The ownership does not change
                   * until the new owner accepts the ownership transfer.
                   * @param newOwner The address to transfer ownership to
                   */
                  function proposeOwnership(address newOwner) external virtual onlyOwner {
                      if (newOwner == address(0)) revert InvalidOwnerAddress();
                      emit OwnershipTransferStarted(newOwner);
                      assembly {
                          sstore(_OWNERSHIP_TRANSFER_SLOT, newOwner)
                      }
                  }
                  /**
                   * @notice Accepts ownership of the contract.
                   * @dev Can only be called by the pending owner
                   */
                  function acceptOwnership() external virtual {
                      address newOwner = pendingOwner();
                      if (newOwner != msg.sender) revert InvalidOwner();
                      _transferOwnership(newOwner);
                  }
                  /**
                   * @notice Internal function to transfer ownership of the contract to a new account `newOwner`.
                   * @dev Called in the constructor to set the initial owner.
                   * @param newOwner The address to transfer ownership to
                   */
                  function _transferOwnership(address newOwner) internal virtual {
                      if (newOwner == address(0)) revert InvalidOwnerAddress();
                      emit OwnershipTransferred(newOwner);
                      assembly {
                          sstore(_OWNER_SLOT, newOwner)
                          sstore(_OWNERSHIP_TRANSFER_SLOT, 0)
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
              import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol';
              import { InterchainGasEstimation, GasInfo } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/gas-estimation/InterchainGasEstimation.sol';
              import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol';
              import { SafeTokenTransfer, SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
              import { SafeNativeTransfer } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeNativeTransfer.sol';
              /**
               * @title AxelarGasService
               * @notice This contract manages gas payments and refunds for cross-chain communication on the Axelar network.
               * @dev The owner address of this contract should be the microservice that pays for gas.
               * @dev Users pay gas for cross-chain calls, and the gasCollector can collect accumulated fees and/or refund users if needed.
               */
              contract AxelarGasService is InterchainGasEstimation, Upgradable, IAxelarGasService {
                  using SafeTokenTransfer for IERC20;
                  using SafeTokenTransferFrom for IERC20;
                  using SafeNativeTransfer for address payable;
                  address public immutable gasCollector;
                  /**
                   * @notice Constructs the AxelarGasService contract.
                   * @param gasCollector_ The address of the gas collector
                   */
                  constructor(address gasCollector_) {
                      gasCollector = gasCollector_;
                  }
                  /**
                   * @notice Modifier that ensures the caller is the designated gas collector.
                   */
                  modifier onlyCollector() {
                      if (msg.sender != gasCollector) revert NotCollector();
                      _;
                  }
                  /**
                   * @notice Pay for gas for any type of contract execution on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @dev If estimateOnChain is true, the function will estimate the gas cost and revert if the payment is insufficient.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param executionGasLimit The gas limit for the contract call
                   * @param estimateOnChain Flag to enable on-chain gas estimation
                   * @param refundAddress The address where refunds, if any, should be sent
                   * @param params Additional parameters for gas payment
                   */
                  function payGas(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      uint256 executionGasLimit,
                      bool estimateOnChain,
                      address refundAddress,
                      bytes calldata params
                  ) external payable override {
                      if (params.length > 0) {
                          revert InvalidParams();
                      }
                      if (estimateOnChain) {
                          uint256 gasEstimate = estimateGasFee(destinationChain, destinationAddress, payload, executionGasLimit, params);
                          if (gasEstimate > msg.value) {
                              revert InsufficientGasPayment(gasEstimate, msg.value);
                          }
                      }
                      emit NativeGasPaidForContractCall(sender, destinationChain, destinationAddress, keccak256(payload), msg.value, refundAddress);
                  }
                  /**
                   * @notice Pay for gas using ERC20 tokens for a contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForContractCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external override {
                      emit GasPaidForContractCall(
                          sender,
                          destinationChain,
                          destinationAddress,
                          keccak256(payload),
                          gasToken,
                          gasFeeAmount,
                          refundAddress
                      );
                      IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
                  }
                  /**
                   * @notice Pay for gas using ERC20 tokens for a contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForContractCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string memory symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external override {
                      emit GasPaidForContractCallWithToken(
                          sender,
                          destinationChain,
                          destinationAddress,
                          keccak256(payload),
                          symbol,
                          amount,
                          gasToken,
                          gasFeeAmount,
                          refundAddress
                      );
                      IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
                  }
                  /**
                   * @notice Pay for gas using native currency for a contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForContractCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address refundAddress
                  ) external payable override {
                      emit NativeGasPaidForContractCall(sender, destinationChain, destinationAddress, keccak256(payload), msg.value, refundAddress);
                  }
                  /**
                   * @notice Pay for gas using native currency for a contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForContractCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address refundAddress
                  ) external payable override {
                      emit NativeGasPaidForContractCallWithToken(
                          sender,
                          destinationChain,
                          destinationAddress,
                          keccak256(payload),
                          symbol,
                          amount,
                          msg.value,
                          refundAddress
                      );
                  }
                  /**
                   * @notice Pay for gas using ERC20 tokens for an express contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForExpressCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external override {
                      emit GasPaidForExpressCall(sender, destinationChain, destinationAddress, keccak256(payload), gasToken, gasFeeAmount, refundAddress);
                      IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
                  }
                  /**
                   * @notice Pay for gas using ERC20 tokens for an express contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to express execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param gasToken The address of the ERC20 token used to pay for gas
                   * @param gasFeeAmount The amount of tokens to pay for gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payGasForExpressCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string memory symbol,
                      uint256 amount,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external override {
                      emit GasPaidForExpressCallWithToken(
                          sender,
                          destinationChain,
                          destinationAddress,
                          keccak256(payload),
                          symbol,
                          amount,
                          gasToken,
                          gasFeeAmount,
                          refundAddress
                      );
                      IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
                  }
                  /**
                   * @notice Pay for gas using native currency for an express contract call on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForExpressCall(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      address refundAddress
                  ) external payable override {
                      emit NativeGasPaidForExpressCall(sender, destinationChain, destinationAddress, keccak256(payload), msg.value, refundAddress);
                  }
                  /**
                   * @notice Pay for gas using native currency for an express contract call with tokens on a destination chain.
                   * @dev This function is called on the source chain before calling the gateway to execute a remote contract.
                   * @param sender The address making the payment
                   * @param destinationChain The target chain where the contract call with tokens will be made
                   * @param destinationAddress The target address on the destination chain
                   * @param payload Data payload for the contract call with tokens
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function payNativeGasForExpressCallWithToken(
                      address sender,
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount,
                      address refundAddress
                  ) external payable override {
                      emit NativeGasPaidForExpressCallWithToken(
                          sender,
                          destinationChain,
                          destinationAddress,
                          keccak256(payload),
                          symbol,
                          amount,
                          msg.value,
                          refundAddress
                      );
                  }
                  /**
                   * @notice Add additional gas payment using ERC20 tokens after initiating a cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param gasToken The ERC20 token address used to add gas
                   * @param gasFeeAmount The amount of tokens to add as gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external override {
                      emit GasAdded(txHash, logIndex, gasToken, gasFeeAmount, refundAddress);
                      IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
                  }
                  /**
                   * @notice Add additional gas payment using native currency after initiating a cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addNativeGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address refundAddress
                  ) external payable override {
                      emit NativeGasAdded(txHash, logIndex, msg.value, refundAddress);
                  }
                  /**
                   * @notice Add additional gas payment using ERC20 tokens after initiating an express cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param gasToken The ERC20 token address used to add gas
                   * @param gasFeeAmount The amount of tokens to add as gas
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addExpressGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address gasToken,
                      uint256 gasFeeAmount,
                      address refundAddress
                  ) external override {
                      emit ExpressGasAdded(txHash, logIndex, gasToken, gasFeeAmount, refundAddress);
                      IERC20(gasToken).safeTransferFrom(msg.sender, address(this), gasFeeAmount);
                  }
                  /**
                   * @notice Add additional gas payment using native currency after initiating an express cross-chain call.
                   * @dev This function can be called on the source chain after calling the gateway to express execute a remote contract.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param refundAddress The address where refunds, if any, should be sent
                   */
                  function addNativeExpressGas(
                      bytes32 txHash,
                      uint256 logIndex,
                      address refundAddress
                  ) external payable override {
                      emit NativeExpressGasAdded(txHash, logIndex, msg.value, refundAddress);
                  }
                  /**
                   * @notice Updates the gas price for a specific chain.
                   * @dev This function is called by the gas oracle to update the gas prices for a specific chains.
                   * @param chains Array of chain names
                   * @param gasUpdates Array of gas updates
                   */
                  function updateGasInfo(string[] calldata chains, GasInfo[] calldata gasUpdates) external onlyCollector {
                      uint256 chainsLength = chains.length;
                      if (chainsLength != gasUpdates.length) revert InvalidGasUpdates();
                      for (uint256 i; i < chainsLength; i++) {
                          string calldata chain = chains[i];
                          GasInfo calldata gasUpdate = gasUpdates[i];
                          _setGasInfo(chain, gasUpdate);
                      }
                  }
                  /**
                   * @notice Allows the gasCollector to collect accumulated fees from the contract.
                   * @dev Use address(0) as the token address for native currency.
                   * @param receiver The address to receive the collected fees
                   * @param tokens Array of token addresses to be collected
                   * @param amounts Array of amounts to be collected for each respective token address
                   */
                  function collectFees(
                      address payable receiver,
                      address[] calldata tokens,
                      uint256[] calldata amounts
                  ) external onlyCollector {
                      if (receiver == address(0)) revert InvalidAddress();
                      uint256 tokensLength = tokens.length;
                      if (tokensLength != amounts.length) revert InvalidAmounts();
                      for (uint256 i; i < tokensLength; i++) {
                          address token = tokens[i];
                          uint256 amount = amounts[i];
                          if (amount == 0) revert InvalidAmounts();
                          if (token == address(0)) {
                              if (amount <= address(this).balance) receiver.safeNativeTransfer(amount);
                          } else {
                              // slither-disable-next-line calls-loop
                              if (amount <= IERC20(token).balanceOf(address(this))) IERC20(token).safeTransfer(receiver, amount);
                          }
                      }
                  }
                  /**
                   * @dev Deprecated refund function, kept for backward compatibility.
                   */
                  function refund(
                      address payable receiver,
                      address token,
                      uint256 amount
                  ) external onlyCollector {
                      _refund(bytes32(0), 0, receiver, token, amount);
                  }
                  /**
                   * @notice Refunds gas payment to the receiver in relation to a specific cross-chain transaction.
                   * @dev Only callable by the gasCollector.
                   * @dev Use address(0) as the token address to refund native currency.
                   * @param txHash The transaction hash of the cross-chain call
                   * @param logIndex The log index for the cross-chain call
                   * @param receiver The address to receive the refund
                   * @param token The token address to be refunded
                   * @param amount The amount to refund
                   */
                  function refund(
                      bytes32 txHash,
                      uint256 logIndex,
                      address payable receiver,
                      address token,
                      uint256 amount
                  ) external onlyCollector {
                      _refund(txHash, logIndex, receiver, token, amount);
                  }
                  /**
                   * @dev Internal function to implement gas refund logic.
                   */
                  function _refund(
                      bytes32 txHash,
                      uint256 logIndex,
                      address payable receiver,
                      address token,
                      uint256 amount
                  ) private {
                      if (receiver == address(0)) revert InvalidAddress();
                      emit Refunded(txHash, logIndex, receiver, token, amount);
                      if (token == address(0)) {
                          receiver.safeNativeTransfer(amount);
                      } else {
                          IERC20(token).safeTransfer(receiver, amount);
                      }
                  }
                  /**
                   * @notice Returns a unique identifier for the contract.
                   * @return bytes32 Hash of the contract identifier
                   */
                  function contractId() external pure returns (bytes32) {
                      return keccak256('axelar-gas-service');
                  }
              }
              

              File 7 of 7: AxelarGateway
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IGovernable } from './IGovernable.sol';
              import { IImplementation } from './IImplementation.sol';
              interface IAxelarGateway is IImplementation, IGovernable {
                  /**********\\
                  |* Errors *|
                  \\**********/
                  error NotSelf();
                  error InvalidCodeHash();
                  error SetupFailed();
                  error InvalidAuthModule();
                  error InvalidTokenDeployer();
                  error InvalidAmount();
                  error InvalidChainId();
                  error InvalidCommands();
                  error TokenDoesNotExist(string symbol);
                  error TokenAlreadyExists(string symbol);
                  error TokenDeployFailed(string symbol);
                  error TokenContractDoesNotExist(address token);
                  error BurnFailed(string symbol);
                  error MintFailed(string symbol);
                  error InvalidSetMintLimitsParams();
                  error ExceedMintLimit(string symbol);
                  /**********\\
                  |* Events *|
                  \\**********/
                  event TokenSent(
                      address indexed sender,
                      string destinationChain,
                      string destinationAddress,
                      string symbol,
                      uint256 amount
                  );
                  event ContractCall(
                      address indexed sender,
                      string destinationChain,
                      string destinationContractAddress,
                      bytes32 indexed payloadHash,
                      bytes payload
                  );
                  event ContractCallWithToken(
                      address indexed sender,
                      string destinationChain,
                      string destinationContractAddress,
                      bytes32 indexed payloadHash,
                      bytes payload,
                      string symbol,
                      uint256 amount
                  );
                  event Executed(bytes32 indexed commandId);
                  event TokenDeployed(string symbol, address tokenAddresses);
                  event ContractCallApproved(
                      bytes32 indexed commandId,
                      string sourceChain,
                      string sourceAddress,
                      address indexed contractAddress,
                      bytes32 indexed payloadHash,
                      bytes32 sourceTxHash,
                      uint256 sourceEventIndex
                  );
                  event ContractCallApprovedWithMint(
                      bytes32 indexed commandId,
                      string sourceChain,
                      string sourceAddress,
                      address indexed contractAddress,
                      bytes32 indexed payloadHash,
                      string symbol,
                      uint256 amount,
                      bytes32 sourceTxHash,
                      uint256 sourceEventIndex
                  );
                  event ContractCallExecuted(bytes32 indexed commandId);
                  event TokenMintLimitUpdated(string symbol, uint256 limit);
                  event OperatorshipTransferred(bytes newOperatorsData);
                  event Upgraded(address indexed implementation);
                  /********************\\
                  |* Public Functions *|
                  \\********************/
                  function sendToken(
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      string calldata symbol,
                      uint256 amount
                  ) external;
                  function callContract(
                      string calldata destinationChain,
                      string calldata contractAddress,
                      bytes calldata payload
                  ) external;
                  function callContractWithToken(
                      string calldata destinationChain,
                      string calldata contractAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount
                  ) external;
                  function isContractCallApproved(
                      bytes32 commandId,
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash
                  ) external view returns (bool);
                  function isContractCallAndMintApproved(
                      bytes32 commandId,
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash,
                      string calldata symbol,
                      uint256 amount
                  ) external view returns (bool);
                  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);
                  /***********\\
                  |* Getters *|
                  \\***********/
                  function authModule() external view returns (address);
                  function tokenDeployer() external view returns (address);
                  function tokenMintLimit(string memory symbol) external view returns (uint256);
                  function tokenMintAmount(string memory symbol) external view returns (uint256);
                  function allTokensFrozen() external view returns (bool);
                  function implementation() external view returns (address);
                  function tokenAddresses(string memory symbol) external view returns (address);
                  function tokenFrozen(string memory symbol) external view returns (bool);
                  function isCommandExecuted(bytes32 commandId) external view returns (bool);
                  /************************\\
                  |* Governance Functions *|
                  \\************************/
                  function setTokenMintLimits(string[] calldata symbols, uint256[] calldata limits) external;
                  function upgrade(
                      address newImplementation,
                      bytes32 newImplementationCodeHash,
                      bytes calldata setupParams
                  ) external;
                  /**********************\\
                  |* External Functions *|
                  \\**********************/
                  function execute(bytes calldata input) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              // General interface for upgradable contracts
              interface IContractIdentifier {
                  /**
                   * @notice Returns the contract ID. It can be used as a check during upgrades.
                   * @dev Meant to be overridden in derived contracts.
                   * @return bytes32 The contract ID
                   */
                  function contractId() external pure returns (bytes32);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  error InvalidAccount();
                  /**
                   * @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 `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
                      address recipient,
                      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);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /**
               * @title IGovernable Interface
               * @notice This is an interface used by the AxelarGateway contract to manage governance and mint limiter roles.
               */
              interface IGovernable {
                  error NotGovernance();
                  error NotMintLimiter();
                  error InvalidGovernance();
                  error InvalidMintLimiter();
                  event GovernanceTransferred(address indexed previousGovernance, address indexed newGovernance);
                  event MintLimiterTransferred(address indexed previousGovernance, address indexed newGovernance);
                  /**
                   * @notice Returns the governance address.
                   * @return address of the governance
                   */
                  function governance() external view returns (address);
                  /**
                   * @notice Returns the mint limiter address.
                   * @return address of the mint limiter
                   */
                  function mintLimiter() external view returns (address);
                  /**
                   * @notice Transfer the governance role to another address.
                   * @param newGovernance The new governance address
                   */
                  function transferGovernance(address newGovernance) external;
                  /**
                   * @notice Transfer the mint limiter role to another address.
                   * @param newGovernance The new mint limiter address
                   */
                  function transferMintLimiter(address newGovernance) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IContractIdentifier } from './IContractIdentifier.sol';
              interface IImplementation is IContractIdentifier {
                  error NotProxy();
                  function setup(bytes calldata data) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              /**
               * @title IOwnable Interface
               * @notice IOwnable is an interface that abstracts the implementation of a
               * contract with ownership control features. It's commonly used in upgradable
               * contracts and includes the functionality to get current owner, transfer
               * ownership, and propose and accept ownership.
               */
              interface IOwnable {
                  error NotOwner();
                  error InvalidOwner();
                  error InvalidOwnerAddress();
                  event OwnershipTransferStarted(address indexed newOwner);
                  event OwnershipTransferred(address indexed newOwner);
                  /**
                   * @notice Returns the current owner of the contract.
                   * @return address The address of the current owner
                   */
                  function owner() external view returns (address);
                  /**
                   * @notice Returns the address of the pending owner of the contract.
                   * @return address The address of the pending owner
                   */
                  function pendingOwner() external view returns (address);
                  /**
                   * @notice Transfers ownership of the contract to a new address
                   * @param newOwner The address to transfer ownership to
                   */
                  function transferOwnership(address newOwner) external;
                  /**
                   * @notice Proposes to transfer the contract's ownership to a new address.
                   * The new owner needs to accept the ownership explicitly.
                   * @param newOwner The address to transfer ownership to
                   */
                  function proposeOwnership(address newOwner) external;
                  /**
                   * @notice Transfers ownership to the pending owner.
                   * @dev Can only be called by the pending owner
                   */
                  function acceptOwnership() external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              library ContractAddress {
                  function isContract(address contractAddress) internal view returns (bool) {
                      bytes32 existingCodeHash = contractAddress.codehash;
                      // https://eips.ethereum.org/EIPS/eip-1052
                      // keccak256('') == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
                      return
                          existingCodeHash != bytes32(0) &&
                          existingCodeHash != 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IERC20 } from '../interfaces/IERC20.sol';
              error TokenTransferFailed();
              /*
               * @title SafeTokenCall
               * @dev This library is used for performing safe token transfers.
               */
              library SafeTokenCall {
                  /*
                   * @notice Make a safe call to a token contract.
                   * @param token The token contract to interact with.
                   * @param callData The function call data.
                   * @throws TokenTransferFailed error if transfer of token is not successful.
                   */
                  function safeCall(IERC20 token, bytes memory callData) internal {
                      (bool success, bytes memory returnData) = address(token).call(callData);
                      bool transferred = success && (returnData.length == uint256(0) || abi.decode(returnData, (bool)));
                      if (!transferred || address(token).code.length == 0) revert TokenTransferFailed();
                  }
              }
              /*
               * @title SafeTokenTransfer
               * @dev This library safely transfers tokens from the contract to a recipient.
               */
              library SafeTokenTransfer {
                  /*
                   * @notice Transfer tokens to a recipient.
                   * @param token The token contract.
                   * @param receiver The recipient of the tokens.
                   * @param amount The amount of tokens to transfer.
                   */
                  function safeTransfer(
                      IERC20 token,
                      address receiver,
                      uint256 amount
                  ) internal {
                      SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20.transfer.selector, receiver, amount));
                  }
              }
              /*
               * @title SafeTokenTransferFrom
               * @dev This library helps to safely transfer tokens on behalf of a token holder.
               */
              library SafeTokenTransferFrom {
                  /*
                   * @notice Transfer tokens on behalf of a token holder.
                   * @param token The token contract.
                   * @param from The address of the token holder.
                   * @param to The address the tokens are to be sent to.
                   * @param amount The amount of tokens to be transferred.
                   */
                  function safeTransferFrom(
                      IERC20 token,
                      address from,
                      address to,
                      uint256 amount
                  ) internal {
                      SafeTokenCall.safeCall(token, abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, amount));
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IImplementation } from '../interfaces/IImplementation.sol';
              /**
               * @title Implementation
               * @notice This contract serves as a base for other contracts and enforces a proxy-first access restriction.
               * @dev Derived contracts must implement the setup function.
               */
              abstract contract Implementation is IImplementation {
                  address private immutable implementationAddress;
                  /**
                   * @dev Contract constructor that sets the implementation address to the address of this contract.
                   */
                  constructor() {
                      implementationAddress = address(this);
                  }
                  /**
                   * @dev Modifier to require the caller to be the proxy contract.
                   * Reverts if the caller is the current contract (i.e., the implementation contract itself).
                   */
                  modifier onlyProxy() {
                      if (implementationAddress == address(this)) revert NotProxy();
                      _;
                  }
                  /**
                   * @notice Initializes contract parameters.
                   * This function is intended to be overridden by derived contracts.
                   * The overriding function must have the onlyProxy modifier.
                   * @param params The parameters to be used for initialization
                   */
                  function setup(bytes calldata params) external virtual;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';
              import { IImplementation } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IImplementation.sol';
              import { IContractIdentifier } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IContractIdentifier.sol';
              import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol';
              import { SafeTokenCall, SafeTokenTransfer, SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
              import { ContractAddress } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/ContractAddress.sol';
              import { Implementation } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Implementation.sol';
              import { IAxelarAuth } from './interfaces/IAxelarAuth.sol';
              import { IBurnableMintableCappedERC20 } from './interfaces/IBurnableMintableCappedERC20.sol';
              import { ITokenDeployer } from './interfaces/ITokenDeployer.sol';
              import { ECDSA } from './ECDSA.sol';
              import { DepositHandler } from './DepositHandler.sol';
              import { EternalStorage } from './EternalStorage.sol';
              /**
               * @title AxelarGateway Contract
               * @notice This contract serves as the gateway for cross-chain contract calls,
               * and token transfers within the Axelar network.
               * It includes functions for sending tokens, calling contracts, and validating contract calls.
               * The contract is managed via the decentralized governance mechanism on the Axelar network.
               * @dev EternalStorage is used to simplify storage for upgradability, and InterchainGovernance module is used for governance.
               */
              contract AxelarGateway is IAxelarGateway, Implementation, EternalStorage {
                  using SafeTokenCall for IERC20;
                  using SafeTokenTransfer for IERC20;
                  using SafeTokenTransferFrom for IERC20;
                  using ContractAddress for address;
                  error InvalidImplementation();
                  enum TokenType {
                      InternalBurnable,
                      InternalBurnableFrom,
                      External
                  }
                  /**
                   * @dev Deprecated slots. Should not be reused.
                   */
                  // bytes32 internal constant KEY_ALL_TOKENS_FROZEN = keccak256('all-tokens-frozen');
                  // bytes32 internal constant PREFIX_TOKEN_FROZEN = keccak256('token-frozen');
                  /**
                   * @dev Storage slot with the address of the current implementation. `keccak256('eip1967.proxy.implementation') - 1`.
                   */
                  bytes32 internal constant KEY_IMPLEMENTATION = bytes32(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);
                  /**
                   * @dev Storage slot with the address of the current governance. keccak256('governance')) - 1
                   */
                  bytes32 internal constant KEY_GOVERNANCE = bytes32(0xabea6fd3db56a6e6d0242111b43ebb13d1c42709651c032c7894962023a1f909);
                  /**
                   * @dev Storage slot with the address of the current mint limiter. keccak256('mint-limiter')) - 1
                   */
                  bytes32 internal constant KEY_MINT_LIMITER = bytes32(0x627f0c11732837b3240a2de89c0b6343512886dd50978b99c76a68c6416a4d92);
                  bytes32 internal constant PREFIX_COMMAND_EXECUTED = keccak256('command-executed');
                  bytes32 internal constant PREFIX_TOKEN_ADDRESS = keccak256('token-address');
                  bytes32 internal constant PREFIX_TOKEN_TYPE = keccak256('token-type');
                  bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED = keccak256('contract-call-approved');
                  bytes32 internal constant PREFIX_CONTRACT_CALL_APPROVED_WITH_MINT = keccak256('contract-call-approved-with-mint');
                  bytes32 internal constant PREFIX_TOKEN_MINT_LIMIT = keccak256('token-mint-limit');
                  bytes32 internal constant PREFIX_TOKEN_MINT_AMOUNT = keccak256('token-mint-amount');
                  bytes32 internal constant SELECTOR_BURN_TOKEN = keccak256('burnToken');
                  bytes32 internal constant SELECTOR_DEPLOY_TOKEN = keccak256('deployToken');
                  bytes32 internal constant SELECTOR_MINT_TOKEN = keccak256('mintToken');
                  bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL = keccak256('approveContractCall');
                  bytes32 internal constant SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT = keccak256('approveContractCallWithMint');
                  bytes32 internal constant SELECTOR_TRANSFER_OPERATORSHIP = keccak256('transferOperatorship');
                  address public immutable authModule;
                  address public immutable tokenDeployer;
                  /**
                   * @notice Constructs the AxelarGateway contract.
                   * @param authModule_ The address of the authentication module
                   * @param tokenDeployer_ The address of the token deployer
                   */
                  constructor(address authModule_, address tokenDeployer_) {
                      if (authModule_.code.length == 0) revert InvalidAuthModule();
                      if (tokenDeployer_.code.length == 0) revert InvalidTokenDeployer();
                      authModule = authModule_;
                      tokenDeployer = tokenDeployer_;
                  }
                  /**
                   * @notice Ensures that the caller of the function is the gateway contract itself.
                   */
                  modifier onlySelf() {
                      if (msg.sender != address(this)) revert NotSelf();
                      _;
                  }
                  /**
                   * @notice Ensures that the caller of the function is the governance address.
                   */
                  modifier onlyGovernance() {
                      if (msg.sender != getAddress(KEY_GOVERNANCE)) revert NotGovernance();
                      _;
                  }
                  /**
                   * @notice Ensures that the caller of the function is either the mint limiter or governance.
                   */
                  modifier onlyMintLimiter() {
                      if (msg.sender != getAddress(KEY_MINT_LIMITER) && msg.sender != getAddress(KEY_GOVERNANCE)) revert NotMintLimiter();
                      _;
                  }
                  /******************\\
                  |* Public Methods *|
                  \\******************/
                  /**
                   * @notice Send the specified token to the destination chain and address.
                   * @param destinationChain The chain to send tokens to. A registered chain name on Axelar must be used here
                   * @param destinationAddress The address on the destination chain to send tokens to
                   * @param symbol The symbol of the token to send
                   * @param amount The amount of tokens to send
                   */
                  function sendToken(
                      string calldata destinationChain,
                      string calldata destinationAddress,
                      string calldata symbol,
                      uint256 amount
                  ) external {
                      _burnTokenFrom(msg.sender, symbol, amount);
                      emit TokenSent(msg.sender, destinationChain, destinationAddress, symbol, amount);
                  }
                  /**
                   * @notice Calls a contract on the specified destination chain with a given payload.
                   * This function is the entry point for general message passing between chains.
                   * @param destinationChain The chain where the destination contract exists. A registered chain name on Axelar must be used here
                   * @param destinationContractAddress The address of the contract to call on the destination chain
                   * @param payload The payload to be sent to the destination contract, usually representing an encoded function call with arguments
                   */
                  function callContract(
                      string calldata destinationChain,
                      string calldata destinationContractAddress,
                      bytes calldata payload
                  ) external {
                      emit ContractCall(msg.sender, destinationChain, destinationContractAddress, keccak256(payload), payload);
                  }
                  /**
                   * @notice Calls a contract on the specified destination chain with a given payload and token amount.
                   * This function is the entry point for general message passing with token transfer between chains.
                   * @param destinationChain The chain where the destination contract exists. A registered chain name on Axelar must be used here
                   * @param destinationContractAddress The address of the contract to call with tokens on the destination chain
                   * @param payload The payload to be sent to the destination contract, usually representing an encoded function call with arguments
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   */
                  function callContractWithToken(
                      string calldata destinationChain,
                      string calldata destinationContractAddress,
                      bytes calldata payload,
                      string calldata symbol,
                      uint256 amount
                  ) external {
                      _burnTokenFrom(msg.sender, symbol, amount);
                      emit ContractCallWithToken(msg.sender, destinationChain, destinationContractAddress, keccak256(payload), payload, symbol, amount);
                  }
                  /**
                   * @notice Checks whether a contract call has been approved by the gateway.
                   * @param commandId The gateway command ID
                   * @param sourceChain The source chain of the contract call
                   * @param sourceAddress The source address of the contract call
                   * @param contractAddress The contract address that will be called
                   * @param payloadHash The hash of the payload for that will be sent with the call
                   * @return bool A boolean value indicating whether the contract call has been approved by the gateway.
                   */
                  function isContractCallApproved(
                      bytes32 commandId,
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash
                  ) external view override returns (bool) {
                      return getBool(_getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash));
                  }
                  /**
                   * @notice Checks whether a contract call with token has been approved by the gateway.
                   * @param commandId The gateway command ID
                   * @param sourceChain The source chain of the contract call
                   * @param sourceAddress The source address of the contract call
                   * @param contractAddress The contract address that will be called, and where tokens will be sent
                   * @param payloadHash The hash of the payload for that will be sent with the call
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @return bool A boolean value indicating whether the contract call with token has been approved by the gateway.
                   */
                  function isContractCallAndMintApproved(
                      bytes32 commandId,
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash,
                      string calldata symbol,
                      uint256 amount
                  ) external view override returns (bool) {
                      return
                          getBool(
                              _getIsContractCallApprovedWithMintKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, symbol, amount)
                          );
                  }
                  /**
                   * @notice Called on the destination chain gateway by the recipient of the cross-chain contract call to validate it and only allow execution
                   * if this function returns true.
                   * @dev Once validated, the gateway marks the message as executed so the contract call is not executed twice.
                   * @param commandId The gateway command ID
                   * @param sourceChain The source chain of the contract call
                   * @param sourceAddress The source address of the contract call
                   * @param payloadHash The hash of the payload for that will be sent with the call
                   * @return valid True if the contract call is approved, false otherwise
                   */
                  function validateContractCall(
                      bytes32 commandId,
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      bytes32 payloadHash
                  ) external override returns (bool valid) {
                      bytes32 key = _getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, msg.sender, payloadHash);
                      valid = getBool(key);
                      if (valid) {
                          _setBool(key, false);
                          emit ContractCallExecuted(commandId);
                      }
                  }
                  /**
                   * @notice Called on the destination chain gateway to validate the approval of a contract call with token transfer and only
                   * allow execution if this function returns true.
                   * @dev Once validated, the gateway marks the message as executed so the contract call with token is not executed twice.
                   * @param commandId The gateway command ID
                   * @param sourceChain The source chain of the contract call
                   * @param sourceAddress The source address of the contract call
                   * @param payloadHash The hash of the payload for that will be sent with the call
                   * @param symbol The symbol of the token to be sent with the call
                   * @param amount The amount of tokens to be sent with the call
                   * @return valid True if the contract call with token is approved, false otherwise
                   */
                  function validateContractCallAndMint(
                      bytes32 commandId,
                      string calldata sourceChain,
                      string calldata sourceAddress,
                      bytes32 payloadHash,
                      string calldata symbol,
                      uint256 amount
                  ) external override returns (bool valid) {
                      bytes32 key = _getIsContractCallApprovedWithMintKey(commandId, sourceChain, sourceAddress, msg.sender, payloadHash, symbol, amount);
                      valid = getBool(key);
                      if (valid) {
                          // Prevent re-entrancy
                          _setBool(key, false);
                          emit ContractCallExecuted(commandId);
                          _mintToken(symbol, msg.sender, amount);
                      }
                  }
                  /***********\\
                  |* Getters *|
                  \\***********/
                  /**
                   * @notice Gets the address of governance, should be the address of InterchainGovernance.
                   * @return address The address of governance.
                   */
                  function governance() public view override returns (address) {
                      return getAddress(KEY_GOVERNANCE);
                  }
                  /**
                   * @notice Gets the address of the mint limiter, should be the address of Multisig.
                   * @return address The address of the mint limiter.
                   */
                  function mintLimiter() public view override returns (address) {
                      return getAddress(KEY_MINT_LIMITER);
                  }
                  /**
                   * @notice Gets the transfer limit for a specific token symbol within the configured epoch.
                   * @param symbol The symbol of the token
                   * @return uint The transfer limit for the given token.
                   */
                  function tokenMintLimit(string memory symbol) public view override returns (uint256) {
                      return getUint(_getTokenMintLimitKey(symbol));
                  }
                  /**
                   * @notice Gets the transfer amount for a specific token symbol within the configured epoch.
                   * @param symbol The symbol of the token
                   * @return uint The transfer amount for the given token.
                   */
                  function tokenMintAmount(string memory symbol) public view override returns (uint256) {
                      return getUint(_getTokenMintAmountKey(symbol, block.timestamp / 6 hours));
                  }
                  /**
                   * @dev This function is kept around to keep things working for internal
                   * tokens that were deployed before the token freeze functionality was removed
                   */
                  function allTokensFrozen() external pure override returns (bool) {
                      return false;
                  }
                  /**
                   * @notice Gets the address of the gateway implementation contract.
                   * @return address The address of the gateway implementation.
                   */
                  function implementation() public view override returns (address) {
                      return getAddress(KEY_IMPLEMENTATION);
                  }
                  /**
                   * @notice Gets the address of a specific token using its symbol.
                   * @param symbol The symbol of the token
                   * @return address The address of the token associated with the given symbol.
                   */
                  function tokenAddresses(string memory symbol) public view override returns (address) {
                      return getAddress(_getTokenAddressKey(symbol));
                  }
                  /**
                   * @dev Deprecated. This function is kept around to keep things working for internal tokens that were deployed before the token freeze functionality was removed
                   */
                  function tokenFrozen(string memory) external pure override returns (bool) {
                      return false;
                  }
                  /**
                   * @notice Checks whether a command with a given command ID has been executed.
                   * @param commandId The command ID to check
                   * @return bool True if the command has been executed, false otherwise
                   */
                  function isCommandExecuted(bytes32 commandId) public view override returns (bool) {
                      return getBool(_getIsCommandExecutedKey(commandId));
                  }
                  /**
                   * @notice Gets the contract ID of the Axelar Gateway.
                   * @return bytes32 The keccak256 hash of the string 'axelar-gateway'
                   */
                  function contractId() public pure returns (bytes32) {
                      return keccak256('axelar-gateway');
                  }
                  /************************\\
                  |* Governance Functions *|
                  \\************************/
                  /**
                   * @notice Transfers the governance role to a new address.
                   * @param newGovernance The address to transfer the governance role to.
                   * @dev Only the current governance entity can call this function.
                   */
                  function transferGovernance(address newGovernance) external override onlyGovernance {
                      if (newGovernance == address(0)) revert InvalidGovernance();
                      _transferGovernance(newGovernance);
                  }
                  /**
                   * @notice Transfers the mint limiter role to a new address.
                   * @param newMintLimiter The address to transfer the mint limiter role to.
                   * @dev Only the current mint limiter or the governance address can call this function.
                   */
                  function transferMintLimiter(address newMintLimiter) external override onlyMintLimiter {
                      if (newMintLimiter == address(0)) revert InvalidMintLimiter();
                      _transferMintLimiter(newMintLimiter);
                  }
                  /**
                   * @notice Sets the transfer limits for an array of tokens.
                   * @param symbols The array of token symbols to set the transfer limits for
                   * @param limits The array of transfer limits corresponding to the symbols
                   * @dev Only the mint limiter or the governance address can call this function.
                   */
                  function setTokenMintLimits(string[] calldata symbols, uint256[] calldata limits) external override onlyMintLimiter {
                      uint256 length = symbols.length;
                      if (length != limits.length) revert InvalidSetMintLimitsParams();
                      for (uint256 i; i < length; ++i) {
                          string memory symbol = symbols[i];
                          uint256 limit = limits[i];
                          if (tokenAddresses(symbol) == address(0)) revert TokenDoesNotExist(symbol);
                          _setTokenMintLimit(symbol, limit);
                      }
                  }
                  /**
                   * @notice Upgrades the contract to a new implementation.
                   * @param newImplementation The address of the new implementation
                   * @param newImplementationCodeHash The code hash of the new implementation
                   * @param setupParams Optional setup params for the new implementation
                   * @dev Only the governance address can call this function.
                   */
                  function upgrade(
                      address newImplementation,
                      bytes32 newImplementationCodeHash,
                      bytes calldata setupParams
                  ) external override onlyGovernance {
                      if (newImplementationCodeHash != newImplementation.codehash) revert InvalidCodeHash();
                      if (contractId() != IContractIdentifier(newImplementation).contractId()) revert InvalidImplementation();
                      emit Upgraded(newImplementation);
                      _setImplementation(newImplementation);
                      if (setupParams.length != 0) {
                          // slither-disable-next-line controlled-delegatecall
                          (bool success, ) = newImplementation.delegatecall(abi.encodeWithSelector(IImplementation.setup.selector, setupParams));
                          if (!success) revert SetupFailed();
                      }
                  }
                  /**********************\\
                  |* External Functions *|
                  \\**********************/
                  /**
                   * @notice Sets up the governance and mint limiter roles, and transfers operatorship if necessary.
                   * This function is called by the proxy during initial deployment, and optionally called during gateway upgrades.
                   * @param params The encoded parameters containing the governance and mint limiter addresses, as well as the new operator data.
                   * @dev Not publicly accessible as it's overshadowed in the proxy.
                   */
                  function setup(bytes calldata params) external override(IImplementation, Implementation) onlyProxy {
                      (address governance_, address mintLimiter_, bytes memory newOperatorsData) = abi.decode(params, (address, address, bytes));
                      if (governance_ != address(0)) _transferGovernance(governance_);
                      if (mintLimiter_ != address(0)) _transferMintLimiter(mintLimiter_);
                      if (newOperatorsData.length != 0) {
                          emit OperatorshipTransferred(newOperatorsData);
                          IAxelarAuth(authModule).transferOperatorship(newOperatorsData);
                      }
                  }
                  /**
                   * @notice Executes a batch of commands signed by the Axelar network. There are a finite set of command types that can be executed.
                   * @param input The encoded input containing the data for the batch of commands, as well as the proof that verifies the integrity of the data.
                   * @dev Each command has a corresponding commandID that is guaranteed to be unique from the Axelar network.
                   * @dev This function allows retrying a commandID if the command initially failed to be processed.
                   * @dev Ignores unknown commands or duplicate commandIDs.
                   * @dev Emits an Executed event for successfully executed commands.
                   */
                  // slither-disable-next-line cyclomatic-complexity
                  function execute(bytes calldata input) external override {
                      (bytes memory data, bytes memory proof) = abi.decode(input, (bytes, bytes));
                      bytes32 messageHash = ECDSA.toEthSignedMessageHash(keccak256(data));
                      // returns true for current operators
                      // slither-disable-next-line reentrancy-no-eth
                      bool allowOperatorshipTransfer = IAxelarAuth(authModule).validateProof(messageHash, proof);
                      uint256 chainId;
                      bytes32[] memory commandIds;
                      string[] memory commands;
                      bytes[] memory params;
                      (chainId, commandIds, commands, params) = abi.decode(data, (uint256, bytes32[], string[], bytes[]));
                      if (chainId != block.chainid) revert InvalidChainId();
                      uint256 commandsLength = commandIds.length;
                      if (commandsLength != commands.length || commandsLength != params.length) revert InvalidCommands();
                      for (uint256 i; i < commandsLength; ++i) {
                          bytes32 commandId = commandIds[i];
                          // Ignore if duplicate commandId received
                          if (isCommandExecuted(commandId)) continue;
                          bytes4 commandSelector;
                          bytes32 commandHash = keccak256(abi.encodePacked(commands[i]));
                          if (commandHash == SELECTOR_DEPLOY_TOKEN) {
                              commandSelector = AxelarGateway.deployToken.selector;
                          } else if (commandHash == SELECTOR_MINT_TOKEN) {
                              commandSelector = AxelarGateway.mintToken.selector;
                          } else if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL) {
                              commandSelector = AxelarGateway.approveContractCall.selector;
                          } else if (commandHash == SELECTOR_APPROVE_CONTRACT_CALL_WITH_MINT) {
                              commandSelector = AxelarGateway.approveContractCallWithMint.selector;
                          } else if (commandHash == SELECTOR_BURN_TOKEN) {
                              commandSelector = AxelarGateway.burnToken.selector;
                          } else if (commandHash == SELECTOR_TRANSFER_OPERATORSHIP) {
                              if (!allowOperatorshipTransfer) continue;
                              allowOperatorshipTransfer = false;
                              commandSelector = AxelarGateway.transferOperatorship.selector;
                          } else {
                              // Ignore unknown commands
                              continue;
                          }
                          // Prevent a re-entrancy from executing this command before it can be marked as successful.
                          _setCommandExecuted(commandId, true);
                          // slither-disable-next-line calls-loop,reentrancy-no-eth
                          (bool success, ) = address(this).call(abi.encodeWithSelector(commandSelector, params[i], commandId));
                          // slither-disable-next-line reentrancy-events
                          if (success) emit Executed(commandId);
                          else _setCommandExecuted(commandId, false);
                      }
                  }
                  /******************\\
                  |* Self Functions *|
                  \\******************/
                  /**
                   * @notice Deploys a new token or registers an existing token in the gateway contract itself.
                   * @param params Encoded parameters including the token name, symbol, decimals, cap, token address, and mint limit
                   * @dev If the token address is not specified, a new token is deployed and registed as InternalBurnableFrom
                   * @dev If the token address is specified, the token is marked as External.
                   * @dev Emits a TokenDeployed event with the symbol and token address.
                   */
                  function deployToken(bytes calldata params, bytes32) external onlySelf {
                      (string memory name, string memory symbol, uint8 decimals, uint256 cap, address tokenAddress, uint256 mintLimit) = abi.decode(
                          params,
                          (string, string, uint8, uint256, address, uint256)
                      );
                      // Ensure that this symbol has not been taken.
                      if (tokenAddresses(symbol) != address(0)) revert TokenAlreadyExists(symbol);
                      _setTokenMintLimit(symbol, mintLimit);
                      if (tokenAddress == address(0)) {
                          // If token address is not specified, it indicates a request to deploy one.
                          bytes32 salt = keccak256(abi.encodePacked(symbol));
                          _setTokenType(symbol, TokenType.InternalBurnableFrom);
                          // slither-disable-next-line reentrancy-no-eth,controlled-delegatecall
                          (bool success, bytes memory data) = tokenDeployer.delegatecall(
                              abi.encodeWithSelector(ITokenDeployer.deployToken.selector, name, symbol, decimals, cap, salt)
                          );
                          if (!success) revert TokenDeployFailed(symbol);
                          tokenAddress = abi.decode(data, (address));
                      } else {
                          // If token address is specified, ensure that there is a contact at the specified address.
                          if (tokenAddress.code.length == uint256(0)) revert TokenContractDoesNotExist(tokenAddress);
                          // Mark that this symbol is an external token, which is needed to differentiate between operations on mint and burn.
                          _setTokenType(symbol, TokenType.External);
                      }
                      // slither-disable-next-line reentrancy-events
                      emit TokenDeployed(symbol, tokenAddress);
                      _setTokenAddress(symbol, tokenAddress);
                  }
                  /**
                   * @notice Transfers a specific amount of tokens to an account, based on the provided symbol.
                   * @param params Encoded parameters including the token symbol, recipient address, and amount to mint.
                   * @dev This function will revert if the token is not registered with the gatewaty.
                   * @dev If the token type is External, a safe transfer is performed to the recipient account.
                   * @dev If the token type is Internal (InternalBurnable or InternalBurnableFrom), the mint function is called on the token address.
                   */
                  function mintToken(bytes calldata params, bytes32) external onlySelf {
                      (string memory symbol, address account, uint256 amount) = abi.decode(params, (string, address, uint256));
                      _mintToken(symbol, account, amount);
                  }
                  /**
                   * @notice Burns tokens of a given symbol, either through an external deposit handler or a token defined burn method.
                   * @param params Encoded parameters including the token symbol and a salt value for the deposit handler
                   */
                  function burnToken(bytes calldata params, bytes32) external onlySelf {
                      (string memory symbol, bytes32 salt) = abi.decode(params, (string, bytes32));
                      address tokenAddress = tokenAddresses(symbol);
                      if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
                      if (_getTokenType(symbol) == TokenType.External) {
                          address depositHandlerAddress = _getCreate2Address(salt, keccak256(abi.encodePacked(type(DepositHandler).creationCode)));
                          if (depositHandlerAddress.isContract()) return;
                          DepositHandler depositHandler = new DepositHandler{ salt: salt }();
                          (bool success, bytes memory returnData) = depositHandler.execute(
                              tokenAddress,
                              abi.encodeWithSelector(IERC20.transfer.selector, address(this), IERC20(tokenAddress).balanceOf(address(depositHandler)))
                          );
                          if (!success || (returnData.length != uint256(0) && !abi.decode(returnData, (bool)))) revert BurnFailed(symbol);
                          // NOTE: `depositHandler` must always be destroyed in the same runtime context that it is deployed.
                          depositHandler.destroy(address(this));
                      } else {
                          IBurnableMintableCappedERC20(tokenAddress).burn(salt);
                      }
                  }
                  /**
                   * @notice Approves a contract call.
                   * @param params Encoded parameters including the source chain, source address, contract address, payload hash, transaction hash, and event index
                   * @param commandId to associate with the approval
                   */
                  function approveContractCall(bytes calldata params, bytes32 commandId) external onlySelf {
                      (
                          string memory sourceChain,
                          string memory sourceAddress,
                          address contractAddress,
                          bytes32 payloadHash,
                          bytes32 sourceTxHash,
                          uint256 sourceEventIndex
                      ) = abi.decode(params, (string, string, address, bytes32, bytes32, uint256));
                      _setContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash);
                      emit ContractCallApproved(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, sourceTxHash, sourceEventIndex);
                  }
                  /**
                   * @notice Approves a contract call with token transfer.
                   * @param params Encoded parameters including the source chain, source address, contract address, payload hash, token symbol,
                   * token amount, transaction hash, and event index.
                   * @param commandId to associate with the approval
                   */
                  function approveContractCallWithMint(bytes calldata params, bytes32 commandId) external onlySelf {
                      (
                          string memory sourceChain,
                          string memory sourceAddress,
                          address contractAddress,
                          bytes32 payloadHash,
                          string memory symbol,
                          uint256 amount,
                          bytes32 sourceTxHash,
                          uint256 sourceEventIndex
                      ) = abi.decode(params, (string, string, address, bytes32, string, uint256, bytes32, uint256));
                      _setContractCallApprovedWithMint(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, symbol, amount);
                      emit ContractCallApprovedWithMint(
                          commandId,
                          sourceChain,
                          sourceAddress,
                          contractAddress,
                          payloadHash,
                          symbol,
                          amount,
                          sourceTxHash,
                          sourceEventIndex
                      );
                  }
                  /**
                   * @notice Transfers operatorship with the provided data by calling the transferOperatorship function on the auth module.
                   * @param newOperatorsData Encoded data for the new operators
                   */
                  function transferOperatorship(bytes calldata newOperatorsData, bytes32) external onlySelf {
                      emit OperatorshipTransferred(newOperatorsData);
                      IAxelarAuth(authModule).transferOperatorship(newOperatorsData);
                  }
                  /********************\\
                  |* Internal Methods *|
                  \\********************/
                  function _mintToken(
                      string memory symbol,
                      address account,
                      uint256 amount
                  ) internal {
                      address tokenAddress = tokenAddresses(symbol);
                      if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
                      _setTokenMintAmount(symbol, tokenMintAmount(symbol) + amount);
                      if (_getTokenType(symbol) == TokenType.External) {
                          IERC20(tokenAddress).safeTransfer(account, amount);
                      } else {
                          IBurnableMintableCappedERC20(tokenAddress).mint(account, amount);
                      }
                  }
                  /**
                   * @notice Burns or locks a specific amount of tokens from a sender's account based on the provided symbol.
                   * @param sender Address of the account from which to burn the tokens
                   * @param symbol Symbol of the token to burn
                   * @param amount Amount of tokens to burn
                   * @dev Depending on the token type (External, InternalBurnableFrom, or InternalBurnable), the function either
                   * transfers the tokens to gateway contract itself or calls a burn function on the token contract.
                   */
                  function _burnTokenFrom(
                      address sender,
                      string memory symbol,
                      uint256 amount
                  ) internal {
                      address tokenAddress = tokenAddresses(symbol);
                      if (tokenAddress == address(0)) revert TokenDoesNotExist(symbol);
                      if (amount == 0) revert InvalidAmount();
                      TokenType tokenType = _getTokenType(symbol);
                      if (tokenType == TokenType.External) {
                          IERC20(tokenAddress).safeTransferFrom(sender, address(this), amount);
                      } else if (tokenType == TokenType.InternalBurnableFrom) {
                          IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IBurnableMintableCappedERC20.burnFrom.selector, sender, amount));
                      } else {
                          IERC20(tokenAddress).safeTransferFrom(sender, IBurnableMintableCappedERC20(tokenAddress).depositAddress(bytes32(0)), amount);
                          IBurnableMintableCappedERC20(tokenAddress).burn(bytes32(0));
                      }
                  }
                  /********************\\
                  |* Pure Key Getters *|
                  \\********************/
                  function _getTokenMintLimitKey(string memory symbol) internal pure returns (bytes32) {
                      return keccak256(abi.encodePacked(PREFIX_TOKEN_MINT_LIMIT, symbol));
                  }
                  function _getTokenMintAmountKey(string memory symbol, uint256 day) internal pure returns (bytes32) {
                      return keccak256(abi.encode(PREFIX_TOKEN_MINT_AMOUNT, symbol, day));
                  }
                  function _getTokenTypeKey(string memory symbol) internal pure returns (bytes32) {
                      return keccak256(abi.encodePacked(PREFIX_TOKEN_TYPE, symbol));
                  }
                  function _getTokenAddressKey(string memory symbol) internal pure returns (bytes32) {
                      return keccak256(abi.encodePacked(PREFIX_TOKEN_ADDRESS, symbol));
                  }
                  function _getIsCommandExecutedKey(bytes32 commandId) internal pure returns (bytes32) {
                      return keccak256(abi.encodePacked(PREFIX_COMMAND_EXECUTED, commandId));
                  }
                  function _getIsContractCallApprovedKey(
                      bytes32 commandId,
                      string memory sourceChain,
                      string memory sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash
                  ) internal pure returns (bytes32) {
                      return keccak256(abi.encode(PREFIX_CONTRACT_CALL_APPROVED, commandId, sourceChain, sourceAddress, contractAddress, payloadHash));
                  }
                  function _getIsContractCallApprovedWithMintKey(
                      bytes32 commandId,
                      string memory sourceChain,
                      string memory sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash,
                      string memory symbol,
                      uint256 amount
                  ) internal pure returns (bytes32) {
                      return
                          keccak256(
                              abi.encode(
                                  PREFIX_CONTRACT_CALL_APPROVED_WITH_MINT,
                                  commandId,
                                  sourceChain,
                                  sourceAddress,
                                  contractAddress,
                                  payloadHash,
                                  symbol,
                                  amount
                              )
                          );
                  }
                  /********************\\
                  |* Internal Getters *|
                  \\********************/
                  function _getCreate2Address(bytes32 salt, bytes32 codeHash) internal view returns (address) {
                      return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, codeHash)))));
                  }
                  function _getTokenType(string memory symbol) internal view returns (TokenType) {
                      return TokenType(getUint(_getTokenTypeKey(symbol)));
                  }
                  /********************\\
                  |* Internal Setters *|
                  \\********************/
                  function _setTokenMintLimit(string memory symbol, uint256 limit) internal {
                      emit TokenMintLimitUpdated(symbol, limit);
                      _setUint(_getTokenMintLimitKey(symbol), limit);
                  }
                  function _setTokenMintAmount(string memory symbol, uint256 amount) internal {
                      uint256 limit = tokenMintLimit(symbol);
                      if (limit > 0 && amount > limit) revert ExceedMintLimit(symbol);
                      _setUint(_getTokenMintAmountKey(symbol, block.timestamp / 6 hours), amount);
                  }
                  function _setTokenType(string memory symbol, TokenType tokenType) internal {
                      _setUint(_getTokenTypeKey(symbol), uint256(tokenType));
                  }
                  function _setTokenAddress(string memory symbol, address tokenAddress) internal {
                      _setAddress(_getTokenAddressKey(symbol), tokenAddress);
                  }
                  function _setCommandExecuted(bytes32 commandId, bool executed) internal {
                      _setBool(_getIsCommandExecutedKey(commandId), executed);
                  }
                  function _setContractCallApproved(
                      bytes32 commandId,
                      string memory sourceChain,
                      string memory sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash
                  ) internal {
                      _setBool(_getIsContractCallApprovedKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash), true);
                  }
                  function _setContractCallApprovedWithMint(
                      bytes32 commandId,
                      string memory sourceChain,
                      string memory sourceAddress,
                      address contractAddress,
                      bytes32 payloadHash,
                      string memory symbol,
                      uint256 amount
                  ) internal {
                      _setBool(
                          _getIsContractCallApprovedWithMintKey(commandId, sourceChain, sourceAddress, contractAddress, payloadHash, symbol, amount),
                          true
                      );
                  }
                  function _setImplementation(address newImplementation) internal {
                      _setAddress(KEY_IMPLEMENTATION, newImplementation);
                  }
                  function _transferGovernance(address newGovernance) internal {
                      emit GovernanceTransferred(getAddress(KEY_GOVERNANCE), newGovernance);
                      _setAddress(KEY_GOVERNANCE, newGovernance);
                  }
                  function _transferMintLimiter(address newMintLimiter) internal {
                      emit MintLimiterTransferred(getAddress(KEY_MINT_LIMITER), newMintLimiter);
                      _setAddress(KEY_MINT_LIMITER, newMintLimiter);
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.9;
              contract DepositHandler {
                  error IsLocked();
                  error NotContract();
                  uint256 internal constant IS_NOT_LOCKED = uint256(1);
                  uint256 internal constant IS_LOCKED = uint256(2);
                  uint256 internal _lockedStatus = IS_NOT_LOCKED;
                  modifier noReenter() {
                      if (_lockedStatus == IS_LOCKED) revert IsLocked();
                      _lockedStatus = IS_LOCKED;
                      _;
                      _lockedStatus = IS_NOT_LOCKED;
                  }
                  function execute(address callee, bytes calldata data) external noReenter returns (bool success, bytes memory returnData) {
                      if (callee.code.length == 0) revert NotContract();
                      (success, returnData) = callee.call(data);
                  }
                  // NOTE: The gateway should always destroy the `DepositHandler` in the same runtime context that deploys it.
                  function destroy(address etherDestination) external noReenter {
                      selfdestruct(payable(etherDestination));
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.9;
              /**
               * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
               *
               * These functions can be used to verify that a message was signed by the holder
               * of the private keys of a given address.
               */
              library ECDSA {
                  error InvalidSignatureLength();
                  error InvalidS();
                  error InvalidV();
                  error InvalidSignature();
                  /**
                   * @dev Returns the address that signed a hashed message (`hash`) with
                   * `signature`. This address can then be used for verification purposes.
                   *
                   * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
                   * this function rejects them by requiring the `s` value to be in the lower
                   * half order, and the `v` value to be either 27 or 28.
                   *
                   * IMPORTANT: `hash` _must_ be the result of a hash operation for the
                   * verification to be secure: it is possible to craft signatures that
                   * recover to arbitrary addresses for non-hashed data. A safe way to ensure
                   * this is by receiving a hash of the original message (which may otherwise
                   * be too long), and then calling {toEthSignedMessageHash} on it.
                   */
                  function recover(bytes32 hash, bytes memory signature) internal pure returns (address signer) {
                      // Check the signature length
                      if (signature.length != 65) revert InvalidSignatureLength();
                      // Divide the signature in r, s and v variables
                      bytes32 r;
                      bytes32 s;
                      uint8 v;
                      // ecrecover takes the signature parameters, and the only way to get them
                      // currently is to use assembly.
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          r := mload(add(signature, 0x20))
                          s := mload(add(signature, 0x40))
                          v := byte(0, mload(add(signature, 0x60)))
                      }
                      // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                      // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                      // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
                      // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                      //
                      // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                      // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                      // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                      // these malleable signatures as well.
                      if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) revert InvalidS();
                      if (v != 27 && v != 28) revert InvalidV();
                      // If the signature is valid (and not malleable), return the signer address
                      if ((signer = ecrecover(hash, v, r, s)) == address(0)) revert InvalidSignature();
                  }
                  /**
                   * @dev Returns an Ethereum Signed Message, created from a `hash`. This
                   * replicates the behavior of the
                   * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
                   * JSON-RPC method.
                   *
                   * See {recover}.
                   */
                  function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
                      // 32 is the length in bytes of hash,
                      // enforced by the type signature above
                      return keccak256(abi.encodePacked('\\x19Ethereum Signed Message:\
              32', hash));
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity 0.8.9;
              /**
               * @title EternalStorage
               * @dev This contract holds all the necessary state variables to carry out the storage of any contract.
               */
              contract EternalStorage {
                  mapping(bytes32 => uint256) private _uintStorage;
                  mapping(bytes32 => string) private _stringStorage;
                  mapping(bytes32 => address) private _addressStorage;
                  mapping(bytes32 => bytes) private _bytesStorage;
                  mapping(bytes32 => bool) private _boolStorage;
                  mapping(bytes32 => int256) private _intStorage;
                  // *** Getter Methods ***
                  function getUint(bytes32 key) public view returns (uint256) {
                      return _uintStorage[key];
                  }
                  function getString(bytes32 key) public view returns (string memory) {
                      return _stringStorage[key];
                  }
                  function getAddress(bytes32 key) public view returns (address) {
                      return _addressStorage[key];
                  }
                  function getBytes(bytes32 key) public view returns (bytes memory) {
                      return _bytesStorage[key];
                  }
                  function getBool(bytes32 key) public view returns (bool) {
                      return _boolStorage[key];
                  }
                  function getInt(bytes32 key) public view returns (int256) {
                      return _intStorage[key];
                  }
                  // *** Setter Methods ***
                  function _setUint(bytes32 key, uint256 value) internal {
                      _uintStorage[key] = value;
                  }
                  function _setString(bytes32 key, string memory value) internal {
                      _stringStorage[key] = value;
                  }
                  function _setAddress(bytes32 key, address value) internal {
                      _addressStorage[key] = value;
                  }
                  function _setBytes(bytes32 key, bytes memory value) internal {
                      _bytesStorage[key] = value;
                  }
                  function _setBool(bytes32 key, bool value) internal {
                      _boolStorage[key] = value;
                  }
                  function _setInt(bytes32 key, int256 value) internal {
                      _intStorage[key] = value;
                  }
                  // *** Delete Methods ***
                  function _deleteUint(bytes32 key) internal {
                      delete _uintStorage[key];
                  }
                  function _deleteString(bytes32 key) internal {
                      delete _stringStorage[key];
                  }
                  function _deleteAddress(bytes32 key) internal {
                      delete _addressStorage[key];
                  }
                  function _deleteBytes(bytes32 key) internal {
                      delete _bytesStorage[key];
                  }
                  function _deleteBool(bytes32 key) internal {
                      delete _boolStorage[key];
                  }
                  function _deleteInt(bytes32 key) internal {
                      delete _intStorage[key];
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              import { IOwnable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IOwnable.sol';
              interface IAxelarAuth is IOwnable {
                  function validateProof(bytes32 messageHash, bytes calldata proof) external returns (bool currentOperators);
                  function transferOperatorship(bytes calldata params) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.9;
              import { IERC20Burn } from './IERC20Burn.sol';
              import { IERC20BurnFrom } from './IERC20BurnFrom.sol';
              import { IMintableCappedERC20 } from './IMintableCappedERC20.sol';
              interface IBurnableMintableCappedERC20 is IERC20Burn, IERC20BurnFrom, IMintableCappedERC20 {
                  function depositAddress(bytes32 salt) external view returns (address);
                  function burn(bytes32 salt) external;
                  function burnFrom(address account, uint256 amount) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.9;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  error InvalidAccount();
                  /**
                   * @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 `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
                      address recipient,
                      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);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.9;
              interface IERC20Burn {
                  function burn(bytes32 salt) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.9;
              interface IERC20BurnFrom {
                  function burnFrom(address account, uint256 amount) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.9;
              interface IERC20Permit {
                  function DOMAIN_SEPARATOR() external view returns (bytes32);
                  function nonces(address account) external view returns (uint256);
                  function permit(
                      address issuer,
                      address spender,
                      uint256 value,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.9;
              import { IERC20 } from './IERC20.sol';
              import { IERC20Permit } from './IERC20Permit.sol';
              import { IOwnable } from './IOwnable.sol';
              interface IMintableCappedERC20 is IERC20, IERC20Permit, IOwnable {
                  error CapExceeded();
                  function cap() external view returns (uint256);
                  function mint(address account, uint256 amount) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.9;
              interface IOwnable {
                  error NotOwner();
                  error InvalidOwner();
                  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                  function owner() external view returns (address);
                  function transferOwnership(address newOwner) external;
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.8.0;
              interface ITokenDeployer {
                  function deployToken(
                      string calldata name,
                      string calldata symbol,
                      uint8 decimals,
                      uint256 cap,
                      bytes32 salt
                  ) external returns (address tokenAddress);
              }