ETH Price: $2,459.32 (-0.12%)

Transaction Decoder

Block:
21733484 at Jan-29-2025 11:31:23 PM +UTC
Transaction Fee:
0.000109943483039742 ETH $0.27
Gas Used:
40,359 Gas / 2.724137938 Gwei

Emitted Events:

564 TransparentUpgradeableProxy.0x8b65b80ac62fde507cb8196bad6c93c114c2babc6ac846aae39ed6943ad36c49( 0x8b65b80ac62fde507cb8196bad6c93c114c2babc6ac846aae39ed6943ad36c49, 0x0000000000000000000000007a93fc67e62b5ead973ae530827823ae9a093d33, 0x0000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599, 0x000000000000000000000000000000000000000000000000000000000000123a, 000000000000000000000000000000000000000000000000000000006902a3cb, 000000000000000000000000000000000000000000000000000000000167e980 )

Account State Difference:

  Address   Before After State Difference Code
0x7A93FC67...e9A093D33
0.002123688729538884 Eth
Nonce: 16
0.002013745246499142 Eth
Nonce: 17
0.000109943483039742
(beaverbuild)
11.229574506050666216 Eth11.229576402923666216 Eth0.000001896873
0xAB13B8ee...1fD5B8A39
(Mezo: Portal Proxy)

Execution Trace

TransparentUpgradeableProxy.f5e8d327( )
  • Portal.lock( token=0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, depositId=4666, lockPeriod=23587200 )
    lock[Portal (ln:1726)]
    File 1 of 2: TransparentUpgradeableProxy
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
    pragma solidity ^0.8.20;
    import {Context} from "../utils/Context.sol";
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * The initial owner is set to the address provided by the deployer. This can
     * later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract Ownable is Context {
        address private _owner;
        /**
         * @dev The caller account is not authorized to perform an operation.
         */
        error OwnableUnauthorizedAccount(address account);
        /**
         * @dev The owner is not a valid owner account. (eg. `address(0)`)
         */
        error OwnableInvalidOwner(address owner);
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
         */
        constructor(address initialOwner) {
            if (initialOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(initialOwner);
        }
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            if (owner() != _msgSender()) {
                revert OwnableUnauthorizedAccount(_msgSender());
            }
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            if (newOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            address oldOwner = _owner;
            _owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1967.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
     */
    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 v5.0.0) (proxy/beacon/BeaconProxy.sol)
    pragma solidity ^0.8.20;
    import {IBeacon} from "./IBeacon.sol";
    import {Proxy} from "../Proxy.sol";
    import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
    /**
     * @dev This contract implements a proxy that gets the implementation address for each call from an {UpgradeableBeacon}.
     *
     * The beacon address can only be set once during construction, and cannot be changed afterwards. It is stored in an
     * immutable variable to avoid unnecessary storage reads, and also in the beacon storage slot specified by
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] so that it can be accessed externally.
     *
     * CAUTION: Since the beacon address can never be changed, you must ensure that you either control the beacon, or trust
     * the beacon to not upgrade the implementation maliciously.
     *
     * IMPORTANT: Do not use the implementation logic to modify the beacon storage slot. Doing so would leave the proxy in
     * an inconsistent state where the beacon storage slot does not match the beacon address.
     */
    contract BeaconProxy is Proxy {
        // An immutable address for the beacon to avoid unnecessary SLOADs before each delegate call.
        address private immutable _beacon;
        /**
         * @dev Initializes the proxy with `beacon`.
         *
         * If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This
         * will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity
         * constructor.
         *
         * Requirements:
         *
         * - `beacon` must be a contract with the interface {IBeacon}.
         * - If `data` is empty, `msg.value` must be zero.
         */
        constructor(address beacon, bytes memory data) payable {
            ERC1967Utils.upgradeBeaconToAndCall(beacon, data);
            _beacon = beacon;
        }
        /**
         * @dev Returns the current implementation address of the associated beacon.
         */
        function _implementation() internal view virtual override returns (address) {
            return IBeacon(_getBeacon()).implementation();
        }
        /**
         * @dev Returns the beacon.
         */
        function _getBeacon() internal view virtual returns (address) {
            return _beacon;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/IBeacon.sol)
    pragma solidity ^0.8.20;
    /**
     * @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.
         *
         * {UpgradeableBeacon} will check that this address is a contract.
         */
        function implementation() external view returns (address);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/beacon/UpgradeableBeacon.sol)
    pragma solidity ^0.8.20;
    import {IBeacon} from "./IBeacon.sol";
    import {Ownable} from "../../access/Ownable.sol";
    /**
     * @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their
     * implementation contract, which is where they will delegate all function calls.
     *
     * An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon.
     */
    contract UpgradeableBeacon is IBeacon, Ownable {
        address private _implementation;
        /**
         * @dev The `implementation` of the beacon is invalid.
         */
        error BeaconInvalidImplementation(address implementation);
        /**
         * @dev Emitted when the implementation returned by the beacon is changed.
         */
        event Upgraded(address indexed implementation);
        /**
         * @dev Sets the address of the initial implementation, and the initial owner who can upgrade the beacon.
         */
        constructor(address implementation_, address initialOwner) Ownable(initialOwner) {
            _setImplementation(implementation_);
        }
        /**
         * @dev Returns the current implementation address.
         */
        function implementation() public view virtual returns (address) {
            return _implementation;
        }
        /**
         * @dev Upgrades the beacon to a new implementation.
         *
         * Emits an {Upgraded} event.
         *
         * Requirements:
         *
         * - msg.sender must be the owner of the contract.
         * - `newImplementation` must be a contract.
         */
        function upgradeTo(address newImplementation) public virtual onlyOwner {
            _setImplementation(newImplementation);
        }
        /**
         * @dev Sets the implementation contract address for this beacon
         *
         * Requirements:
         *
         * - `newImplementation` must be a contract.
         */
        function _setImplementation(address newImplementation) private {
            if (newImplementation.code.length == 0) {
                revert BeaconInvalidImplementation(newImplementation);
            }
            _implementation = newImplementation;
            emit Upgraded(newImplementation);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Proxy.sol)
    pragma solidity ^0.8.20;
    import {Proxy} from "../Proxy.sol";
    import {ERC1967Utils} from "./ERC1967Utils.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 {
        /**
         * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.
         *
         * If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an
         * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.
         *
         * Requirements:
         *
         * - If `data` is empty, `msg.value` must be zero.
         */
        constructor(address implementation, bytes memory _data) payable {
            ERC1967Utils.upgradeToAndCall(implementation, _data);
        }
        /**
         * @dev Returns the current implementation address.
         *
         * 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 _implementation() internal view virtual override returns (address) {
            return ERC1967Utils.getImplementation();
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/ERC1967/ERC1967Utils.sol)
    pragma solidity ^0.8.20;
    import {IBeacon} from "../beacon/IBeacon.sol";
    import {Address} from "../../utils/Address.sol";
    import {StorageSlot} from "../../utils/StorageSlot.sol";
    /**
     * @dev This abstract contract provides getters and event emitting update functions for
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
     */
    library ERC1967Utils {
        // We re-declare ERC-1967 events here because they can't be used directly from IERC1967.
        // This will be fixed in Solidity 0.8.21. At that point we should remove these events.
        /**
         * @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);
        /**
         * @dev Storage slot with the address of the current implementation.
         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1.
         */
        // solhint-disable-next-line private-vars-leading-underscore
        bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
        /**
         * @dev The `implementation` of the proxy is invalid.
         */
        error ERC1967InvalidImplementation(address implementation);
        /**
         * @dev The `admin` of the proxy is invalid.
         */
        error ERC1967InvalidAdmin(address admin);
        /**
         * @dev The `beacon` of the proxy is invalid.
         */
        error ERC1967InvalidBeacon(address beacon);
        /**
         * @dev An upgrade function sees `msg.value > 0` that may be lost.
         */
        error ERC1967NonPayable();
        /**
         * @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 {
            if (newImplementation.code.length == 0) {
                revert ERC1967InvalidImplementation(newImplementation);
            }
            StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation;
        }
        /**
         * @dev Performs implementation upgrade with additional setup call if data is nonempty.
         * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
         * to avoid stuck value in the contract.
         *
         * Emits an {IERC1967-Upgraded} event.
         */
        function upgradeToAndCall(address newImplementation, bytes memory data) internal {
            _setImplementation(newImplementation);
            emit Upgraded(newImplementation);
            if (data.length > 0) {
                Address.functionDelegateCall(newImplementation, data);
            } else {
                _checkNonPayable();
            }
        }
        /**
         * @dev Storage slot with the admin of the contract.
         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1.
         */
        // solhint-disable-next-line private-vars-leading-underscore
        bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
        /**
         * @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 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 {
            if (newAdmin == address(0)) {
                revert ERC1967InvalidAdmin(address(0));
            }
            StorageSlot.getAddressSlot(ADMIN_SLOT).value = newAdmin;
        }
        /**
         * @dev Changes the admin of the proxy.
         *
         * Emits an {IERC1967-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 the keccak-256 hash of "eip1967.proxy.beacon" subtracted by 1.
         */
        // solhint-disable-next-line private-vars-leading-underscore
        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 {
            if (newBeacon.code.length == 0) {
                revert ERC1967InvalidBeacon(newBeacon);
            }
            StorageSlot.getAddressSlot(BEACON_SLOT).value = newBeacon;
            address beaconImplementation = IBeacon(newBeacon).implementation();
            if (beaconImplementation.code.length == 0) {
                revert ERC1967InvalidImplementation(beaconImplementation);
            }
        }
        /**
         * @dev Change the beacon and trigger a setup call if data is nonempty.
         * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected
         * to avoid stuck value in the contract.
         *
         * Emits an {IERC1967-BeaconUpgraded} event.
         *
         * CAUTION: Invoking this function has no effect on an instance of {BeaconProxy} since v5, since
         * it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for
         * efficiency.
         */
        function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal {
            _setBeacon(newBeacon);
            emit BeaconUpgraded(newBeacon);
            if (data.length > 0) {
                Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
            } else {
                _checkNonPayable();
            }
        }
        /**
         * @dev Reverts if `msg.value` is not zero. It can be used to avoid `msg.value` stuck in the contract
         * if an upgrade doesn't perform an initialization call.
         */
        function _checkNonPayable() private {
            if (msg.value > 0) {
                revert ERC1967NonPayable();
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/Proxy.sol)
    pragma solidity ^0.8.20;
    /**
     * @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 {
            _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();
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol)
    pragma solidity ^0.8.20;
    import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol";
    import {Ownable} from "../../access/Ownable.sol";
    /**
     * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
     * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
     */
    contract ProxyAdmin is Ownable {
        /**
         * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)`
         * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
         * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string.
         * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must
         * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
         * during an upgrade.
         */
        string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
        /**
         * @dev Sets the initial owner who can perform upgrades.
         */
        constructor(address initialOwner) Ownable(initialOwner) {}
        /**
         * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
         * See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}.
         *
         * Requirements:
         *
         * - This contract must be the admin of `proxy`.
         * - If `data` is empty, `msg.value` must be zero.
         */
        function upgradeAndCall(
            ITransparentUpgradeableProxy proxy,
            address implementation,
            bytes memory data
        ) public payable virtual onlyOwner {
            proxy.upgradeToAndCall{value: msg.value}(implementation, data);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
    pragma solidity ^0.8.20;
    import {ERC1967Utils} from "../ERC1967/ERC1967Utils.sol";
    import {ERC1967Proxy} from "../ERC1967/ERC1967Proxy.sol";
    import {IERC1967} from "../../interfaces/IERC1967.sol";
    import {ProxyAdmin} from "./ProxyAdmin.sol";
    /**
     * @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
     * does not implement this interface directly, and its upgradeability mechanism is 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 upgradeToAndCall(address, bytes calldata) external payable;
    }
    /**
     * @dev This contract implements a proxy that is upgradeable through an associated {ProxyAdmin} instance.
     *
     * 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 the {ITransparentUpgradeableProxy-upgradeToAndCall} function exposed by the proxy itself.
     * 2. If the admin calls the proxy, it can call the `upgradeToAndCall` function but any other call won't be forwarded to
     * the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating
     * the proxy admin cannot fallback to the target implementation.
     *
     * These properties mean that the admin account can only be used for upgrading the proxy, 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. For this reason, the proxy deploys an instance of {ProxyAdmin} and
     * allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative
     * interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership.
     *
     * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
     * inherit from that interface, and instead `upgradeToAndCall` is 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.
     *
     * NOTE: This proxy does not inherit from {Context} deliberately. The {ProxyAdmin} of this contract won't send a
     * meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract.
     *
     * IMPORTANT: This contract avoids unnecessary storage reads by setting the admin only during construction as an
     * immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be
     * overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an
     * undesirable state where the admin slot is different from the actual admin.
     *
     * 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 `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency.
     */
    contract TransparentUpgradeableProxy is ERC1967Proxy {
        // An immutable address for the admin to avoid unnecessary SLOADs before each call
        // at the expense of removing the ability to change the admin once it's set.
        // This is acceptable if the admin is always a ProxyAdmin instance or similar contract
        // with its own ability to transfer the permissions to another account.
        address private immutable _admin;
        /**
         * @dev The proxy caller is the current admin, and can't fallback to the proxy target.
         */
        error ProxyDeniedAdminAccess();
        /**
         * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`,
         * backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in
         * {ERC1967Proxy-constructor}.
         */
        constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
            _admin = address(new ProxyAdmin(initialOwner));
            // Set the storage value and emit an event for ERC-1967 compatibility
            ERC1967Utils.changeAdmin(_proxyAdmin());
        }
        /**
         * @dev Returns the admin of this proxy.
         */
        function _proxyAdmin() internal virtual returns (address) {
            return _admin;
        }
        /**
         * @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 == _proxyAdmin()) {
                if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
                    revert ProxyDeniedAdminAccess();
                } else {
                    _dispatchUpgradeToAndCall();
                }
            } else {
                super._fallback();
            }
        }
        /**
         * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}.
         *
         * Requirements:
         *
         * - If `data` is empty, `msg.value` must be zero.
         */
        function _dispatchUpgradeToAndCall() private {
            (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
            ERC1967Utils.upgradeToAndCall(newImplementation, data);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev The ETH balance of the account is not enough to perform the operation.
         */
        error AddressInsufficientBalance(address account);
        /**
         * @dev There's no code at `target` (it is not a contract).
         */
        error AddressEmptyCode(address target);
        /**
         * @dev A call to an address target failed. The target may have reverted.
         */
        error FailedInnerCall();
        /**
         * @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            if (address(this).balance < amount) {
                revert AddressInsufficientBalance(address(this));
            }
            (bool success, ) = recipient.call{value: amount}("");
            if (!success) {
                revert FailedInnerCall();
            }
        }
        /**
         * @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 or custom error, it is bubbled
         * up by this function (like regular Solidity function calls). However, if
         * the call reverted with no returned reason, this function reverts with a
         * {FailedInnerCall} error.
         *
         * 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.
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0);
        }
        /**
         * @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`.
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            if (address(this).balance < value) {
                revert AddressInsufficientBalance(address(this));
            }
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
         * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
         * unsuccessful call.
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata
        ) internal view returns (bytes memory) {
            if (!success) {
                _revert(returndata);
            } else {
                // only check if target is a contract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                if (returndata.length == 0 && target.code.length == 0) {
                    revert AddressEmptyCode(target);
                }
                return returndata;
            }
        }
        /**
         * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
         * revert reason or with a default {FailedInnerCall} error.
         */
        function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
            if (!success) {
                _revert(returndata);
            } else {
                return returndata;
            }
        }
        /**
         * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
         */
        function _revert(bytes memory returndata) 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 FailedInnerCall();
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/StorageSlot.sol)
    // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
    pragma solidity ^0.8.20;
    /**
     * @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(newImplementation.code.length > 0);
     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
     *     }
     * }
     * ```
     */
    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 2: Portal
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)
    pragma solidity ^0.8.20;
    import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
    import {Initializable} from "../proxy/utils/Initializable.sol";
    /**
     * @dev Contract module which provides access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * The initial owner is specified at deployment time in the constructor for `Ownable`. This
     * can later be changed with {transferOwnership} and {acceptOwnership}.
     *
     * This module is used through inheritance. It will make available all functions
     * from parent (Ownable).
     */
    abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
        /// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
        struct Ownable2StepStorage {
            address _pendingOwner;
        }
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;
        function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
            assembly {
                $.slot := Ownable2StepStorageLocation
            }
        }
        event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
        function __Ownable2Step_init() internal onlyInitializing {
        }
        function __Ownable2Step_init_unchained() internal onlyInitializing {
        }
        /**
         * @dev Returns the address of the pending owner.
         */
        function pendingOwner() public view virtual returns (address) {
            Ownable2StepStorage storage $ = _getOwnable2StepStorage();
            return $._pendingOwner;
        }
        /**
         * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual override onlyOwner {
            Ownable2StepStorage storage $ = _getOwnable2StepStorage();
            $._pendingOwner = newOwner;
            emit OwnershipTransferStarted(owner(), newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual override {
            Ownable2StepStorage storage $ = _getOwnable2StepStorage();
            delete $._pendingOwner;
            super._transferOwnership(newOwner);
        }
        /**
         * @dev The new owner accepts the ownership transfer.
         */
        function acceptOwnership() public virtual {
            address sender = _msgSender();
            if (pendingOwner() != sender) {
                revert OwnableUnauthorizedAccount(sender);
            }
            _transferOwnership(sender);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
    pragma solidity ^0.8.20;
    import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
    import {Initializable} from "../proxy/utils/Initializable.sol";
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * The initial owner is set to the address provided by the deployer. This can
     * later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
        /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
        struct OwnableStorage {
            address _owner;
        }
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
        function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
            assembly {
                $.slot := OwnableStorageLocation
            }
        }
        /**
         * @dev The caller account is not authorized to perform an operation.
         */
        error OwnableUnauthorizedAccount(address account);
        /**
         * @dev The owner is not a valid owner account. (eg. `address(0)`)
         */
        error OwnableInvalidOwner(address owner);
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
         */
        function __Ownable_init(address initialOwner) internal onlyInitializing {
            __Ownable_init_unchained(initialOwner);
        }
        function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
            if (initialOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(initialOwner);
        }
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            _checkOwner();
            _;
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            OwnableStorage storage $ = _getOwnableStorage();
            return $._owner;
        }
        /**
         * @dev Throws if the sender is not the owner.
         */
        function _checkOwner() internal view virtual {
            if (owner() != _msgSender()) {
                revert OwnableUnauthorizedAccount(_msgSender());
            }
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby disabling any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            if (newOwner == address(0)) {
                revert OwnableInvalidOwner(address(0));
            }
            _transferOwnership(newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            OwnableStorage storage $ = _getOwnableStorage();
            address oldOwner = $._owner;
            $._owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
    pragma solidity ^0.8.20;
    /**
     * @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 Storage of the initializable contract.
         *
         * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
         * when using with upgradeable contracts.
         *
         * @custom:storage-location erc7201:openzeppelin.storage.Initializable
         */
        struct InitializableStorage {
            /**
             * @dev Indicates that the contract has been initialized.
             */
            uint64 _initialized;
            /**
             * @dev Indicates that the contract is in the process of being initialized.
             */
            bool _initializing;
        }
        // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
        bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
        /**
         * @dev The contract is already initialized.
         */
        error InvalidInitialization();
        /**
         * @dev The contract is not initializing.
         */
        error NotInitializing();
        /**
         * @dev Triggered when the contract has been initialized or reinitialized.
         */
        event Initialized(uint64 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 in the context of a constructor an `initializer` may be invoked any
         * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
         * production.
         *
         * Emits an {Initialized} event.
         */
        modifier initializer() {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
            // Cache values to avoid duplicated sloads
            bool isTopLevelCall = !$._initializing;
            uint64 initialized = $._initialized;
            // Allowed calls:
            // - initialSetup: the contract is not in the initializing state and no previous version was
            //                 initialized
            // - construction: the contract is initialized at version 1 (no reininitialization) and the
            //                 current contract is just being deployed
            bool initialSetup = initialized == 0 && isTopLevelCall;
            bool construction = initialized == 1 && address(this).code.length == 0;
            if (!initialSetup && !construction) {
                revert InvalidInitialization();
            }
            $._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 2**64 - 1 will prevent any future reinitialization.
         *
         * Emits an {Initialized} event.
         */
        modifier reinitializer(uint64 version) {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
            if ($._initializing || $._initialized >= version) {
                revert InvalidInitialization();
            }
            $._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() {
            _checkInitializing();
            _;
        }
        /**
         * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
         */
        function _checkInitializing() internal view virtual {
            if (!_isInitializing()) {
                revert NotInitializing();
            }
        }
        /**
         * @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 {
            // solhint-disable-next-line var-name-mixedcase
            InitializableStorage storage $ = _getInitializableStorage();
            if ($._initializing) {
                revert InvalidInitialization();
            }
            if ($._initialized != type(uint64).max) {
                $._initialized = type(uint64).max;
                emit Initialized(type(uint64).max);
            }
        }
        /**
         * @dev Returns the highest version that has been initialized. See {reinitializer}.
         */
        function _getInitializedVersion() internal view returns (uint64) {
            return _getInitializableStorage()._initialized;
        }
        /**
         * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
         */
        function _isInitializing() internal view returns (bool) {
            return _getInitializableStorage()._initializing;
        }
        /**
         * @dev Returns a pointer to the storage namespace.
         */
        // solhint-disable-next-line var-name-mixedcase
        function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
            assembly {
                $.slot := INITIALIZABLE_STORAGE
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
    pragma solidity ^0.8.20;
    import {Initializable} from "../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;
        }
        function _contextSuffixLength() internal view virtual returns (uint256) {
            return 0;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
    pragma solidity ^0.8.20;
    /**
     * @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.
     *
     * ==== Security Considerations
     *
     * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
     * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
     * considered as an intention to spend the allowance in any specific way. The second is that because permits have
     * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
     * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
     * generally recommended is:
     *
     * ```solidity
     * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
     *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
     *     doThing(..., value);
     * }
     *
     * function doThing(..., uint256 value) public {
     *     token.safeTransferFrom(msg.sender, address(this), value);
     *     ...
     * }
     * ```
     *
     * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
     * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
     * {SafeERC20-safeTransferFrom}).
     *
     * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
     * contracts should have entry points that don't rely on permit.
     */
    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].
         *
         * CAUTION: See Security Considerations above.
         */
        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 v5.0.0) (token/ERC20/IERC20.sol)
    pragma solidity ^0.8.20;
    /**
     * @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 value of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the value of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
        /**
         * @dev Moves a `value` amount of tokens from `from` to `to` using the
         * allowance mechanism. `value` 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 value) external returns (bool);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
    pragma solidity ^0.8.20;
    import {IERC20} from "../IERC20.sol";
    import {IERC20Permit} from "../extensions/IERC20Permit.sol";
    import {Address} from "../../../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 An operation with an ERC20 token failed.
         */
        error SafeERC20FailedOperation(address token);
        /**
         * @dev Indicates a failed `decreaseAllowance` request.
         */
        error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
        /**
         * @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, 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);
            forceApprove(token, spender, oldAllowance + value);
        }
        /**
         * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
         * value, non-reverting calls are assumed to be successful.
         */
        function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
            unchecked {
                uint256 currentAllowance = token.allowance(address(this), spender);
                if (currentAllowance < requestedDecrease) {
                    revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
                }
                forceApprove(token, spender, currentAllowance - requestedDecrease);
            }
        }
        /**
         * @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.encodeCall(token.approve, (spender, value));
            if (!_callOptionalReturnBool(token, approvalCall)) {
                _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
                _callOptionalReturn(token, approvalCall);
            }
        }
        /**
         * @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);
            if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
                revert SafeERC20FailedOperation(address(token));
            }
        }
        /**
         * @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(token).code.length > 0;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
    pragma solidity ^0.8.20;
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev The ETH balance of the account is not enough to perform the operation.
         */
        error AddressInsufficientBalance(address account);
        /**
         * @dev There's no code at `target` (it is not a contract).
         */
        error AddressEmptyCode(address target);
        /**
         * @dev A call to an address target failed. The target may have reverted.
         */
        error FailedInnerCall();
        /**
         * @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            if (address(this).balance < amount) {
                revert AddressInsufficientBalance(address(this));
            }
            (bool success, ) = recipient.call{value: amount}("");
            if (!success) {
                revert FailedInnerCall();
            }
        }
        /**
         * @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 or custom error, it is bubbled
         * up by this function (like regular Solidity function calls). However, if
         * the call reverted with no returned reason, this function reverts with a
         * {FailedInnerCall} error.
         *
         * 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.
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0);
        }
        /**
         * @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`.
         */
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
            if (address(this).balance < value) {
                revert AddressInsufficientBalance(address(this));
            }
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResultFromTarget(target, success, returndata);
        }
        /**
         * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
         * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
         * unsuccessful call.
         */
        function verifyCallResultFromTarget(
            address target,
            bool success,
            bytes memory returndata
        ) internal view returns (bytes memory) {
            if (!success) {
                _revert(returndata);
            } else {
                // only check if target is a contract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                if (returndata.length == 0 && target.code.length == 0) {
                    revert AddressEmptyCode(target);
                }
                return returndata;
            }
        }
        /**
         * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
         * revert reason or with a default {FailedInnerCall} error.
         */
        function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
            if (!success) {
                _revert(returndata);
            } else {
                return returndata;
            }
        }
        /**
         * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
         */
        function _revert(bytes memory returndata) 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 FailedInnerCall();
            }
        }
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.24;
    /// @notice An interface to be optionally implemented by ERC20 tokens to retrieve
    ///         the decimals places of the token. We need this interface because to
    ///         properly calculate amounts when 2 tokens are involved we need to know
    ///         their decimals places.
    ///         As decimals() is optional in the ERC20 standard, additional interface
    ///         is needed to retrieve the decimals places of the token.
    ///         Portal contract assumes that only tokens supporting this interface
    ///         are accepted as supported tokens or receipt tokens.
    interface IERC20WithDecimals {
        /// @notice Returns the decimals places of the token
        function decimals() external view returns (uint8);
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.24;
    /// @notice An interface to be optionally implemented by ERC20 tokens to enable
    ///         gasless approvals.
    interface IERC20WithPermit {
        /// @notice EIP2612 approval made with secp256k1 signature.
        ///         Users can authorize a transfer of their tokens with a signature
        ///         conforming EIP712 standard, rather than an on-chain transaction
        ///         from their address. Anyone can submit this signature on the
        ///         user's behalf by calling the permit function, paying gas fees,
        ///         and possibly performing other actions in the same transaction.
        function permit(
            address owner,
            address spender,
            uint256 amount,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.24;
    /// @notice IReceiptToken is an interface that should be implemented by ERC20
    ///         receipt tokens used in the Portal contract.
    interface IReceiptToken {
        /// @notice Mints `amount` receipt tokens and transfers them to the `to`
        ///         address.
        function mintReceipt(address to, uint256 amount) external;
        /// @notice Burns `amount` of receipt tokens owned by `msg.sender`.
        function burnReceipt(uint256 amount) external;
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.24;
    /// @notice An interface that should be implemented by contracts supporting
    ///         `approveAndCall`/`receiveApproval` pattern.
    interface IReceiveApproval {
        /// @notice Receives approval to spend tokens. Called as a result of
        ///         `approveAndCall` call on the token.
        function receiveApproval(
            address from,
            uint256 amount,
            address token,
            bytes calldata extraData
        ) external;
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity 0.8.24;
    import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    import "./interfaces/IReceiveApproval.sol";
    import "./interfaces/IReceiptToken.sol";
    import "./interfaces/IERC20WithPermit.sol";
    import "./interfaces/IERC20WithDecimals.sol";
    contract Portal is Ownable2StepUpgradeable, IReceiveApproval {
        using SafeERC20 for IERC20;
        /// @notice Supported token ability defines what can be done with
        ///         a given token. Some tokens can be deposited but can not be locked.
        enum TokenAbility {
            None,
            Deposit,
            DepositAndLock
        }
        /// @notice Represents the to-tBTC migration state of a deposit. Note that
        ///         to-tBTC migration is only available for selected assets, as set
        ///         by the governance.
        enum TbtcMigrationState {
            NotRequested,
            Requested,
            InProgress,
            Completed
        }
        /// @notice Supported token struct to pair a token address with its ability
        /// @dev This is an auxiliary struct used to initialize the contract with
        ///      the supported tokens and for governance to add new supported tokens.
        ///      This struct is not used in the storage.
        struct SupportedToken {
            address token;
            TokenAbility tokenAbility;
        }
        /// @notice Groups depositor address to their deposit ID to pass in an array
        ///         to trigger and complete the to-tBTC migration by the tBTC
        ///         migration treasury.
        /// @dev This is an auxiliary struct used as a parameter in the
        ///      `withdrawForTbtcMigration` and `finalizeTbtcMigration` functions.
        ///      This struct is not used in the storage.
        struct DepositToMigrate {
            address depositor;
            uint256 depositId;
        }
        /// @notice DepositInfo keeps track of the deposit balance and unlock time.
        ///         Each deposit is tracked separately and associated with a specific
        ///         token. Some tokens can be deposited but can not be locked - in
        ///         that case the unlockAt is the block timestamp of when the deposit
        ///         was created. The same is true for tokens that can be locked but
        ///         the depositor decided not to lock them. Some deposits can mint
        ///         a receipt tokens against them: receiptMinted is the amount of
        ///         receipt tokens minted against a deposit, while feeOwed is the
        ///         fee owed by the deposit to Portal, and the lastFeeIntegral is
        ///         the last updated value of the fee integral.
        struct DepositInfo {
            uint96 balance;
            uint32 unlockAt;
            // The amount of the receipt token minted for the deposit. Zero if no
            // receipt token was minted.
            uint96 receiptMinted;
            // The fee owed if the receipt token was minted and the fee is non-zero.
            // Fee owed is calculated based on the receipt tokens minted and is stored
            // using receipt token decimals. The fee is collected when the deposit is
            // withdrawn, adjusted to the deposit token decimals.
            uint96 feeOwed;
            // The last value of the receipt token fee integral. Zero if no receipt
            // token was minted, if the fee is zero, or if the integral was not yet
            // calculated.
            uint88 lastFeeIntegral;
            // The state of tBTC migration. If the to-tBTC migration is not allowed
            // for the asset or if it was not requested by the depositor, the state
            // is NotRequested.
            TbtcMigrationState tbtcMigrationState;
        }
        /// @notice Keeps information about to-tBTC migration status for the asset:
        ///         whether the migration is allowed and what is the total amount
        ///         of the asset being currently in-progress for the migration.
        struct TbtcMigrationInfo {
            // Indicates whether the migration to tBTC was enabled by the governance
            // for the asset.
            bool isAllowed;
            // The total amount that is currently migrated to tBTC. The value uses
            // the same number of decimals as the asset being migrated.
            uint96 totalMigrating;
        }
        /// @notice FeeInfo keeps track of the receipt token minting situation per
        ///         the supported deposit token.
        ///
        ///         The current fee rate for minting against deposits is
        ///         calculated separately for each token, and tracked as integral.
        ///
        ///         The fee rate has a single component: a governable base annual
        ///         fee, capped at 100%.
        ///
        ///         The annual fee is converted to a non-compounding fee per second,
        ///         R. This rate is then multiplied by time in seconds to get the
        ///         accumulated fee integral. The difference between the integral
        ///         in time T_1 and T_2 tracks how much fee is accrued per minted
        ///         token unit during that time. The minted amount A is multiplied
        ///         by R, and divided by a scalar constant to get the amount of fee
        ///         in the same units as A.
        struct FeeInfo {
            // XXX: not tracking total deposited - use token balance
            // someone could send token to the contract outside locked deposits
            // The amount of receipt tokens minted across all deposits for the given
            // deposit token.
            uint96 totalMinted;
            // The timestamp of the block of the last fee update.
            uint32 lastFeeUpdateAt;
            // The integral tracks how much fee is accrued over time.
            uint88 feeIntegral;
            // Fee per annum in percentage.
            uint8 annualFee;
            // Receipt token minting cap in percentage, per deposit.
            uint8 mintCap;
            // A token supporting the IReceiptToken interface and used as a receipt
            // token. Zero address if the given deposit token does not allow minting
            // receipt tokens.
            address receiptToken;
            // Counts fees collected across all withdrawn deposits for the given
            // deposit token. Fees are collected based on the receipt tokens earlier
            // minted and the annual fee.
            uint96 feeCollected;
        }
        /// @notice Mapping of the details of all the deposited funds:
        ///         depositor address -> token address -> deposit id -> DepositInfo
        mapping(address => mapping(address => mapping(uint256 => DepositInfo)))
            public deposits;
        /// @notice The number of deposits created. Includes the deposits that
        ///         were fully withdrawn. This is also the identifier of the most
        ///         recently created deposit.
        uint256 public depositCount;
        /// @notice List of tokens enabled for deposit or for deposit and lock.
        mapping(address => TokenAbility) public tokenAbility;
        /// @notice Minimum time a deposit can be locked.
        uint32 public minLockPeriod;
        /// @notice Maximum time a deposit can be locked.
        uint32 public maxLockPeriod;
        /// @notice Address of the liquidity treasury multisig.
        address public liquidityTreasury;
        /// @notice Mapping to store whether an asset is managed by the liquidity
        ///         treasury multisig.
        mapping(address => bool) public liquidityTreasuryManaged;
        /// @notice Mapping of depositable tokens to their receipt token fee
        ///         parameters.
        mapping(address => FeeInfo) public feeInfo;
        /// @notice Address of the tBTC token. Used for the to-tBTC migration of
        ///         deposited assets for which the migration was enabled by the
        ///         governance.
        address public tbtcToken;
        /// @notice Address of the tBTC migration treasury multisig. The tBTC
        ///         migration treasury is responsible for conducting the
        ///         to-tBTC migration of deposited assets for which the migration
        ///         was enabled by the governance.
        address public tbtcMigrationTreasury;
        /// @notice Mapping indicating whether the given asset can be migrated to
        ///         tBTC by the tBTC migration treasury and if so, what is the
        ///         global status of migrations. The key to the mapping is the
        ///         address of the asset being migrated.
        mapping(address => TbtcMigrationInfo) public tbtcMigrations;
        event Deposited(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId,
            uint256 amount
        );
        event Withdrawn(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId,
            uint256 amount
        );
        event Locked(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId,
            uint32 unlockAt,
            uint32 lockPeriod
        );
        event SupportedTokenAdded(address indexed token, TokenAbility tokenAbility);
        event MaxLockPeriodUpdated(uint32 maxLockPeriod);
        event MinLockPeriodUpdated(uint32 minLockPeriod);
        /// @notice Event emitted when the liquidity treasury address is updated.
        event LiquidityTreasuryUpdated(
            address indexed previousLiquidityTreasury,
            address indexed newLiquidityTreasury
        );
        /// @notice Event emitted when an asset is withdrawn by the liquidity
        ///         treasury multisig.
        event WithdrawnByLiquidityTreasury(address indexed token, uint256 amount);
        /// @notice Event emitted when an asset is set/unset as a liquidity
        ///         treasury multisig managed asset.
        event LiquidityTreasuryManagedAssetUpdated(
            address indexed asset,
            bool isManaged
        );
        /// @notice Event emitted when the receipt parameters for a deposit token
        ///         are updated.
        event ReceiptParamsUpdated(
            address indexed token,
            uint8 annualFee,
            uint8 mintCap,
            address receiptToken
        );
        /// @notice Event emitted when a receipt token is minted for a deposit.
        event ReceiptMinted(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId,
            uint256 amount
        );
        /// @notice Event emitted when a receipt token is repaid for a deposit.
        event ReceiptRepaid(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId,
            uint256 amount
        );
        /// @notice Event emitted when a fee is collected for a deposit.
        event FeeCollected(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId,
            uint256 fee
        );
        /// @notice Event emitted when the tBTC token address used for the
        ///         to-tBTC migration was set by the governance.
        /// @dev This event can be emitted only one time.
        event TbtcTokenAddressSet(address tbtc);
        /// @notice Event emitted when the to-tBTC migration treasury address was
        ///         updated.
        event TbtcMigrationTreasuryUpdated(
            address indexed previousMigrationTreasury,
            address indexed newMigrationTreasury
        );
        /// @notice Event emitted when the eligibility for the to-tBTC migration
        ///         for the asset was updated.
        event TbtcMigrationAllowedUpdated(address indexed token, bool isAllowed);
        /// @notice Event emitted when the to-tBTC migration was requested for the
        ///         deposit.
        event TbtcMigrationRequested(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId
        );
        /// @notice Event emitted when the to-tBTC migration was started for the
        ///         deposit.
        event TbtcMigrationStarted(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId
        );
        /// @notice Event emitted when the tBTC migration treasury started to-tBTC
        ///         migration for a set of deposits withdrawing the given amount of
        ///         the token from the Portal contract.
        event WithdrawnForTbtcMigration(address indexed token, uint256 amount);
        /// @notice Event emitted when the to-tBTC migration was completed for the
        ///         deposit.
        event TbtcMigrationCompleted(
            address indexed depositor,
            address indexed token,
            uint256 indexed depositId
        );
        /// @notice Event emitted when the to-tBTC migration was completed for one
        ///         or multiple deposits and the Portal contract was funded with
        ///         tBTC coming from the migrated assets.
        /// @dev The `amount` in the event is the amount in tBTC, using tBTC token's
        ///      precision.
        event FundedFromTbtcMigration(uint256 amount);
        /// @notice Event emitted in similar circumstances as `Withdrawn` except
        ///         that it represents a to-tBTC migrated deposit where the
        ///         withdrawal is performed in tBTC and not in the original deposit
        ///         token.
        event WithdrawnTbtcMigrated(
            address indexed depositor,
            address indexed token,
            address tbtcToken,
            uint256 indexed depositId,
            uint256 amountInTbtc
        );
        /// @notice Event emitted in similar circumstances as `FeeCollected`
        ///         except that it represents a to-tBTC migrated deposit where the
        ///         fee is collected in tBTC and not in the original deposit token.
        event FeeCollectedTbtcMigrated(
            address indexed depositor,
            address indexed token,
            address tbtcToken,
            uint256 indexed depositId,
            uint256 feeInTbtc
        );
        error IncorrectTokenAddress(address token);
        error IncorrectTokenAbility(TokenAbility ability);
        error IncorrectDepositor(address depositor);
        error IncorrectAmount(uint256 amount);
        error TokenNotSupported(address token);
        error TokenAlreadySupported(address token, TokenAbility tokenAbility);
        error InsufficientTokenAbility(address token, TokenAbility tokenAbility);
        error LockPeriodOutOfRange(uint32 lockPeriod);
        error IncorrectLockPeriod(uint256 lockPeriod);
        error LockPeriodTooShort(
            uint32 lockPeriod,
            uint32 newUnlockAt,
            uint32 existingUnlockAt
        );
        error DepositLocked(uint32 unlockAt);
        error DepositNotFound();
        /// @notice Error when the partial withdrawal amount is too high. Either it
        ///         equals the deposit amount (not a partial withdrawal) or it is
        ///         higher than the deposit amount.
        error PartialWithdrawalAmountTooHigh(uint256 depositAmount);
        /// @notice Error when the sender is not the liquidity treasury.
        error SenderNotLiquidityTreasury(address sender);
        /// @notice Error when the asset is not managed by the liquidity treasury.
        error AssetNotManagedByLiquidityTreasury(address asset);
        /// @notice Error when trying to withdraw without repaying the minted
        ///         receipt tokens back to Portal.
        error ReceiptNotRepaid(uint256 receiptMinted);
        /// @notice Error when the user tries to partially withdraw but the fee for
        ///         minting the receipt token is non-zero. In this case only a full
        ///         deposit withdrawal is possible.
        error ReceiptFeeOwed(uint256 feeOwed);
        /// @notice Error when the user tries to mint more than the mint limit
        ///         allows.
        error ReceiptMintLimitExceeded(
            uint256 mintLimit,
            uint96 currentlyMinted,
            uint256 feeOwed,
            uint256 amount
        );
        /// @notice Error when the user tries to repay more than the minted debt.
        error RepayAmountExceededDebt(uint96 mintedDebt, uint256 amount);
        /// @notice Error when the annual fee proposed exceeds 100%.
        error MaxAnnualFeeExceeded(uint8 annualFee);
        /// @notice Error when the mint cap proposed exceeds 100%.
        error MaxReceiptMintCapExceeded(uint8 mintCap);
        /// @notice Error when there is no receipt token set for the asset.
        error ReceiptMintingDisabled();
        /// @notice Error when the receipt token is already initialized.
        error ReceiptTokenAlreadyInitialized();
        /// @notice Error when the receipt token decimals are not 18.
        error IncorrectReceiptTokenDecimals(address receiptToken);
        /// @notice Error when token being accepted as deposit token does not
        ///         support decimals() function or the function is malfunctioning.
        error UnknownTokenDecimals(address token);
        /// @notice The given asset can be marked as eligible for tBTC migration if
        ///         it is not managed by the liquidity treasury. The given asset
        ///         can be managed by the liquidity treasury if it is not marked as
        ///         eligible for tBTC migration. The transaction reverts with this
        ///         error when the governance changes would conflict with this
        ///         limitation.
        error TbtcMigrationAndLiquidityManagementConflict();
        /// @notice Error when the governance tries to add tBTC as an asset eligible
        ///         for to-tBTC migration.
        error TbtcCanNotBeMigrated();
        /// @notice Error when the tBTC token address used for to-tBTC migrations
        ///         was already set by the governance.
        error TbtcTokenAddressAlreadySet();
        /// @notice Error when the tBTC token address used for to-tBTC migrations
        ///         was not set by the governance but there is an attempt to enable
        ///         to-tBTC migration for some asset.
        error TbtcTokenAddressNotSet();
        /// @notice Error when someone else but the tBTC migration treasury tried
        ///         to withdraw tokens for tBTC migration or to complete the
        ///         migration process.
        error SenderNotTbtcMigrationTreasury();
        /// @notice Error when the given to-tBTC transition state change is
        ///         requested from an unexpected state. For example, it is not
        ///         allowed to complete the migration of a deposit for which the
        ///         migration was not requested.
        error UnexpectedTbtcMigrationState(
            uint256 depositId,
            TbtcMigrationState currentState,
            TbtcMigrationState expectedState
        );
        /// @notice Error when the to-tBTC migration for the asset was not allowed
        ///         by the governance but the depositor tried to request a migration.
        error TbtcMigrationNotAllowed();
        /// @notice Error when the to-tBTC migration has been requested but not yet
        ///         completed and the depositor tries to withdraw the deposit.
        error TbtcMigrationNotCompleted();
        /// @notice Error when the to-tBTC migration was requested and the depositor
        ///         tries to partially withdraw the deposit.
        /// @dev The `Err` suffix is to distinguish this error from the
        ///      `TbtcMigrationRequested` event.
        error TbtcMigrationRequestedErr();
        /// @custom:oz-upgrades-unsafe-allow constructor
        constructor() {
            _disableInitializers();
        }
        /// @notice Initialize the contract with the supported tokens and their
        ///         abilities.
        /// @dev We are using OpenZeppelin's initializer pattern to initialize
        ///      the contract. This function is called only once during the contract
        ///      deployment.
        /// @param supportedTokens List of supported tokens and their abilities
        function initialize(
            SupportedToken[] memory supportedTokens
        ) external initializer {
            __Ownable_init(msg.sender);
            minLockPeriod = 4 weeks;
            maxLockPeriod = 39 weeks;
            depositCount = 0;
            for (uint256 i = 0; i < supportedTokens.length; i++) {
                address token = supportedTokens[i].token;
                TokenAbility ability = supportedTokens[i].tokenAbility;
                if (token == address(0)) {
                    revert IncorrectTokenAddress(token);
                }
                tokenAbility[token] = ability;
            }
        }
        /// @notice Add a new supported token and define its ability.
        /// @dev Only the owner can add a new supported token. Supported token must
        ///      implement the decimals() method so Portal is able to perform
        ///      correct calculations.
        /// @param supportedToken Supported token and its ability
        function addSupportedToken(
            SupportedToken calldata supportedToken
        ) external onlyOwner {
            address token = supportedToken.token;
            TokenAbility ability = supportedToken.tokenAbility;
            if (token == address(0)) {
                revert IncorrectTokenAddress(token);
            }
            if (ability == TokenAbility.None) {
                revert IncorrectTokenAbility(ability);
            }
            if (tokenAbility[token] != TokenAbility.None) {
                revert TokenAlreadySupported(token, tokenAbility[token]);
            }
            // Attempt to call decimals() on the token to verify it exists
            // slither-disable-next-line unused-return
            try IERC20WithDecimals(token).decimals() {} catch {
                revert UnknownTokenDecimals(token);
            }
            tokenAbility[token] = ability;
            emit SupportedTokenAdded(token, ability);
        }
        /// @notice Set the liquidity treasury multisig address.
        /// @dev Only the owner can set the liquidity treasury multisig address. It
        ///      is possible to set the liquidity treasury multisig address to zero
        ///      in case the liquidity mining should be disabled.
        /// @param  _liquidityTreasury address of the liquidity treasury multisig
        function setLiquidityTreasury(
            address _liquidityTreasury
        ) external onlyOwner {
            emit LiquidityTreasuryUpdated(liquidityTreasury, _liquidityTreasury);
            // slither-disable-next-line missing-zero-check
            liquidityTreasury = _liquidityTreasury;
        }
        /// @notice Set whether an asset is managed by the liquidity treasury multisig.
        /// @dev Only the owner can set whether an asset is managed by the liquidity
        ///      treasury multisig. Only assets which can be locked in the Portal can
        ///      be managed by the liquidity treasury multisig.
        /// @param asset address of the asset to be managed by the liquidity treasury
        /// @param isManaged boolean value to set whether the asset is managed by the
        ///        liquidity treasury multisig
        function setAssetAsLiquidityTreasuryManaged(
            address asset,
            bool isManaged
        ) external onlyOwner {
            TokenAbility ability = tokenAbility[asset];
            if (ability == TokenAbility.None) {
                revert TokenNotSupported(asset);
            }
            if (ability != TokenAbility.DepositAndLock) {
                revert InsufficientTokenAbility(asset, tokenAbility[asset]);
            }
            if (tbtcMigrations[asset].isAllowed) {
                revert TbtcMigrationAndLiquidityManagementConflict();
            }
            liquidityTreasuryManaged[asset] = isManaged;
            emit LiquidityTreasuryManagedAssetUpdated(asset, isManaged);
        }
        /// @notice Set the tBTC token address. Setting the tBTC token address
        ///         allows the governance to enable to-tBTC migrations of selected
        ///         deposited assets.
        /// @dev Can only be executed one time. Can only be executed by the
        ///      governance.
        /// @param _tbtcToken address of the tBTC token
        function setTbtcTokenAddress(address _tbtcToken) external onlyOwner {
            if (_tbtcToken == address(0)) {
                revert IncorrectTokenAddress(_tbtcToken);
            }
            if (tbtcToken != address(0)) {
                revert TbtcTokenAddressAlreadySet();
            }
            tbtcToken = _tbtcToken;
            emit TbtcTokenAddressSet(_tbtcToken);
        }
        /// @notice Set the tBTC migration treasury multisig address.
        /// @dev Only the owner can set the tBTC migraton treasury multisig address.
        ///      It is possible to set the migration treasury address to zero in
        ///      case the migration should be disabled.
        /// @param  _tbtcMigrationTreasury the new address of the tBTC migration
        ///         treasury multisig
        function setTbtcMigrationTreasury(
            address _tbtcMigrationTreasury
        ) external onlyOwner {
            emit TbtcMigrationTreasuryUpdated(
                tbtcMigrationTreasury,
                _tbtcMigrationTreasury
            );
            // slither-disable-next-line missing-zero-check
            tbtcMigrationTreasury = _tbtcMigrationTreasury;
        }
        /// @notice Set whether an asset can be migrated to tBTC by the migration
        ///         treasury multisig. Only assets that can be deposited in the
        ///         Portal can be marked as eligible for migration. Only assets that
        ///         represent Bitcoin and can be mapped 1:1 to tBTC can be marked
        ///         as eligible for migration.
        /// @param asset address of the asset to upgrade the to-tBTC migration
        ///        settings for
        /// @param isAllowed boolean value indicating whether to-tBTC migration
        ///        is allowed
        function setAssetTbtcMigrationAllowed(
            address asset,
            bool isAllowed
        ) external onlyOwner {
            if (tbtcToken == address(0)) {
                revert TbtcTokenAddressNotSet();
            }
            if (asset == tbtcToken) {
                revert TbtcCanNotBeMigrated();
            }
            TokenAbility ability = tokenAbility[asset];
            if (ability == TokenAbility.None) {
                revert TokenNotSupported(asset);
            }
            if (liquidityTreasuryManaged[asset]) {
                revert TbtcMigrationAndLiquidityManagementConflict();
            }
            tbtcMigrations[asset].isAllowed = isAllowed;
            emit TbtcMigrationAllowedUpdated(asset, isAllowed);
        }
        /// @notice Set the minimum lock period for deposits.
        /// @dev Only the owner can update the minimum lock period. The new value
        ///      must be normalized to weeks, non-zero, and not higher than the
        ///      maximum lock period.
        /// @param _minLockPeriod new minimum lock period
        function setMinLockPeriod(uint32 _minLockPeriod) external onlyOwner {
            uint32 normalizedLockPeriod = _normalizeLockPeriod(_minLockPeriod);
            if (
                _minLockPeriod != normalizedLockPeriod ||
                normalizedLockPeriod == 0 ||
                _minLockPeriod > maxLockPeriod
            ) {
                revert IncorrectLockPeriod(_minLockPeriod);
            }
            minLockPeriod = _minLockPeriod;
            emit MinLockPeriodUpdated(_minLockPeriod);
        }
        /// @notice Set the maximum lock period for deposits. Maximum lock
        ///      period is used as a limit to prevent users from accidentally
        ///      locking their deposits for too long.
        /// @dev Only the owner can update the maximum lock period. The new value
        ///      must be normalized to weeks and not lower than the minimum lock
        ///      period.
        /// @param _maxLockPeriod new maximum lock period
        function setMaxLockPeriod(uint32 _maxLockPeriod) external onlyOwner {
            if (
                _maxLockPeriod != _normalizeLockPeriod(_maxLockPeriod) ||
                _maxLockPeriod < minLockPeriod
            ) {
                revert IncorrectLockPeriod(_maxLockPeriod);
            }
            maxLockPeriod = _maxLockPeriod;
            emit MaxLockPeriodUpdated(_maxLockPeriod);
        }
        /// @notice Set the receipt parameters for a supported deposit token. The
        ///         receipt token address can be set only one time. All following
        ///         calls to this function for the same deposit token needs to pass
        ///         the same address of the receipt token as set initially.
        /// @dev Only the owner can set the receipt parameters. Receipt token must
        ///      implement the decimals() method so Portal is able to perform correct
        ///      calculations for minting receipt tokens.
        /// @param token deposit token address to set the parameters
        /// @param annualFee annual fee in percentage for minting the receipt token
        /// @param mintCap mint cap in percentage for minting the receipt token
        /// @param receiptToken receipt token address
        function setReceiptParams(
            address token,
            uint8 annualFee,
            uint8 mintCap,
            address receiptToken
        ) external onlyOwner {
            if (tokenAbility[token] == TokenAbility.None) {
                revert TokenNotSupported(token);
            }
            if (annualFee > 100) {
                revert MaxAnnualFeeExceeded(annualFee);
            }
            if (mintCap > 100) {
                revert MaxReceiptMintCapExceeded(mintCap);
            }
            if (receiptToken == address(0)) {
                revert IncorrectTokenAddress(receiptToken);
            }
            FeeInfo storage i = feeInfo[token];
            if (i.receiptToken != address(0) && i.receiptToken != receiptToken) {
                revert ReceiptTokenAlreadyInitialized();
            }
            uint8 decimals = IERC20WithDecimals(receiptToken).decimals();
            if (decimals != 18) {
                revert IncorrectReceiptTokenDecimals(receiptToken);
            }
            _updateFeeIntegral(token);
            i.annualFee = annualFee;
            i.mintCap = mintCap;
            i.receiptToken = receiptToken;
            // solhint-disable-next-line not-rely-on-time
            i.lastFeeUpdateAt = uint32(block.timestamp);
            emit ReceiptParamsUpdated(token, annualFee, mintCap, receiptToken);
        }
        /// @notice Withdraws all deposited tokens.
        ///
        ///         Deposited lockable tokens can be withdrawn at any time if
        ///         there is no lock set on the deposit or the lock period has passed.
        ///         There is no way to withdraw locked deposit. Tokens that are not
        ///         lockable can be withdrawn at any time.
        ///
        ///         Deposits for which receipt tokens were minted and not fully
        ///         repaid can not be withdrawn even if the lock expired. Repaying
        ///         all receipt tokens is a must to withdraw the deposit. Upon
        ///         withdrawing a deposit for which the receipt tokens were minted,
        ///         the fee is collected based on the annual fee and the amount
        ///         of minted receipt tokens.
        ///
        ///         Deposits for which to-tBTC migration was requested but not yet
        ///         completed yet can not be withdrawn. The deposits for which the
        ///         to-tBTC migration was completed are withdrawn in tBTC.
        ///
        ///         This function withdraws all deposited tokens. For partial
        ///         withdrawals, use `withdrawPartially`.
        /// @param token deposited token address
        /// @param depositId id of the deposit
        function withdraw(address token, uint256 depositId) external {
            TokenAbility ability = tokenAbility[token];
            if (ability == TokenAbility.None) {
                revert TokenNotSupported(token);
            }
            DepositInfo storage selectedDeposit = deposits[msg.sender][token][
                depositId
            ];
            if (
                selectedDeposit.tbtcMigrationState ==
                TbtcMigrationState.Requested ||
                selectedDeposit.tbtcMigrationState == TbtcMigrationState.InProgress
            ) {
                revert TbtcMigrationNotCompleted();
            }
            if (
                ability == TokenAbility.DepositAndLock &&
                // solhint-disable-next-line not-rely-on-time
                block.timestamp < selectedDeposit.unlockAt
            ) {
                revert DepositLocked(selectedDeposit.unlockAt);
            }
            if (selectedDeposit.receiptMinted > 0) {
                revert ReceiptNotRepaid(selectedDeposit.receiptMinted);
            }
            uint96 depositedAmount = selectedDeposit.balance;
            if (depositedAmount == 0) {
                revert DepositNotFound();
            }
            if (
                selectedDeposit.tbtcMigrationState == TbtcMigrationState.Completed
            ) {
                uint96 feeInTbtc = 0;
                // The fee is collected based on the pre-migration rules, so based
                // on the annual fee as set by the governance for the original token.
                // The fee is collected in tBTC as the entire deposit was migrated
                // to tBTC.
                if (selectedDeposit.feeOwed > 0) {
                    FeeInfo storage tokenFeeInfo = feeInfo[token];
                    feeInTbtc = _adjustTokenDecimals(
                        tokenFeeInfo.receiptToken,
                        tbtcToken,
                        selectedDeposit.feeOwed
                    );
                }
                uint96 depositedAmountInTbtc = _adjustTokenDecimals(
                    token,
                    tbtcToken,
                    depositedAmount
                );
                uint96 withdrawableInTbtc = depositedAmountInTbtc - feeInTbtc;
                emit WithdrawnTbtcMigrated(
                    msg.sender,
                    token,
                    tbtcToken,
                    depositId,
                    withdrawableInTbtc
                );
                emit FeeCollectedTbtcMigrated(
                    msg.sender,
                    token,
                    tbtcToken,
                    depositId,
                    feeInTbtc
                );
                feeInfo[tbtcToken].feeCollected += feeInTbtc;
                delete deposits[msg.sender][token][depositId];
                IERC20(tbtcToken).safeTransfer(msg.sender, withdrawableInTbtc);
            } else {
                uint96 fee = 0;
                if (selectedDeposit.feeOwed > 0) {
                    FeeInfo storage tokenFeeInfo = feeInfo[token];
                    fee = _adjustTokenDecimals(
                        tokenFeeInfo.receiptToken,
                        token,
                        selectedDeposit.feeOwed
                    );
                }
                uint96 withdrawable = depositedAmount - fee;
                emit Withdrawn(msg.sender, token, depositId, withdrawable);
                emit FeeCollected(msg.sender, token, depositId, fee);
                feeInfo[token].feeCollected += fee;
                delete deposits[msg.sender][token][depositId];
                IERC20(token).safeTransfer(msg.sender, withdrawable);
            }
        }
        /// @notice Withdraws part of the deposited tokens.
        ///
        ///         Deposited lockable tokens can be withdrawn at any time if
        ///         there is no lock set on the deposit or the lock period has passed.
        ///         There is no way to withdraw locked deposit. Tokens that are not
        ///         lockable can be withdrawn at any time.
        ///
        ///         Deposits for which receipt tokens were minted and fully repaid
        ///         can not be partially withdrawn even if the lock expired.
        ///         Repaying all receipt tokens is a must to partially withdraw the
        ///         deposit. If the fee for receipt tokens minted is non-zero, the
        ///         deposit can not be partially withdrawn and only a full
        ///         withdrawal is possible.
        ///
        ///         Deposits for which the to-tBTC migration was requested can not
        ///         be withdrawn partially.
        ///
        ///         This function allows only for partial withdrawals. For full
        ///         withdrawals, use `withdraw`.
        /// @param token deposited token address
        /// @param depositId id of the deposit
        /// @param amount the amount to be withdrawn
        function withdrawPartially(
            address token,
            uint256 depositId,
            uint96 amount
        ) external {
            TokenAbility ability = tokenAbility[token];
            if (ability == TokenAbility.None) {
                revert TokenNotSupported(token);
            }
            if (amount == 0) {
                revert IncorrectAmount(amount);
            }
            DepositInfo storage selectedDeposit = deposits[msg.sender][token][
                depositId
            ];
            if (selectedDeposit.balance == 0) {
                revert DepositNotFound();
            }
            if (amount >= selectedDeposit.balance) {
                revert PartialWithdrawalAmountTooHigh(selectedDeposit.balance);
            }
            if (
                ability == TokenAbility.DepositAndLock &&
                // solhint-disable-next-line not-rely-on-time
                block.timestamp < selectedDeposit.unlockAt
            ) {
                revert DepositLocked(selectedDeposit.unlockAt);
            }
            if (selectedDeposit.receiptMinted > 0) {
                revert ReceiptNotRepaid(selectedDeposit.receiptMinted);
            }
            if (selectedDeposit.feeOwed > 0) {
                revert ReceiptFeeOwed(selectedDeposit.feeOwed);
            }
            if (
                selectedDeposit.tbtcMigrationState !=
                TbtcMigrationState.NotRequested
            ) {
                revert TbtcMigrationRequestedErr();
            }
            emit Withdrawn(msg.sender, token, depositId, amount);
            selectedDeposit.balance -= amount;
            IERC20(token).safeTransfer(msg.sender, amount);
        }
        /// @notice Receive approval from a token contract and deposit the tokens in
        ///         one transaction. If the the token is lockable and lock period is
        ///         greater than 0, the deposit will be locked for the given period.
        /// @dev This function is called by the token following the `approveAndCall`
        ///      pattern.
        //       Encoded lock period is counted in seconds, will be normalized to
        ///      weeks. If non-zero, it must not be shorter than the minimum lock
        ///      period and must not be longer than the maximum lock period. To not
        ///      lock the deposit, the lock period must be 0.
        /// @param from address which approved and sent the tokens
        /// @param amount amount of tokens sent
        /// @param token address of the token contract
        /// @param data encoded lock period
        function receiveApproval(
            address from,
            uint256 amount,
            address token,
            bytes calldata data
        ) external override {
            if (token != msg.sender) {
                revert IncorrectTokenAddress(token);
            }
            if (amount > type(uint96).max) {
                revert IncorrectAmount(amount);
            }
            uint32 lockPeriod = abi.decode(data, (uint32));
            _depositFor(from, from, token, uint96(amount), lockPeriod);
        }
        /// @notice Deposit and optionally lock tokens for the given period.
        /// @dev Lock period will be normalized to weeks. If non-zero, it must not
        ///      be shorter than the minimum lock period and must not be longer than
        ///      the maximum lock period.
        /// @param token token address to deposit
        /// @param amount amount of tokens to deposit
        /// @param lockPeriod lock period in seconds, 0 to not lock the deposit
        function deposit(address token, uint96 amount, uint32 lockPeriod) external {
            _depositFor(msg.sender, msg.sender, token, amount, lockPeriod);
        }
        /// @notice Deposit and optionally lock tokens to the contract on behalf of
        ///         someone else.
        /// @dev Lock period will be normalized to weeks. If non-zero, it must not
        ///      be shorter than the minimum lock period and must not be longer than
        ///      the maximum lock period.
        /// @param depositOwner address that will be able to withdraw the deposit
        /// @param token token address to deposit
        /// @param amount amount of tokens to deposit
        /// @param lockPeriod lock period in seconds, 0 to not lock the deposit
        function depositFor(
            address depositOwner,
            address token,
            uint96 amount,
            uint32 lockPeriod
        ) external {
            _depositFor(depositOwner, msg.sender, token, amount, lockPeriod);
        }
        /// @notice Lock existing deposit for a given period. This function can be
        ///         used to lock a deposit that was not locked when it was created or
        ///         to extend the lock period of an already locked deposit.
        /// @param token address of the deposited token
        /// @param depositId id of the deposit
        /// @param lockPeriod new lock period in seconds
        function lock(
            address token,
            uint256 depositId,
            uint32 lockPeriod
        ) external {
            DepositInfo storage depositInfo = deposits[msg.sender][token][
                depositId
            ];
            if (depositInfo.balance == 0) {
                revert DepositNotFound();
            }
            TokenAbility ability = tokenAbility[token];
            if (ability != TokenAbility.DepositAndLock) {
                revert InsufficientTokenAbility(token, ability);
            }
            uint32 existingUnlockAt = depositInfo.unlockAt;
            uint32 newUnlockAt = _calculateUnlockTime(
                msg.sender,
                token,
                depositId,
                lockPeriod
            );
            if (newUnlockAt <= existingUnlockAt) {
                revert LockPeriodTooShort(
                    lockPeriod,
                    newUnlockAt,
                    existingUnlockAt
                );
            }
            depositInfo.unlockAt = newUnlockAt;
        }
        /// @notice External withdraw function to enable the liquidity treasury to
        ///         withdraw tokens from the contract. Only the liquidity treasury
        ///         can withdraw tokens from the contract using this function.
        /// @param token token address to withdraw
        /// @param amount amount of respective token to withdraw
        function withdrawAsLiquidityTreasury(
            address token,
            uint256 amount
        ) external {
            if (msg.sender != liquidityTreasury) {
                revert SenderNotLiquidityTreasury(msg.sender);
            }
            if (amount == 0) {
                revert IncorrectAmount(amount);
            }
            if (!liquidityTreasuryManaged[token]) {
                revert AssetNotManagedByLiquidityTreasury(token);
            }
            emit WithdrawnByLiquidityTreasury(token, amount);
            IERC20(token).safeTransfer(msg.sender, amount);
        }
        /// @notice Mint a deposit receipt token against an existing deposit.
        /// @param token the token address related with the deposit
        /// @param depositId the ID of the deposit
        /// @param amount amount of the receipt token to mint
        function mintReceipt(
            address token,
            uint256 depositId,
            uint256 amount
        ) external {
            FeeInfo storage fee = feeInfo[token];
            if (fee.receiptToken == address(0)) {
                revert ReceiptMintingDisabled();
            }
            if (amount == 0) {
                revert IncorrectAmount(amount);
            }
            DepositInfo storage depositInfo = deposits[msg.sender][token][
                depositId
            ];
            if (depositInfo.balance == 0) {
                revert DepositNotFound();
            }
            _updateFee(depositInfo, token);
            // Normalize deposit balance to match receipt token decimals
            uint96 normalizedBalance = _adjustTokenDecimals(
                token,
                fee.receiptToken,
                depositInfo.balance
            );
            uint96 mintLimit = (normalizedBalance * fee.mintCap) / 100;
            if (
                amount + depositInfo.receiptMinted + depositInfo.feeOwed > mintLimit
            ) {
                revert ReceiptMintLimitExceeded(
                    mintLimit,
                    depositInfo.receiptMinted,
                    depositInfo.feeOwed,
                    amount
                );
            }
            depositInfo.receiptMinted += uint96(amount);
            fee.totalMinted += uint96(amount);
            emit ReceiptMinted(msg.sender, token, depositId, amount);
            IReceiptToken(fee.receiptToken).mintReceipt(msg.sender, amount);
        }
        /// @notice Repay the deposit receipt token of a particular deposit.
        /// @param token the token address related with the deposit
        /// @param depositId the ID of the deposit
        /// @param amount how much of the token to repay, up to outstanding balance
        function repayReceipt(
            address token,
            uint256 depositId,
            uint256 amount
        ) public {
            FeeInfo storage fee = feeInfo[token];
            if (fee.receiptToken == address(0)) {
                revert ReceiptMintingDisabled();
            }
            if (amount == 0) {
                revert IncorrectAmount(amount);
            }
            DepositInfo storage depositInfo = deposits[msg.sender][token][
                depositId
            ];
            if (depositInfo.balance == 0) {
                revert DepositNotFound();
            }
            if (depositInfo.receiptMinted < amount) {
                revert RepayAmountExceededDebt(depositInfo.receiptMinted, amount);
            }
            _updateFee(depositInfo, token);
            depositInfo.receiptMinted -= uint96(amount);
            fee.totalMinted -= uint96(amount);
            emit ReceiptRepaid(msg.sender, token, depositId, amount);
            // transfer the repaid receipt tokens and burn them
            IERC20(fee.receiptToken).safeTransferFrom(
                msg.sender,
                address(this),
                amount
            );
            IReceiptToken(fee.receiptToken).burnReceipt(amount);
        }
        /// @notice Request the to-tBTC migration for the given deposit. The
        ///         migration happens asynchronously and is managed by the tBTC
        ///         migration treasury. The contract gives no guarantees when the
        ///         migration will be completed.
        /// @param token deposited token address
        /// @param depositId id of the deposit
        function requestTbtcMigration(address token, uint256 depositId) external {
            DepositInfo storage depositInfo = deposits[msg.sender][token][
                depositId
            ];
            TbtcMigrationInfo storage migrationInfo = tbtcMigrations[token];
            if (!migrationInfo.isAllowed) {
                revert TbtcMigrationNotAllowed();
            }
            if (depositInfo.tbtcMigrationState != TbtcMigrationState.NotRequested) {
                revert UnexpectedTbtcMigrationState(
                    depositId,
                    depositInfo.tbtcMigrationState,
                    TbtcMigrationState.NotRequested
                );
            }
            depositInfo.tbtcMigrationState = TbtcMigrationState.Requested;
            emit TbtcMigrationRequested(msg.sender, token, depositId);
        }
        /// @notice Used by the tBTC migration treasury to withdraw tokens from the
        ///         deposits marked by their owners as requested for to-tBTC
        ///         migration. The caller specifies which deposits are to begin the
        ///         migration. Calling this function for the deposit marks it
        ///         as to-tBTC migration started but gives no promise as when the
        ///         to-tBTC migration will be completed. For all deposits for which
        ///         the to-tBTC migration was started, the tBTC migration multisig
        ///         must ensure to call the `finalizeTbtcMigration` function once
        ///         the migration is completed.
        /// @param token deposited token address
        /// @param depositsToMigrate an array of deposits for which the to-tBTC
        ///        migration was requested and from which the assets should be
        ///        withdrawn for the to-tBTC migration
        function withdrawForTbtcMigration(
            address token,
            DepositToMigrate[] memory depositsToMigrate
        ) external {
            if (msg.sender != tbtcMigrationTreasury) {
                revert SenderNotTbtcMigrationTreasury();
            }
            uint96 totalToMigrate = 0;
            for (uint256 i = 0; i < depositsToMigrate.length; i++) {
                address depositOwner = depositsToMigrate[i].depositor;
                uint256 depositId = depositsToMigrate[i].depositId;
                DepositInfo storage depositInfo = deposits[depositOwner][token][
                    depositId
                ];
                if (depositInfo.balance == 0) {
                    revert DepositNotFound();
                }
                if (
                    depositInfo.tbtcMigrationState != TbtcMigrationState.Requested
                ) {
                    revert UnexpectedTbtcMigrationState(
                        depositId,
                        depositInfo.tbtcMigrationState,
                        TbtcMigrationState.Requested
                    );
                }
                depositInfo.tbtcMigrationState = TbtcMigrationState.InProgress;
                totalToMigrate += depositInfo.balance;
                emit TbtcMigrationStarted(depositOwner, token, depositId);
            }
            TbtcMigrationInfo storage migrationInfo = tbtcMigrations[token];
            migrationInfo.totalMigrating += totalToMigrate;
            emit WithdrawnForTbtcMigration(token, totalToMigrate);
            IERC20(token).safeTransfer(tbtcMigrationTreasury, totalToMigrate);
        }
        /// @notice Used by the tBTC migration treasury to complete the to-tBTC
        ///         migration for the selected set of deposits. For all of those
        ///         deposits the migration should be started by calling
        ///         `withdrawForTbtcMigration` function before. The treasury has to
        ///         approve the amount of tBTC to the Portal contract equal to the
        ///         total migrated amount of the migrated deposits, adjusted to tBTC
        ///         token decimals.
        /// @param token pre-migration deposited token address
        /// @param migratedDeposits an array of deposits for which the to-tBTC
        ///        migration was completed
        function completeTbtcMigration(
            address token,
            DepositToMigrate[] memory migratedDeposits
        ) external {
            if (msg.sender != tbtcMigrationTreasury) {
                revert SenderNotTbtcMigrationTreasury();
            }
            uint96 totalMigrated = 0;
            for (uint256 i = 0; i < migratedDeposits.length; i++) {
                address depositOwner = migratedDeposits[i].depositor;
                uint256 depositId = migratedDeposits[i].depositId;
                DepositInfo storage depositInfo = deposits[depositOwner][token][
                    depositId
                ];
                if (
                    depositInfo.tbtcMigrationState != TbtcMigrationState.InProgress
                ) {
                    revert UnexpectedTbtcMigrationState(
                        depositId,
                        depositInfo.tbtcMigrationState,
                        TbtcMigrationState.InProgress
                    );
                }
                totalMigrated += depositInfo.balance;
                depositInfo.tbtcMigrationState = TbtcMigrationState.Completed;
                emit TbtcMigrationCompleted(depositOwner, token, depositId);
            }
            TbtcMigrationInfo storage migrationInfo = tbtcMigrations[token];
            migrationInfo.totalMigrating -= totalMigrated;
            uint96 totalMigratedInTbtc = _adjustTokenDecimals(
                token,
                tbtcToken,
                totalMigrated
            );
            emit FundedFromTbtcMigration(totalMigratedInTbtc);
            IERC20(tbtcToken).safeTransferFrom(
                msg.sender,
                address(this),
                totalMigratedInTbtc
            );
        }
        /// @notice Get the balance and unlock time of a given deposit. Note that
        ///         the deposit could be migrated to tBTC so even though the `token`
        ///         key under which it is available could still point to the
        ///         pre-migration version, the witdrawal - when requested - will
        ///         happen in tBTC.
        /// @param depositor depositor address
        /// @param token token address to get the balance
        /// @param depositId id of the deposit
        function getDeposit(
            address depositor,
            address token,
            uint256 depositId
        ) external view returns (DepositInfo memory) {
            return deposits[depositor][token][depositId];
        }
        /// @notice Internal deposit function to deposit tokens on behalf of a given
        ///         address - sender or someone else. If the lock period passed as
        ///         a parameter is non-zero, the function will lock the deposit.
        /// @param depositOwner address that will be able to withdraw the deposit
        /// @param token token address to deposit
        /// @param amount amount of tokens to deposit
        /// @param lockPeriod lock period in seconds, 0 to not lock the deposit
        function _depositFor(
            address depositOwner,
            address depositFunder,
            address token,
            uint96 amount,
            uint32 lockPeriod
        ) internal {
            TokenAbility ability = tokenAbility[token];
            if (ability == TokenAbility.None) {
                revert TokenNotSupported(token);
            }
            if (lockPeriod > 0 && ability == TokenAbility.Deposit) {
                revert InsufficientTokenAbility(token, ability);
            }
            if (depositOwner == address(0)) {
                revert IncorrectDepositor(depositOwner);
            }
            if (amount == 0) {
                revert IncorrectAmount(amount);
            }
            depositCount++;
            uint256 depositId = depositCount;
            emit Deposited(depositOwner, token, depositId, amount);
            deposits[depositOwner][token][depositId] = DepositInfo({
                balance: amount,
                // solhint-disable-next-line not-rely-on-time
                unlockAt: _calculateUnlockTime(
                    depositOwner,
                    token,
                    depositId,
                    lockPeriod
                ),
                receiptMinted: 0,
                feeOwed: 0,
                lastFeeIntegral: 0,
                tbtcMigrationState: TbtcMigrationState.NotRequested
            });
            IERC20(token).safeTransferFrom(depositFunder, address(this), amount);
        }
        /// @notice Calculates the unlock time normalizing the lock time value and
        ///         making sure it is in the allowed range. Returns the timestamp at
        ///         which the deposit can be unlocked.
        /// @dev This function DOES NOT check if the deposit exists and if the
        ///      existing deposit already has a lock time. Please validate it before
        ///      calling this function.
        /// @param depositor depositor address
        /// @param token token of the deposit that will be locked
        /// @param depositId id of the deposit that will be locked
        /// @param lockPeriod lock period in seconds
        function _calculateUnlockTime(
            address depositor,
            address token,
            uint depositId,
            uint32 lockPeriod
        ) internal returns (uint32) {
            // Short-circuit if there is no intention to lock. There is no need to
            // check the minimum/maximum lock time or normalize the lock period.
            if (lockPeriod == 0) {
                // solhint-disable-next-line not-rely-on-time
                return uint32(block.timestamp);
            }
            uint32 normalizedLockPeriod = _normalizeLockPeriod(lockPeriod);
            if (
                normalizedLockPeriod == 0 ||
                normalizedLockPeriod < minLockPeriod ||
                normalizedLockPeriod > maxLockPeriod
            ) {
                revert LockPeriodOutOfRange(lockPeriod);
            }
            // solhint-disable-next-line not-rely-on-time
            uint32 unlockAt = uint32(block.timestamp) + normalizedLockPeriod;
            // It is gas-cheaper to pass the required parameters and emit the event
            // here instead of checking the result and emit the event in the calling
            // function. Keep in mind internal functions are inlined.
            emit Locked(
                depositor,
                token,
                depositId,
                unlockAt,
                normalizedLockPeriod
            );
            return unlockAt;
        }
        /// @notice Internal function to update the fee information of a given
        ///         deposit.
        /// @param depositInfo Storage reference to the deposit info to be updated.
        /// @param token Address of the token related to the deposit.
        function _updateFee(
            DepositInfo storage depositInfo,
            address token
        ) internal {
            _updateFeeIntegral(token);
            FeeInfo memory fee = feeInfo[token];
            depositInfo.feeOwed += _calculateFeeAccrued(
                fee.feeIntegral - depositInfo.lastFeeIntegral,
                depositInfo.receiptMinted
            );
            depositInfo.lastFeeIntegral = fee.feeIntegral;
        }
        /// @notice Internal function to update the fee integral for a given token.
        /// @param token Address of the token to update the fee integral.
        function _updateFeeIntegral(address token) internal {
            FeeInfo storage i = feeInfo[token];
            uint96 feePerSecond = _calculateFeeRate(token);
            // solhint-disable-next-line not-rely-on-time
            uint32 timeInterval = uint32(block.timestamp) - i.lastFeeUpdateAt;
            uint96 accumulated = timeInterval * feePerSecond;
            i.feeIntegral += uint88(accumulated);
            // solhint-disable-next-line not-rely-on-time
            i.lastFeeUpdateAt = uint32(block.timestamp);
        }
        /// @notice Internal function to calculate the fee rate per second for a
        ///         given token.
        /// @param token Address of the token.
        function _calculateFeeRate(address token) internal view returns (uint96) {
            FeeInfo memory i = feeInfo[token];
            // The i.annualFee is expressed in % so we divide by 100 to get the
            // fee rate in token units: 10^18 / 10^2 = 10^16
            return (uint96(i.annualFee) * (10 ** 16)) / (365 days);
        }
        /// @notice Calculates the fee accrued based on the given integral
        ///         difference and minted amount.
        /// @dev Multiplies the integral difference by the minted amount and scales
        ///      it down to fit the desired precision.
        /// @param integralDiff The difference in integral values.
        /// @param mintedAmount The amount of tokens minted.
        function _calculateFeeAccrued(
            uint88 integralDiff,
            uint96 mintedAmount
        ) internal pure returns (uint96) {
            return
                uint96((uint256(integralDiff) * uint256(mintedAmount)) / 10 ** 18);
        }
        /// @notice Normalizes the lock period to weeks. Will round down if not
        ///         normalized.
        /// @param lockPeriod the lock period to be normalized
        function _normalizeLockPeriod(
            uint32 lockPeriod
        ) internal pure returns (uint32) {
            // slither-disable-next-line divide-before-multiply
            return (lockPeriod / 1 weeks) * 1 weeks;
        }
        /// @notice Adjusts the decimals amount to desired precision for the two
        ///         token addresses and amount expressed in the precision used by
        ///         one of them.
        /// @param sourceToken the token address of the source amount
        /// @param targetToken the token address of the target amount
        /// @param amount the source amount to be adjusted
        function _adjustTokenDecimals(
            address sourceToken,
            address targetToken,
            uint96 amount
        ) internal view returns (uint96) {
            uint8 sourceDecimals = IERC20WithDecimals(sourceToken).decimals();
            uint8 targetDecimals = IERC20WithDecimals(targetToken).decimals();
            if (sourceDecimals < targetDecimals) {
                return uint96(amount * (10 ** (targetDecimals - sourceDecimals)));
            } else if (sourceDecimals > targetDecimals) {
                return uint96(amount / (10 ** (sourceDecimals - targetDecimals)));
            }
            return amount;
        }
    }