ETH Price: $2,508.75 (-2.90%)

Transaction Decoder

Block:
22299027 at Apr-18-2025 11:00:11 PM +UTC
Transaction Fee:
0.000073988181005996 ETH $0.19
Gas Used:
88,546 Gas / 0.835590326 Gwei

Emitted Events:

255 AtomicQueue.AtomicRequestUpdated( user=[Sender] 0x9f7cdd854fa94b2696d39ae8992a1284daa38dfa, offerToken=BoringVault, wantToken=TransparentUpgradeableProxy, amount=173000, deadline=1745276379, minPrice=101023377, timestamp=1745017211 )

Account State Difference:

  Address   Before After State Difference Code
0x3b4aCd88...C5E7DbBf8
(beaverbuild)
8.202469408994114128 Eth8.202513681994114128 Eth0.000044273
0x9f7CdD85...4daa38dfA
0.011477315554433822 Eth
Nonce: 39
0.011403327373427826 Eth
Nonce: 40
0.000073988181005996

Execution Trace

AtomicQueue.safeUpdateAtomicRequest( offer=0x5401b8620E5FB570064CA9114fd1e135fd77D57c, want=0x8236a87084f8B84306f72007F36F2618A5634494, userRequest=[{name:deadline, type:uint64, order:1, indexed:false, value:1745276379, valueString:1745276379}, {name:atomicPrice, type:uint88, order:2, indexed:false, value:0, valueString:0}, {name:offerAmount, type:uint96, order:3, indexed:false, value:173000, valueString:173000}, {name:inSolve, type:bool, order:4, indexed:false, value:false, valueString:False}], accountant=0x28634D0c5edC67CF2450E74deA49B90a4FF93dCE, discount=100 )
  • RolesAuthority.canCall( user=0x9f7CdD854Fa94B2696d39ae8992a1284daa38dfA, target=0x3b4aCd8879fb60586cCd74bC2F831A4C5E7DbBf8, functionSig=System.Byte[] ) => ( True )
  • BoringVault.balanceOf( 0x9f7CdD854Fa94B2696d39ae8992a1284daa38dfA ) => ( 173000 )
  • BoringVault.allowance( 0x9f7CdD854Fa94B2696d39ae8992a1284daa38dfA, 0x3b4aCd8879fb60586cCd74bC2F831A4C5E7DbBf8 ) => ( 173000 )
  • AccountantWithRateProviders.STATICCALL( )
  • AccountantWithRateProviders.getRateInQuoteSafe( quote=0x8236a87084f8B84306f72007F36F2618A5634494 ) => ( rateInQuote=101033481 )
    • TransparentUpgradeableProxy.STATICCALL( )
      • LBTC.DELEGATECALL( )
        safeUpdateAtomicRequest[AtomicQueue (ln:223)]
        File 1 of 6: AtomicQueue
        // SPDX-License-Identifier: Apache-2.0
        pragma solidity 0.8.21;
        import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";
        import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";
        import {ERC20} from "@solmate/tokens/ERC20.sol";
        import {ReentrancyGuard} from "@solmate/utils/ReentrancyGuard.sol";
        import {IAtomicSolver} from "./IAtomicSolver.sol";
        import {AccountantWithRateProviders} from "src/base/Roles/AccountantWithRateProviders.sol";
        import {IPausable} from "src/interfaces/IPausable.sol";
        import {Auth, Authority} from "@solmate/auth/Auth.sol";
        /**
         * @title AtomicQueue
         * @notice Allows users to create `AtomicRequests` that specify an ERC20 asset to `offer`
         *         and an ERC20 asset to `want` in return.
         * @notice Making atomic requests where the exchange rate between offer and want is not
         *         relatively stable is effectively the same as placing a limit order between
         *         those assets, so requests can be filled at a rate worse than the current market rate.
         * @notice It is possible for a user to make multiple requests that use the same offer asset.
         *         If this is done it is important that the user has approved the queue to spend the
         *         total amount of assets aggregated from all their requests, and to also have enough
         *         `offer` asset to cover the aggregate total request of `offerAmount`.
         * @author crispymangoes
         */
        contract AtomicQueue is Auth, ReentrancyGuard, IPausable {
            using SafeTransferLib for ERC20;
            using FixedPointMathLib for uint256;
            // ========================================= STRUCTS =========================================
            /**
             * @notice Stores request information needed to fulfill a users atomic request.
             * @param deadline unix timestamp for when request is no longer valid
             * @param atomicPrice the price in terms of `want` asset the user wants their `offer` assets "sold" at
             * @dev atomicPrice MUST be in terms of `want` asset decimals.
             * @param offerAmount the amount of `offer` asset the user wants converted to `want` asset
             * @param inSolve bool used during solves to prevent duplicate users, and to prevent redoing multiple checks
             */
            struct AtomicRequest {
                uint64 deadline; // deadline to fulfill request
                uint88 atomicPrice; // In terms of want asset decimals
                uint96 offerAmount; // The amount of offer asset the user wants to sell.
                bool inSolve; // Indicates whether this user is currently having their request fulfilled.
            }
            /**
             * @notice Used in `viewSolveMetaData` helper function to return data in a clean struct.
             * @param user the address of the user
             * @param flags 8 bits indicating the state of the user only the first 4 bits are used XXXX0000
             *              Either all flags are false(user is solvable) or only 1 is true(an error occurred).
             *              From right to left
             *              - 0: indicates user deadline has passed.
             *              - 1: indicates user request has zero offer amount.
             *              - 2: indicates user does not have enough offer asset in wallet.
             *              - 3: indicates user has not given AtomicQueue approval.
             * @param assetsToOffer the amount of offer asset to solve
             * @param assetsForWant the amount of assets users want for their offer assets
             */
            struct SolveMetaData {
                address user;
                uint8 flags;
                uint256 assetsToOffer;
                uint256 assetsForWant;
            }
            /**
             * @notice Used in `viewVerboseSolveMetaData` helper function to return data in a clean struct.
             * @param user the address of the user
             * @param deadlineExceeded indicates if the user has passed their deadline
             * @param zeroOfferAmount indicates if the user has a zero offer amount
             * @param insufficientOfferBalance indicates if the user has insufficient offer balance
             * @param insufficientOfferAllowance indicates if the user has insufficient offer allowance
             * @param assetsToOffer the amount of offer asset to solve
             * @param assetsForWant the amount of assets users want for their offer assets
             */
            struct VerboseSolveMetaData {
                address user;
                bool deadlineExceeded;
                bool zeroOfferAmount;
                bool insufficientOfferBalance;
                bool insufficientOfferAllowance;
                uint256 assetsToOffer;
                uint256 assetsForWant;
            }
            // ========================================= CONSTANTS =========================================
            /**
             * @notice When using safeUpdateAtomicRequest, this is the max discount that can be applied.
             */
            uint256 public constant MAX_DISCOUNT = 0.01e6;
            // ========================================= GLOBAL STATE =========================================
            /**
             * @notice Maps user address to offer asset to want asset to a AtomicRequest struct.
             */
            mapping(address => mapping(ERC20 => mapping(ERC20 => AtomicRequest))) public userAtomicRequest;
            /**
             * @notice Used to pause calls to `updateAtomicRequest` and `solve`.
             */
            bool public isPaused;
            //============================== ERRORS ===============================
            error AtomicQueue__UserRepeated(address user);
            error AtomicQueue__RequestDeadlineExceeded(address user);
            error AtomicQueue__UserNotInSolve(address user);
            error AtomicQueue__ZeroOfferAmount(address user);
            error AtomicQueue__SafeRequestOfferAmountGreaterThanOfferBalance(uint256 offerAmount, uint256 offerBalance);
            error AtomicQueue__SafeRequestDeadlineExceeded(uint256 deadline);
            error AtomicQueue__SafeRequestInsufficientOfferAllowance(uint256 offerAmount, uint256 offerAllowance);
            error AtomicQueue__SafeRequestOfferAmountZero();
            error AtomicQueue__SafeRequestDiscountTooLarge();
            error AtomicQueue__SafeRequestAccountantOfferMismatch();
            error AtomicQueue__SafeRequestCannotCastToUint88();
            error AtomicQueue__Paused();
            //============================== EVENTS ===============================
            /**
             * @notice Emitted when `updateAtomicRequest` is called.
             */
            event AtomicRequestUpdated(
                address indexed user,
                address indexed offerToken,
                address indexed wantToken,
                uint256 amount,
                uint256 deadline,
                uint256 minPrice,
                uint256 timestamp
            );
            /**
             * @notice Emitted when `solve` exchanges a users offer asset for their want asset.
             */
            event AtomicRequestFulfilled(
                address indexed user,
                address indexed offerToken,
                address indexed wantToken,
                uint256 offerAmountSpent,
                uint256 wantAmountReceived,
                uint256 timestamp
            );
            /**
             * @notice Emitted when the contract is paused.
             */
            event Paused();
            /**
             * @notice Emitted when the contract is unpaused.
             */
            event Unpaused();
            //============================== IMMUTABLES ===============================
            constructor(address _owner, Authority _auth) Auth(_owner, _auth) {}
            // ========================================= ADMIN FUNCTIONS =========================================
            /**
             * @notice Pause this contract, which prevents future calls to `updateExchangeRate`, and any safe rate
             *         calls will revert.
             * @dev Callable by MULTISIG_ROLE.
             */
            function pause() external requiresAuth {
                isPaused = true;
                emit Paused();
            }
            /**
             * @notice Unpause this contract, which allows future calls to `updateExchangeRate`, and any safe rate
             *         calls will stop reverting.
             * @dev Callable by MULTISIG_ROLE.
             */
            function unpause() external requiresAuth {
                isPaused = false;
                emit Unpaused();
            }
            //============================== USER FUNCTIONS ===============================
            /**
             * @notice Get a users Atomic Request.
             * @param user the address of the user to get the request for
             * @param offer the ERC0 token they want to exchange for the want
             * @param want the ERC20 token they want in exchange for the offer
             */
            function getUserAtomicRequest(address user, ERC20 offer, ERC20 want) external view returns (AtomicRequest memory) {
                return userAtomicRequest[user][offer][want];
            }
            /**
             * @notice Helper function that returns either
             *         true: Withdraw request is valid.
             *         false: Withdraw request is not valid.
             * @dev It is possible for a withdraw request to return false from this function, but using the
             *      request in `updateAtomicRequest` will succeed, but solvers will not be able to include
             *      the user in `solve` unless some other state is changed.
             * @param offer the ERC0 token they want to exchange for the want
             * @param user the address of the user making the request
             * @param userRequest the request struct to validate
             */
            function isAtomicRequestValid(ERC20 offer, address user, AtomicRequest calldata userRequest)
                external
                view
                returns (bool)
            {
                // Validate amount.
                if (userRequest.offerAmount > offer.balanceOf(user)) return false;
                // Validate deadline.
                if (block.timestamp > userRequest.deadline) return false;
                // Validate approval.
                if (offer.allowance(user, address(this)) < userRequest.offerAmount) return false;
                // Validate offerAmount is nonzero.
                if (userRequest.offerAmount == 0) return false;
                // Validate atomicPrice is nonzero.
                if (userRequest.atomicPrice == 0) return false;
                return true;
            }
            /**
             * @notice Allows user to add/update their withdraw request.
             * @notice It is possible for a withdraw request with a zero atomicPrice to be made, and solved.
             *         If this happens, users will be selling their shares for no assets in return.
             *         To determine a safe atomicPrice, share.previewRedeem should be used to get
             *         a good share price, then the user can lower it from there to make their request fill faster.
             * @param offer the ERC20 token the user is offering in exchange for the want
             * @param want the ERC20 token the user wants in exchange for offer
             * @param userRequest the users request
             */
            function updateAtomicRequest(ERC20 offer, ERC20 want, AtomicRequest memory userRequest)
                external
                nonReentrant
                requiresAuth
            {
                _updateAtomicRequest(offer, want, userRequest);
            }
            /**
             * @notice Mostly identical to `updateAtomicRequest` but with additional checks to ensure the request is safe.
             * @notice Adds in accountant and discount to calculate a safe atomicPrice.
             * @dev This function will completely ignore the provided atomic price and calculate a new one based off the
             *      the accountant rate in quote and the discount provided.
             * @param accountant the accountant to use to get the rate in quote
             * @param discount the discount to apply to the rate in quote
             */
            function safeUpdateAtomicRequest(
                ERC20 offer,
                ERC20 want,
                AtomicRequest memory userRequest,
                AccountantWithRateProviders accountant,
                uint256 discount
            ) external nonReentrant requiresAuth {
                // Validate amount.
                uint256 offerBalance = offer.balanceOf(msg.sender);
                if (userRequest.offerAmount > offerBalance) {
                    revert AtomicQueue__SafeRequestOfferAmountGreaterThanOfferBalance(userRequest.offerAmount, offerBalance);
                }
                // Validate deadline.
                if (block.timestamp > userRequest.deadline) {
                    revert AtomicQueue__SafeRequestDeadlineExceeded(userRequest.deadline);
                }
                // Validate approval.
                uint256 offerAllowance = offer.allowance(msg.sender, address(this));
                if (offerAllowance < userRequest.offerAmount) {
                    revert AtomicQueue__SafeRequestInsufficientOfferAllowance(userRequest.offerAmount, offerAllowance);
                }
                // Validate offerAmount is nonzero.
                if (userRequest.offerAmount == 0) revert AtomicQueue__SafeRequestOfferAmountZero();
                // Calculate atomic price.
                if (discount > MAX_DISCOUNT) revert AtomicQueue__SafeRequestDiscountTooLarge();
                if (address(offer) != address(accountant.vault())) revert AtomicQueue__SafeRequestAccountantOfferMismatch();
                uint256 safeRate = accountant.getRateInQuoteSafe(want);
                uint256 safeAtomicPrice = safeRate.mulDivDown(1e6 - discount, 1e6);
                if (safeAtomicPrice > type(uint88).max) revert AtomicQueue__SafeRequestCannotCastToUint88();
                userRequest.atomicPrice = uint88(safeAtomicPrice);
                _updateAtomicRequest(offer, want, userRequest);
            }
            //============================== SOLVER FUNCTIONS ===============================
            /**
             * @notice Called by solvers in order to exchange offer asset for want asset.
             * @notice Solvers are optimistically transferred the offer asset, then are required to
             *         approve this contract to spend enough of want assets to cover all requests.
             * @dev It is very likely `solve` TXs will be front run if broadcasted to public mem pools,
             *      so solvers should use private mem pools.
             * @param offer the ERC20 offer token to solve for
             * @param want the ERC20 want token to solve for
             * @param users an array of user addresses to solve for
             * @param runData extra data that is passed back to solver when `finishSolve` is called
             * @param solver the address to make `finishSolve` callback to
             */
            function solve(ERC20 offer, ERC20 want, address[] calldata users, bytes calldata runData, address solver)
                external
                nonReentrant
                requiresAuth
            {
                if (isPaused) revert AtomicQueue__Paused();
                // Save offer asset decimals.
                uint8 offerDecimals = offer.decimals();
                uint256 assetsToOffer;
                uint256 assetsForWant;
                for (uint256 i; i < users.length; ++i) {
                    AtomicRequest storage request = userAtomicRequest[users[i]][offer][want];
                    if (request.inSolve) revert AtomicQueue__UserRepeated(users[i]);
                    if (block.timestamp > request.deadline) revert AtomicQueue__RequestDeadlineExceeded(users[i]);
                    if (request.offerAmount == 0) revert AtomicQueue__ZeroOfferAmount(users[i]);
                    // User gets whatever their atomic price * offerAmount is.
                    assetsForWant += _calculateAssetAmount(request.offerAmount, request.atomicPrice, offerDecimals);
                    // If all checks above passed, the users request is valid and should be fulfilled.
                    assetsToOffer += request.offerAmount;
                    request.inSolve = true;
                    // Transfer shares from user to solver.
                    offer.safeTransferFrom(users[i], solver, request.offerAmount);
                }
                IAtomicSolver(solver).finishSolve(runData, msg.sender, offer, want, assetsToOffer, assetsForWant);
                for (uint256 i; i < users.length; ++i) {
                    AtomicRequest storage request = userAtomicRequest[users[i]][offer][want];
                    if (request.inSolve) {
                        // We know that the minimum price and deadline arguments are satisfied since this can only be true if they were.
                        // Send user their share of assets.
                        uint256 assetsToUser = _calculateAssetAmount(request.offerAmount, request.atomicPrice, offerDecimals);
                        want.safeTransferFrom(solver, users[i], assetsToUser);
                        emit AtomicRequestFulfilled(
                            users[i], address(offer), address(want), request.offerAmount, assetsToUser, block.timestamp
                        );
                        // Set shares to withdraw to 0.
                        request.offerAmount = 0;
                        request.inSolve = false;
                    } else {
                        revert AtomicQueue__UserNotInSolve(users[i]);
                    }
                }
            }
            /**
             * @notice Helper function solvers can use to determine if users are solvable, and the required amounts to do so.
             * @notice Repeated users are not accounted for in this setup, so if solvers have repeat users in their `users`
             *         array the results can be wrong.
             * @dev Since a user can have multiple requests with the same offer asset but different want asset, it is
             *      possible for `viewSolveMetaData` to report no errors, but for a solve to fail, if any solves were done
             *      between the time `viewSolveMetaData` and before `solve` is called.
             * @param offer the ERC20 offer token to check for solvability
             * @param want the ERC20 want token to check for solvability
             * @param users an array of user addresses to check for solvability
             */
            function viewSolveMetaData(ERC20 offer, ERC20 want, address[] calldata users)
                external
                view
                returns (SolveMetaData[] memory metaData, uint256 totalAssetsForWant, uint256 totalAssetsToOffer)
            {
                // Save offer asset decimals.
                uint8 offerDecimals = offer.decimals();
                // Setup meta data.
                metaData = new SolveMetaData[](users.length);
                for (uint256 i; i < users.length; ++i) {
                    AtomicRequest memory request = userAtomicRequest[users[i]][offer][want];
                    metaData[i].user = users[i];
                    if (block.timestamp > request.deadline) {
                        metaData[i].flags |= uint8(1);
                    }
                    if (request.offerAmount == 0) {
                        metaData[i].flags |= uint8(1) << 1;
                    }
                    if (offer.balanceOf(users[i]) < request.offerAmount) {
                        metaData[i].flags |= uint8(1) << 2;
                    }
                    if (offer.allowance(users[i], address(this)) < request.offerAmount) {
                        metaData[i].flags |= uint8(1) << 3;
                    }
                    metaData[i].assetsToOffer = request.offerAmount;
                    // User gets whatever their execution share price is.
                    uint256 userAssets = _calculateAssetAmount(request.offerAmount, request.atomicPrice, offerDecimals);
                    metaData[i].assetsForWant = userAssets;
                    // If flags is zero, no errors occurred.
                    if (metaData[i].flags == 0) {
                        totalAssetsForWant += userAssets;
                        totalAssetsToOffer += request.offerAmount;
                    }
                }
            }
            /**
             * @notice Helper function solvers can use to determine if users are solvable, and the required amounts to do so.
             * @notice Repeated users are not accounted for in this setup, so if solvers have repeat users in their `users`
             *         array the results can be wrong.
             * @dev Since a user can have multiple requests with the same offer asset but different want asset, it is
             *      possible for `viewSolveMetaData` to report no errors, but for a solve to fail, if any solves were done
             *      between the time `viewSolveMetaData` and before `solve` is called.
             * @param offer the ERC20 offer token to check for solvability
             * @param want the ERC20 want token to check for solvability
             * @param users an array of user addresses to check for solvability
             */
            function viewVerboseSolveMetaData(ERC20 offer, ERC20 want, address[] calldata users)
                external
                view
                returns (VerboseSolveMetaData[] memory metaData, uint256 totalAssetsForWant, uint256 totalAssetsToOffer)
            {
                // Save offer asset decimals.
                uint8 offerDecimals = offer.decimals();
                // Setup meta data.
                metaData = new VerboseSolveMetaData[](users.length);
                for (uint256 i; i < users.length; ++i) {
                    AtomicRequest memory request = userAtomicRequest[users[i]][offer][want];
                    metaData[i].user = users[i];
                    if (block.timestamp > request.deadline) {
                        metaData[i].deadlineExceeded = true;
                    }
                    if (request.offerAmount == 0) {
                        metaData[i].zeroOfferAmount = true;
                    }
                    if (offer.balanceOf(users[i]) < request.offerAmount) {
                        metaData[i].insufficientOfferBalance = true;
                    }
                    if (offer.allowance(users[i], address(this)) < request.offerAmount) {
                        metaData[i].insufficientOfferAllowance = true;
                    }
                    metaData[i].assetsToOffer = request.offerAmount;
                    // User gets whatever their execution share price is.
                    uint256 userAssets = _calculateAssetAmount(request.offerAmount, request.atomicPrice, offerDecimals);
                    metaData[i].assetsForWant = userAssets;
                    // If flags is zero, no errors occurred.
                    if (
                        !metaData[i].deadlineExceeded && !metaData[i].zeroOfferAmount && !metaData[i].insufficientOfferBalance
                            && !metaData[i].insufficientOfferAllowance
                    ) {
                        totalAssetsForWant += userAssets;
                        totalAssetsToOffer += request.offerAmount;
                    }
                }
            }
            //============================== INTERNAL FUNCTIONS ===============================
            /**
             * @notice Allows user to add/update their withdraw request.
             * @notice It is possible for a withdraw request with a zero atomicPrice to be made, and solved.
             *         If this happens, users will be selling their shares for no assets in return.
             *         To determine a safe atomicPrice, share.previewRedeem should be used to get
             *         a good share price, then the user can lower it from there to make their request fill faster.
             * @param offer the ERC20 token the user is offering in exchange for the want
             * @param want the ERC20 token the user wants in exchange for offer
             * @param userRequest the users request
             */
            function _updateAtomicRequest(ERC20 offer, ERC20 want, AtomicRequest memory userRequest) internal {
                if (isPaused) revert AtomicQueue__Paused();
                AtomicRequest storage request = userAtomicRequest[msg.sender][offer][want];
                request.deadline = userRequest.deadline;
                request.atomicPrice = userRequest.atomicPrice;
                request.offerAmount = userRequest.offerAmount;
                // Emit full amount user has.
                emit AtomicRequestUpdated(
                    msg.sender,
                    address(offer),
                    address(want),
                    userRequest.offerAmount,
                    userRequest.deadline,
                    userRequest.atomicPrice,
                    block.timestamp
                );
            }
            /**
             * @notice Helper function to calculate the amount of want assets a users wants in exchange for
             *         `offerAmount` of offer asset.
             */
            function _calculateAssetAmount(uint256 offerAmount, uint256 atomicPrice, uint8 offerDecimals)
                internal
                pure
                returns (uint256)
            {
                return atomicPrice.mulDivDown(offerAmount, 10 ** offerDecimals);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Arithmetic library with operations for fixed-point numbers.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
        /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
        library FixedPointMathLib {
            /*//////////////////////////////////////////////////////////////
                            SIMPLIFIED FIXED POINT OPERATIONS
            //////////////////////////////////////////////////////////////*/
            uint256 internal constant MAX_UINT256 = 2**256 - 1;
            uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
            function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
            }
            function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
            }
            function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
            }
            function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
            }
            /*//////////////////////////////////////////////////////////////
                            LOW LEVEL FIXED POINT OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function mulDivDown(
                uint256 x,
                uint256 y,
                uint256 denominator
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                    if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                        revert(0, 0)
                    }
                    // Divide x * y by the denominator.
                    z := div(mul(x, y), denominator)
                }
            }
            function mulDivUp(
                uint256 x,
                uint256 y,
                uint256 denominator
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                    if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                        revert(0, 0)
                    }
                    // If x * y modulo the denominator is strictly greater than 0,
                    // 1 is added to round up the division of x * y by the denominator.
                    z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
                }
            }
            function rpow(
                uint256 x,
                uint256 n,
                uint256 scalar
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    switch x
                    case 0 {
                        switch n
                        case 0 {
                            // 0 ** 0 = 1
                            z := scalar
                        }
                        default {
                            // 0 ** n = 0
                            z := 0
                        }
                    }
                    default {
                        switch mod(n, 2)
                        case 0 {
                            // If n is even, store scalar in z for now.
                            z := scalar
                        }
                        default {
                            // If n is odd, store x in z for now.
                            z := x
                        }
                        // Shifting right by 1 is like dividing by 2.
                        let half := shr(1, scalar)
                        for {
                            // Shift n right by 1 before looping to halve it.
                            n := shr(1, n)
                        } n {
                            // Shift n right by 1 each iteration to halve it.
                            n := shr(1, n)
                        } {
                            // Revert immediately if x ** 2 would overflow.
                            // Equivalent to iszero(eq(div(xx, x), x)) here.
                            if shr(128, x) {
                                revert(0, 0)
                            }
                            // Store x squared.
                            let xx := mul(x, x)
                            // Round to the nearest number.
                            let xxRound := add(xx, half)
                            // Revert if xx + half overflowed.
                            if lt(xxRound, xx) {
                                revert(0, 0)
                            }
                            // Set x to scaled xxRound.
                            x := div(xxRound, scalar)
                            // If n is even:
                            if mod(n, 2) {
                                // Compute z * x.
                                let zx := mul(z, x)
                                // If z * x overflowed:
                                if iszero(eq(div(zx, x), z)) {
                                    // Revert if x is non-zero.
                                    if iszero(iszero(x)) {
                                        revert(0, 0)
                                    }
                                }
                                // Round to the nearest number.
                                let zxRound := add(zx, half)
                                // Revert if zx + half overflowed.
                                if lt(zxRound, zx) {
                                    revert(0, 0)
                                }
                                // Return properly scaled zxRound.
                                z := div(zxRound, scalar)
                            }
                        }
                    }
                }
            }
            /*//////////////////////////////////////////////////////////////
                                GENERAL NUMBER UTILITIES
            //////////////////////////////////////////////////////////////*/
            function sqrt(uint256 x) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    let y := x // We start y at x, which will help us make our initial estimate.
                    z := 181 // The "correct" value is 1, but this saves a multiplication later.
                    // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
                    // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
                    // We check y >= 2^(k + 8) but shift right by k bits
                    // each branch to ensure that if x >= 256, then y >= 256.
                    if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                        y := shr(128, y)
                        z := shl(64, z)
                    }
                    if iszero(lt(y, 0x1000000000000000000)) {
                        y := shr(64, y)
                        z := shl(32, z)
                    }
                    if iszero(lt(y, 0x10000000000)) {
                        y := shr(32, y)
                        z := shl(16, z)
                    }
                    if iszero(lt(y, 0x1000000)) {
                        y := shr(16, y)
                        z := shl(8, z)
                    }
                    // Goal was to get z*z*y within a small factor of x. More iterations could
                    // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
                    // We ensured y >= 256 so that the relative difference between y and y+1 is small.
                    // That's not possible if x < 256 but we can just verify those cases exhaustively.
                    // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
                    // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
                    // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
                    // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
                    // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
                    // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
                    // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
                    // There is no overflow risk here since y < 2^136 after the first branch above.
                    z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
                    // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    // If x+1 is a perfect square, the Babylonian method cycles between
                    // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
                    // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
                    // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
                    // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
                    z := sub(z, lt(div(x, z), z))
                }
            }
            function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Mod x by y. Note this will return
                    // 0 instead of reverting if y is zero.
                    z := mod(x, y)
                }
            }
            function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Divide x by y. Note this will return
                    // 0 instead of reverting if y is zero.
                    r := div(x, y)
                }
            }
            function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Add 1 to x * y if x % y > 0. Note this will
                    // return 0 instead of reverting if y is zero.
                    z := add(gt(mod(x, y), 0), div(x, y))
                }
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        import {ERC20} from "../tokens/ERC20.sol";
        /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
        /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
        /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
        library SafeTransferLib {
            /*//////////////////////////////////////////////////////////////
                                     ETH OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function safeTransferETH(address to, uint256 amount) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Transfer the ETH and store if it succeeded or not.
                    success := call(gas(), to, amount, 0, 0, 0, 0)
                }
                require(success, "ETH_TRANSFER_FAILED");
            }
            /*//////////////////////////////////////////////////////////////
                                    ERC20 OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function safeTransferFrom(
                ERC20 token,
                address from,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
                    mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
                    )
                }
                require(success, "TRANSFER_FROM_FAILED");
            }
            function safeTransfer(
                ERC20 token,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
                    )
                }
                require(success, "TRANSFER_FAILED");
            }
            function safeApprove(
                ERC20 token,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
                    )
                }
                require(success, "APPROVE_FAILED");
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
        /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
        /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
        abstract contract ERC20 {
            /*//////////////////////////////////////////////////////////////
                                         EVENTS
            //////////////////////////////////////////////////////////////*/
            event Transfer(address indexed from, address indexed to, uint256 amount);
            event Approval(address indexed owner, address indexed spender, uint256 amount);
            /*//////////////////////////////////////////////////////////////
                                    METADATA STORAGE
            //////////////////////////////////////////////////////////////*/
            string public name;
            string public symbol;
            uint8 public immutable decimals;
            /*//////////////////////////////////////////////////////////////
                                      ERC20 STORAGE
            //////////////////////////////////////////////////////////////*/
            uint256 public totalSupply;
            mapping(address => uint256) public balanceOf;
            mapping(address => mapping(address => uint256)) public allowance;
            /*//////////////////////////////////////////////////////////////
                                    EIP-2612 STORAGE
            //////////////////////////////////////////////////////////////*/
            uint256 internal immutable INITIAL_CHAIN_ID;
            bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
            mapping(address => uint256) public nonces;
            /*//////////////////////////////////////////////////////////////
                                       CONSTRUCTOR
            //////////////////////////////////////////////////////////////*/
            constructor(
                string memory _name,
                string memory _symbol,
                uint8 _decimals
            ) {
                name = _name;
                symbol = _symbol;
                decimals = _decimals;
                INITIAL_CHAIN_ID = block.chainid;
                INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
            }
            /*//////////////////////////////////////////////////////////////
                                       ERC20 LOGIC
            //////////////////////////////////////////////////////////////*/
            function approve(address spender, uint256 amount) public virtual returns (bool) {
                allowance[msg.sender][spender] = amount;
                emit Approval(msg.sender, spender, amount);
                return true;
            }
            function transfer(address to, uint256 amount) public virtual returns (bool) {
                balanceOf[msg.sender] -= amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(msg.sender, to, amount);
                return true;
            }
            function transferFrom(
                address from,
                address to,
                uint256 amount
            ) public virtual returns (bool) {
                uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
                if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
                balanceOf[from] -= amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(from, to, amount);
                return true;
            }
            /*//////////////////////////////////////////////////////////////
                                     EIP-2612 LOGIC
            //////////////////////////////////////////////////////////////*/
            function permit(
                address owner,
                address spender,
                uint256 value,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) public virtual {
                require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
                // Unchecked because the only math done is incrementing
                // the owner's nonce which cannot realistically overflow.
                unchecked {
                    address recoveredAddress = ecrecover(
                        keccak256(
                            abi.encodePacked(
                                "\\x19\\x01",
                                DOMAIN_SEPARATOR(),
                                keccak256(
                                    abi.encode(
                                        keccak256(
                                            "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                        ),
                                        owner,
                                        spender,
                                        value,
                                        nonces[owner]++,
                                        deadline
                                    )
                                )
                            )
                        ),
                        v,
                        r,
                        s
                    );
                    require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
                    allowance[recoveredAddress][spender] = value;
                }
                emit Approval(owner, spender, value);
            }
            function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
                return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
            }
            function computeDomainSeparator() internal view virtual returns (bytes32) {
                return
                    keccak256(
                        abi.encode(
                            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                            keccak256(bytes(name)),
                            keccak256("1"),
                            block.chainid,
                            address(this)
                        )
                    );
            }
            /*//////////////////////////////////////////////////////////////
                                INTERNAL MINT/BURN LOGIC
            //////////////////////////////////////////////////////////////*/
            function _mint(address to, uint256 amount) internal virtual {
                totalSupply += amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(address(0), to, amount);
            }
            function _burn(address from, uint256 amount) internal virtual {
                balanceOf[from] -= amount;
                // Cannot underflow because a user's balance
                // will never be larger than the total supply.
                unchecked {
                    totalSupply -= amount;
                }
                emit Transfer(from, address(0), amount);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Gas optimized reentrancy protection for smart contracts.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
        /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
        abstract contract ReentrancyGuard {
            uint256 private locked = 1;
            modifier nonReentrant() virtual {
                require(locked == 1, "REENTRANCY");
                locked = 2;
                _;
                locked = 1;
            }
        }
        // SPDX-License-Identifier: GPL-2.0-or-later
        pragma solidity >=0.8.0;
        import { ERC20 } from "@solmate/tokens/ERC20.sol";
        interface IAtomicSolver {
            /**
             * @notice This function must be implemented in order for an address to be a `solver`
             *         for the AtomicQueue
             * @param runData arbitrary bytes data that is dependent on how each solver is setup
             *        it could contain swap data, or flash loan data, etc..
             * @param initiator the address that initiated a solve
             * @param offer the ERC20 asset sent to the solver
             * @param want the ERC20 asset the solver must approve the queue for
             * @param assetsToOffer the amount of `offer` sent to the solver
             * @param assetsForWant the amount of `want` the solver must approve the queue for
             */
            function finishSolve(
                bytes calldata runData,
                address initiator,
                ERC20 offer,
                ERC20 want,
                uint256 assetsToOffer,
                uint256 assetsForWant
            ) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";
        import {IRateProvider} from "src/interfaces/IRateProvider.sol";
        import {ERC20} from "@solmate/tokens/ERC20.sol";
        import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";
        import {BoringVault} from "src/base/BoringVault.sol";
        import {Auth, Authority} from "@solmate/auth/Auth.sol";
        import {IPausable} from "src/interfaces/IPausable.sol";
        contract AccountantWithRateProviders is Auth, IRateProvider, IPausable {
            using FixedPointMathLib for uint256;
            using SafeTransferLib for ERC20;
            // ========================================= STRUCTS =========================================
            /**
             * @param payoutAddress the address `claimFees` sends fees to
             * @param highwaterMark the highest value of the BoringVault's share price
             * @param feesOwedInBase total pending fees owed in terms of base
             * @param totalSharesLastUpdate total amount of shares the last exchange rate update
             * @param exchangeRate the current exchange rate in terms of base
             * @param allowedExchangeRateChangeUpper the max allowed change to exchange rate from an update
             * @param allowedExchangeRateChangeLower the min allowed change to exchange rate from an update
             * @param lastUpdateTimestamp the block timestamp of the last exchange rate update
             * @param isPaused whether or not this contract is paused
             * @param minimumUpdateDelayInSeconds the minimum amount of time that must pass between
             *        exchange rate updates, such that the update won't trigger the contract to be paused
             * @param managementFee the management fee
             * @param performanceFee the performance fee
             */
            struct AccountantState {
                address payoutAddress;
                uint96 highwaterMark;
                uint128 feesOwedInBase;
                uint128 totalSharesLastUpdate;
                uint96 exchangeRate;
                uint16 allowedExchangeRateChangeUpper;
                uint16 allowedExchangeRateChangeLower;
                uint64 lastUpdateTimestamp;
                bool isPaused;
                uint24 minimumUpdateDelayInSeconds;
                uint16 managementFee;
                uint16 performanceFee;
            }
            /**
             * @param isPeggedToBase whether or not the asset is 1:1 with the base asset
             * @param rateProvider the rate provider for this asset if `isPeggedToBase` is false
             */
            struct RateProviderData {
                bool isPeggedToBase;
                IRateProvider rateProvider;
            }
            // ========================================= STATE =========================================
            /**
             * @notice Store the accountant state in 3 packed slots.
             */
            AccountantState public accountantState;
            /**
             * @notice Maps ERC20s to their RateProviderData.
             */
            mapping(ERC20 => RateProviderData) public rateProviderData;
            //============================== ERRORS ===============================
            error AccountantWithRateProviders__UpperBoundTooSmall();
            error AccountantWithRateProviders__LowerBoundTooLarge();
            error AccountantWithRateProviders__ManagementFeeTooLarge();
            error AccountantWithRateProviders__PerformanceFeeTooLarge();
            error AccountantWithRateProviders__Paused();
            error AccountantWithRateProviders__ZeroFeesOwed();
            error AccountantWithRateProviders__OnlyCallableByBoringVault();
            error AccountantWithRateProviders__UpdateDelayTooLarge();
            error AccountantWithRateProviders__ExchangeRateAboveHighwaterMark();
            //============================== EVENTS ===============================
            event Paused();
            event Unpaused();
            event DelayInSecondsUpdated(uint24 oldDelay, uint24 newDelay);
            event UpperBoundUpdated(uint16 oldBound, uint16 newBound);
            event LowerBoundUpdated(uint16 oldBound, uint16 newBound);
            event ManagementFeeUpdated(uint16 oldFee, uint16 newFee);
            event PerformanceFeeUpdated(uint16 oldFee, uint16 newFee);
            event PayoutAddressUpdated(address oldPayout, address newPayout);
            event RateProviderUpdated(address asset, bool isPegged, address rateProvider);
            event ExchangeRateUpdated(uint96 oldRate, uint96 newRate, uint64 currentTime);
            event FeesClaimed(address indexed feeAsset, uint256 amount);
            event HighwaterMarkReset();
            //============================== IMMUTABLES ===============================
            /**
             * @notice The base asset rates are provided in.
             */
            ERC20 public immutable base;
            /**
             * @notice The decimals rates are provided in.
             */
            uint8 public immutable decimals;
            /**
             * @notice The BoringVault this accountant is working with.
             *         Used to determine share supply for fee calculation.
             */
            BoringVault public immutable vault;
            /**
             * @notice One share of the BoringVault.
             */
            uint256 internal immutable ONE_SHARE;
            constructor(
                address _owner,
                address _vault,
                address payoutAddress,
                uint96 startingExchangeRate,
                address _base,
                uint16 allowedExchangeRateChangeUpper,
                uint16 allowedExchangeRateChangeLower,
                uint24 minimumUpdateDelayInSeconds,
                uint16 managementFee,
                uint16 performanceFee
            ) Auth(_owner, Authority(address(0))) {
                base = ERC20(_base);
                decimals = ERC20(_base).decimals();
                vault = BoringVault(payable(_vault));
                ONE_SHARE = 10 ** vault.decimals();
                accountantState = AccountantState({
                    payoutAddress: payoutAddress,
                    highwaterMark: startingExchangeRate,
                    feesOwedInBase: 0,
                    totalSharesLastUpdate: uint128(vault.totalSupply()),
                    exchangeRate: startingExchangeRate,
                    allowedExchangeRateChangeUpper: allowedExchangeRateChangeUpper,
                    allowedExchangeRateChangeLower: allowedExchangeRateChangeLower,
                    lastUpdateTimestamp: uint64(block.timestamp),
                    isPaused: false,
                    minimumUpdateDelayInSeconds: minimumUpdateDelayInSeconds,
                    managementFee: managementFee,
                    performanceFee: performanceFee
                });
            }
            // ========================================= ADMIN FUNCTIONS =========================================
            /**
             * @notice Pause this contract, which prevents future calls to `updateExchangeRate`, and any safe rate
             *         calls will revert.
             * @dev Callable by MULTISIG_ROLE.
             */
            function pause() external requiresAuth {
                accountantState.isPaused = true;
                emit Paused();
            }
            /**
             * @notice Unpause this contract, which allows future calls to `updateExchangeRate`, and any safe rate
             *         calls will stop reverting.
             * @dev Callable by MULTISIG_ROLE.
             */
            function unpause() external requiresAuth {
                accountantState.isPaused = false;
                emit Unpaused();
            }
            /**
             * @notice Update the minimum time delay between `updateExchangeRate` calls.
             * @dev There are no input requirements, as it is possible the admin would want
             *      the exchange rate updated as frequently as needed.
             * @dev Callable by OWNER_ROLE.
             */
            function updateDelay(uint24 minimumUpdateDelayInSeconds) external requiresAuth {
                if (minimumUpdateDelayInSeconds > 14 days) revert AccountantWithRateProviders__UpdateDelayTooLarge();
                uint24 oldDelay = accountantState.minimumUpdateDelayInSeconds;
                accountantState.minimumUpdateDelayInSeconds = minimumUpdateDelayInSeconds;
                emit DelayInSecondsUpdated(oldDelay, minimumUpdateDelayInSeconds);
            }
            /**
             * @notice Update the allowed upper bound change of exchange rate between `updateExchangeRateCalls`.
             * @dev Callable by OWNER_ROLE.
             */
            function updateUpper(uint16 allowedExchangeRateChangeUpper) external requiresAuth {
                if (allowedExchangeRateChangeUpper < 1e4) revert AccountantWithRateProviders__UpperBoundTooSmall();
                uint16 oldBound = accountantState.allowedExchangeRateChangeUpper;
                accountantState.allowedExchangeRateChangeUpper = allowedExchangeRateChangeUpper;
                emit UpperBoundUpdated(oldBound, allowedExchangeRateChangeUpper);
            }
            /**
             * @notice Update the allowed lower bound change of exchange rate between `updateExchangeRateCalls`.
             * @dev Callable by OWNER_ROLE.
             */
            function updateLower(uint16 allowedExchangeRateChangeLower) external requiresAuth {
                if (allowedExchangeRateChangeLower > 1e4) revert AccountantWithRateProviders__LowerBoundTooLarge();
                uint16 oldBound = accountantState.allowedExchangeRateChangeLower;
                accountantState.allowedExchangeRateChangeLower = allowedExchangeRateChangeLower;
                emit LowerBoundUpdated(oldBound, allowedExchangeRateChangeLower);
            }
            /**
             * @notice Update the management fee to a new value.
             * @dev Callable by OWNER_ROLE.
             */
            function updateManagementFee(uint16 managementFee) external requiresAuth {
                if (managementFee > 0.2e4) revert AccountantWithRateProviders__ManagementFeeTooLarge();
                uint16 oldFee = accountantState.managementFee;
                accountantState.managementFee = managementFee;
                emit ManagementFeeUpdated(oldFee, managementFee);
            }
            /**
             * @notice Update the performance fee to a new value.
             * @dev Callable by OWNER_ROLE.
             */
            function updatePerformanceFee(uint16 performanceFee) external requiresAuth {
                if (performanceFee > 0.5e4) revert AccountantWithRateProviders__PerformanceFeeTooLarge();
                uint16 oldFee = accountantState.performanceFee;
                accountantState.performanceFee = performanceFee;
                emit PerformanceFeeUpdated(oldFee, performanceFee);
            }
            /**
             * @notice Update the payout address fees are sent to.
             * @dev Callable by OWNER_ROLE.
             */
            function updatePayoutAddress(address payoutAddress) external requiresAuth {
                address oldPayout = accountantState.payoutAddress;
                accountantState.payoutAddress = payoutAddress;
                emit PayoutAddressUpdated(oldPayout, payoutAddress);
            }
            /**
             * @notice Update the rate provider data for a specific `asset`.
             * @dev Rate providers must return rates in terms of `base` or
             * an asset pegged to base and they must use the same decimals
             * as `asset`.
             * @dev Callable by OWNER_ROLE.
             */
            function setRateProviderData(ERC20 asset, bool isPeggedToBase, address rateProvider) external requiresAuth {
                rateProviderData[asset] =
                    RateProviderData({isPeggedToBase: isPeggedToBase, rateProvider: IRateProvider(rateProvider)});
                emit RateProviderUpdated(address(asset), isPeggedToBase, rateProvider);
            }
            /**
             * @notice Reset the highwater mark to the current exchange rate.
             * @dev Callable by OWNER_ROLE.
             */
            function resetHighwaterMark() external requiresAuth {
                AccountantState storage state = accountantState;
                if (state.exchangeRate > state.highwaterMark) {
                    revert AccountantWithRateProviders__ExchangeRateAboveHighwaterMark();
                }
                uint64 currentTime = uint64(block.timestamp);
                uint256 currentTotalShares = vault.totalSupply();
                _calculateFeesOwed(state, state.exchangeRate, state.exchangeRate, currentTotalShares, currentTime);
                state.totalSharesLastUpdate = uint128(currentTotalShares);
                state.highwaterMark = accountantState.exchangeRate;
                state.lastUpdateTimestamp = currentTime;
                emit HighwaterMarkReset();
            }
            // ========================================= UPDATE EXCHANGE RATE/FEES FUNCTIONS =========================================
            /**
             * @notice Updates this contract exchangeRate.
             * @dev If new exchange rate is outside of accepted bounds, or if not enough time has passed, this
             *      will pause the contract, and this function will NOT calculate fees owed.
             * @dev Callable by UPDATE_EXCHANGE_RATE_ROLE.
             */
            function updateExchangeRate(uint96 newExchangeRate) external requiresAuth {
                AccountantState storage state = accountantState;
                if (state.isPaused) revert AccountantWithRateProviders__Paused();
                uint64 currentTime = uint64(block.timestamp);
                uint256 currentExchangeRate = state.exchangeRate;
                uint256 currentTotalShares = vault.totalSupply();
                if (
                    currentTime < state.lastUpdateTimestamp + state.minimumUpdateDelayInSeconds
                        || newExchangeRate > currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeUpper, 1e4)
                        || newExchangeRate < currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeLower, 1e4)
                ) {
                    // Instead of reverting, pause the contract. This way the exchange rate updater is able to update the exchange rate
                    // to a better value, and pause it.
                    state.isPaused = true;
                } else {
                    _calculateFeesOwed(state, newExchangeRate, currentExchangeRate, currentTotalShares, currentTime);
                }
                state.exchangeRate = newExchangeRate;
                state.totalSharesLastUpdate = uint128(currentTotalShares);
                state.lastUpdateTimestamp = currentTime;
                emit ExchangeRateUpdated(uint96(currentExchangeRate), newExchangeRate, currentTime);
            }
            /**
             * @notice Claim pending fees.
             * @dev This function must be called by the BoringVault.
             * @dev This function will lose precision if the exchange rate
             *      decimals is greater than the feeAsset's decimals.
             */
            function claimFees(ERC20 feeAsset) external {
                if (msg.sender != address(vault)) revert AccountantWithRateProviders__OnlyCallableByBoringVault();
                AccountantState storage state = accountantState;
                if (state.isPaused) revert AccountantWithRateProviders__Paused();
                if (state.feesOwedInBase == 0) revert AccountantWithRateProviders__ZeroFeesOwed();
                // Determine amount of fees owed in feeAsset.
                uint256 feesOwedInFeeAsset;
                RateProviderData memory data = rateProviderData[feeAsset];
                if (address(feeAsset) == address(base)) {
                    feesOwedInFeeAsset = state.feesOwedInBase;
                } else {
                    uint8 feeAssetDecimals = ERC20(feeAsset).decimals();
                    uint256 feesOwedInBaseUsingFeeAssetDecimals =
                        changeDecimals(state.feesOwedInBase, decimals, feeAssetDecimals);
                    if (data.isPeggedToBase) {
                        feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals;
                    } else {
                        uint256 rate = data.rateProvider.getRate();
                        feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals.mulDivDown(10 ** feeAssetDecimals, rate);
                    }
                }
                // Zero out fees owed.
                state.feesOwedInBase = 0;
                // Transfer fee asset to payout address.
                feeAsset.safeTransferFrom(msg.sender, state.payoutAddress, feesOwedInFeeAsset);
                emit FeesClaimed(address(feeAsset), feesOwedInFeeAsset);
            }
            // ========================================= RATE FUNCTIONS =========================================
            /**
             * @notice Get this BoringVault's current rate in the base.
             */
            function getRate() public view returns (uint256 rate) {
                rate = accountantState.exchangeRate;
            }
            /**
             * @notice Get this BoringVault's current rate in the base.
             * @dev Revert if paused.
             */
            function getRateSafe() external view returns (uint256 rate) {
                if (accountantState.isPaused) revert AccountantWithRateProviders__Paused();
                rate = getRate();
            }
            /**
             * @notice Get this BoringVault's current rate in the provided quote.
             * @dev `quote` must have its RateProviderData set, else this will revert.
             * @dev This function will lose precision if the exchange rate
             *      decimals is greater than the quote's decimals.
             */
            function getRateInQuote(ERC20 quote) public view returns (uint256 rateInQuote) {
                if (address(quote) == address(base)) {
                    rateInQuote = accountantState.exchangeRate;
                } else {
                    RateProviderData memory data = rateProviderData[quote];
                    uint8 quoteDecimals = ERC20(quote).decimals();
                    uint256 exchangeRateInQuoteDecimals = changeDecimals(accountantState.exchangeRate, decimals, quoteDecimals);
                    if (data.isPeggedToBase) {
                        rateInQuote = exchangeRateInQuoteDecimals;
                    } else {
                        uint256 quoteRate = data.rateProvider.getRate();
                        uint256 oneQuote = 10 ** quoteDecimals;
                        rateInQuote = oneQuote.mulDivDown(exchangeRateInQuoteDecimals, quoteRate);
                    }
                }
            }
            /**
             * @notice Get this BoringVault's current rate in the provided quote.
             * @dev `quote` must have its RateProviderData set, else this will revert.
             * @dev Revert if paused.
             */
            function getRateInQuoteSafe(ERC20 quote) external view returns (uint256 rateInQuote) {
                if (accountantState.isPaused) revert AccountantWithRateProviders__Paused();
                rateInQuote = getRateInQuote(quote);
            }
            // ========================================= INTERNAL HELPER FUNCTIONS =========================================
            /**
             * @notice Used to change the decimals of precision used for an amount.
             */
            function changeDecimals(uint256 amount, uint8 fromDecimals, uint8 toDecimals) internal pure returns (uint256) {
                if (fromDecimals == toDecimals) {
                    return amount;
                } else if (fromDecimals < toDecimals) {
                    return amount * 10 ** (toDecimals - fromDecimals);
                } else {
                    return amount / 10 ** (fromDecimals - toDecimals);
                }
            }
            /**
             * @notice Calculate fees owed in base.
             * @dev This function will update the highwater mark if the new exchange rate is higher.
             */
            function _calculateFeesOwed(
                AccountantState storage state,
                uint96 newExchangeRate,
                uint256 currentExchangeRate,
                uint256 currentTotalShares,
                uint64 currentTime
            ) internal {
                // Only update fees if we are not paused.
                // Update fee accounting.
                uint256 shareSupplyToUse = currentTotalShares;
                // Use the minimum between current total supply and total supply for last update.
                if (state.totalSharesLastUpdate < shareSupplyToUse) {
                    shareSupplyToUse = state.totalSharesLastUpdate;
                }
                // Determine management fees owned.
                uint256 timeDelta = currentTime - state.lastUpdateTimestamp;
                uint256 minimumAssets = newExchangeRate > currentExchangeRate
                    ? shareSupplyToUse.mulDivDown(currentExchangeRate, ONE_SHARE)
                    : shareSupplyToUse.mulDivDown(newExchangeRate, ONE_SHARE);
                uint256 managementFeesAnnual = minimumAssets.mulDivDown(state.managementFee, 1e4);
                uint256 newFeesOwedInBase = managementFeesAnnual.mulDivDown(timeDelta, 365 days);
                // Account for performance fees.
                if (newExchangeRate > state.highwaterMark) {
                    if (state.performanceFee > 0) {
                        uint256 changeInExchangeRate = newExchangeRate - state.highwaterMark;
                        uint256 yieldEarned = changeInExchangeRate.mulDivDown(shareSupplyToUse, ONE_SHARE);
                        uint256 performanceFeesOwedInBase = yieldEarned.mulDivDown(state.performanceFee, 1e4);
                        newFeesOwedInBase += performanceFeesOwedInBase;
                    }
                    // Always update the highwater mark if the new exchange rate is higher.
                    // This way if we are not iniitiall taking performance fees, we can start taking them
                    // without back charging them on past performance.
                    state.highwaterMark = newExchangeRate;
                }
                state.feesOwedInBase += uint128(newFeesOwedInBase);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        interface IPausable {
            function pause() external;
            function unpause() external;
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
        abstract contract Auth {
            event OwnershipTransferred(address indexed user, address indexed newOwner);
            event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
            address public owner;
            Authority public authority;
            constructor(address _owner, Authority _authority) {
                owner = _owner;
                authority = _authority;
                emit OwnershipTransferred(msg.sender, _owner);
                emit AuthorityUpdated(msg.sender, _authority);
            }
            modifier requiresAuth() virtual {
                require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED");
                _;
            }
            function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
                Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
                // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
                // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
                return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
            }
            function setAuthority(Authority newAuthority) public virtual {
                // We check if the caller is the owner first because we want to ensure they can
                // always swap out the authority even if it's reverting or using up a lot of gas.
                require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
                authority = newAuthority;
                emit AuthorityUpdated(msg.sender, newAuthority);
            }
            function transferOwnership(address newOwner) public virtual requiresAuth {
                owner = newOwner;
                emit OwnershipTransferred(msg.sender, newOwner);
            }
        }
        /// @notice A generic interface for a contract which provides authorization data to an Auth instance.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
        interface Authority {
            function canCall(
                address user,
                address target,
                bytes4 functionSig
            ) external view returns (bool);
        }
        // SPDX-License-Identifier: GPL-3.0-or-later
        // This program is free software: you can redistribute it and/or modify
        // it under the terms of the GNU General Public License as published by
        // the Free Software Foundation, either version 3 of the License, or
        // (at your option) any later version.
        // This program is distributed in the hope that it will be useful,
        // but WITHOUT ANY WARRANTY; without even the implied warranty of
        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        // GNU General Public License for more details.
        // You should have received a copy of the GNU General Public License
        // along with this program.  If not, see <http://www.gnu.org/licenses/>.
        pragma solidity ^0.8.0;
        interface IRateProvider {
            function getRate() external view returns (uint256);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        import {Address} from "@openzeppelin/contracts/utils/Address.sol";
        import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
        import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
        import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";
        import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";
        import {ERC20} from "@solmate/tokens/ERC20.sol";
        import {BeforeTransferHook} from "src/interfaces/BeforeTransferHook.sol";
        import {Auth, Authority} from "@solmate/auth/Auth.sol";
        contract BoringVault is ERC20, Auth, ERC721Holder, ERC1155Holder {
            using Address for address;
            using SafeTransferLib for ERC20;
            using FixedPointMathLib for uint256;
            // ========================================= STATE =========================================
            /**
             * @notice Contract responsbile for implementing `beforeTransfer`.
             */
            BeforeTransferHook public hook;
            //============================== EVENTS ===============================
            event Enter(address indexed from, address indexed asset, uint256 amount, address indexed to, uint256 shares);
            event Exit(address indexed to, address indexed asset, uint256 amount, address indexed from, uint256 shares);
            //============================== CONSTRUCTOR ===============================
            constructor(address _owner, string memory _name, string memory _symbol, uint8 _decimals)
                ERC20(_name, _symbol, _decimals)
                Auth(_owner, Authority(address(0)))
            {}
            //============================== MANAGE ===============================
            /**
             * @notice Allows manager to make an arbitrary function call from this contract.
             * @dev Callable by MANAGER_ROLE.
             */
            function manage(address target, bytes calldata data, uint256 value)
                external
                requiresAuth
                returns (bytes memory result)
            {
                result = target.functionCallWithValue(data, value);
            }
            /**
             * @notice Allows manager to make arbitrary function calls from this contract.
             * @dev Callable by MANAGER_ROLE.
             */
            function manage(address[] calldata targets, bytes[] calldata data, uint256[] calldata values)
                external
                requiresAuth
                returns (bytes[] memory results)
            {
                uint256 targetsLength = targets.length;
                results = new bytes[](targetsLength);
                for (uint256 i; i < targetsLength; ++i) {
                    results[i] = targets[i].functionCallWithValue(data[i], values[i]);
                }
            }
            //============================== ENTER ===============================
            /**
             * @notice Allows minter to mint shares, in exchange for assets.
             * @dev If assetAmount is zero, no assets are transferred in.
             * @dev Callable by MINTER_ROLE.
             */
            function enter(address from, ERC20 asset, uint256 assetAmount, address to, uint256 shareAmount)
                external
                requiresAuth
            {
                // Transfer assets in
                if (assetAmount > 0) asset.safeTransferFrom(from, address(this), assetAmount);
                // Mint shares.
                _mint(to, shareAmount);
                emit Enter(from, address(asset), assetAmount, to, shareAmount);
            }
            //============================== EXIT ===============================
            /**
             * @notice Allows burner to burn shares, in exchange for assets.
             * @dev If assetAmount is zero, no assets are transferred out.
             * @dev Callable by BURNER_ROLE.
             */
            function exit(address to, ERC20 asset, uint256 assetAmount, address from, uint256 shareAmount)
                external
                requiresAuth
            {
                // Burn shares.
                _burn(from, shareAmount);
                // Transfer assets out.
                if (assetAmount > 0) asset.safeTransfer(to, assetAmount);
                emit Exit(to, address(asset), assetAmount, from, shareAmount);
            }
            //============================== BEFORE TRANSFER HOOK ===============================
            /**
             * @notice Sets the share locker.
             * @notice If set to zero address, the share locker logic is disabled.
             * @dev Callable by OWNER_ROLE.
             */
            function setBeforeTransferHook(address _hook) external requiresAuth {
                hook = BeforeTransferHook(_hook);
            }
            /**
             * @notice Call `beforeTransferHook` passing in `from` `to`, and `msg.sender`.
             */
            function _callBeforeTransfer(address from, address to) internal view {
                if (address(hook) != address(0)) hook.beforeTransfer(from, to, msg.sender);
            }
            function transfer(address to, uint256 amount) public override returns (bool) {
                _callBeforeTransfer(msg.sender, to);
                return super.transfer(to, amount);
            }
            function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
                _callBeforeTransfer(from, to);
                return super.transferFrom(from, to, amount);
            }
            //============================== RECEIVE ===============================
            receive() external payable {}
        }
        // 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.0) (token/ERC721/utils/ERC721Holder.sol)
        pragma solidity ^0.8.20;
        import {IERC721Receiver} from "../IERC721Receiver.sol";
        /**
         * @dev Implementation of the {IERC721Receiver} interface.
         *
         * Accepts all token transfers.
         * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or
         * {IERC721-setApprovalForAll}.
         */
        abstract contract ERC721Holder is IERC721Receiver {
            /**
             * @dev See {IERC721Receiver-onERC721Received}.
             *
             * Always returns `IERC721Receiver.onERC721Received.selector`.
             */
            function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
                return this.onERC721Received.selector;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/utils/ERC1155Holder.sol)
        pragma solidity ^0.8.20;
        import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol";
        import {IERC1155Receiver} from "../IERC1155Receiver.sol";
        /**
         * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC1155 tokens.
         *
         * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
         * stuck.
         */
        abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
            /**
             * @dev See {IERC165-supportsInterface}.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
                return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
            }
            function onERC1155Received(
                address,
                address,
                uint256,
                uint256,
                bytes memory
            ) public virtual override returns (bytes4) {
                return this.onERC1155Received.selector;
            }
            function onERC1155BatchReceived(
                address,
                address,
                uint256[] memory,
                uint256[] memory,
                bytes memory
            ) public virtual override returns (bytes4) {
                return this.onERC1155BatchReceived.selector;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        interface BeforeTransferHook {
            function beforeTransfer(address from, address to, address operator) external view;
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
        pragma solidity ^0.8.20;
        /**
         * @title ERC721 token receiver interface
         * @dev Interface for any contract that wants to support safeTransfers
         * from ERC721 asset contracts.
         */
        interface IERC721Receiver {
            /**
             * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
             * by `operator` from `from`, this function is called.
             *
             * It must return its Solidity selector to confirm the token transfer.
             * If any other value is returned or the interface is not implemented by the recipient, the transfer will be
             * reverted.
             *
             * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
             */
            function onERC721Received(
                address operator,
                address from,
                uint256 tokenId,
                bytes calldata data
            ) external returns (bytes4);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
        pragma solidity ^0.8.20;
        import {IERC165} from "./IERC165.sol";
        /**
         * @dev Implementation of the {IERC165} interface.
         *
         * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
         * for the additional interface id that will be supported. For example:
         *
         * ```solidity
         * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
         *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
         * }
         * ```
         */
        abstract contract ERC165 is IERC165 {
            /**
             * @dev See {IERC165-supportsInterface}.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
                return interfaceId == type(IERC165).interfaceId;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol)
        pragma solidity ^0.8.20;
        import {IERC165} from "../../utils/introspection/IERC165.sol";
        /**
         * @dev Interface that must be implemented by smart contracts in order to receive
         * ERC-1155 token transfers.
         */
        interface IERC1155Receiver is IERC165 {
            /**
             * @dev Handles the receipt of a single ERC1155 token type. This function is
             * called at the end of a `safeTransferFrom` after the balance has been updated.
             *
             * NOTE: To accept the transfer, this must return
             * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
             * (i.e. 0xf23a6e61, or its own function selector).
             *
             * @param operator The address which initiated the transfer (i.e. msg.sender)
             * @param from The address which previously owned the token
             * @param id The ID of the token being transferred
             * @param value The amount of tokens being transferred
             * @param data Additional data with no specified format
             * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
             */
            function onERC1155Received(
                address operator,
                address from,
                uint256 id,
                uint256 value,
                bytes calldata data
            ) external returns (bytes4);
            /**
             * @dev Handles the receipt of a multiple ERC1155 token types. This function
             * is called at the end of a `safeBatchTransferFrom` after the balances have
             * been updated.
             *
             * NOTE: To accept the transfer(s), this must return
             * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
             * (i.e. 0xbc197c81, or its own function selector).
             *
             * @param operator The address which initiated the batch transfer (i.e. msg.sender)
             * @param from The address which previously owned the token
             * @param ids An array containing ids of each token being transferred (order and length must match values array)
             * @param values An array containing amounts of each token being transferred (order and length must match ids array)
             * @param data Additional data with no specified format
             * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
             */
            function onERC1155BatchReceived(
                address operator,
                address from,
                uint256[] calldata ids,
                uint256[] calldata values,
                bytes calldata data
            ) external returns (bytes4);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
        pragma solidity ^0.8.20;
        /**
         * @dev Interface of the ERC165 standard, as defined in the
         * https://eips.ethereum.org/EIPS/eip-165[EIP].
         *
         * Implementers can declare support of contract interfaces, which can then be
         * queried by others ({ERC165Checker}).
         *
         * For an implementation, see {ERC165}.
         */
        interface IERC165 {
            /**
             * @dev Returns true if this contract implements the interface defined by
             * `interfaceId`. See the corresponding
             * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
             * to learn more about how these ids are created.
             *
             * This function call must use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        

        File 2 of 6: RolesAuthority
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        import {Auth, Authority} from "../Auth.sol";
        /// @notice Role based Authority that supports up to 256 roles.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol)
        contract RolesAuthority is Auth, Authority {
            /*//////////////////////////////////////////////////////////////
                                         EVENTS
            //////////////////////////////////////////////////////////////*/
            event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled);
            event PublicCapabilityUpdated(address indexed target, bytes4 indexed functionSig, bool enabled);
            event RoleCapabilityUpdated(uint8 indexed role, address indexed target, bytes4 indexed functionSig, bool enabled);
            /*//////////////////////////////////////////////////////////////
                                       CONSTRUCTOR
            //////////////////////////////////////////////////////////////*/
            constructor(address _owner, Authority _authority) Auth(_owner, _authority) {}
            /*//////////////////////////////////////////////////////////////
                                    ROLE/USER STORAGE
            //////////////////////////////////////////////////////////////*/
            mapping(address => bytes32) public getUserRoles;
            mapping(address => mapping(bytes4 => bool)) public isCapabilityPublic;
            mapping(address => mapping(bytes4 => bytes32)) public getRolesWithCapability;
            function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) {
                return (uint256(getUserRoles[user]) >> role) & 1 != 0;
            }
            function doesRoleHaveCapability(
                uint8 role,
                address target,
                bytes4 functionSig
            ) public view virtual returns (bool) {
                return (uint256(getRolesWithCapability[target][functionSig]) >> role) & 1 != 0;
            }
            /*//////////////////////////////////////////////////////////////
                                   AUTHORIZATION LOGIC
            //////////////////////////////////////////////////////////////*/
            function canCall(
                address user,
                address target,
                bytes4 functionSig
            ) public view virtual override returns (bool) {
                return
                    isCapabilityPublic[target][functionSig] ||
                    bytes32(0) != getUserRoles[user] & getRolesWithCapability[target][functionSig];
            }
            /*//////////////////////////////////////////////////////////////
                           ROLE CAPABILITY CONFIGURATION LOGIC
            //////////////////////////////////////////////////////////////*/
            function setPublicCapability(
                address target,
                bytes4 functionSig,
                bool enabled
            ) public virtual requiresAuth {
                isCapabilityPublic[target][functionSig] = enabled;
                emit PublicCapabilityUpdated(target, functionSig, enabled);
            }
            function setRoleCapability(
                uint8 role,
                address target,
                bytes4 functionSig,
                bool enabled
            ) public virtual requiresAuth {
                if (enabled) {
                    getRolesWithCapability[target][functionSig] |= bytes32(1 << role);
                } else {
                    getRolesWithCapability[target][functionSig] &= ~bytes32(1 << role);
                }
                emit RoleCapabilityUpdated(role, target, functionSig, enabled);
            }
            /*//////////////////////////////////////////////////////////////
                               USER ROLE ASSIGNMENT LOGIC
            //////////////////////////////////////////////////////////////*/
            function setUserRole(
                address user,
                uint8 role,
                bool enabled
            ) public virtual requiresAuth {
                if (enabled) {
                    getUserRoles[user] |= bytes32(1 << role);
                } else {
                    getUserRoles[user] &= ~bytes32(1 << role);
                }
                emit UserRoleUpdated(user, role, enabled);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
        abstract contract Auth {
            event OwnershipTransferred(address indexed user, address indexed newOwner);
            event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
            address public owner;
            Authority public authority;
            constructor(address _owner, Authority _authority) {
                owner = _owner;
                authority = _authority;
                emit OwnershipTransferred(msg.sender, _owner);
                emit AuthorityUpdated(msg.sender, _authority);
            }
            modifier requiresAuth() virtual {
                require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED");
                _;
            }
            function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
                Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
                // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
                // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
                return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
            }
            function setAuthority(Authority newAuthority) public virtual {
                // We check if the caller is the owner first because we want to ensure they can
                // always swap out the authority even if it's reverting or using up a lot of gas.
                require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
                authority = newAuthority;
                emit AuthorityUpdated(msg.sender, newAuthority);
            }
            function transferOwnership(address newOwner) public virtual requiresAuth {
                owner = newOwner;
                emit OwnershipTransferred(msg.sender, newOwner);
            }
        }
        /// @notice A generic interface for a contract which provides authorization data to an Auth instance.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
        interface Authority {
            function canCall(
                address user,
                address target,
                bytes4 functionSig
            ) external view returns (bool);
        }
        

        File 3 of 6: BoringVault
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        import {Address} from "@openzeppelin/contracts/utils/Address.sol";
        import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
        import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
        import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";
        import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";
        import {ERC20} from "@solmate/tokens/ERC20.sol";
        import {BeforeTransferHook} from "src/interfaces/BeforeTransferHook.sol";
        import {Auth, Authority} from "@solmate/auth/Auth.sol";
        contract BoringVault is ERC20, Auth, ERC721Holder, ERC1155Holder {
            using Address for address;
            using SafeTransferLib for ERC20;
            using FixedPointMathLib for uint256;
            // ========================================= STATE =========================================
            /**
             * @notice Contract responsbile for implementing `beforeTransfer`.
             */
            BeforeTransferHook public hook;
            //============================== EVENTS ===============================
            event Enter(address indexed from, address indexed asset, uint256 amount, address indexed to, uint256 shares);
            event Exit(address indexed to, address indexed asset, uint256 amount, address indexed from, uint256 shares);
            //============================== CONSTRUCTOR ===============================
            constructor(address _owner, string memory _name, string memory _symbol, uint8 _decimals)
                ERC20(_name, _symbol, _decimals)
                Auth(_owner, Authority(address(0)))
            {}
            //============================== MANAGE ===============================
            /**
             * @notice Allows manager to make an arbitrary function call from this contract.
             * @dev Callable by MANAGER_ROLE.
             */
            function manage(address target, bytes calldata data, uint256 value)
                external
                requiresAuth
                returns (bytes memory result)
            {
                result = target.functionCallWithValue(data, value);
            }
            /**
             * @notice Allows manager to make arbitrary function calls from this contract.
             * @dev Callable by MANAGER_ROLE.
             */
            function manage(address[] calldata targets, bytes[] calldata data, uint256[] calldata values)
                external
                requiresAuth
                returns (bytes[] memory results)
            {
                uint256 targetsLength = targets.length;
                results = new bytes[](targetsLength);
                for (uint256 i; i < targetsLength; ++i) {
                    results[i] = targets[i].functionCallWithValue(data[i], values[i]);
                }
            }
            //============================== ENTER ===============================
            /**
             * @notice Allows minter to mint shares, in exchange for assets.
             * @dev If assetAmount is zero, no assets are transferred in.
             * @dev Callable by MINTER_ROLE.
             */
            function enter(address from, ERC20 asset, uint256 assetAmount, address to, uint256 shareAmount)
                external
                requiresAuth
            {
                // Transfer assets in
                if (assetAmount > 0) asset.safeTransferFrom(from, address(this), assetAmount);
                // Mint shares.
                _mint(to, shareAmount);
                emit Enter(from, address(asset), assetAmount, to, shareAmount);
            }
            //============================== EXIT ===============================
            /**
             * @notice Allows burner to burn shares, in exchange for assets.
             * @dev If assetAmount is zero, no assets are transferred out.
             * @dev Callable by BURNER_ROLE.
             */
            function exit(address to, ERC20 asset, uint256 assetAmount, address from, uint256 shareAmount)
                external
                requiresAuth
            {
                // Burn shares.
                _burn(from, shareAmount);
                // Transfer assets out.
                if (assetAmount > 0) asset.safeTransfer(to, assetAmount);
                emit Exit(to, address(asset), assetAmount, from, shareAmount);
            }
            //============================== BEFORE TRANSFER HOOK ===============================
            /**
             * @notice Sets the share locker.
             * @notice If set to zero address, the share locker logic is disabled.
             * @dev Callable by OWNER_ROLE.
             */
            function setBeforeTransferHook(address _hook) external requiresAuth {
                hook = BeforeTransferHook(_hook);
            }
            /**
             * @notice Call `beforeTransferHook` passing in `from` `to`, and `msg.sender`.
             */
            function _callBeforeTransfer(address from, address to) internal view {
                if (address(hook) != address(0)) hook.beforeTransfer(from, to, msg.sender);
            }
            function transfer(address to, uint256 amount) public override returns (bool) {
                _callBeforeTransfer(msg.sender, to);
                return super.transfer(to, amount);
            }
            function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
                _callBeforeTransfer(from, to);
                return super.transferFrom(from, to, amount);
            }
            //============================== RECEIVE ===============================
            receive() external payable {}
        }
        // 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.0) (token/ERC721/utils/ERC721Holder.sol)
        pragma solidity ^0.8.20;
        import {IERC721Receiver} from "../IERC721Receiver.sol";
        /**
         * @dev Implementation of the {IERC721Receiver} interface.
         *
         * Accepts all token transfers.
         * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or
         * {IERC721-setApprovalForAll}.
         */
        abstract contract ERC721Holder is IERC721Receiver {
            /**
             * @dev See {IERC721Receiver-onERC721Received}.
             *
             * Always returns `IERC721Receiver.onERC721Received.selector`.
             */
            function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
                return this.onERC721Received.selector;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/utils/ERC1155Holder.sol)
        pragma solidity ^0.8.20;
        import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol";
        import {IERC1155Receiver} from "../IERC1155Receiver.sol";
        /**
         * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC1155 tokens.
         *
         * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
         * stuck.
         */
        abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
            /**
             * @dev See {IERC165-supportsInterface}.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
                return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
            }
            function onERC1155Received(
                address,
                address,
                uint256,
                uint256,
                bytes memory
            ) public virtual override returns (bytes4) {
                return this.onERC1155Received.selector;
            }
            function onERC1155BatchReceived(
                address,
                address,
                uint256[] memory,
                uint256[] memory,
                bytes memory
            ) public virtual override returns (bytes4) {
                return this.onERC1155BatchReceived.selector;
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Arithmetic library with operations for fixed-point numbers.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
        /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
        library FixedPointMathLib {
            /*//////////////////////////////////////////////////////////////
                            SIMPLIFIED FIXED POINT OPERATIONS
            //////////////////////////////////////////////////////////////*/
            uint256 internal constant MAX_UINT256 = 2**256 - 1;
            uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
            function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
            }
            function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
            }
            function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
            }
            function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
            }
            /*//////////////////////////////////////////////////////////////
                            LOW LEVEL FIXED POINT OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function mulDivDown(
                uint256 x,
                uint256 y,
                uint256 denominator
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                    if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                        revert(0, 0)
                    }
                    // Divide x * y by the denominator.
                    z := div(mul(x, y), denominator)
                }
            }
            function mulDivUp(
                uint256 x,
                uint256 y,
                uint256 denominator
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                    if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                        revert(0, 0)
                    }
                    // If x * y modulo the denominator is strictly greater than 0,
                    // 1 is added to round up the division of x * y by the denominator.
                    z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
                }
            }
            function rpow(
                uint256 x,
                uint256 n,
                uint256 scalar
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    switch x
                    case 0 {
                        switch n
                        case 0 {
                            // 0 ** 0 = 1
                            z := scalar
                        }
                        default {
                            // 0 ** n = 0
                            z := 0
                        }
                    }
                    default {
                        switch mod(n, 2)
                        case 0 {
                            // If n is even, store scalar in z for now.
                            z := scalar
                        }
                        default {
                            // If n is odd, store x in z for now.
                            z := x
                        }
                        // Shifting right by 1 is like dividing by 2.
                        let half := shr(1, scalar)
                        for {
                            // Shift n right by 1 before looping to halve it.
                            n := shr(1, n)
                        } n {
                            // Shift n right by 1 each iteration to halve it.
                            n := shr(1, n)
                        } {
                            // Revert immediately if x ** 2 would overflow.
                            // Equivalent to iszero(eq(div(xx, x), x)) here.
                            if shr(128, x) {
                                revert(0, 0)
                            }
                            // Store x squared.
                            let xx := mul(x, x)
                            // Round to the nearest number.
                            let xxRound := add(xx, half)
                            // Revert if xx + half overflowed.
                            if lt(xxRound, xx) {
                                revert(0, 0)
                            }
                            // Set x to scaled xxRound.
                            x := div(xxRound, scalar)
                            // If n is even:
                            if mod(n, 2) {
                                // Compute z * x.
                                let zx := mul(z, x)
                                // If z * x overflowed:
                                if iszero(eq(div(zx, x), z)) {
                                    // Revert if x is non-zero.
                                    if iszero(iszero(x)) {
                                        revert(0, 0)
                                    }
                                }
                                // Round to the nearest number.
                                let zxRound := add(zx, half)
                                // Revert if zx + half overflowed.
                                if lt(zxRound, zx) {
                                    revert(0, 0)
                                }
                                // Return properly scaled zxRound.
                                z := div(zxRound, scalar)
                            }
                        }
                    }
                }
            }
            /*//////////////////////////////////////////////////////////////
                                GENERAL NUMBER UTILITIES
            //////////////////////////////////////////////////////////////*/
            function sqrt(uint256 x) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    let y := x // We start y at x, which will help us make our initial estimate.
                    z := 181 // The "correct" value is 1, but this saves a multiplication later.
                    // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
                    // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
                    // We check y >= 2^(k + 8) but shift right by k bits
                    // each branch to ensure that if x >= 256, then y >= 256.
                    if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                        y := shr(128, y)
                        z := shl(64, z)
                    }
                    if iszero(lt(y, 0x1000000000000000000)) {
                        y := shr(64, y)
                        z := shl(32, z)
                    }
                    if iszero(lt(y, 0x10000000000)) {
                        y := shr(32, y)
                        z := shl(16, z)
                    }
                    if iszero(lt(y, 0x1000000)) {
                        y := shr(16, y)
                        z := shl(8, z)
                    }
                    // Goal was to get z*z*y within a small factor of x. More iterations could
                    // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
                    // We ensured y >= 256 so that the relative difference between y and y+1 is small.
                    // That's not possible if x < 256 but we can just verify those cases exhaustively.
                    // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
                    // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
                    // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
                    // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
                    // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
                    // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
                    // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
                    // There is no overflow risk here since y < 2^136 after the first branch above.
                    z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
                    // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    // If x+1 is a perfect square, the Babylonian method cycles between
                    // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
                    // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
                    // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
                    // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
                    z := sub(z, lt(div(x, z), z))
                }
            }
            function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Mod x by y. Note this will return
                    // 0 instead of reverting if y is zero.
                    z := mod(x, y)
                }
            }
            function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Divide x by y. Note this will return
                    // 0 instead of reverting if y is zero.
                    r := div(x, y)
                }
            }
            function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Add 1 to x * y if x % y > 0. Note this will
                    // return 0 instead of reverting if y is zero.
                    z := add(gt(mod(x, y), 0), div(x, y))
                }
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        import {ERC20} from "../tokens/ERC20.sol";
        /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
        /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
        /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
        library SafeTransferLib {
            /*//////////////////////////////////////////////////////////////
                                     ETH OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function safeTransferETH(address to, uint256 amount) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Transfer the ETH and store if it succeeded or not.
                    success := call(gas(), to, amount, 0, 0, 0, 0)
                }
                require(success, "ETH_TRANSFER_FAILED");
            }
            /*//////////////////////////////////////////////////////////////
                                    ERC20 OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function safeTransferFrom(
                ERC20 token,
                address from,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
                    mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
                    )
                }
                require(success, "TRANSFER_FROM_FAILED");
            }
            function safeTransfer(
                ERC20 token,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
                    )
                }
                require(success, "TRANSFER_FAILED");
            }
            function safeApprove(
                ERC20 token,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
                    )
                }
                require(success, "APPROVE_FAILED");
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
        /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
        /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
        abstract contract ERC20 {
            /*//////////////////////////////////////////////////////////////
                                         EVENTS
            //////////////////////////////////////////////////////////////*/
            event Transfer(address indexed from, address indexed to, uint256 amount);
            event Approval(address indexed owner, address indexed spender, uint256 amount);
            /*//////////////////////////////////////////////////////////////
                                    METADATA STORAGE
            //////////////////////////////////////////////////////////////*/
            string public name;
            string public symbol;
            uint8 public immutable decimals;
            /*//////////////////////////////////////////////////////////////
                                      ERC20 STORAGE
            //////////////////////////////////////////////////////////////*/
            uint256 public totalSupply;
            mapping(address => uint256) public balanceOf;
            mapping(address => mapping(address => uint256)) public allowance;
            /*//////////////////////////////////////////////////////////////
                                    EIP-2612 STORAGE
            //////////////////////////////////////////////////////////////*/
            uint256 internal immutable INITIAL_CHAIN_ID;
            bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
            mapping(address => uint256) public nonces;
            /*//////////////////////////////////////////////////////////////
                                       CONSTRUCTOR
            //////////////////////////////////////////////////////////////*/
            constructor(
                string memory _name,
                string memory _symbol,
                uint8 _decimals
            ) {
                name = _name;
                symbol = _symbol;
                decimals = _decimals;
                INITIAL_CHAIN_ID = block.chainid;
                INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
            }
            /*//////////////////////////////////////////////////////////////
                                       ERC20 LOGIC
            //////////////////////////////////////////////////////////////*/
            function approve(address spender, uint256 amount) public virtual returns (bool) {
                allowance[msg.sender][spender] = amount;
                emit Approval(msg.sender, spender, amount);
                return true;
            }
            function transfer(address to, uint256 amount) public virtual returns (bool) {
                balanceOf[msg.sender] -= amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(msg.sender, to, amount);
                return true;
            }
            function transferFrom(
                address from,
                address to,
                uint256 amount
            ) public virtual returns (bool) {
                uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
                if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
                balanceOf[from] -= amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(from, to, amount);
                return true;
            }
            /*//////////////////////////////////////////////////////////////
                                     EIP-2612 LOGIC
            //////////////////////////////////////////////////////////////*/
            function permit(
                address owner,
                address spender,
                uint256 value,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) public virtual {
                require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
                // Unchecked because the only math done is incrementing
                // the owner's nonce which cannot realistically overflow.
                unchecked {
                    address recoveredAddress = ecrecover(
                        keccak256(
                            abi.encodePacked(
                                "\\x19\\x01",
                                DOMAIN_SEPARATOR(),
                                keccak256(
                                    abi.encode(
                                        keccak256(
                                            "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                        ),
                                        owner,
                                        spender,
                                        value,
                                        nonces[owner]++,
                                        deadline
                                    )
                                )
                            )
                        ),
                        v,
                        r,
                        s
                    );
                    require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
                    allowance[recoveredAddress][spender] = value;
                }
                emit Approval(owner, spender, value);
            }
            function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
                return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
            }
            function computeDomainSeparator() internal view virtual returns (bytes32) {
                return
                    keccak256(
                        abi.encode(
                            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                            keccak256(bytes(name)),
                            keccak256("1"),
                            block.chainid,
                            address(this)
                        )
                    );
            }
            /*//////////////////////////////////////////////////////////////
                                INTERNAL MINT/BURN LOGIC
            //////////////////////////////////////////////////////////////*/
            function _mint(address to, uint256 amount) internal virtual {
                totalSupply += amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(address(0), to, amount);
            }
            function _burn(address from, uint256 amount) internal virtual {
                balanceOf[from] -= amount;
                // Cannot underflow because a user's balance
                // will never be larger than the total supply.
                unchecked {
                    totalSupply -= amount;
                }
                emit Transfer(from, address(0), amount);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        interface BeforeTransferHook {
            function beforeTransfer(address from, address to, address operator) external view;
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
        abstract contract Auth {
            event OwnershipTransferred(address indexed user, address indexed newOwner);
            event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
            address public owner;
            Authority public authority;
            constructor(address _owner, Authority _authority) {
                owner = _owner;
                authority = _authority;
                emit OwnershipTransferred(msg.sender, _owner);
                emit AuthorityUpdated(msg.sender, _authority);
            }
            modifier requiresAuth() virtual {
                require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED");
                _;
            }
            function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
                Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
                // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
                // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
                return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
            }
            function setAuthority(Authority newAuthority) public virtual {
                // We check if the caller is the owner first because we want to ensure they can
                // always swap out the authority even if it's reverting or using up a lot of gas.
                require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
                authority = newAuthority;
                emit AuthorityUpdated(msg.sender, newAuthority);
            }
            function transferOwnership(address newOwner) public virtual requiresAuth {
                owner = newOwner;
                emit OwnershipTransferred(msg.sender, newOwner);
            }
        }
        /// @notice A generic interface for a contract which provides authorization data to an Auth instance.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
        interface Authority {
            function canCall(
                address user,
                address target,
                bytes4 functionSig
            ) external view returns (bool);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
        pragma solidity ^0.8.20;
        /**
         * @title ERC721 token receiver interface
         * @dev Interface for any contract that wants to support safeTransfers
         * from ERC721 asset contracts.
         */
        interface IERC721Receiver {
            /**
             * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
             * by `operator` from `from`, this function is called.
             *
             * It must return its Solidity selector to confirm the token transfer.
             * If any other value is returned or the interface is not implemented by the recipient, the transfer will be
             * reverted.
             *
             * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
             */
            function onERC721Received(
                address operator,
                address from,
                uint256 tokenId,
                bytes calldata data
            ) external returns (bytes4);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
        pragma solidity ^0.8.20;
        import {IERC165} from "./IERC165.sol";
        /**
         * @dev Implementation of the {IERC165} interface.
         *
         * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
         * for the additional interface id that will be supported. For example:
         *
         * ```solidity
         * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
         *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
         * }
         * ```
         */
        abstract contract ERC165 is IERC165 {
            /**
             * @dev See {IERC165-supportsInterface}.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
                return interfaceId == type(IERC165).interfaceId;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol)
        pragma solidity ^0.8.20;
        import {IERC165} from "../../utils/introspection/IERC165.sol";
        /**
         * @dev Interface that must be implemented by smart contracts in order to receive
         * ERC-1155 token transfers.
         */
        interface IERC1155Receiver is IERC165 {
            /**
             * @dev Handles the receipt of a single ERC1155 token type. This function is
             * called at the end of a `safeTransferFrom` after the balance has been updated.
             *
             * NOTE: To accept the transfer, this must return
             * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
             * (i.e. 0xf23a6e61, or its own function selector).
             *
             * @param operator The address which initiated the transfer (i.e. msg.sender)
             * @param from The address which previously owned the token
             * @param id The ID of the token being transferred
             * @param value The amount of tokens being transferred
             * @param data Additional data with no specified format
             * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
             */
            function onERC1155Received(
                address operator,
                address from,
                uint256 id,
                uint256 value,
                bytes calldata data
            ) external returns (bytes4);
            /**
             * @dev Handles the receipt of a multiple ERC1155 token types. This function
             * is called at the end of a `safeBatchTransferFrom` after the balances have
             * been updated.
             *
             * NOTE: To accept the transfer(s), this must return
             * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
             * (i.e. 0xbc197c81, or its own function selector).
             *
             * @param operator The address which initiated the batch transfer (i.e. msg.sender)
             * @param from The address which previously owned the token
             * @param ids An array containing ids of each token being transferred (order and length must match values array)
             * @param values An array containing amounts of each token being transferred (order and length must match ids array)
             * @param data Additional data with no specified format
             * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
             */
            function onERC1155BatchReceived(
                address operator,
                address from,
                uint256[] calldata ids,
                uint256[] calldata values,
                bytes calldata data
            ) external returns (bytes4);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
        pragma solidity ^0.8.20;
        /**
         * @dev Interface of the ERC165 standard, as defined in the
         * https://eips.ethereum.org/EIPS/eip-165[EIP].
         *
         * Implementers can declare support of contract interfaces, which can then be
         * queried by others ({ERC165Checker}).
         *
         * For an implementation, see {ERC165}.
         */
        interface IERC165 {
            /**
             * @dev Returns true if this contract implements the interface defined by
             * `interfaceId`. See the corresponding
             * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
             * to learn more about how these ids are created.
             *
             * This function call must use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        

        File 4 of 6: AccountantWithRateProviders
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";
        import {IRateProvider} from "src/interfaces/IRateProvider.sol";
        import {ERC20} from "@solmate/tokens/ERC20.sol";
        import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";
        import {BoringVault} from "src/base/BoringVault.sol";
        import {Auth, Authority} from "@solmate/auth/Auth.sol";
        contract AccountantWithRateProviders is Auth, IRateProvider {
            using FixedPointMathLib for uint256;
            using SafeTransferLib for ERC20;
            // ========================================= STRUCTS =========================================
            /**
             * @param payoutAddress the address `claimFees` sends fees to
             * @param highwaterMark the highest value of the BoringVault's share price
             * @param feesOwedInBase total pending fees owed in terms of base
             * @param totalSharesLastUpdate total amount of shares the last exchange rate update
             * @param exchangeRate the current exchange rate in terms of base
             * @param allowedExchangeRateChangeUpper the max allowed change to exchange rate from an update
             * @param allowedExchangeRateChangeLower the min allowed change to exchange rate from an update
             * @param lastUpdateTimestamp the block timestamp of the last exchange rate update
             * @param isPaused whether or not this contract is paused
             * @param minimumUpdateDelayInSeconds the minimum amount of time that must pass between
             *        exchange rate updates, such that the update won't trigger the contract to be paused
             * @param managementFee the management fee
             * @param performanceFee the performance fee
             */
            struct AccountantState {
                address payoutAddress;
                uint96 highwaterMark;
                uint128 feesOwedInBase;
                uint128 totalSharesLastUpdate;
                uint96 exchangeRate;
                uint16 allowedExchangeRateChangeUpper;
                uint16 allowedExchangeRateChangeLower;
                uint64 lastUpdateTimestamp;
                bool isPaused;
                uint24 minimumUpdateDelayInSeconds;
                uint16 managementFee;
                uint16 performanceFee;
            }
            /**
             * @param isPeggedToBase whether or not the asset is 1:1 with the base asset
             * @param rateProvider the rate provider for this asset if `isPeggedToBase` is false
             */
            struct RateProviderData {
                bool isPeggedToBase;
                IRateProvider rateProvider;
            }
            // ========================================= STATE =========================================
            /**
             * @notice Store the accountant state in 3 packed slots.
             */
            AccountantState public accountantState;
            /**
             * @notice Maps ERC20s to their RateProviderData.
             */
            mapping(ERC20 => RateProviderData) public rateProviderData;
            //============================== ERRORS ===============================
            error AccountantWithRateProviders__UpperBoundTooSmall();
            error AccountantWithRateProviders__LowerBoundTooLarge();
            error AccountantWithRateProviders__ManagementFeeTooLarge();
            error AccountantWithRateProviders__PerformanceFeeTooLarge();
            error AccountantWithRateProviders__Paused();
            error AccountantWithRateProviders__ZeroFeesOwed();
            error AccountantWithRateProviders__OnlyCallableByBoringVault();
            error AccountantWithRateProviders__UpdateDelayTooLarge();
            error AccountantWithRateProviders__ExchangeRateAboveHighwaterMark();
            //============================== EVENTS ===============================
            event Paused();
            event Unpaused();
            event DelayInSecondsUpdated(uint24 oldDelay, uint24 newDelay);
            event UpperBoundUpdated(uint16 oldBound, uint16 newBound);
            event LowerBoundUpdated(uint16 oldBound, uint16 newBound);
            event ManagementFeeUpdated(uint16 oldFee, uint16 newFee);
            event PerformanceFeeUpdated(uint16 oldFee, uint16 newFee);
            event PayoutAddressUpdated(address oldPayout, address newPayout);
            event RateProviderUpdated(address asset, bool isPegged, address rateProvider);
            event ExchangeRateUpdated(uint96 oldRate, uint96 newRate, uint64 currentTime);
            event FeesClaimed(address indexed feeAsset, uint256 amount);
            event HighwaterMarkReset();
            //============================== IMMUTABLES ===============================
            /**
             * @notice The base asset rates are provided in.
             */
            ERC20 public immutable base;
            /**
             * @notice The decimals rates are provided in.
             */
            uint8 public immutable decimals;
            /**
             * @notice The BoringVault this accountant is working with.
             *         Used to determine share supply for fee calculation.
             */
            BoringVault public immutable vault;
            /**
             * @notice One share of the BoringVault.
             */
            uint256 internal immutable ONE_SHARE;
            constructor(
                address _owner,
                address _vault,
                address payoutAddress,
                uint96 startingExchangeRate,
                address _base,
                uint16 allowedExchangeRateChangeUpper,
                uint16 allowedExchangeRateChangeLower,
                uint24 minimumUpdateDelayInSeconds,
                uint16 managementFee,
                uint16 performanceFee
            ) Auth(_owner, Authority(address(0))) {
                base = ERC20(_base);
                decimals = ERC20(_base).decimals();
                vault = BoringVault(payable(_vault));
                ONE_SHARE = 10 ** vault.decimals();
                accountantState = AccountantState({
                    payoutAddress: payoutAddress,
                    highwaterMark: startingExchangeRate,
                    feesOwedInBase: 0,
                    totalSharesLastUpdate: uint128(vault.totalSupply()),
                    exchangeRate: startingExchangeRate,
                    allowedExchangeRateChangeUpper: allowedExchangeRateChangeUpper,
                    allowedExchangeRateChangeLower: allowedExchangeRateChangeLower,
                    lastUpdateTimestamp: uint64(block.timestamp),
                    isPaused: false,
                    minimumUpdateDelayInSeconds: minimumUpdateDelayInSeconds,
                    managementFee: managementFee,
                    performanceFee: performanceFee
                });
            }
            // ========================================= ADMIN FUNCTIONS =========================================
            /**
             * @notice Pause this contract, which prevents future calls to `updateExchangeRate`, and any safe rate
             *         calls will revert.
             * @dev Callable by MULTISIG_ROLE.
             */
            function pause() external requiresAuth {
                accountantState.isPaused = true;
                emit Paused();
            }
            /**
             * @notice Unpause this contract, which allows future calls to `updateExchangeRate`, and any safe rate
             *         calls will stop reverting.
             * @dev Callable by MULTISIG_ROLE.
             */
            function unpause() external requiresAuth {
                accountantState.isPaused = false;
                emit Unpaused();
            }
            /**
             * @notice Update the minimum time delay between `updateExchangeRate` calls.
             * @dev There are no input requirements, as it is possible the admin would want
             *      the exchange rate updated as frequently as needed.
             * @dev Callable by OWNER_ROLE.
             */
            function updateDelay(uint24 minimumUpdateDelayInSeconds) external requiresAuth {
                if (minimumUpdateDelayInSeconds > 14 days) revert AccountantWithRateProviders__UpdateDelayTooLarge();
                uint24 oldDelay = accountantState.minimumUpdateDelayInSeconds;
                accountantState.minimumUpdateDelayInSeconds = minimumUpdateDelayInSeconds;
                emit DelayInSecondsUpdated(oldDelay, minimumUpdateDelayInSeconds);
            }
            /**
             * @notice Update the allowed upper bound change of exchange rate between `updateExchangeRateCalls`.
             * @dev Callable by OWNER_ROLE.
             */
            function updateUpper(uint16 allowedExchangeRateChangeUpper) external requiresAuth {
                if (allowedExchangeRateChangeUpper < 1e4) revert AccountantWithRateProviders__UpperBoundTooSmall();
                uint16 oldBound = accountantState.allowedExchangeRateChangeUpper;
                accountantState.allowedExchangeRateChangeUpper = allowedExchangeRateChangeUpper;
                emit UpperBoundUpdated(oldBound, allowedExchangeRateChangeUpper);
            }
            /**
             * @notice Update the allowed lower bound change of exchange rate between `updateExchangeRateCalls`.
             * @dev Callable by OWNER_ROLE.
             */
            function updateLower(uint16 allowedExchangeRateChangeLower) external requiresAuth {
                if (allowedExchangeRateChangeLower > 1e4) revert AccountantWithRateProviders__LowerBoundTooLarge();
                uint16 oldBound = accountantState.allowedExchangeRateChangeLower;
                accountantState.allowedExchangeRateChangeLower = allowedExchangeRateChangeLower;
                emit LowerBoundUpdated(oldBound, allowedExchangeRateChangeLower);
            }
            /**
             * @notice Update the management fee to a new value.
             * @dev Callable by OWNER_ROLE.
             */
            function updateManagementFee(uint16 managementFee) external requiresAuth {
                if (managementFee > 0.2e4) revert AccountantWithRateProviders__ManagementFeeTooLarge();
                uint16 oldFee = accountantState.managementFee;
                accountantState.managementFee = managementFee;
                emit ManagementFeeUpdated(oldFee, managementFee);
            }
            /**
             * @notice Update the performance fee to a new value.
             * @dev Callable by OWNER_ROLE.
             */
            function updatePerformanceFee(uint16 performanceFee) external requiresAuth {
                if (performanceFee > 0.5e4) revert AccountantWithRateProviders__PerformanceFeeTooLarge();
                uint16 oldFee = accountantState.performanceFee;
                accountantState.performanceFee = performanceFee;
                emit PerformanceFeeUpdated(oldFee, performanceFee);
            }
            /**
             * @notice Update the payout address fees are sent to.
             * @dev Callable by OWNER_ROLE.
             */
            function updatePayoutAddress(address payoutAddress) external requiresAuth {
                address oldPayout = accountantState.payoutAddress;
                accountantState.payoutAddress = payoutAddress;
                emit PayoutAddressUpdated(oldPayout, payoutAddress);
            }
            /**
             * @notice Update the rate provider data for a specific `asset`.
             * @dev Rate providers must return rates in terms of `base` or
             * an asset pegged to base and they must use the same decimals
             * as `asset`.
             * @dev Callable by OWNER_ROLE.
             */
            function setRateProviderData(ERC20 asset, bool isPeggedToBase, address rateProvider) external requiresAuth {
                rateProviderData[asset] =
                    RateProviderData({isPeggedToBase: isPeggedToBase, rateProvider: IRateProvider(rateProvider)});
                emit RateProviderUpdated(address(asset), isPeggedToBase, rateProvider);
            }
            /**
             * @notice Reset the highwater mark to the current exchange rate.
             * @dev Callable by OWNER_ROLE.
             */
            function resetHighwaterMark() external requiresAuth {
                AccountantState storage state = accountantState;
                if (state.exchangeRate > state.highwaterMark) {
                    revert AccountantWithRateProviders__ExchangeRateAboveHighwaterMark();
                }
                uint64 currentTime = uint64(block.timestamp);
                uint256 currentTotalShares = vault.totalSupply();
                _calculateFeesOwed(state, state.exchangeRate, state.exchangeRate, currentTotalShares, currentTime);
                state.totalSharesLastUpdate = uint128(currentTotalShares);
                state.highwaterMark = accountantState.exchangeRate;
                state.lastUpdateTimestamp = currentTime;
                emit HighwaterMarkReset();
            }
            // ========================================= UPDATE EXCHANGE RATE/FEES FUNCTIONS =========================================
            /**
             * @notice Updates this contract exchangeRate.
             * @dev If new exchange rate is outside of accepted bounds, or if not enough time has passed, this
             *      will pause the contract, and this function will NOT calculate fees owed.
             * @dev Callable by UPDATE_EXCHANGE_RATE_ROLE.
             */
            function updateExchangeRate(uint96 newExchangeRate) external requiresAuth {
                AccountantState storage state = accountantState;
                if (state.isPaused) revert AccountantWithRateProviders__Paused();
                uint64 currentTime = uint64(block.timestamp);
                uint256 currentExchangeRate = state.exchangeRate;
                uint256 currentTotalShares = vault.totalSupply();
                if (
                    currentTime < state.lastUpdateTimestamp + state.minimumUpdateDelayInSeconds
                        || newExchangeRate > currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeUpper, 1e4)
                        || newExchangeRate < currentExchangeRate.mulDivDown(state.allowedExchangeRateChangeLower, 1e4)
                ) {
                    // Instead of reverting, pause the contract. This way the exchange rate updater is able to update the exchange rate
                    // to a better value, and pause it.
                    state.isPaused = true;
                } else {
                    _calculateFeesOwed(state, newExchangeRate, currentExchangeRate, currentTotalShares, currentTime);
                }
                state.exchangeRate = newExchangeRate;
                state.totalSharesLastUpdate = uint128(currentTotalShares);
                state.lastUpdateTimestamp = currentTime;
                emit ExchangeRateUpdated(uint96(currentExchangeRate), newExchangeRate, currentTime);
            }
            /**
             * @notice Claim pending fees.
             * @dev This function must be called by the BoringVault.
             * @dev This function will lose precision if the exchange rate
             *      decimals is greater than the feeAsset's decimals.
             */
            function claimFees(ERC20 feeAsset) external {
                if (msg.sender != address(vault)) revert AccountantWithRateProviders__OnlyCallableByBoringVault();
                AccountantState storage state = accountantState;
                if (state.isPaused) revert AccountantWithRateProviders__Paused();
                if (state.feesOwedInBase == 0) revert AccountantWithRateProviders__ZeroFeesOwed();
                // Determine amount of fees owed in feeAsset.
                uint256 feesOwedInFeeAsset;
                RateProviderData memory data = rateProviderData[feeAsset];
                if (address(feeAsset) == address(base)) {
                    feesOwedInFeeAsset = state.feesOwedInBase;
                } else {
                    uint8 feeAssetDecimals = ERC20(feeAsset).decimals();
                    uint256 feesOwedInBaseUsingFeeAssetDecimals =
                        changeDecimals(state.feesOwedInBase, decimals, feeAssetDecimals);
                    if (data.isPeggedToBase) {
                        feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals;
                    } else {
                        uint256 rate = data.rateProvider.getRate();
                        feesOwedInFeeAsset = feesOwedInBaseUsingFeeAssetDecimals.mulDivDown(10 ** feeAssetDecimals, rate);
                    }
                }
                // Zero out fees owed.
                state.feesOwedInBase = 0;
                // Transfer fee asset to payout address.
                feeAsset.safeTransferFrom(msg.sender, state.payoutAddress, feesOwedInFeeAsset);
                emit FeesClaimed(address(feeAsset), feesOwedInFeeAsset);
            }
            // ========================================= RATE FUNCTIONS =========================================
            /**
             * @notice Get this BoringVault's current rate in the base.
             */
            function getRate() public view returns (uint256 rate) {
                rate = accountantState.exchangeRate;
            }
            /**
             * @notice Get this BoringVault's current rate in the base.
             * @dev Revert if paused.
             */
            function getRateSafe() external view returns (uint256 rate) {
                if (accountantState.isPaused) revert AccountantWithRateProviders__Paused();
                rate = getRate();
            }
            /**
             * @notice Get this BoringVault's current rate in the provided quote.
             * @dev `quote` must have its RateProviderData set, else this will revert.
             * @dev This function will lose precision if the exchange rate
             *      decimals is greater than the quote's decimals.
             */
            function getRateInQuote(ERC20 quote) public view returns (uint256 rateInQuote) {
                if (address(quote) == address(base)) {
                    rateInQuote = accountantState.exchangeRate;
                } else {
                    RateProviderData memory data = rateProviderData[quote];
                    uint8 quoteDecimals = ERC20(quote).decimals();
                    uint256 exchangeRateInQuoteDecimals = changeDecimals(accountantState.exchangeRate, decimals, quoteDecimals);
                    if (data.isPeggedToBase) {
                        rateInQuote = exchangeRateInQuoteDecimals;
                    } else {
                        uint256 quoteRate = data.rateProvider.getRate();
                        uint256 oneQuote = 10 ** quoteDecimals;
                        rateInQuote = oneQuote.mulDivDown(exchangeRateInQuoteDecimals, quoteRate);
                    }
                }
            }
            /**
             * @notice Get this BoringVault's current rate in the provided quote.
             * @dev `quote` must have its RateProviderData set, else this will revert.
             * @dev Revert if paused.
             */
            function getRateInQuoteSafe(ERC20 quote) external view returns (uint256 rateInQuote) {
                if (accountantState.isPaused) revert AccountantWithRateProviders__Paused();
                rateInQuote = getRateInQuote(quote);
            }
            // ========================================= INTERNAL HELPER FUNCTIONS =========================================
            /**
             * @notice Used to change the decimals of precision used for an amount.
             */
            function changeDecimals(uint256 amount, uint8 fromDecimals, uint8 toDecimals) internal pure returns (uint256) {
                if (fromDecimals == toDecimals) {
                    return amount;
                } else if (fromDecimals < toDecimals) {
                    return amount * 10 ** (toDecimals - fromDecimals);
                } else {
                    return amount / 10 ** (fromDecimals - toDecimals);
                }
            }
            /**
             * @notice Calculate fees owed in base.
             * @dev This function will update the highwater mark if the new exchange rate is higher.
             */
            function _calculateFeesOwed(
                AccountantState storage state,
                uint96 newExchangeRate,
                uint256 currentExchangeRate,
                uint256 currentTotalShares,
                uint64 currentTime
            ) internal {
                // Only update fees if we are not paused.
                // Update fee accounting.
                uint256 shareSupplyToUse = currentTotalShares;
                // Use the minimum between current total supply and total supply for last update.
                if (state.totalSharesLastUpdate < shareSupplyToUse) {
                    shareSupplyToUse = state.totalSharesLastUpdate;
                }
                // Determine management fees owned.
                uint256 timeDelta = currentTime - state.lastUpdateTimestamp;
                uint256 minimumAssets = newExchangeRate > currentExchangeRate
                    ? shareSupplyToUse.mulDivDown(currentExchangeRate, ONE_SHARE)
                    : shareSupplyToUse.mulDivDown(newExchangeRate, ONE_SHARE);
                uint256 managementFeesAnnual = minimumAssets.mulDivDown(state.managementFee, 1e4);
                uint256 newFeesOwedInBase = managementFeesAnnual.mulDivDown(timeDelta, 365 days);
                // Account for performance fees.
                if (newExchangeRate > state.highwaterMark) {
                    if (state.performanceFee > 0) {
                        uint256 changeInExchangeRate = newExchangeRate - state.highwaterMark;
                        uint256 yieldEarned = changeInExchangeRate.mulDivDown(shareSupplyToUse, ONE_SHARE);
                        uint256 performanceFeesOwedInBase = yieldEarned.mulDivDown(state.performanceFee, 1e4);
                        newFeesOwedInBase += performanceFeesOwedInBase;
                    }
                    // Always update the highwater mark if the new exchange rate is higher.
                    // This way if we are not iniitiall taking performance fees, we can start taking them
                    // without back charging them on past performance.
                    state.highwaterMark = newExchangeRate;
                }
                state.feesOwedInBase += uint128(newFeesOwedInBase);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Arithmetic library with operations for fixed-point numbers.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
        /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
        library FixedPointMathLib {
            /*//////////////////////////////////////////////////////////////
                            SIMPLIFIED FIXED POINT OPERATIONS
            //////////////////////////////////////////////////////////////*/
            uint256 internal constant MAX_UINT256 = 2**256 - 1;
            uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
            function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
            }
            function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
            }
            function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
            }
            function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
                return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
            }
            /*//////////////////////////////////////////////////////////////
                            LOW LEVEL FIXED POINT OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function mulDivDown(
                uint256 x,
                uint256 y,
                uint256 denominator
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                    if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                        revert(0, 0)
                    }
                    // Divide x * y by the denominator.
                    z := div(mul(x, y), denominator)
                }
            }
            function mulDivUp(
                uint256 x,
                uint256 y,
                uint256 denominator
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
                    if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                        revert(0, 0)
                    }
                    // If x * y modulo the denominator is strictly greater than 0,
                    // 1 is added to round up the division of x * y by the denominator.
                    z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
                }
            }
            function rpow(
                uint256 x,
                uint256 n,
                uint256 scalar
            ) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    switch x
                    case 0 {
                        switch n
                        case 0 {
                            // 0 ** 0 = 1
                            z := scalar
                        }
                        default {
                            // 0 ** n = 0
                            z := 0
                        }
                    }
                    default {
                        switch mod(n, 2)
                        case 0 {
                            // If n is even, store scalar in z for now.
                            z := scalar
                        }
                        default {
                            // If n is odd, store x in z for now.
                            z := x
                        }
                        // Shifting right by 1 is like dividing by 2.
                        let half := shr(1, scalar)
                        for {
                            // Shift n right by 1 before looping to halve it.
                            n := shr(1, n)
                        } n {
                            // Shift n right by 1 each iteration to halve it.
                            n := shr(1, n)
                        } {
                            // Revert immediately if x ** 2 would overflow.
                            // Equivalent to iszero(eq(div(xx, x), x)) here.
                            if shr(128, x) {
                                revert(0, 0)
                            }
                            // Store x squared.
                            let xx := mul(x, x)
                            // Round to the nearest number.
                            let xxRound := add(xx, half)
                            // Revert if xx + half overflowed.
                            if lt(xxRound, xx) {
                                revert(0, 0)
                            }
                            // Set x to scaled xxRound.
                            x := div(xxRound, scalar)
                            // If n is even:
                            if mod(n, 2) {
                                // Compute z * x.
                                let zx := mul(z, x)
                                // If z * x overflowed:
                                if iszero(eq(div(zx, x), z)) {
                                    // Revert if x is non-zero.
                                    if iszero(iszero(x)) {
                                        revert(0, 0)
                                    }
                                }
                                // Round to the nearest number.
                                let zxRound := add(zx, half)
                                // Revert if zx + half overflowed.
                                if lt(zxRound, zx) {
                                    revert(0, 0)
                                }
                                // Return properly scaled zxRound.
                                z := div(zxRound, scalar)
                            }
                        }
                    }
                }
            }
            /*//////////////////////////////////////////////////////////////
                                GENERAL NUMBER UTILITIES
            //////////////////////////////////////////////////////////////*/
            function sqrt(uint256 x) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    let y := x // We start y at x, which will help us make our initial estimate.
                    z := 181 // The "correct" value is 1, but this saves a multiplication later.
                    // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
                    // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
                    // We check y >= 2^(k + 8) but shift right by k bits
                    // each branch to ensure that if x >= 256, then y >= 256.
                    if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                        y := shr(128, y)
                        z := shl(64, z)
                    }
                    if iszero(lt(y, 0x1000000000000000000)) {
                        y := shr(64, y)
                        z := shl(32, z)
                    }
                    if iszero(lt(y, 0x10000000000)) {
                        y := shr(32, y)
                        z := shl(16, z)
                    }
                    if iszero(lt(y, 0x1000000)) {
                        y := shr(16, y)
                        z := shl(8, z)
                    }
                    // Goal was to get z*z*y within a small factor of x. More iterations could
                    // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
                    // We ensured y >= 256 so that the relative difference between y and y+1 is small.
                    // That's not possible if x < 256 but we can just verify those cases exhaustively.
                    // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
                    // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
                    // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.
                    // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
                    // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.
                    // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
                    // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.
                    // There is no overflow risk here since y < 2^136 after the first branch above.
                    z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.
                    // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    z := shr(1, add(z, div(x, z)))
                    // If x+1 is a perfect square, the Babylonian method cycles between
                    // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
                    // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
                    // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
                    // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
                    z := sub(z, lt(div(x, z), z))
                }
            }
            function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Mod x by y. Note this will return
                    // 0 instead of reverting if y is zero.
                    z := mod(x, y)
                }
            }
            function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Divide x by y. Note this will return
                    // 0 instead of reverting if y is zero.
                    r := div(x, y)
                }
            }
            function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Add 1 to x * y if x % y > 0. Note this will
                    // return 0 instead of reverting if y is zero.
                    z := add(gt(mod(x, y), 0), div(x, y))
                }
            }
        }
        // SPDX-License-Identifier: GPL-3.0-or-later
        // This program is free software: you can redistribute it and/or modify
        // it under the terms of the GNU General Public License as published by
        // the Free Software Foundation, either version 3 of the License, or
        // (at your option) any later version.
        // This program is distributed in the hope that it will be useful,
        // but WITHOUT ANY WARRANTY; without even the implied warranty of
        // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        // GNU General Public License for more details.
        // You should have received a copy of the GNU General Public License
        // along with this program.  If not, see <http://www.gnu.org/licenses/>.
        pragma solidity ^0.8.0;
        interface IRateProvider {
            function getRate() external view returns (uint256);
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
        /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
        /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
        abstract contract ERC20 {
            /*//////////////////////////////////////////////////////////////
                                         EVENTS
            //////////////////////////////////////////////////////////////*/
            event Transfer(address indexed from, address indexed to, uint256 amount);
            event Approval(address indexed owner, address indexed spender, uint256 amount);
            /*//////////////////////////////////////////////////////////////
                                    METADATA STORAGE
            //////////////////////////////////////////////////////////////*/
            string public name;
            string public symbol;
            uint8 public immutable decimals;
            /*//////////////////////////////////////////////////////////////
                                      ERC20 STORAGE
            //////////////////////////////////////////////////////////////*/
            uint256 public totalSupply;
            mapping(address => uint256) public balanceOf;
            mapping(address => mapping(address => uint256)) public allowance;
            /*//////////////////////////////////////////////////////////////
                                    EIP-2612 STORAGE
            //////////////////////////////////////////////////////////////*/
            uint256 internal immutable INITIAL_CHAIN_ID;
            bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
            mapping(address => uint256) public nonces;
            /*//////////////////////////////////////////////////////////////
                                       CONSTRUCTOR
            //////////////////////////////////////////////////////////////*/
            constructor(
                string memory _name,
                string memory _symbol,
                uint8 _decimals
            ) {
                name = _name;
                symbol = _symbol;
                decimals = _decimals;
                INITIAL_CHAIN_ID = block.chainid;
                INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
            }
            /*//////////////////////////////////////////////////////////////
                                       ERC20 LOGIC
            //////////////////////////////////////////////////////////////*/
            function approve(address spender, uint256 amount) public virtual returns (bool) {
                allowance[msg.sender][spender] = amount;
                emit Approval(msg.sender, spender, amount);
                return true;
            }
            function transfer(address to, uint256 amount) public virtual returns (bool) {
                balanceOf[msg.sender] -= amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(msg.sender, to, amount);
                return true;
            }
            function transferFrom(
                address from,
                address to,
                uint256 amount
            ) public virtual returns (bool) {
                uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
                if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
                balanceOf[from] -= amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(from, to, amount);
                return true;
            }
            /*//////////////////////////////////////////////////////////////
                                     EIP-2612 LOGIC
            //////////////////////////////////////////////////////////////*/
            function permit(
                address owner,
                address spender,
                uint256 value,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) public virtual {
                require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
                // Unchecked because the only math done is incrementing
                // the owner's nonce which cannot realistically overflow.
                unchecked {
                    address recoveredAddress = ecrecover(
                        keccak256(
                            abi.encodePacked(
                                "\\x19\\x01",
                                DOMAIN_SEPARATOR(),
                                keccak256(
                                    abi.encode(
                                        keccak256(
                                            "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                        ),
                                        owner,
                                        spender,
                                        value,
                                        nonces[owner]++,
                                        deadline
                                    )
                                )
                            )
                        ),
                        v,
                        r,
                        s
                    );
                    require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
                    allowance[recoveredAddress][spender] = value;
                }
                emit Approval(owner, spender, value);
            }
            function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
                return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
            }
            function computeDomainSeparator() internal view virtual returns (bytes32) {
                return
                    keccak256(
                        abi.encode(
                            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                            keccak256(bytes(name)),
                            keccak256("1"),
                            block.chainid,
                            address(this)
                        )
                    );
            }
            /*//////////////////////////////////////////////////////////////
                                INTERNAL MINT/BURN LOGIC
            //////////////////////////////////////////////////////////////*/
            function _mint(address to, uint256 amount) internal virtual {
                totalSupply += amount;
                // Cannot overflow because the sum of all user
                // balances can't exceed the max uint256 value.
                unchecked {
                    balanceOf[to] += amount;
                }
                emit Transfer(address(0), to, amount);
            }
            function _burn(address from, uint256 amount) internal virtual {
                balanceOf[from] -= amount;
                // Cannot underflow because a user's balance
                // will never be larger than the total supply.
                unchecked {
                    totalSupply -= amount;
                }
                emit Transfer(from, address(0), amount);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        import {ERC20} from "../tokens/ERC20.sol";
        /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
        /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
        /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
        library SafeTransferLib {
            /*//////////////////////////////////////////////////////////////
                                     ETH OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function safeTransferETH(address to, uint256 amount) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Transfer the ETH and store if it succeeded or not.
                    success := call(gas(), to, amount, 0, 0, 0, 0)
                }
                require(success, "ETH_TRANSFER_FAILED");
            }
            /*//////////////////////////////////////////////////////////////
                                    ERC20 OPERATIONS
            //////////////////////////////////////////////////////////////*/
            function safeTransferFrom(
                ERC20 token,
                address from,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
                    mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
                    )
                }
                require(success, "TRANSFER_FROM_FAILED");
            }
            function safeTransfer(
                ERC20 token,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
                    )
                }
                require(success, "TRANSFER_FAILED");
            }
            function safeApprove(
                ERC20 token,
                address to,
                uint256 amount
            ) internal {
                bool success;
                /// @solidity memory-safe-assembly
                assembly {
                    // Get a pointer to some free memory.
                    let freeMemoryPointer := mload(0x40)
                    // Write the abi-encoded calldata into memory, beginning with the function selector.
                    mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
                    mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
                    mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
                    success := and(
                        // Set success to whether the call reverted, if not we check it either
                        // returned exactly 1 (can't just be non-zero data), or had no return data.
                        or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                        // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                        // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                        // Counterintuitively, this call must be positioned second to the or() call in the
                        // surrounding and() call or else returndatasize() will be zero during the computation.
                        call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
                    )
                }
                require(success, "APPROVE_FAILED");
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        import {Address} from "@openzeppelin/contracts/utils/Address.sol";
        import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
        import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
        import {FixedPointMathLib} from "@solmate/utils/FixedPointMathLib.sol";
        import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";
        import {ERC20} from "@solmate/tokens/ERC20.sol";
        import {BeforeTransferHook} from "src/interfaces/BeforeTransferHook.sol";
        import {Auth, Authority} from "@solmate/auth/Auth.sol";
        contract BoringVault is ERC20, Auth, ERC721Holder, ERC1155Holder {
            using Address for address;
            using SafeTransferLib for ERC20;
            using FixedPointMathLib for uint256;
            // ========================================= STATE =========================================
            /**
             * @notice Contract responsbile for implementing `beforeTransfer`.
             */
            BeforeTransferHook public hook;
            //============================== EVENTS ===============================
            event Enter(address indexed from, address indexed asset, uint256 amount, address indexed to, uint256 shares);
            event Exit(address indexed to, address indexed asset, uint256 amount, address indexed from, uint256 shares);
            //============================== CONSTRUCTOR ===============================
            constructor(address _owner, string memory _name, string memory _symbol, uint8 _decimals)
                ERC20(_name, _symbol, _decimals)
                Auth(_owner, Authority(address(0)))
            {}
            //============================== MANAGE ===============================
            /**
             * @notice Allows manager to make an arbitrary function call from this contract.
             * @dev Callable by MANAGER_ROLE.
             */
            function manage(address target, bytes calldata data, uint256 value)
                external
                requiresAuth
                returns (bytes memory result)
            {
                result = target.functionCallWithValue(data, value);
            }
            /**
             * @notice Allows manager to make arbitrary function calls from this contract.
             * @dev Callable by MANAGER_ROLE.
             */
            function manage(address[] calldata targets, bytes[] calldata data, uint256[] calldata values)
                external
                requiresAuth
                returns (bytes[] memory results)
            {
                uint256 targetsLength = targets.length;
                results = new bytes[](targetsLength);
                for (uint256 i; i < targetsLength; ++i) {
                    results[i] = targets[i].functionCallWithValue(data[i], values[i]);
                }
            }
            //============================== ENTER ===============================
            /**
             * @notice Allows minter to mint shares, in exchange for assets.
             * @dev If assetAmount is zero, no assets are transferred in.
             * @dev Callable by MINTER_ROLE.
             */
            function enter(address from, ERC20 asset, uint256 assetAmount, address to, uint256 shareAmount)
                external
                requiresAuth
            {
                // Transfer assets in
                if (assetAmount > 0) asset.safeTransferFrom(from, address(this), assetAmount);
                // Mint shares.
                _mint(to, shareAmount);
                emit Enter(from, address(asset), assetAmount, to, shareAmount);
            }
            //============================== EXIT ===============================
            /**
             * @notice Allows burner to burn shares, in exchange for assets.
             * @dev If assetAmount is zero, no assets are transferred out.
             * @dev Callable by BURNER_ROLE.
             */
            function exit(address to, ERC20 asset, uint256 assetAmount, address from, uint256 shareAmount)
                external
                requiresAuth
            {
                // Burn shares.
                _burn(from, shareAmount);
                // Transfer assets out.
                if (assetAmount > 0) asset.safeTransfer(to, assetAmount);
                emit Exit(to, address(asset), assetAmount, from, shareAmount);
            }
            //============================== BEFORE TRANSFER HOOK ===============================
            /**
             * @notice Sets the share locker.
             * @notice If set to zero address, the share locker logic is disabled.
             * @dev Callable by OWNER_ROLE.
             */
            function setBeforeTransferHook(address _hook) external requiresAuth {
                hook = BeforeTransferHook(_hook);
            }
            /**
             * @notice Call `beforeTransferHook` passing in `from` `to`, and `msg.sender`.
             */
            function _callBeforeTransfer(address from, address to) internal view {
                if (address(hook) != address(0)) hook.beforeTransfer(from, to, msg.sender);
            }
            function transfer(address to, uint256 amount) public override returns (bool) {
                _callBeforeTransfer(msg.sender, to);
                return super.transfer(to, amount);
            }
            function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
                _callBeforeTransfer(from, to);
                return super.transferFrom(from, to, amount);
            }
            //============================== RECEIVE ===============================
            receive() external payable {}
        }
        // SPDX-License-Identifier: AGPL-3.0-only
        pragma solidity >=0.8.0;
        /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
        abstract contract Auth {
            event OwnershipTransferred(address indexed user, address indexed newOwner);
            event AuthorityUpdated(address indexed user, Authority indexed newAuthority);
            address public owner;
            Authority public authority;
            constructor(address _owner, Authority _authority) {
                owner = _owner;
                authority = _authority;
                emit OwnershipTransferred(msg.sender, _owner);
                emit AuthorityUpdated(msg.sender, _authority);
            }
            modifier requiresAuth() virtual {
                require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED");
                _;
            }
            function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) {
                Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas.
                // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be
                // aware that this makes protected functions uncallable even to the owner if the authority is out of order.
                return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner;
            }
            function setAuthority(Authority newAuthority) public virtual {
                // We check if the caller is the owner first because we want to ensure they can
                // always swap out the authority even if it's reverting or using up a lot of gas.
                require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));
                authority = newAuthority;
                emit AuthorityUpdated(msg.sender, newAuthority);
            }
            function transferOwnership(address newOwner) public virtual requiresAuth {
                owner = newOwner;
                emit OwnershipTransferred(msg.sender, newOwner);
            }
        }
        /// @notice A generic interface for a contract which provides authorization data to an Auth instance.
        /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol)
        /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol)
        interface Authority {
            function canCall(
                address user,
                address target,
                bytes4 functionSig
            ) external view returns (bool);
        }
        // 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.0) (token/ERC721/utils/ERC721Holder.sol)
        pragma solidity ^0.8.20;
        import {IERC721Receiver} from "../IERC721Receiver.sol";
        /**
         * @dev Implementation of the {IERC721Receiver} interface.
         *
         * Accepts all token transfers.
         * Make sure the contract is able to use its token with {IERC721-safeTransferFrom}, {IERC721-approve} or
         * {IERC721-setApprovalForAll}.
         */
        abstract contract ERC721Holder is IERC721Receiver {
            /**
             * @dev See {IERC721Receiver-onERC721Received}.
             *
             * Always returns `IERC721Receiver.onERC721Received.selector`.
             */
            function onERC721Received(address, address, uint256, bytes memory) public virtual returns (bytes4) {
                return this.onERC721Received.selector;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/utils/ERC1155Holder.sol)
        pragma solidity ^0.8.20;
        import {IERC165, ERC165} from "../../../utils/introspection/ERC165.sol";
        import {IERC1155Receiver} from "../IERC1155Receiver.sol";
        /**
         * @dev Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC1155 tokens.
         *
         * IMPORTANT: When inheriting this contract, you must include a way to use the received tokens, otherwise they will be
         * stuck.
         */
        abstract contract ERC1155Holder is ERC165, IERC1155Receiver {
            /**
             * @dev See {IERC165-supportsInterface}.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
                return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
            }
            function onERC1155Received(
                address,
                address,
                uint256,
                uint256,
                bytes memory
            ) public virtual override returns (bytes4) {
                return this.onERC1155Received.selector;
            }
            function onERC1155BatchReceived(
                address,
                address,
                uint256[] memory,
                uint256[] memory,
                bytes memory
            ) public virtual override returns (bytes4) {
                return this.onERC1155BatchReceived.selector;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.21;
        interface BeforeTransferHook {
            function beforeTransfer(address from, address to, address operator) external view;
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
        pragma solidity ^0.8.20;
        /**
         * @title ERC721 token receiver interface
         * @dev Interface for any contract that wants to support safeTransfers
         * from ERC721 asset contracts.
         */
        interface IERC721Receiver {
            /**
             * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
             * by `operator` from `from`, this function is called.
             *
             * It must return its Solidity selector to confirm the token transfer.
             * If any other value is returned or the interface is not implemented by the recipient, the transfer will be
             * reverted.
             *
             * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
             */
            function onERC721Received(
                address operator,
                address from,
                uint256 tokenId,
                bytes calldata data
            ) external returns (bytes4);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)
        pragma solidity ^0.8.20;
        import {IERC165} from "./IERC165.sol";
        /**
         * @dev Implementation of the {IERC165} interface.
         *
         * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
         * for the additional interface id that will be supported. For example:
         *
         * ```solidity
         * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
         *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
         * }
         * ```
         */
        abstract contract ERC165 is IERC165 {
            /**
             * @dev See {IERC165-supportsInterface}.
             */
            function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
                return interfaceId == type(IERC165).interfaceId;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/IERC1155Receiver.sol)
        pragma solidity ^0.8.20;
        import {IERC165} from "../../utils/introspection/IERC165.sol";
        /**
         * @dev Interface that must be implemented by smart contracts in order to receive
         * ERC-1155 token transfers.
         */
        interface IERC1155Receiver is IERC165 {
            /**
             * @dev Handles the receipt of a single ERC1155 token type. This function is
             * called at the end of a `safeTransferFrom` after the balance has been updated.
             *
             * NOTE: To accept the transfer, this must return
             * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
             * (i.e. 0xf23a6e61, or its own function selector).
             *
             * @param operator The address which initiated the transfer (i.e. msg.sender)
             * @param from The address which previously owned the token
             * @param id The ID of the token being transferred
             * @param value The amount of tokens being transferred
             * @param data Additional data with no specified format
             * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
             */
            function onERC1155Received(
                address operator,
                address from,
                uint256 id,
                uint256 value,
                bytes calldata data
            ) external returns (bytes4);
            /**
             * @dev Handles the receipt of a multiple ERC1155 token types. This function
             * is called at the end of a `safeBatchTransferFrom` after the balances have
             * been updated.
             *
             * NOTE: To accept the transfer(s), this must return
             * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
             * (i.e. 0xbc197c81, or its own function selector).
             *
             * @param operator The address which initiated the batch transfer (i.e. msg.sender)
             * @param from The address which previously owned the token
             * @param ids An array containing ids of each token being transferred (order and length must match values array)
             * @param values An array containing amounts of each token being transferred (order and length must match ids array)
             * @param data Additional data with no specified format
             * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
             */
            function onERC1155BatchReceived(
                address operator,
                address from,
                uint256[] calldata ids,
                uint256[] calldata values,
                bytes calldata data
            ) external returns (bytes4);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
        pragma solidity ^0.8.20;
        /**
         * @dev Interface of the ERC165 standard, as defined in the
         * https://eips.ethereum.org/EIPS/eip-165[EIP].
         *
         * Implementers can declare support of contract interfaces, which can then be
         * queried by others ({ERC165Checker}).
         *
         * For an implementation, see {ERC165}.
         */
        interface IERC165 {
            /**
             * @dev Returns true if this contract implements the interface defined by
             * `interfaceId`. See the corresponding
             * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
             * to learn more about how these ids are created.
             *
             * This function call must use less than 30 000 gas.
             */
            function supportsInterface(bytes4 interfaceId) external view returns (bool);
        }
        

        File 5 of 6: 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 6 of 6: LBTC
        // 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.0) (token/ERC20/ERC20.sol)
        pragma solidity ^0.8.20;
        import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
        import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
        import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
        import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
        import {Initializable} from "../../proxy/utils/Initializable.sol";
        /**
         * @dev Implementation of the {IERC20} interface.
         *
         * This implementation is agnostic to the way tokens are created. This means
         * that a supply mechanism has to be added in a derived contract using {_mint}.
         *
         * TIP: For a detailed writeup see our guide
         * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
         * to implement supply mechanisms].
         *
         * The default value of {decimals} is 18. To change this, you should override
         * this function so it returns a different value.
         *
         * We have followed general OpenZeppelin Contracts guidelines: functions revert
         * instead returning `false` on failure. This behavior is nonetheless
         * conventional and does not conflict with the expectations of ERC20
         * applications.
         *
         * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
         * This allows applications to reconstruct the allowance for all accounts just
         * by listening to said events. Other implementations of the EIP may not emit
         * these events, as it isn't required by the specification.
         */
        abstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors {
            /// @custom:storage-location erc7201:openzeppelin.storage.ERC20
            struct ERC20Storage {
                mapping(address account => uint256) _balances;
                mapping(address account => mapping(address spender => uint256)) _allowances;
                uint256 _totalSupply;
                string _name;
                string _symbol;
            }
            // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00;
            function _getERC20Storage() private pure returns (ERC20Storage storage $) {
                assembly {
                    $.slot := ERC20StorageLocation
                }
            }
            /**
             * @dev Sets the values for {name} and {symbol}.
             *
             * All two of these values are immutable: they can only be set once during
             * construction.
             */
            function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
                __ERC20_init_unchained(name_, symbol_);
            }
            function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
                ERC20Storage storage $ = _getERC20Storage();
                $._name = name_;
                $._symbol = symbol_;
            }
            /**
             * @dev Returns the name of the token.
             */
            function name() public view virtual returns (string memory) {
                ERC20Storage storage $ = _getERC20Storage();
                return $._name;
            }
            /**
             * @dev Returns the symbol of the token, usually a shorter version of the
             * name.
             */
            function symbol() public view virtual returns (string memory) {
                ERC20Storage storage $ = _getERC20Storage();
                return $._symbol;
            }
            /**
             * @dev Returns the number of decimals used to get its user representation.
             * For example, if `decimals` equals `2`, a balance of `505` tokens should
             * be displayed to a user as `5.05` (`505 / 10 ** 2`).
             *
             * Tokens usually opt for a value of 18, imitating the relationship between
             * Ether and Wei. This is the default value returned by this function, unless
             * it's overridden.
             *
             * NOTE: This information is only used for _display_ purposes: it in
             * no way affects any of the arithmetic of the contract, including
             * {IERC20-balanceOf} and {IERC20-transfer}.
             */
            function decimals() public view virtual returns (uint8) {
                return 18;
            }
            /**
             * @dev See {IERC20-totalSupply}.
             */
            function totalSupply() public view virtual returns (uint256) {
                ERC20Storage storage $ = _getERC20Storage();
                return $._totalSupply;
            }
            /**
             * @dev See {IERC20-balanceOf}.
             */
            function balanceOf(address account) public view virtual returns (uint256) {
                ERC20Storage storage $ = _getERC20Storage();
                return $._balances[account];
            }
            /**
             * @dev See {IERC20-transfer}.
             *
             * Requirements:
             *
             * - `to` cannot be the zero address.
             * - the caller must have a balance of at least `value`.
             */
            function transfer(address to, uint256 value) public virtual returns (bool) {
                address owner = _msgSender();
                _transfer(owner, to, value);
                return true;
            }
            /**
             * @dev See {IERC20-allowance}.
             */
            function allowance(address owner, address spender) public view virtual returns (uint256) {
                ERC20Storage storage $ = _getERC20Storage();
                return $._allowances[owner][spender];
            }
            /**
             * @dev See {IERC20-approve}.
             *
             * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
             * `transferFrom`. This is semantically equivalent to an infinite approval.
             *
             * Requirements:
             *
             * - `spender` cannot be the zero address.
             */
            function approve(address spender, uint256 value) public virtual returns (bool) {
                address owner = _msgSender();
                _approve(owner, spender, value);
                return true;
            }
            /**
             * @dev See {IERC20-transferFrom}.
             *
             * Emits an {Approval} event indicating the updated allowance. This is not
             * required by the EIP. See the note at the beginning of {ERC20}.
             *
             * NOTE: Does not update the allowance if the current allowance
             * is the maximum `uint256`.
             *
             * Requirements:
             *
             * - `from` and `to` cannot be the zero address.
             * - `from` must have a balance of at least `value`.
             * - the caller must have allowance for ``from``'s tokens of at least
             * `value`.
             */
            function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
                address spender = _msgSender();
                _spendAllowance(from, spender, value);
                _transfer(from, to, value);
                return true;
            }
            /**
             * @dev Moves a `value` amount of tokens from `from` to `to`.
             *
             * This internal function is equivalent to {transfer}, and can be used to
             * e.g. implement automatic token fees, slashing mechanisms, etc.
             *
             * Emits a {Transfer} event.
             *
             * NOTE: This function is not virtual, {_update} should be overridden instead.
             */
            function _transfer(address from, address to, uint256 value) internal {
                if (from == address(0)) {
                    revert ERC20InvalidSender(address(0));
                }
                if (to == address(0)) {
                    revert ERC20InvalidReceiver(address(0));
                }
                _update(from, to, value);
            }
            /**
             * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
             * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
             * this function.
             *
             * Emits a {Transfer} event.
             */
            function _update(address from, address to, uint256 value) internal virtual {
                ERC20Storage storage $ = _getERC20Storage();
                if (from == address(0)) {
                    // Overflow check required: The rest of the code assumes that totalSupply never overflows
                    $._totalSupply += value;
                } else {
                    uint256 fromBalance = $._balances[from];
                    if (fromBalance < value) {
                        revert ERC20InsufficientBalance(from, fromBalance, value);
                    }
                    unchecked {
                        // Overflow not possible: value <= fromBalance <= totalSupply.
                        $._balances[from] = fromBalance - value;
                    }
                }
                if (to == address(0)) {
                    unchecked {
                        // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                        $._totalSupply -= value;
                    }
                } else {
                    unchecked {
                        // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                        $._balances[to] += value;
                    }
                }
                emit Transfer(from, to, value);
            }
            /**
             * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
             * Relies on the `_update` mechanism
             *
             * Emits a {Transfer} event with `from` set to the zero address.
             *
             * NOTE: This function is not virtual, {_update} should be overridden instead.
             */
            function _mint(address account, uint256 value) internal {
                if (account == address(0)) {
                    revert ERC20InvalidReceiver(address(0));
                }
                _update(address(0), account, value);
            }
            /**
             * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
             * Relies on the `_update` mechanism.
             *
             * Emits a {Transfer} event with `to` set to the zero address.
             *
             * NOTE: This function is not virtual, {_update} should be overridden instead
             */
            function _burn(address account, uint256 value) internal {
                if (account == address(0)) {
                    revert ERC20InvalidSender(address(0));
                }
                _update(account, address(0), value);
            }
            /**
             * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
             *
             * This internal function is equivalent to `approve`, and can be used to
             * e.g. set automatic allowances for certain subsystems, etc.
             *
             * Emits an {Approval} event.
             *
             * Requirements:
             *
             * - `owner` cannot be the zero address.
             * - `spender` cannot be the zero address.
             *
             * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
             */
            function _approve(address owner, address spender, uint256 value) internal {
                _approve(owner, spender, value, true);
            }
            /**
             * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
             *
             * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
             * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
             * `Approval` event during `transferFrom` operations.
             *
             * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
             * true using the following override:
             * ```
             * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
             *     super._approve(owner, spender, value, true);
             * }
             * ```
             *
             * Requirements are the same as {_approve}.
             */
            function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
                ERC20Storage storage $ = _getERC20Storage();
                if (owner == address(0)) {
                    revert ERC20InvalidApprover(address(0));
                }
                if (spender == address(0)) {
                    revert ERC20InvalidSpender(address(0));
                }
                $._allowances[owner][spender] = value;
                if (emitEvent) {
                    emit Approval(owner, spender, value);
                }
            }
            /**
             * @dev Updates `owner` s allowance for `spender` based on spent `value`.
             *
             * Does not update the allowance value in case of infinite allowance.
             * Revert if not enough allowance is available.
             *
             * Does not emit an {Approval} event.
             */
            function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
                uint256 currentAllowance = allowance(owner, spender);
                if (currentAllowance != type(uint256).max) {
                    if (currentAllowance < value) {
                        revert ERC20InsufficientAllowance(spender, currentAllowance, value);
                    }
                    unchecked {
                        _approve(owner, spender, currentAllowance - value, false);
                    }
                }
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Pausable.sol)
        pragma solidity ^0.8.20;
        import {ERC20Upgradeable} from "../ERC20Upgradeable.sol";
        import {PausableUpgradeable} from "../../../utils/PausableUpgradeable.sol";
        import {Initializable} from "../../../proxy/utils/Initializable.sol";
        /**
         * @dev ERC20 token with pausable token transfers, minting and burning.
         *
         * Useful for scenarios such as preventing trades until the end of an evaluation
         * period, or having an emergency switch for freezing all token transfers in the
         * event of a large bug.
         *
         * IMPORTANT: This contract does not include public pause and unpause functions. In
         * addition to inheriting this contract, you must define both functions, invoking the
         * {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate
         * access control, e.g. using {AccessControl} or {Ownable}. Not doing so will
         * make the contract pause mechanism of the contract unreachable, and thus unusable.
         */
        abstract contract ERC20PausableUpgradeable is Initializable, ERC20Upgradeable, PausableUpgradeable {
            function __ERC20Pausable_init() internal onlyInitializing {
                __Pausable_init_unchained();
            }
            function __ERC20Pausable_init_unchained() internal onlyInitializing {
            }
            /**
             * @dev See {ERC20-_update}.
             *
             * Requirements:
             *
             * - the contract must not be paused.
             */
            function _update(address from, address to, uint256 value) internal virtual override whenNotPaused {
                super._update(from, to, value);
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Permit.sol)
        pragma solidity ^0.8.20;
        import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
        import {ERC20Upgradeable} from "../ERC20Upgradeable.sol";
        import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
        import {EIP712Upgradeable} from "../../../utils/cryptography/EIP712Upgradeable.sol";
        import {NoncesUpgradeable} from "../../../utils/NoncesUpgradeable.sol";
        import {Initializable} from "../../../proxy/utils/Initializable.sol";
        /**
         * @dev Implementation 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.
         */
        abstract contract ERC20PermitUpgradeable is Initializable, ERC20Upgradeable, IERC20Permit, EIP712Upgradeable, NoncesUpgradeable {
            bytes32 private constant PERMIT_TYPEHASH =
                keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
            /**
             * @dev Permit deadline has expired.
             */
            error ERC2612ExpiredSignature(uint256 deadline);
            /**
             * @dev Mismatched signature.
             */
            error ERC2612InvalidSigner(address signer, address owner);
            /**
             * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
             *
             * It's a good idea to use the same `name` that is defined as the ERC20 token name.
             */
            function __ERC20Permit_init(string memory name) internal onlyInitializing {
                __EIP712_init_unchained(name, "1");
            }
            function __ERC20Permit_init_unchained(string memory) internal onlyInitializing {}
            /**
             * @inheritdoc IERC20Permit
             */
            function permit(
                address owner,
                address spender,
                uint256 value,
                uint256 deadline,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) public virtual {
                if (block.timestamp > deadline) {
                    revert ERC2612ExpiredSignature(deadline);
                }
                bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
                bytes32 hash = _hashTypedDataV4(structHash);
                address signer = ECDSA.recover(hash, v, r, s);
                if (signer != owner) {
                    revert ERC2612InvalidSigner(signer, owner);
                }
                _approve(owner, spender, value);
            }
            /**
             * @inheritdoc IERC20Permit
             */
            function nonces(address owner) public view virtual override(IERC20Permit, NoncesUpgradeable) returns (uint256) {
                return super.nonces(owner);
            }
            /**
             * @inheritdoc IERC20Permit
             */
            // solhint-disable-next-line func-name-mixedcase
            function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
                return _domainSeparatorV4();
            }
        }
        // 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) (utils/cryptography/EIP712.sol)
        pragma solidity ^0.8.20;
        import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
        import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
        import {Initializable} from "../../proxy/utils/Initializable.sol";
        /**
         * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
         *
         * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
         * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
         * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
         * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
         *
         * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
         * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
         * ({_hashTypedDataV4}).
         *
         * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
         * the chain id to protect against replay attacks on an eventual fork of the chain.
         *
         * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
         * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
         *
         * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
         * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
         * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
         */
        abstract contract EIP712Upgradeable is Initializable, IERC5267 {
            bytes32 private constant TYPE_HASH =
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
            /// @custom:storage-location erc7201:openzeppelin.storage.EIP712
            struct EIP712Storage {
                /// @custom:oz-renamed-from _HASHED_NAME
                bytes32 _hashedName;
                /// @custom:oz-renamed-from _HASHED_VERSION
                bytes32 _hashedVersion;
                string _name;
                string _version;
            }
            // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.EIP712")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100;
            function _getEIP712Storage() private pure returns (EIP712Storage storage $) {
                assembly {
                    $.slot := EIP712StorageLocation
                }
            }
            /**
             * @dev Initializes the domain separator and parameter caches.
             *
             * The meaning of `name` and `version` is specified in
             * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
             *
             * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
             * - `version`: the current major version of the signing domain.
             *
             * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
             * contract upgrade].
             */
            function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
                __EIP712_init_unchained(name, version);
            }
            function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
                EIP712Storage storage $ = _getEIP712Storage();
                $._name = name;
                $._version = version;
                // Reset prior values in storage if upgrading
                $._hashedName = 0;
                $._hashedVersion = 0;
            }
            /**
             * @dev Returns the domain separator for the current chain.
             */
            function _domainSeparatorV4() internal view returns (bytes32) {
                return _buildDomainSeparator();
            }
            function _buildDomainSeparator() private view returns (bytes32) {
                return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
            }
            /**
             * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
             * function returns the hash of the fully encoded EIP712 message for this domain.
             *
             * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
             *
             * ```solidity
             * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
             *     keccak256("Mail(address to,string contents)"),
             *     mailTo,
             *     keccak256(bytes(mailContents))
             * )));
             * address signer = ECDSA.recover(digest, signature);
             * ```
             */
            function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
                return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
            }
            /**
             * @dev See {IERC-5267}.
             */
            function eip712Domain()
                public
                view
                virtual
                returns (
                    bytes1 fields,
                    string memory name,
                    string memory version,
                    uint256 chainId,
                    address verifyingContract,
                    bytes32 salt,
                    uint256[] memory extensions
                )
            {
                EIP712Storage storage $ = _getEIP712Storage();
                // If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
                // and the EIP712 domain is not reliable, as it will be missing name and version.
                require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized");
                return (
                    hex"0f", // 01111
                    _EIP712Name(),
                    _EIP712Version(),
                    block.chainid,
                    address(this),
                    bytes32(0),
                    new uint256[](0)
                );
            }
            /**
             * @dev The name parameter for the EIP712 domain.
             *
             * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
             * are a concern.
             */
            function _EIP712Name() internal view virtual returns (string memory) {
                EIP712Storage storage $ = _getEIP712Storage();
                return $._name;
            }
            /**
             * @dev The version parameter for the EIP712 domain.
             *
             * NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
             * are a concern.
             */
            function _EIP712Version() internal view virtual returns (string memory) {
                EIP712Storage storage $ = _getEIP712Storage();
                return $._version;
            }
            /**
             * @dev The hash of the name parameter for the EIP712 domain.
             *
             * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
             */
            function _EIP712NameHash() internal view returns (bytes32) {
                EIP712Storage storage $ = _getEIP712Storage();
                string memory name = _EIP712Name();
                if (bytes(name).length > 0) {
                    return keccak256(bytes(name));
                } else {
                    // If the name is empty, the contract may have been upgraded without initializing the new storage.
                    // We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
                    bytes32 hashedName = $._hashedName;
                    if (hashedName != 0) {
                        return hashedName;
                    } else {
                        return keccak256("");
                    }
                }
            }
            /**
             * @dev The hash of the version parameter for the EIP712 domain.
             *
             * NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
             */
            function _EIP712VersionHash() internal view returns (bytes32) {
                EIP712Storage storage $ = _getEIP712Storage();
                string memory version = _EIP712Version();
                if (bytes(version).length > 0) {
                    return keccak256(bytes(version));
                } else {
                    // If the version is empty, the contract may have been upgraded without initializing the new storage.
                    // We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
                    bytes32 hashedVersion = $._hashedVersion;
                    if (hashedVersion != 0) {
                        return hashedVersion;
                    } else {
                        return keccak256("");
                    }
                }
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol)
        pragma solidity ^0.8.20;
        import {Initializable} from "../proxy/utils/Initializable.sol";
        /**
         * @dev Provides tracking nonces for addresses. Nonces will only increment.
         */
        abstract contract NoncesUpgradeable is Initializable {
            /**
             * @dev The nonce used for an `account` is not the expected current nonce.
             */
            error InvalidAccountNonce(address account, uint256 currentNonce);
            /// @custom:storage-location erc7201:openzeppelin.storage.Nonces
            struct NoncesStorage {
                mapping(address account => uint256) _nonces;
            }
            // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Nonces")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant NoncesStorageLocation = 0x5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb00;
            function _getNoncesStorage() private pure returns (NoncesStorage storage $) {
                assembly {
                    $.slot := NoncesStorageLocation
                }
            }
            function __Nonces_init() internal onlyInitializing {
            }
            function __Nonces_init_unchained() internal onlyInitializing {
            }
            /**
             * @dev Returns the next unused nonce for an address.
             */
            function nonces(address owner) public view virtual returns (uint256) {
                NoncesStorage storage $ = _getNoncesStorage();
                return $._nonces[owner];
            }
            /**
             * @dev Consumes a nonce.
             *
             * Returns the current value and increments nonce.
             */
            function _useNonce(address owner) internal virtual returns (uint256) {
                NoncesStorage storage $ = _getNoncesStorage();
                // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be
                // decremented or reset. This guarantees that the nonce never overflows.
                unchecked {
                    // It is important to do x++ and not ++x here.
                    return $._nonces[owner]++;
                }
            }
            /**
             * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.
             */
            function _useCheckedNonce(address owner, uint256 nonce) internal virtual {
                uint256 current = _useNonce(owner);
                if (nonce != current) {
                    revert InvalidAccountNonce(owner, current);
                }
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
        pragma solidity ^0.8.20;
        import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
        import {Initializable} from "../proxy/utils/Initializable.sol";
        /**
         * @dev Contract module which allows children to implement an emergency stop
         * mechanism that can be triggered by an authorized account.
         *
         * This module is used through inheritance. It will make available the
         * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
         * the functions of your contract. Note that they will not be pausable by
         * simply including this module, only once the modifiers are put in place.
         */
        abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
            /// @custom:storage-location erc7201:openzeppelin.storage.Pausable
            struct PausableStorage {
                bool _paused;
            }
            // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
            function _getPausableStorage() private pure returns (PausableStorage storage $) {
                assembly {
                    $.slot := PausableStorageLocation
                }
            }
            /**
             * @dev Emitted when the pause is triggered by `account`.
             */
            event Paused(address account);
            /**
             * @dev Emitted when the pause is lifted by `account`.
             */
            event Unpaused(address account);
            /**
             * @dev The operation failed because the contract is paused.
             */
            error EnforcedPause();
            /**
             * @dev The operation failed because the contract is not paused.
             */
            error ExpectedPause();
            /**
             * @dev Initializes the contract in unpaused state.
             */
            function __Pausable_init() internal onlyInitializing {
                __Pausable_init_unchained();
            }
            function __Pausable_init_unchained() internal onlyInitializing {
                PausableStorage storage $ = _getPausableStorage();
                $._paused = false;
            }
            /**
             * @dev Modifier to make a function callable only when the contract is not paused.
             *
             * Requirements:
             *
             * - The contract must not be paused.
             */
            modifier whenNotPaused() {
                _requireNotPaused();
                _;
            }
            /**
             * @dev Modifier to make a function callable only when the contract is paused.
             *
             * Requirements:
             *
             * - The contract must be paused.
             */
            modifier whenPaused() {
                _requirePaused();
                _;
            }
            /**
             * @dev Returns true if the contract is paused, and false otherwise.
             */
            function paused() public view virtual returns (bool) {
                PausableStorage storage $ = _getPausableStorage();
                return $._paused;
            }
            /**
             * @dev Throws if the contract is paused.
             */
            function _requireNotPaused() internal view virtual {
                if (paused()) {
                    revert EnforcedPause();
                }
            }
            /**
             * @dev Throws if the contract is not paused.
             */
            function _requirePaused() internal view virtual {
                if (!paused()) {
                    revert ExpectedPause();
                }
            }
            /**
             * @dev Triggers stopped state.
             *
             * Requirements:
             *
             * - The contract must not be paused.
             */
            function _pause() internal virtual whenNotPaused {
                PausableStorage storage $ = _getPausableStorage();
                $._paused = true;
                emit Paused(_msgSender());
            }
            /**
             * @dev Returns to normal state.
             *
             * Requirements:
             *
             * - The contract must be paused.
             */
            function _unpause() internal virtual whenPaused {
                PausableStorage storage $ = _getPausableStorage();
                $._paused = false;
                emit Unpaused(_msgSender());
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
        pragma solidity ^0.8.20;
        import {Initializable} from "../proxy/utils/Initializable.sol";
        /**
         * @dev Contract module that helps prevent reentrant calls to a function.
         *
         * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
         * available, which can be applied to functions to make sure there are no nested
         * (reentrant) calls to them.
         *
         * Note that because there is a single `nonReentrant` guard, functions marked as
         * `nonReentrant` may not call one another. This can be worked around by making
         * those functions `private`, and then adding `external` `nonReentrant` entry
         * points to them.
         *
         * TIP: If you would like to learn more about reentrancy and alternative ways
         * to protect against it, check out our blog post
         * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
         */
        abstract contract ReentrancyGuardUpgradeable is Initializable {
            // Booleans are more expensive than uint256 or any type that takes up a full
            // word because each write operation emits an extra SLOAD to first read the
            // slot's contents, replace the bits taken up by the boolean, and then write
            // back. This is the compiler's defense against contract upgrades and
            // pointer aliasing, and it cannot be disabled.
            // The values being non-zero value makes deployment a bit more expensive,
            // but in exchange the refund on every call to nonReentrant will be lower in
            // amount. Since refunds are capped to a percentage of the total
            // transaction's gas, it is best to keep them low in cases like this one, to
            // increase the likelihood of the full refund coming into effect.
            uint256 private constant NOT_ENTERED = 1;
            uint256 private constant ENTERED = 2;
            /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
            struct ReentrancyGuardStorage {
                uint256 _status;
            }
            // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
            function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
                assembly {
                    $.slot := ReentrancyGuardStorageLocation
                }
            }
            /**
             * @dev Unauthorized reentrant call.
             */
            error ReentrancyGuardReentrantCall();
            function __ReentrancyGuard_init() internal onlyInitializing {
                __ReentrancyGuard_init_unchained();
            }
            function __ReentrancyGuard_init_unchained() internal onlyInitializing {
                ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
                $._status = NOT_ENTERED;
            }
            /**
             * @dev Prevents a contract from calling itself, directly or indirectly.
             * Calling a `nonReentrant` function from another `nonReentrant`
             * function is not supported. It is possible to prevent this from happening
             * by making the `nonReentrant` function external, and making it call a
             * `private` function that does the actual work.
             */
            modifier nonReentrant() {
                _nonReentrantBefore();
                _;
                _nonReentrantAfter();
            }
            function _nonReentrantBefore() private {
                ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
                // On the first call to nonReentrant, _status will be NOT_ENTERED
                if ($._status == ENTERED) {
                    revert ReentrancyGuardReentrantCall();
                }
                // Any calls to nonReentrant after this point will fail
                $._status = ENTERED;
            }
            function _nonReentrantAfter() private {
                ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
                // By storing the original value once again, a refund is triggered (see
                // https://eips.ethereum.org/EIPS/eip-2200)
                $._status = NOT_ENTERED;
            }
            /**
             * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
             * `nonReentrant` function in the call stack.
             */
            function _reentrancyGuardEntered() internal view returns (bool) {
                ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
                return $._status == ENTERED;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
        pragma solidity ^0.8.20;
        /**
         * @dev Standard ERC20 Errors
         * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
         */
        interface IERC20Errors {
            /**
             * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
             * @param sender Address whose tokens are being transferred.
             * @param balance Current balance for the interacting account.
             * @param needed Minimum amount required to perform a transfer.
             */
            error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
            /**
             * @dev Indicates a failure with the token `sender`. Used in transfers.
             * @param sender Address whose tokens are being transferred.
             */
            error ERC20InvalidSender(address sender);
            /**
             * @dev Indicates a failure with the token `receiver`. Used in transfers.
             * @param receiver Address to which tokens are being transferred.
             */
            error ERC20InvalidReceiver(address receiver);
            /**
             * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
             * @param spender Address that may be allowed to operate on tokens without being their owner.
             * @param allowance Amount of tokens a `spender` is allowed to operate with.
             * @param needed Minimum amount required to perform a transfer.
             */
            error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
            /**
             * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
             * @param approver Address initiating an approval operation.
             */
            error ERC20InvalidApprover(address approver);
            /**
             * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
             * @param spender Address that may be allowed to operate on tokens without being their owner.
             */
            error ERC20InvalidSpender(address spender);
        }
        /**
         * @dev Standard ERC721 Errors
         * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
         */
        interface IERC721Errors {
            /**
             * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
             * Used in balance queries.
             * @param owner Address of the current owner of a token.
             */
            error ERC721InvalidOwner(address owner);
            /**
             * @dev Indicates a `tokenId` whose `owner` is the zero address.
             * @param tokenId Identifier number of a token.
             */
            error ERC721NonexistentToken(uint256 tokenId);
            /**
             * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
             * @param sender Address whose tokens are being transferred.
             * @param tokenId Identifier number of a token.
             * @param owner Address of the current owner of a token.
             */
            error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
            /**
             * @dev Indicates a failure with the token `sender`. Used in transfers.
             * @param sender Address whose tokens are being transferred.
             */
            error ERC721InvalidSender(address sender);
            /**
             * @dev Indicates a failure with the token `receiver`. Used in transfers.
             * @param receiver Address to which tokens are being transferred.
             */
            error ERC721InvalidReceiver(address receiver);
            /**
             * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
             * @param operator Address that may be allowed to operate on tokens without being their owner.
             * @param tokenId Identifier number of a token.
             */
            error ERC721InsufficientApproval(address operator, uint256 tokenId);
            /**
             * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
             * @param approver Address initiating an approval operation.
             */
            error ERC721InvalidApprover(address approver);
            /**
             * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
             * @param operator Address that may be allowed to operate on tokens without being their owner.
             */
            error ERC721InvalidOperator(address operator);
        }
        /**
         * @dev Standard ERC1155 Errors
         * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
         */
        interface IERC1155Errors {
            /**
             * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
             * @param sender Address whose tokens are being transferred.
             * @param balance Current balance for the interacting account.
             * @param needed Minimum amount required to perform a transfer.
             * @param tokenId Identifier number of a token.
             */
            error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
            /**
             * @dev Indicates a failure with the token `sender`. Used in transfers.
             * @param sender Address whose tokens are being transferred.
             */
            error ERC1155InvalidSender(address sender);
            /**
             * @dev Indicates a failure with the token `receiver`. Used in transfers.
             * @param receiver Address to which tokens are being transferred.
             */
            error ERC1155InvalidReceiver(address receiver);
            /**
             * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
             * @param operator Address that may be allowed to operate on tokens without being their owner.
             * @param owner Address of the current owner of a token.
             */
            error ERC1155MissingApprovalForAll(address operator, address owner);
            /**
             * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
             * @param approver Address initiating an approval operation.
             */
            error ERC1155InvalidApprover(address approver);
            /**
             * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
             * @param operator Address that may be allowed to operate on tokens without being their owner.
             */
            error ERC1155InvalidOperator(address operator);
            /**
             * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
             * Used in batch transfers.
             * @param idsLength Length of the array of token identifiers
             * @param valuesLength Length of the array of token amounts
             */
            error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC1271.sol)
        pragma solidity ^0.8.20;
        /**
         * @dev Interface of the ERC1271 standard signature validation method for
         * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
         */
        interface IERC1271 {
            /**
             * @dev Should return whether the signature provided is valid for the provided data
             * @param hash      Hash of the data to be signed
             * @param signature Signature byte array associated with _data
             */
            function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)
        pragma solidity ^0.8.20;
        interface IERC5267 {
            /**
             * @dev MAY be emitted to signal that the domain could have changed.
             */
            event EIP712DomainChanged();
            /**
             * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
             * signature.
             */
            function eip712Domain()
                external
                view
                returns (
                    bytes1 fields,
                    string memory name,
                    string memory version,
                    uint256 chainId,
                    address verifyingContract,
                    bytes32 salt,
                    uint256[] memory extensions
                );
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
        pragma solidity ^0.8.20;
        import {IERC20} from "../IERC20.sol";
        /**
         * @dev Interface for the optional metadata functions from the ERC20 standard.
         */
        interface IERC20Metadata is IERC20 {
            /**
             * @dev Returns the name of the token.
             */
            function name() external view returns (string memory);
            /**
             * @dev Returns the symbol of the token.
             */
            function symbol() external view returns (string memory);
            /**
             * @dev Returns the decimals places of the token.
             */
            function decimals() external view returns (uint8);
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated 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) (utils/cryptography/ECDSA.sol)
        pragma solidity ^0.8.20;
        /**
         * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
         *
         * These functions can be used to verify that a message was signed by the holder
         * of the private keys of a given address.
         */
        library ECDSA {
            enum RecoverError {
                NoError,
                InvalidSignature,
                InvalidSignatureLength,
                InvalidSignatureS
            }
            /**
             * @dev The signature derives the `address(0)`.
             */
            error ECDSAInvalidSignature();
            /**
             * @dev The signature has an invalid length.
             */
            error ECDSAInvalidSignatureLength(uint256 length);
            /**
             * @dev The signature has an S value that is in the upper half order.
             */
            error ECDSAInvalidSignatureS(bytes32 s);
            /**
             * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
             * return address(0) without also returning an error description. Errors are documented using an enum (error type)
             * and a bytes32 providing additional information about the error.
             *
             * If no error is returned, then the address can be used for verification purposes.
             *
             * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
             * this function rejects them by requiring the `s` value to be in the lower
             * half order, and the `v` value to be either 27 or 28.
             *
             * IMPORTANT: `hash` _must_ be the result of a hash operation for the
             * verification to be secure: it is possible to craft signatures that
             * recover to arbitrary addresses for non-hashed data. A safe way to ensure
             * this is by receiving a hash of the original message (which may otherwise
             * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
             *
             * Documentation for signature generation:
             * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
             * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
             */
            function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
                if (signature.length == 65) {
                    bytes32 r;
                    bytes32 s;
                    uint8 v;
                    // ecrecover takes the signature parameters, and the only way to get them
                    // currently is to use assembly.
                    /// @solidity memory-safe-assembly
                    assembly {
                        r := mload(add(signature, 0x20))
                        s := mload(add(signature, 0x40))
                        v := byte(0, mload(add(signature, 0x60)))
                    }
                    return tryRecover(hash, v, r, s);
                } else {
                    return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
                }
            }
            /**
             * @dev Returns the address that signed a hashed message (`hash`) with
             * `signature`. This address can then be used for verification purposes.
             *
             * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
             * this function rejects them by requiring the `s` value to be in the lower
             * half order, and the `v` value to be either 27 or 28.
             *
             * IMPORTANT: `hash` _must_ be the result of a hash operation for the
             * verification to be secure: it is possible to craft signatures that
             * recover to arbitrary addresses for non-hashed data. A safe way to ensure
             * this is by receiving a hash of the original message (which may otherwise
             * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
             */
            function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
                (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
                _throwError(error, errorArg);
                return recovered;
            }
            /**
             * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
             *
             * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
             */
            function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
                unchecked {
                    bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
                    // We do not check for an overflow here since the shift operation results in 0 or 1.
                    uint8 v = uint8((uint256(vs) >> 255) + 27);
                    return tryRecover(hash, v, r, s);
                }
            }
            /**
             * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
             */
            function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
                (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
                _throwError(error, errorArg);
                return recovered;
            }
            /**
             * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
             * `r` and `s` signature fields separately.
             */
            function tryRecover(
                bytes32 hash,
                uint8 v,
                bytes32 r,
                bytes32 s
            ) internal pure returns (address, RecoverError, bytes32) {
                // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
                // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
                // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
                // signatures from current libraries generate a unique signature with an s-value in the lower half order.
                //
                // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
                // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
                // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
                // these malleable signatures as well.
                if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                    return (address(0), RecoverError.InvalidSignatureS, s);
                }
                // If the signature is valid (and not malleable), return the signer address
                address signer = ecrecover(hash, v, r, s);
                if (signer == address(0)) {
                    return (address(0), RecoverError.InvalidSignature, bytes32(0));
                }
                return (signer, RecoverError.NoError, bytes32(0));
            }
            /**
             * @dev Overload of {ECDSA-recover} that receives the `v`,
             * `r` and `s` signature fields separately.
             */
            function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
                (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
                _throwError(error, errorArg);
                return recovered;
            }
            /**
             * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
             */
            function _throwError(RecoverError error, bytes32 errorArg) private pure {
                if (error == RecoverError.NoError) {
                    return; // no error: do nothing
                } else if (error == RecoverError.InvalidSignature) {
                    revert ECDSAInvalidSignature();
                } else if (error == RecoverError.InvalidSignatureLength) {
                    revert ECDSAInvalidSignatureLength(uint256(errorArg));
                } else if (error == RecoverError.InvalidSignatureS) {
                    revert ECDSAInvalidSignatureS(errorArg);
                }
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol)
        pragma solidity ^0.8.20;
        import {Strings} from "../Strings.sol";
        /**
         * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
         *
         * The library provides methods for generating a hash of a message that conforms to the
         * https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
         * specifications.
         */
        library MessageHashUtils {
            /**
             * @dev Returns the keccak256 digest of an EIP-191 signed data with version
             * `0x45` (`personal_sign` messages).
             *
             * The digest is calculated by prefixing a bytes32 `messageHash` with
             * `"\\x19Ethereum Signed Message:\
        32"` and hashing the result. It corresponds with the
             * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
             *
             * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
             * keccak256, although any bytes32 value can be safely used because the final digest will
             * be re-hashed.
             *
             * See {ECDSA-recover}.
             */
            function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
                /// @solidity memory-safe-assembly
                assembly {
                    mstore(0x00, "\\x19Ethereum Signed Message:\
        32") // 32 is the bytes-length of messageHash
                    mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
                    digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
                }
            }
            /**
             * @dev Returns the keccak256 digest of an EIP-191 signed data with version
             * `0x45` (`personal_sign` messages).
             *
             * The digest is calculated by prefixing an arbitrary `message` with
             * `"\\x19Ethereum Signed Message:\
        " + len(message)` and hashing the result. It corresponds with the
             * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
             *
             * See {ECDSA-recover}.
             */
            function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
                return
                    keccak256(bytes.concat("\\x19Ethereum Signed Message:\
        ", bytes(Strings.toString(message.length)), message));
            }
            /**
             * @dev Returns the keccak256 digest of an EIP-191 signed data with version
             * `0x00` (data with intended validator).
             *
             * The digest is calculated by prefixing an arbitrary `data` with `"\\x19\\x00"` and the intended
             * `validator` address. Then hashing the result.
             *
             * See {ECDSA-recover}.
             */
            function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
                return keccak256(abi.encodePacked(hex"19_00", validator, data));
            }
            /**
             * @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
             *
             * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
             * `\\x19\\x01` and hashing the result. It corresponds to the hash signed by the
             * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
             *
             * See {ECDSA-recover}.
             */
            function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
                /// @solidity memory-safe-assembly
                assembly {
                    let ptr := mload(0x40)
                    mstore(ptr, hex"19_01")
                    mstore(add(ptr, 0x02), domainSeparator)
                    mstore(add(ptr, 0x22), structHash)
                    digest := keccak256(ptr, 0x42)
                }
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
        pragma solidity ^0.8.20;
        /**
         * @dev Standard math utilities missing in the Solidity language.
         */
        library Math {
            /**
             * @dev Muldiv operation overflow.
             */
            error MathOverflowedMulDiv();
            enum Rounding {
                Floor, // Toward negative infinity
                Ceil, // Toward positive infinity
                Trunc, // Toward zero
                Expand // Away from zero
            }
            /**
             * @dev Returns the addition of two unsigned integers, with an overflow flag.
             */
            function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                unchecked {
                    uint256 c = a + b;
                    if (c < a) return (false, 0);
                    return (true, c);
                }
            }
            /**
             * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
             */
            function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                unchecked {
                    if (b > a) return (false, 0);
                    return (true, a - b);
                }
            }
            /**
             * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
             */
            function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                unchecked {
                    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                    // benefit is lost if 'b' is also tested.
                    // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                    if (a == 0) return (true, 0);
                    uint256 c = a * b;
                    if (c / a != b) return (false, 0);
                    return (true, c);
                }
            }
            /**
             * @dev Returns the division of two unsigned integers, with a division by zero flag.
             */
            function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                unchecked {
                    if (b == 0) return (false, 0);
                    return (true, a / b);
                }
            }
            /**
             * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
             */
            function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
                unchecked {
                    if (b == 0) return (false, 0);
                    return (true, a % b);
                }
            }
            /**
             * @dev Returns the largest of two numbers.
             */
            function max(uint256 a, uint256 b) internal pure returns (uint256) {
                return a > b ? a : b;
            }
            /**
             * @dev Returns the smallest of two numbers.
             */
            function min(uint256 a, uint256 b) internal pure returns (uint256) {
                return a < b ? a : b;
            }
            /**
             * @dev Returns the average of two numbers. The result is rounded towards
             * zero.
             */
            function average(uint256 a, uint256 b) internal pure returns (uint256) {
                // (a + b) / 2 can overflow.
                return (a & b) + (a ^ b) / 2;
            }
            /**
             * @dev Returns the ceiling of the division of two numbers.
             *
             * This differs from standard division with `/` in that it rounds towards infinity instead
             * of rounding towards zero.
             */
            function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                if (b == 0) {
                    // Guarantee the same behavior as in a regular Solidity division.
                    return a / b;
                }
                // (a + b - 1) / b can overflow on addition, so we distribute.
                return a == 0 ? 0 : (a - 1) / b + 1;
            }
            /**
             * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
             * denominator == 0.
             * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
             * Uniswap Labs also under MIT license.
             */
            function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
                unchecked {
                    // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                    // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                    // variables such that product = prod1 * 2^256 + prod0.
                    uint256 prod0 = x * y; // Least significant 256 bits of the product
                    uint256 prod1; // Most significant 256 bits of the product
                    assembly {
                        let mm := mulmod(x, y, not(0))
                        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                    }
                    // Handle non-overflow cases, 256 by 256 division.
                    if (prod1 == 0) {
                        // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                        // The surrounding unchecked block does not change this fact.
                        // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                        return prod0 / denominator;
                    }
                    // Make sure the result is less than 2^256. Also prevents denominator == 0.
                    if (denominator <= prod1) {
                        revert MathOverflowedMulDiv();
                    }
                    ///////////////////////////////////////////////
                    // 512 by 256 division.
                    ///////////////////////////////////////////////
                    // Make division exact by subtracting the remainder from [prod1 prod0].
                    uint256 remainder;
                    assembly {
                        // Compute remainder using mulmod.
                        remainder := mulmod(x, y, denominator)
                        // Subtract 256 bit number from 512 bit number.
                        prod1 := sub(prod1, gt(remainder, prod0))
                        prod0 := sub(prod0, remainder)
                    }
                    // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
                    // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
                    uint256 twos = denominator & (0 - denominator);
                    assembly {
                        // Divide denominator by twos.
                        denominator := div(denominator, twos)
                        // Divide [prod1 prod0] by twos.
                        prod0 := div(prod0, twos)
                        // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                        twos := add(div(sub(0, twos), twos), 1)
                    }
                    // Shift in bits from prod1 into prod0.
                    prod0 |= prod1 * twos;
                    // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                    // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                    // four bits. That is, denominator * inv = 1 mod 2^4.
                    uint256 inverse = (3 * denominator) ^ 2;
                    // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
                    // works in modular arithmetic, doubling the correct bits in each step.
                    inverse *= 2 - denominator * inverse; // inverse mod 2^8
                    inverse *= 2 - denominator * inverse; // inverse mod 2^16
                    inverse *= 2 - denominator * inverse; // inverse mod 2^32
                    inverse *= 2 - denominator * inverse; // inverse mod 2^64
                    inverse *= 2 - denominator * inverse; // inverse mod 2^128
                    inverse *= 2 - denominator * inverse; // inverse mod 2^256
                    // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                    // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                    // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                    // is no longer required.
                    result = prod0 * inverse;
                    return result;
                }
            }
            /**
             * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
             */
            function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
                uint256 result = mulDiv(x, y, denominator);
                if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
                    result += 1;
                }
                return result;
            }
            /**
             * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
             * towards zero.
             *
             * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
             */
            function sqrt(uint256 a) internal pure returns (uint256) {
                if (a == 0) {
                    return 0;
                }
                // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
                //
                // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
                // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
                //
                // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
                // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
                // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
                //
                // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
                uint256 result = 1 << (log2(a) >> 1);
                // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
                // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
                // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
                // into the expected uint128 result.
                unchecked {
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    result = (result + a / result) >> 1;
                    return min(result, a / result);
                }
            }
            /**
             * @notice Calculates sqrt(a), following the selected rounding direction.
             */
            function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
                unchecked {
                    uint256 result = sqrt(a);
                    return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
                }
            }
            /**
             * @dev Return the log in base 2 of a positive value rounded towards zero.
             * Returns 0 if given 0.
             */
            function log2(uint256 value) internal pure returns (uint256) {
                uint256 result = 0;
                unchecked {
                    if (value >> 128 > 0) {
                        value >>= 128;
                        result += 128;
                    }
                    if (value >> 64 > 0) {
                        value >>= 64;
                        result += 64;
                    }
                    if (value >> 32 > 0) {
                        value >>= 32;
                        result += 32;
                    }
                    if (value >> 16 > 0) {
                        value >>= 16;
                        result += 16;
                    }
                    if (value >> 8 > 0) {
                        value >>= 8;
                        result += 8;
                    }
                    if (value >> 4 > 0) {
                        value >>= 4;
                        result += 4;
                    }
                    if (value >> 2 > 0) {
                        value >>= 2;
                        result += 2;
                    }
                    if (value >> 1 > 0) {
                        result += 1;
                    }
                }
                return result;
            }
            /**
             * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
             * Returns 0 if given 0.
             */
            function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
                unchecked {
                    uint256 result = log2(value);
                    return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
                }
            }
            /**
             * @dev Return the log in base 10 of a positive value rounded towards zero.
             * Returns 0 if given 0.
             */
            function log10(uint256 value) internal pure returns (uint256) {
                uint256 result = 0;
                unchecked {
                    if (value >= 10 ** 64) {
                        value /= 10 ** 64;
                        result += 64;
                    }
                    if (value >= 10 ** 32) {
                        value /= 10 ** 32;
                        result += 32;
                    }
                    if (value >= 10 ** 16) {
                        value /= 10 ** 16;
                        result += 16;
                    }
                    if (value >= 10 ** 8) {
                        value /= 10 ** 8;
                        result += 8;
                    }
                    if (value >= 10 ** 4) {
                        value /= 10 ** 4;
                        result += 4;
                    }
                    if (value >= 10 ** 2) {
                        value /= 10 ** 2;
                        result += 2;
                    }
                    if (value >= 10 ** 1) {
                        result += 1;
                    }
                }
                return result;
            }
            /**
             * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
             * Returns 0 if given 0.
             */
            function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
                unchecked {
                    uint256 result = log10(value);
                    return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
                }
            }
            /**
             * @dev Return the log in base 256 of a positive value rounded towards zero.
             * Returns 0 if given 0.
             *
             * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
             */
            function log256(uint256 value) internal pure returns (uint256) {
                uint256 result = 0;
                unchecked {
                    if (value >> 128 > 0) {
                        value >>= 128;
                        result += 16;
                    }
                    if (value >> 64 > 0) {
                        value >>= 64;
                        result += 8;
                    }
                    if (value >> 32 > 0) {
                        value >>= 32;
                        result += 4;
                    }
                    if (value >> 16 > 0) {
                        value >>= 16;
                        result += 2;
                    }
                    if (value >> 8 > 0) {
                        result += 1;
                    }
                }
                return result;
            }
            /**
             * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
             * Returns 0 if given 0.
             */
            function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
                unchecked {
                    uint256 result = log256(value);
                    return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
                }
            }
            /**
             * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
             */
            function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
                return uint8(rounding) % 2 == 1;
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
        pragma solidity ^0.8.20;
        /**
         * @dev Standard signed math utilities missing in the Solidity language.
         */
        library SignedMath {
            /**
             * @dev Returns the largest of two signed numbers.
             */
            function max(int256 a, int256 b) internal pure returns (int256) {
                return a > b ? a : b;
            }
            /**
             * @dev Returns the smallest of two signed numbers.
             */
            function min(int256 a, int256 b) internal pure returns (int256) {
                return a < b ? a : b;
            }
            /**
             * @dev Returns the average of two signed numbers without overflow.
             * The result is rounded towards zero.
             */
            function average(int256 a, int256 b) internal pure returns (int256) {
                // Formula from the book "Hacker's Delight"
                int256 x = (a & b) + ((a ^ b) >> 1);
                return x + (int256(uint256(x) >> 255) & (a ^ b));
            }
            /**
             * @dev Returns the absolute unsigned value of a signed value.
             */
            function abs(int256 n) internal pure returns (uint256) {
                unchecked {
                    // must be unchecked in order to support `n = type(int256).min`
                    return uint256(n >= 0 ? n : -n);
                }
            }
        }
        // SPDX-License-Identifier: MIT
        // OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
        pragma solidity ^0.8.20;
        import {Math} from "./math/Math.sol";
        import {SignedMath} from "./math/SignedMath.sol";
        /**
         * @dev String operations.
         */
        library Strings {
            bytes16 private constant HEX_DIGITS = "0123456789abcdef";
            uint8 private constant ADDRESS_LENGTH = 20;
            /**
             * @dev The `value` string doesn't fit in the specified `length`.
             */
            error StringsInsufficientHexLength(uint256 value, uint256 length);
            /**
             * @dev Converts a `uint256` to its ASCII `string` decimal representation.
             */
            function toString(uint256 value) internal pure returns (string memory) {
                unchecked {
                    uint256 length = Math.log10(value) + 1;
                    string memory buffer = new string(length);
                    uint256 ptr;
                    /// @solidity memory-safe-assembly
                    assembly {
                        ptr := add(buffer, add(32, length))
                    }
                    while (true) {
                        ptr--;
                        /// @solidity memory-safe-assembly
                        assembly {
                            mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                        }
                        value /= 10;
                        if (value == 0) break;
                    }
                    return buffer;
                }
            }
            /**
             * @dev Converts a `int256` to its ASCII `string` decimal representation.
             */
            function toStringSigned(int256 value) internal pure returns (string memory) {
                return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
            }
            /**
             * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
             */
            function toHexString(uint256 value) internal pure returns (string memory) {
                unchecked {
                    return toHexString(value, Math.log256(value) + 1);
                }
            }
            /**
             * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
             */
            function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
                uint256 localValue = value;
                bytes memory buffer = new bytes(2 * length + 2);
                buffer[0] = "0";
                buffer[1] = "x";
                for (uint256 i = 2 * length + 1; i > 1; --i) {
                    buffer[i] = HEX_DIGITS[localValue & 0xf];
                    localValue >>= 4;
                }
                if (localValue != 0) {
                    revert StringsInsufficientHexLength(value, length);
                }
                return string(buffer);
            }
            /**
             * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
             * representation.
             */
            function toHexString(address addr) internal pure returns (string memory) {
                return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
            }
            /**
             * @dev Returns true if the two strings are equal.
             */
            function equal(string memory a, string memory b) internal pure returns (bool) {
                return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
            }
        }
        // SPDX-License-Identifier: MIT
        // Compatible with OpenZeppelin Contracts ^5.0.0
        pragma solidity 0.8.24;
        /// Interface of the Bascule contract as used by on-chain contracts.
        /// @custom:security-contact [email protected]
        interface IBascule {
            /**
             * Event emitted when a withdrawal is validated.
             * @param withdrawalAmount Amount of the withdrawal.
             * @param depositID Unique identifier for a deposit that took place on another chain and was withdrawn on this chain.
             */
            event WithdrawalValidated(bytes32 depositID, uint256 withdrawalAmount);
            /**
             * Error on attempt to withdraw an already withdrawn deposit.
             * @param depositID Unique identifier for deposit that failed validation.
             * @param withdrawalAmount Amount of the withdrawal.
             */
            error AlreadyWithdrawn(bytes32 depositID, uint256 withdrawalAmount);
            /**
             * Error when a withdrawal fails validation.
             * This means the corresponding deposit is not in the map.
             * @param depositID Unique identifier for deposit that failed validation.
             * @param withdrawalAmount Amount of the withdrawal.
             */
            error WithdrawalFailedValidation(
                bytes32 depositID,
                uint256 withdrawalAmount
            );
            /**
             * Validate a withdrawal (before executing it) if the amount is above
             * threshold.
             *
             * This function checks if our accounting has recorded a deposit that
             * corresponds to this withdrawal request. A deposit can only be withdrawn
             * once.
             *
             * @param depositID Unique identifier of the deposit on another chain.
             * @param withdrawalAmount Amount of the withdrawal.
             *
             * Emits {WithdrawalValidated}.
             */
            function validateWithdrawal(
                bytes32 depositID,
                uint256 withdrawalAmount
            ) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
        import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
        import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
        import {Actions} from "../libs/Actions.sol";
        import {INotaryConsortium} from "./INotaryConsortium.sol";
        import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
        /// @title The contract utilizes consortium governance functions using multisignature verification
        /// @author Lombard.Finance
        /// @notice The contracts are a part of the Lombard.Finance protocol
        contract Consortium is Ownable2StepUpgradeable, INotaryConsortium {
            struct ValidatorSet {
                /// @notice addresses of the signers
                address[] validators;
                /// @notice weight of each signer
                uint256[] weights;
                /// @notice current threshold for signatures weight to be accepted
                uint256 weightThreshold;
            }
            /// @custom:storage-location erc7201:lombardfinance.storage.Consortium
            struct ConsortiumStorage {
                /// @notice Current epoch
                uint256 epoch;
                /// @notice Store the Validator set for each epoch
                mapping(uint256 => ValidatorSet) validatorSet;
            }
            // keccak256(abi.encode(uint256(keccak256("lombardfinance.storage.Consortium")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant CONSORTIUM_STORAGE_LOCATION =
                0xbac09a3ab0e06910f94a49c10c16eb53146536ec1a9e948951735cde3a58b500;
            /// @dev https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
            /// @custom:oz-upgrades-unsafe-allow constructor
            constructor() {
                _disableInitializers();
            }
            /// @notice Initializes the consortium contract
            /// @param _owner - The address of the initial owner
            function initialize(address _owner) external initializer {
                __Ownable_init(_owner);
                __Ownable2Step_init();
                __Consortium_init();
            }
            /// ONLY OWNER FUNCTIONS ///
            /// @notice Sets the initial validator set from any epoch
            /// @param _initialValSet - The initial list of validators
            function setInitialValidatorSet(
                bytes calldata _initialValSet
            ) external onlyOwner {
                // Payload validation
                if (bytes4(_initialValSet) != Actions.NEW_VALSET)
                    revert UnexpectedAction(bytes4(_initialValSet));
                ConsortiumStorage storage $ = _getConsortiumStorage();
                Actions.ValSetAction memory action = Actions.validateValSet(
                    _initialValSet[4:]
                );
                if ($.epoch != 0) {
                    revert ValSetAlreadySet();
                }
                _setValidatorSet(
                    $,
                    action.validators,
                    action.weights,
                    action.weightThreshold,
                    action.epoch
                );
            }
            /// USER ACTIONS ///
            /// @notice Validates the provided signature against the given hash
            /// @param _payloadHash the hash of the data to be signed
            /// @param _proof nonce, expiry and signatures to validate
            function checkProof(
                bytes32 _payloadHash,
                bytes calldata _proof
            ) public view override {
                _checkProof(_payloadHash, _proof);
            }
            function setNextValidatorSet(
                bytes calldata payload,
                bytes calldata proof
            ) external {
                // payload validation
                if (bytes4(payload) != Actions.NEW_VALSET) {
                    revert UnexpectedAction(bytes4(payload));
                }
                Actions.ValSetAction memory action = Actions.validateValSet(
                    payload[4:]
                );
                ConsortiumStorage storage $ = _getConsortiumStorage();
                // check proof
                bytes32 payloadHash = sha256(payload);
                checkProof(payloadHash, proof);
                if (action.epoch != $.epoch + 1) revert InvalidEpoch();
                _setValidatorSet(
                    $,
                    action.validators,
                    action.weights,
                    action.weightThreshold,
                    action.epoch
                );
            }
            /// GETTERS ///
            /// @notice Returns the validator for a given epoch
            /// @param epoch the epoch to get the threshold for
            function getValidatorSet(
                uint256 epoch
            ) external view returns (ValidatorSet memory) {
                return _getConsortiumStorage().validatorSet[epoch];
            }
            /// @notice Returns the current epoch
            function curEpoch() external view returns (uint256) {
                return _getConsortiumStorage().epoch;
            }
            /// PRIVATE FUNCTIONS ///
            /// @notice Internal initializer for the consortium
            function __Consortium_init() internal onlyInitializing {}
            function _setValidatorSet(
                ConsortiumStorage storage $,
                address[] memory _validators,
                uint256[] memory _weights,
                uint256 _threshold,
                uint256 _epoch
            ) internal {
                // do not allow to rewrite existing valset
                if ($.validatorSet[_epoch].weightThreshold != 0) {
                    revert InvalidEpoch();
                }
                $.epoch = _epoch;
                $.validatorSet[_epoch] = ValidatorSet({
                    validators: _validators,
                    weights: _weights,
                    weightThreshold: _threshold
                });
                emit ValidatorSetUpdated(_epoch, _validators, _weights, _threshold);
            }
            /// @dev Checks that `_proof` is correct
            /// @param _payloadHash data to be signed
            /// @param _proof encoding of signatures array
            /// @dev Negative weight means that the validator did not sign, any positive weight means that the validator signed
            function _checkProof(
                bytes32 _payloadHash,
                bytes calldata _proof
            ) internal view virtual {
                ConsortiumStorage storage $ = _getConsortiumStorage();
                if ($.epoch == 0) {
                    revert NoValidatorSet();
                }
                // decode proof
                bytes[] memory signatures = abi.decode(_proof, (bytes[]));
                address[] storage validators = $.validatorSet[$.epoch].validators;
                uint256 length = validators.length;
                if (signatures.length != length) {
                    revert LengthMismatch();
                }
                uint256 weight = 0;
                uint256[] storage weights = $.validatorSet[$.epoch].weights;
                for (uint256 i; i < length; ++i) {
                    // each signature preset R || S values
                    // V is missed, because validators use Cosmos SDK keyring which is not signing in eth style
                    // We only check signatures which are the expected 64 bytes long - we are expecting
                    // a signatures array with the same amount of items as there are validators, but not all
                    // validators will need to sign for a proof to be valid, so validators who have not signed
                    // will have their corresponding signature set to 0 bytes.
                    // In case of a malformed signature (i.e. length isn't 0 bytes but also isn't 64 bytes)
                    // this signature will be discarded.
                    if (signatures[i].length == 64) {
                        // split signature by R and S values
                        bytes memory sig = signatures[i];
                        bytes32 r;
                        bytes32 s;
                        // load the first 32 bytes (r) and the second 32 bytes (s) from the sig
                        assembly {
                            r := mload(add(sig, 0x20)) // first 32 bytes (offset 0x20)
                            s := mload(add(sig, 0x40)) // next 32 bytes (offset 0x40)
                        }
                        if (r != bytes32(0) && s != bytes32(0)) {
                            // try recover with V = 27
                            (address signer, ECDSA.RecoverError err, ) = ECDSA
                                .tryRecover(_payloadHash, 27, r, s);
                            // ignore if bad signature
                            if (err != ECDSA.RecoverError.NoError) {
                                continue;
                            }
                            // if signer doesn't match try V = 28
                            if (signer != validators[i]) {
                                (signer, err, ) = ECDSA.tryRecover(
                                    _payloadHash,
                                    28,
                                    r,
                                    s
                                );
                                if (err != ECDSA.RecoverError.NoError) {
                                    continue;
                                }
                                if (signer != validators[i]) {
                                    continue;
                                }
                            }
                            // signature accepted
                            unchecked {
                                weight += weights[i];
                            }
                        }
                    }
                }
                if (weight < $.validatorSet[$.epoch].weightThreshold) {
                    revert NotEnoughSignatures();
                }
            }
            /// @notice Retrieve the ConsortiumStorage struct from the specific storage slot
            function _getConsortiumStorage()
                private
                pure
                returns (ConsortiumStorage storage $)
            {
                assembly {
                    $.slot := CONSORTIUM_STORAGE_LOCATION
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
        interface INotaryConsortium {
            /// @dev Error thrown when signature payload is already used
            error PayloadAlreadyUsed();
            /// @dev Error thrown when signatures length is not equal to signers length
            error LengthMismatch();
            /// @dev Error thrown when there are not enough signatures
            error NotEnoughSignatures();
            /// @dev Error thrown when unexpected action is used
            error UnexpectedAction(bytes4 action);
            /// @dev Event emitted when the validator set is updated
            event ValidatorSetUpdated(
                uint256 indexed epoch,
                address[] validators,
                uint256[] weights,
                uint256 threshold
            );
            /// @dev Error thrown when validator set already set
            error ValSetAlreadySet();
            /// @dev Error thrown when no validator set is set
            error NoValidatorSet();
            /// @dev Error thrown when invalid epoch is provided
            error InvalidEpoch();
            function checkProof(
                bytes32 _payloadHash,
                bytes calldata _proof
            ) external view;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        interface ILBTC {
            error ZeroAddress();
            error ZeroContractHash();
            error ZeroChainId();
            error WithdrawalsDisabled();
            error KnownDestination();
            error UnknownDestination();
            error ScriptPubkeyUnsupported();
            error AmountLessThanCommission(uint256 fee);
            error AmountBelowDustLimit(uint256 dustLimit);
            error InvalidDustFeeRate();
            error UnauthorizedAccount(address account);
            error UnexpectedAction(bytes4 action);
            error InvalidUserSignature();
            error PayloadAlreadyUsed();
            error InvalidInputLength();
            error InvalidMintAmount();
            event PauserRoleTransferred(
                address indexed previousPauser,
                address indexed newPauser
            );
            event OperatorRoleTransferred(
                address indexed previousOperator,
                address indexed newOperator
            );
            event UnstakeRequest(
                address indexed fromAddress,
                bytes scriptPubKey,
                uint256 amount
            );
            event WithdrawalsEnabled(bool);
            event NameAndSymbolChanged(string name, string symbol);
            event ConsortiumChanged(address indexed prevVal, address indexed newVal);
            event TreasuryAddressChanged(
                address indexed prevValue,
                address indexed newValue
            );
            event BurnCommissionChanged(
                uint64 indexed prevValue,
                uint64 indexed newValue
            );
            event DustFeeRateChanged(uint256 indexed oldRate, uint256 indexed newRate);
            event BasculeChanged(address indexed prevVal, address indexed newVal);
            event MinterUpdated(address indexed minter, bool isMinter);
            event BridgeChanged(address indexed prevVal, address indexed newVal);
            event ClaimerUpdated(address indexed claimer, bool isClaimer);
            event FeeCharged(uint256 indexed fee, bytes userSignature);
            event FeeChanged(uint256 indexed oldFee, uint256 indexed newFee);
            error FeeGreaterThanAmount();
            event MintProofConsumed(
                address indexed recipient,
                bytes32 indexed payloadHash,
                bytes payload
            );
            event BatchMintSkipped(bytes32 indexed payloadHash, bytes payload);
            function burn(uint256 amount) external;
            function burn(address from, uint256 amount) external;
            function mint(address to, uint256 amount) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import {ERC20Upgradeable, IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
        import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
        import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
        import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
        import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
        import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
        import {BitcoinUtils, OutputType} from "../libs/BitcoinUtils.sol";
        import {IBascule} from "../bascule/interfaces/IBascule.sol";
        import {ILBTC} from "./ILBTC.sol";
        import {FeeUtils} from "../libs/FeeUtils.sol";
        import {Consortium} from "../consortium/Consortium.sol";
        import {Actions} from "../libs/Actions.sol";
        import {EIP1271SignatureUtils} from "../libs/EIP1271SignatureUtils.sol";
        /**
         * @title ERC20 representation of Lombard Staked Bitcoin
         * @author Lombard.Finance
         * @notice The contracts is a part of Lombard.Finace protocol
         */
        contract LBTC is
            ILBTC,
            ERC20PausableUpgradeable,
            Ownable2StepUpgradeable,
            ReentrancyGuardUpgradeable,
            ERC20PermitUpgradeable
        {
            /// @custom:storage-location erc7201:lombardfinance.storage.LBTC
            struct LBTCStorage {
                /// @dev is keccak256(payload[4:]) used
                /// @custom:oz-renamed-from usedProofs
                mapping(bytes32 => bool) legacyUsedPayloads;
                string name;
                string symbol;
                bool isWithdrawalsEnabled;
                address consortium;
                bool isWBTCEnabled;
                IERC20 wbtc;
                address treasury;
                /// @custom:oz-renamed-from destinations
                mapping(uint256 => address) __removed_destinations;
                /// @custom:oz-renamed-from depositCommission
                mapping(uint256 => uint16) __removed_depositCommission;
                /// @custom:oz-renamed-from usedBridgeProofs
                mapping(bytes32 => bool) __removed_usedBridgeProofs;
                /// @custom:oz-renamed-from globalNonce
                uint256 __removed_globalNonce;
                mapping(bytes32 => bytes32) __removed__destinations;
                mapping(bytes32 => uint16) __removed__depositRelativeCommission;
                mapping(bytes32 => uint64) __removed__depositAbsoluteCommission;
                uint64 burnCommission; // absolute commission to charge on burn (unstake)
                uint256 dustFeeRate;
                /// Bascule drawbridge used to confirm deposits before allowing withdrawals
                IBascule bascule;
                address pauser;
                mapping(address => bool) minters;
                mapping(address => bool) claimers;
                /// Maximum fee to apply on mints
                uint256 maximumFee;
                // @dev is sha256(payload) used
                mapping(bytes32 => bool) usedPayloads;
                address operator;
            }
            // keccak256(abi.encode(uint256(keccak256("lombardfinance.storage.LBTC")) - 1)) & ~bytes32(uint256(0xff))
            bytes32 private constant LBTC_STORAGE_LOCATION =
                0xa9a2395ec4edf6682d754acb293b04902817fdb5829dd13adb0367ab3a26c700;
            /// @dev https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
            /// @custom:oz-upgrades-unsafe-allow constructor
            constructor() {
                _disableInitializers();
            }
            /// INTIALIZERS ///
            function initialize(
                address consortium_,
                uint64 burnCommission_,
                address treasury,
                address owner_
            ) external initializer {
                __ERC20_init("", "");
                __ERC20Pausable_init();
                __Ownable_init(owner_);
                __Ownable2Step_init();
                __ReentrancyGuard_init();
                __LBTC_init(
                    "Lombard Staked Bitcoin",
                    "LBTC",
                    consortium_,
                    treasury,
                    burnCommission_
                );
                LBTCStorage storage $ = _getLBTCStorage();
                $.dustFeeRate = BitcoinUtils.DEFAULT_DUST_FEE_RATE;
                emit DustFeeRateChanged(0, $.dustFeeRate);
            }
            function reinitialize() external reinitializer(2) {
                __ERC20Permit_init("Lombard Staked Bitcoin");
            }
            /// MODIFIER ///
            /**
             * PAUSE
             */
            modifier onlyPauser() {
                _checkPauser();
                _;
            }
            modifier onlyMinter() {
                if (!_getLBTCStorage().minters[_msgSender()]) {
                    revert UnauthorizedAccount(_msgSender());
                }
                _;
            }
            modifier onlyClaimer() {
                if (!_getLBTCStorage().claimers[_msgSender()]) {
                    revert UnauthorizedAccount(_msgSender());
                }
                _;
            }
            modifier onlyOperator() {
                if (_getLBTCStorage().operator != _msgSender()) {
                    revert UnauthorizedAccount(_msgSender());
                }
                _;
            }
            /// ONLY OWNER FUNCTIONS ///
            function toggleWithdrawals() external onlyOwner {
                LBTCStorage storage $ = _getLBTCStorage();
                $.isWithdrawalsEnabled = !$.isWithdrawalsEnabled;
                emit WithdrawalsEnabled($.isWithdrawalsEnabled);
            }
            function changeNameAndSymbol(
                string calldata name_,
                string calldata symbol_
            ) external onlyOwner {
                _changeNameAndSymbol(name_, symbol_);
            }
            function changeConsortium(address newVal) external onlyOwner {
                _changeConsortium(newVal);
            }
            /**
             * @notice Set the contract current fee for mint
             * @param fee New fee value
             * @dev zero allowed to disable fee
             */
            function setMintFee(uint256 fee) external onlyOperator {
                LBTCStorage storage $ = _getLBTCStorage();
                uint256 oldFee = $.maximumFee;
                $.maximumFee = fee;
                emit FeeChanged(oldFee, fee);
            }
            function changeTreasuryAddress(address newValue) external onlyOwner {
                _changeTreasuryAddress(newValue);
            }
            function changeBurnCommission(uint64 newValue) external onlyOwner {
                _changeBurnCommission(newValue);
            }
            function pause() external onlyPauser {
                _pause();
            }
            function unpause() external onlyPauser {
                _unpause();
            }
            function addMinter(address newMinter) external onlyOwner {
                _updateMinter(newMinter, true);
            }
            function removeMinter(address oldMinter) external onlyOwner {
                _updateMinter(oldMinter, false);
            }
            function addClaimer(address newClaimer) external onlyOwner {
                _updateClaimer(newClaimer, true);
            }
            function removeClaimer(address oldClaimer) external onlyOwner {
                _updateClaimer(oldClaimer, false);
            }
            /// @notice Change the dust fee rate used for dust limit calculations
            /// @dev Only the contract owner can call this function. The new rate must be positive.
            /// @param newRate The new dust fee rate (in satoshis per 1000 bytes)
            function changeDustFeeRate(uint256 newRate) external onlyOwner {
                if (newRate == 0) revert InvalidDustFeeRate();
                LBTCStorage storage $ = _getLBTCStorage();
                uint256 oldRate = $.dustFeeRate;
                $.dustFeeRate = newRate;
                emit DustFeeRateChanged(oldRate, newRate);
            }
            /**
             * Change the address of the Bascule drawbridge contract.
             * Setting the address to 0 disables the Bascule check.
             * @param newVal The new address.
             *
             * Emits a {BasculeChanged} event.
             */
            function changeBascule(address newVal) external onlyOwner {
                _changeBascule(newVal);
            }
            function transferPauserRole(address newPauser) external onlyOwner {
                if (newPauser == address(0)) {
                    revert ZeroAddress();
                }
                _transferPauserRole(newPauser);
            }
            function transferOperatorRole(address newOperator) external onlyOwner {
                if (newOperator == address(0)) {
                    revert ZeroAddress();
                }
                _transferOperatorRole(newOperator);
            }
            /// GETTERS ///
            /**
             * @notice Returns the current maximum mint fee
             */
            function getMintFee() external view returns (uint256) {
                return _getLBTCStorage().maximumFee;
            }
            /// @notice Calculate the amount that will be unstaked and check if it's above the dust limit
            /// @dev This function can be used by front-ends to verify burn amounts before submitting a transaction
            /// @param scriptPubkey The Bitcoin script public key as a byte array
            /// @param amount The amount of LBTC to be burned
            /// @return amountAfterFee The amount that will be unstaked (after deducting the burn commission)
            /// @return isAboveDust Whether the amountAfterFee is equal to or above the dust limit
            function calcUnstakeRequestAmount(
                bytes calldata scriptPubkey,
                uint256 amount
            ) external view returns (uint256 amountAfterFee, bool isAboveDust) {
                LBTCStorage storage $ = _getLBTCStorage();
                (amountAfterFee, , , isAboveDust) = _calcFeeAndDustLimit(
                    scriptPubkey,
                    amount,
                    $.burnCommission
                );
                return (amountAfterFee, isAboveDust);
            }
            function consortium() external view virtual returns (address) {
                return _getLBTCStorage().consortium;
            }
            /**
             * @dev Returns the number of decimals used to get its user representation.
             *
             * Because LBTC repsents BTC we use the same decimals.
             *
             */
            function decimals() public view virtual override returns (uint8) {
                return 8;
            }
            /**
             * @dev Returns the name of the token.
             */
            function name() public view virtual override returns (string memory) {
                return _getLBTCStorage().name;
            }
            /**
             * @dev Returns the symbol of the token, usually a shorter version of the
             * name.
             */
            function symbol() public view virtual override returns (string memory) {
                return _getLBTCStorage().symbol;
            }
            function getTreasury() public view returns (address) {
                return _getLBTCStorage().treasury;
            }
            function getBurnCommission() public view returns (uint64) {
                return _getLBTCStorage().burnCommission;
            }
            /// @notice Get the current dust fee rate
            /// @return The current dust fee rate (in satoshis per 1000 bytes)
            function getDustFeeRate() public view returns (uint256) {
                return _getLBTCStorage().dustFeeRate;
            }
            /**
             * Get Bascule contract.
             */
            function Bascule() external view returns (IBascule) {
                return _getLBTCStorage().bascule;
            }
            function pauser() public view returns (address) {
                return _getLBTCStorage().pauser;
            }
            function operator() external view returns (address) {
                return _getLBTCStorage().operator;
            }
            function isMinter(address minter) external view returns (bool) {
                return _getLBTCStorage().minters[minter];
            }
            function isClaimer(address claimer) external view returns (bool) {
                return _getLBTCStorage().claimers[claimer];
            }
            /// USER ACTIONS ///
            /**
             * @notice Mint LBTC to the specified address
             * @param to The address to mint to
             * @param amount The amount of LBTC to mint
             * @dev Only callable by whitelisted minters
             */
            function mint(address to, uint256 amount) external override onlyMinter {
                _mint(to, amount);
            }
            /**
             * @notice Mint LBTC in batches
             * @param to The addresses to mint to
             * @param amount The amounts of LBTC to mint
             * @dev Only callable by whitelisted minters
             */
            function batchMint(
                address[] calldata to,
                uint256[] calldata amount
            ) external onlyMinter {
                if (to.length != amount.length) {
                    revert InvalidInputLength();
                }
                for (uint256 i; i < to.length; ++i) {
                    _mint(to[i], amount[i]);
                }
            }
            /**
             * @notice Mint LBTC by proving a stake action happened
             * @param payload The message with the stake data
             * @param proof Signature of the consortium approving the mint
             */
            function mint(
                bytes calldata payload,
                bytes calldata proof
            ) public nonReentrant {
                // payload validation
                if (bytes4(payload) != Actions.DEPOSIT_BTC_ACTION) {
                    revert UnexpectedAction(bytes4(payload));
                }
                Actions.DepositBtcAction memory action = Actions.depositBtc(
                    payload[4:]
                );
                _validateAndMint(
                    action.recipient,
                    action.amount,
                    action.amount,
                    payload,
                    proof
                );
            }
            /**
             * @notice Mint LBTC in batches by proving stake actions happened
             * @param payload The messages with the stake data
             * @param proof Signatures of the consortium approving the mints
             */
            function batchMint(
                bytes[] calldata payload,
                bytes[] calldata proof
            ) external {
                if (payload.length != proof.length) {
                    revert InvalidInputLength();
                }
                LBTCStorage storage $ = _getLBTCStorage();
                for (uint256 i; i < payload.length; ++i) {
                    // Pre-emptive check if payload was used. If so, we can skip the call.
                    bytes32 payloadHash = sha256(payload[i]);
                    if (
                        $.usedPayloads[payloadHash] ||
                        $.legacyUsedPayloads[keccak256(payload[i][4:])]
                    ) {
                        emit BatchMintSkipped(payloadHash, payload[i]);
                        continue;
                    }
                    mint(payload[i], proof[i]);
                }
            }
            /**
             * @notice Mint LBTC applying a commission to the amount
             * @dev Payload should be same as mint to avoid reusing them with and without fee
             * @param mintPayload The message with the stake data
             * @param proof Signature of the consortium approving the mint
             * @param feePayload Contents of the fee approval signed by the user
             * @param userSignature Signature of the user to allow Fee
             */
            function mintWithFee(
                bytes calldata mintPayload,
                bytes calldata proof,
                bytes calldata feePayload,
                bytes calldata userSignature
            ) external onlyClaimer {
                _mintWithFee(mintPayload, proof, feePayload, userSignature);
            }
            /**
             * @notice Mint LBTC in batches proving stake actions happened
             * @param mintPayload The messages with the stake data
             * @param proof Signatures of the consortium approving the mints
             * @param feePayload Contents of the fee approvals signed by the user
             * @param userSignature Signatures of the user to allow Fees
             */
            function batchMintWithFee(
                bytes[] calldata mintPayload,
                bytes[] calldata proof,
                bytes[] calldata feePayload,
                bytes[] calldata userSignature
            ) external onlyClaimer {
                uint256 length = mintPayload.length;
                if (
                    length != proof.length ||
                    length != feePayload.length ||
                    length != userSignature.length
                ) {
                    revert InvalidInputLength();
                }
                LBTCStorage storage $ = _getLBTCStorage();
                for (uint256 i; i < mintPayload.length; ++i) {
                    // Pre-emptive check if payload was used. If so, we can skip the call.
                    bytes32 payloadHash = sha256(mintPayload[i]);
                    if (
                        $.usedPayloads[payloadHash] ||
                        $.legacyUsedPayloads[keccak256(mintPayload[i][4:])]
                    ) {
                        emit BatchMintSkipped(payloadHash, mintPayload[i]);
                        continue;
                    }
                    _mintWithFee(
                        mintPayload[i],
                        proof[i],
                        feePayload[i],
                        userSignature[i]
                    );
                }
            }
            /**
             * @dev Burns LBTC to initiate withdrawal of BTC to provided `scriptPubkey` with `amount`
             *
             * @param scriptPubkey scriptPubkey for output
             * @param amount Amount of LBTC to burn
             */
            function redeem(bytes calldata scriptPubkey, uint256 amount) external {
                LBTCStorage storage $ = _getLBTCStorage();
                if (!$.isWithdrawalsEnabled) {
                    revert WithdrawalsDisabled();
                }
                uint64 fee = $.burnCommission;
                (
                    uint256 amountAfterFee,
                    bool isAboveFee,
                    uint256 dustLimit,
                    bool isAboveDust
                ) = _calcFeeAndDustLimit(scriptPubkey, amount, fee);
                if (!isAboveFee) {
                    revert AmountLessThanCommission(fee);
                }
                if (!isAboveDust) {
                    revert AmountBelowDustLimit(dustLimit);
                }
                address fromAddress = address(_msgSender());
                _transfer(fromAddress, getTreasury(), fee);
                _burn(fromAddress, amountAfterFee);
                emit UnstakeRequest(fromAddress, scriptPubkey, amountAfterFee);
            }
            /**
             * @dev Burns LBTC
             *
             * @param amount Amount of LBTC to burn
             */
            function burn(uint256 amount) external {
                _burn(_msgSender(), amount);
            }
            /**
             * @dev Allows minters to burn LBTC
             *
             * @param amount Amount of LBTC to burn
             */
            function burn(address from, uint256 amount) external override onlyMinter {
                _burn(from, amount);
            }
            /// PRIVATE FUNCTIONS ///
            function __LBTC_init(
                string memory name_,
                string memory symbol_,
                address consortium_,
                address treasury,
                uint64 burnCommission_
            ) internal onlyInitializing {
                _changeNameAndSymbol(name_, symbol_);
                _changeConsortium(consortium_);
                _changeTreasuryAddress(treasury);
                _changeBurnCommission(burnCommission_);
            }
            function _changeNameAndSymbol(
                string memory name_,
                string memory symbol_
            ) internal {
                LBTCStorage storage $ = _getLBTCStorage();
                $.name = name_;
                $.symbol = symbol_;
                emit NameAndSymbolChanged(name_, symbol_);
            }
            function _changeConsortium(address newVal) internal {
                if (newVal == address(0)) {
                    revert ZeroAddress();
                }
                LBTCStorage storage $ = _getLBTCStorage();
                emit ConsortiumChanged($.consortium, newVal);
                $.consortium = newVal;
            }
            function _validateAndMint(
                address recipient,
                uint256 amountToMint,
                uint256 depositAmount,
                bytes calldata payload,
                bytes calldata proof
            ) internal {
                LBTCStorage storage $ = _getLBTCStorage();
                if (amountToMint > depositAmount) revert InvalidMintAmount();
                /// make sure that hash of payload not used before
                /// need to check new sha256 hash and legacy keccak256 from payload without selector
                /// 2 checks made to prevent migration of contract state
                bytes32 payloadHash = sha256(payload);
                bytes32 legacyHash = keccak256(payload[4:]);
                if ($.usedPayloads[payloadHash] || $.legacyUsedPayloads[legacyHash]) {
                    revert PayloadAlreadyUsed();
                }
                Consortium($.consortium).checkProof(payloadHash, proof);
                $.usedPayloads[payloadHash] = true;
                // Confirm deposit against Bascule
                _confirmDeposit($, legacyHash, depositAmount);
                // Actually mint
                _mint(recipient, amountToMint);
                emit MintProofConsumed(recipient, payloadHash, payload);
            }
            function _changeBurnCommission(uint64 newValue) internal {
                LBTCStorage storage $ = _getLBTCStorage();
                uint64 prevValue = $.burnCommission;
                $.burnCommission = newValue;
                emit BurnCommissionChanged(prevValue, newValue);
            }
            /**
             * @dev Checks that the deposit was validated by the Bascule drawbridge.
             * @param self LBTC storage.
             * @param depositID The unique ID of the deposit.
             * @param amount The withdrawal amount.
             */
            function _confirmDeposit(
                LBTCStorage storage self,
                bytes32 depositID,
                uint256 amount
            ) internal {
                IBascule bascule = self.bascule;
                if (address(bascule) != address(0)) {
                    bascule.validateWithdrawal(depositID, amount);
                }
            }
            /**
             * Change the address of the Bascule drawbridge contract.
             * @param newVal The new address.
             *
             * Emits a {BasculeChanged} event.
             */
            function _changeBascule(address newVal) internal {
                LBTCStorage storage $ = _getLBTCStorage();
                emit BasculeChanged(address($.bascule), newVal);
                $.bascule = IBascule(newVal);
            }
            function _transferPauserRole(address newPauser) internal {
                LBTCStorage storage $ = _getLBTCStorage();
                address oldPauser = $.pauser;
                $.pauser = newPauser;
                emit PauserRoleTransferred(oldPauser, newPauser);
            }
            function _transferOperatorRole(address newOperator) internal {
                LBTCStorage storage $ = _getLBTCStorage();
                address oldOperator = $.operator;
                $.operator = newOperator;
                emit OperatorRoleTransferred(oldOperator, newOperator);
            }
            function _mintWithFee(
                bytes calldata mintPayload,
                bytes calldata proof,
                bytes calldata feePayload,
                bytes calldata userSignature
            ) internal nonReentrant {
                // mint payload validation
                if (bytes4(mintPayload) != Actions.DEPOSIT_BTC_ACTION) {
                    revert UnexpectedAction(bytes4(mintPayload));
                }
                Actions.DepositBtcAction memory mintAction = Actions.depositBtc(
                    mintPayload[4:]
                );
                // fee payload validation
                if (bytes4(feePayload) != Actions.FEE_APPROVAL_ACTION) {
                    revert UnexpectedAction(bytes4(feePayload));
                }
                Actions.FeeApprovalAction memory feeAction = Actions.feeApproval(
                    feePayload[4:]
                );
                LBTCStorage storage $ = _getLBTCStorage();
                uint256 fee = $.maximumFee;
                if (fee > feeAction.fee) {
                    fee = feeAction.fee;
                }
                if (fee >= mintAction.amount) {
                    revert FeeGreaterThanAmount();
                }
                {
                    // Fee validation
                    bytes32 digest = _hashTypedDataV4(
                        keccak256(
                            abi.encode(
                                Actions.FEE_APPROVAL_EIP712_ACTION,
                                block.chainid,
                                feeAction.fee,
                                feeAction.expiry
                            )
                        )
                    );
                    if (
                        !EIP1271SignatureUtils.checkSignature(
                            mintAction.recipient,
                            digest,
                            userSignature
                        )
                    ) {
                        revert InvalidUserSignature();
                    }
                }
                // modified payload to be signed
                _validateAndMint(
                    mintAction.recipient,
                    mintAction.amount - fee,
                    mintAction.amount,
                    mintPayload,
                    proof
                );
                // mint fee to treasury
                _mint($.treasury, fee);
                emit FeeCharged(fee, userSignature);
            }
            function _checkPauser() internal view {
                if (pauser() != _msgSender()) {
                    revert UnauthorizedAccount(_msgSender());
                }
            }
            function _updateMinter(address minter, bool _isMinter) internal {
                if (minter == address(0)) {
                    revert ZeroAddress();
                }
                _getLBTCStorage().minters[minter] = _isMinter;
                emit MinterUpdated(minter, _isMinter);
            }
            function _updateClaimer(address claimer, bool _isClaimer) internal {
                if (claimer == address(0)) {
                    revert ZeroAddress();
                }
                _getLBTCStorage().claimers[claimer] = _isClaimer;
                emit ClaimerUpdated(claimer, _isClaimer);
            }
            function _changeTreasuryAddress(address newValue) internal {
                if (newValue == address(0)) {
                    revert ZeroAddress();
                }
                LBTCStorage storage $ = _getLBTCStorage();
                address prevValue = $.treasury;
                $.treasury = newValue;
                emit TreasuryAddressChanged(prevValue, newValue);
            }
            function _calcFeeAndDustLimit(
                bytes calldata scriptPubkey,
                uint256 amount,
                uint64 fee
            ) internal view returns (uint256, bool, uint256, bool) {
                OutputType outType = BitcoinUtils.getOutputType(scriptPubkey);
                if (outType == OutputType.UNSUPPORTED) {
                    revert ScriptPubkeyUnsupported();
                }
                if (amount <= fee) {
                    return (0, false, 0, false);
                }
                LBTCStorage storage $ = _getLBTCStorage();
                uint256 amountAfterFee = amount - fee;
                uint256 dustLimit = BitcoinUtils.getDustLimitForOutput(
                    outType,
                    scriptPubkey,
                    $.dustFeeRate
                );
                bool isAboveDust = amountAfterFee > dustLimit;
                return (amountAfterFee, true, dustLimit, isAboveDust);
            }
            function _getLBTCStorage() private pure returns (LBTCStorage storage $) {
                assembly {
                    $.slot := LBTC_STORAGE_LOCATION
                }
            }
            /**
             * @dev Override of the _update function to satisfy both ERC20Upgradeable and ERC20PausableUpgradeable
             */
            function _update(
                address from,
                address to,
                uint256 value
            ) internal virtual override(ERC20Upgradeable, ERC20PausableUpgradeable) {
                super._update(from, to, value);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        library Actions {
            struct DepositBtcAction {
                uint256 toChain;
                address recipient;
                uint256 amount;
                bytes32 txid;
                uint32 vout;
            }
            struct DepositBridgeAction {
                uint256 fromChain;
                bytes32 fromContract;
                uint256 toChain;
                address toContract;
                address recipient;
                uint64 amount;
                uint256 nonce;
            }
            struct ValSetAction {
                uint256 epoch;
                address[] validators;
                uint256[] weights;
                uint256 weightThreshold;
                uint256 height;
            }
            struct FeeApprovalAction {
                uint256 fee;
                uint256 expiry;
            }
            /// @dev Error thrown when invalid public key is provided
            error InvalidPublicKey(bytes pubKey);
            /// @dev Error thrown when signatures length is not equal to signers length
            error Actions_LengthMismatch();
            /// @dev Error thrown when threshold is invalid
            error InvalidThreshold();
            /// @dev Error thrown when validator set size is invalid
            error InvalidValidatorSetSize();
            /// @dev Error thrown when zero validator is provided
            error ZeroValidator();
            /// @dev Error thrown when wrong chain id is provided
            error WrongChainId();
            /// @dev Error thrown when wrong contract is provided
            error WrongContract();
            /// @dev Error thrown when zero address is provided
            error Actions_ZeroAddress();
            /// @dev Error thrown when zero amount is provided
            error ZeroAmount();
            /// @dev Error thrown when zero weight is provided
            error ZeroWeight();
            /// @dev Error thrown when fee approval is expired
            error UserSignatureExpired(uint256 expiry);
            /// @dev Error thrown when amount is below fee
            error NotEnoughAmountToUseApproval();
            /// @dev Error thrown when zero fee is used
            error ZeroFee();
            /// @dev Error thrown when payload length is too big
            error PayloadTooLarge();
            // bytes4(keccak256("feeApproval(uint256,uint256)"))
            bytes4 internal constant FEE_APPROVAL_ACTION = 0x8175ca94;
            // keccak256("feeApproval(uint256 chainId,uint256 fee,uint256 expiry)")
            bytes32 internal constant FEE_APPROVAL_EIP712_ACTION =
                0x40ac9f6aa27075e64c1ed1ea2e831b20b8c25efdeb6b79fd0cf683c9a9c50725;
            // bytes4(keccak256("payload(bytes32,bytes32,uint64,bytes32,uint32)"))
            bytes4 internal constant DEPOSIT_BTC_ACTION = 0xf2e73f7c;
            // bytes4(keccak256("payload(bytes32,bytes32,bytes32,bytes32,bytes32,uint64,uint256)"))
            bytes4 internal constant DEPOSIT_BRIDGE_ACTION = 0x5c70a505;
            // bytes4(keccak256("payload(uint256,bytes[],uint256[],uint256,uint256)"))
            bytes4 internal constant NEW_VALSET = 0x4aab1d6f;
            /// @dev Maximum number of validators allowed in the consortium.
            /// @notice This value is determined by the minimum of CometBFT consensus limitations and gas considerations:
            /// - CometBFT has a hard limit of 10,000 validators (https://docs.cometbft.com/v0.38/spec/core/state)
            /// - Gas-based calculation:
            ///   - Assumes 4281 gas per ECDSA signature verification
            ///   - Uses a conservative 30 million gas block limit
            ///   - Maximum possible signatures: 30,000,000 / 4,281 ≈ 7007
            ///   - Reverse calculated for BFT consensus (2/3 + 1):
            ///     7,007 = (10,509 * 2/3 + 1) rounded down
            /// - The lower value of 10,000 (CometBFT limit) and 10,509 (gas calculation) is chosen
            /// @dev This limit ensures compatibility with CometBFT while also considering gas limitations
            ///      for signature verification within a single block.
            uint256 private constant MAX_VALIDATOR_SET_SIZE = 102;
            /// @dev Minimum number of validators allowed in the system.
            /// @notice While set to 1 to allow for non-distributed scenarios, this configuration
            /// does not provide Byzantine fault tolerance. For a truly distributed and
            /// fault-tolerant system, a minimum of 4 validators would be recommended to tolerate
            /// at least one Byzantine fault.
            uint256 private constant MIN_VALIDATOR_SET_SIZE = 1;
            /// @dev A constant representing the number of bytes for a slot of information in a payload.
            uint256 internal constant ABI_SLOT_SIZE = 32;
            /**
             * @notice Returns decoded deposit btc msg
             * @dev Message should not contain the selector
             * @param payload Body of the mint payload
             */
            function depositBtc(
                bytes memory payload
            ) internal view returns (DepositBtcAction memory) {
                if (payload.length != ABI_SLOT_SIZE * 5) revert PayloadTooLarge();
                (
                    uint256 toChain,
                    address recipient,
                    uint256 amount,
                    bytes32 txid,
                    uint32 vout
                ) = abi.decode(payload, (uint256, address, uint256, bytes32, uint32));
                if (toChain != block.chainid) {
                    revert WrongChainId();
                }
                if (recipient == address(0)) {
                    revert Actions_ZeroAddress();
                }
                if (amount == 0) {
                    revert ZeroAmount();
                }
                return DepositBtcAction(toChain, recipient, amount, txid, vout);
            }
            /**
             * @notice Returns decoded bridge payload
             * @dev Payload should not contain the selector
             * @param payload Body of the burn payload
             */
            function depositBridge(
                bytes memory payload
            ) internal view returns (DepositBridgeAction memory) {
                if (payload.length != ABI_SLOT_SIZE * 7) revert PayloadTooLarge();
                (
                    uint256 fromChain,
                    bytes32 fromContract,
                    uint256 toChain,
                    address toContract,
                    address recipient,
                    uint64 amount,
                    uint256 nonce
                ) = abi.decode(
                        payload,
                        (uint256, bytes32, uint256, address, address, uint64, uint256)
                    );
                if (toChain != block.chainid) {
                    revert WrongChainId();
                }
                if (recipient == address(0)) {
                    revert Actions_ZeroAddress();
                }
                if (amount == 0) {
                    revert ZeroAmount();
                }
                return
                    DepositBridgeAction(
                        fromChain,
                        fromContract,
                        toChain,
                        toContract,
                        recipient,
                        amount,
                        nonce
                    );
            }
            /**
             * @notice Returns decoded validator set
             * @dev Payload should not contain the selector
             * @param payload Body of the set validators set payload
             */
            function validateValSet(
                bytes memory payload
            ) internal pure returns (ValSetAction memory) {
                (
                    uint256 epoch,
                    bytes[] memory pubKeys,
                    uint256[] memory weights,
                    uint256 weightThreshold,
                    uint256 height
                ) = abi.decode(
                        payload,
                        (uint256, bytes[], uint256[], uint256, uint256)
                    );
                // Since dynamic arrays can variably insert more slots of data for things such as data length,
                // offset etc., we will just encode the received variables again and check for a length match.
                bytes memory reEncodedPayload = abi.encode(
                    epoch,
                    pubKeys,
                    weights,
                    weightThreshold,
                    height
                );
                if (reEncodedPayload.length != payload.length) revert PayloadTooLarge();
                if (
                    pubKeys.length < MIN_VALIDATOR_SET_SIZE ||
                    pubKeys.length > MAX_VALIDATOR_SET_SIZE
                ) revert InvalidValidatorSetSize();
                if (pubKeys.length != weights.length) revert Actions_LengthMismatch();
                if (weightThreshold == 0) revert InvalidThreshold();
                uint256 sum = 0;
                for (uint256 i; i < weights.length; ) {
                    if (weights[i] == 0) {
                        revert ZeroWeight();
                    }
                    sum += weights[i];
                    unchecked {
                        ++i;
                    }
                }
                if (sum < weightThreshold) revert InvalidThreshold();
                address[] memory validators = pubKeysToAddress(pubKeys);
                return
                    ValSetAction(epoch, validators, weights, weightThreshold, height);
            }
            function pubKeysToAddress(
                bytes[] memory _pubKeys
            ) internal pure returns (address[] memory) {
                address[] memory addresses = new address[](_pubKeys.length);
                for (uint256 i; i < _pubKeys.length; ) {
                    // each pubkey represented as uncompressed
                    if (_pubKeys[i].length == 65) {
                        bytes memory data = _pubKeys[i];
                        // Ensure that first byte of pubkey is 0x04
                        if (_pubKeys[i][0] != 0x04)
                            revert InvalidPublicKey(_pubKeys[i]);
                        // create a new array with length - 1 (excluding the first 0x04 byte)
                        bytes memory result = new bytes(data.length - 1);
                        // use inline assembly for memory manipulation
                        assembly {
                            // calculate the start of the `result` and `data` in memory
                            let resultData := add(result, 0x20) // points to the first byte of the result
                            let dataStart := add(data, 0x21) // points to the second byte of data (skip 0x04)
                            // copy 64 bytes from input (excluding the first byte) to result
                            mstore(resultData, mload(dataStart)) // copy the first 32 bytes
                            mstore(add(resultData, 0x20), mload(add(dataStart, 0x20))) // copy the next 32 bytes
                        }
                        addresses[i] = address(uint160(uint256(keccak256(result))));
                    } else {
                        revert InvalidPublicKey(_pubKeys[i]);
                    }
                    unchecked {
                        ++i;
                    }
                }
                return addresses;
            }
            /**
             * @notice Returns decoded fee approval
             * @dev Payload should not contain the selector
             * @param payload Body of the fee approval payload
             */
            function feeApproval(
                bytes memory payload
            ) internal view returns (FeeApprovalAction memory) {
                if (payload.length != ABI_SLOT_SIZE * 2) revert PayloadTooLarge();
                (uint256 fee, uint256 expiry) = abi.decode(payload, (uint256, uint256));
                if (block.timestamp > expiry) {
                    revert UserSignatureExpired(expiry);
                }
                if (fee == 0) {
                    revert ZeroFee();
                }
                return FeeApprovalAction(fee, expiry);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
        enum OutputType {
            UNSUPPORTED,
            P2TR,
            P2WPKH,
            P2WSH
        }
        bytes1 constant OP_0 = 0x00;
        bytes1 constant OP_1 = 0x51;
        bytes1 constant OP_DATA_32 = 0x20;
        bytes1 constant OP_DATA_20 = 0x14;
        uint256 constant BASE_SPEND_COST = 49; // 32 (txid) + 4 (vout) + 1 (scriptSig size) + 4 (nSequence) + 8 (amount)
        // Size of inputs spending different output types
        uint256 constant NON_WITNESS_INPUT_SIZE = 107; // Used for non-witness outputs (P2PKH, P2SH)
        uint256 constant WITNESS_INPUT_SIZE = 26; // floor(107 / 4), used for witness outputs (P2WPKH, P2WSH, P2TR)
        library BitcoinUtils {
            uint256 public constant DEFAULT_DUST_FEE_RATE = 3000; // Default value - 3 satoshis per byte
            function getOutputType(
                bytes calldata scriptPubkey
            ) internal pure returns (OutputType) {
                if (
                    scriptPubkey.length == 22 &&
                    scriptPubkey[0] == OP_0 &&
                    scriptPubkey[1] == OP_DATA_20
                ) {
                    return OutputType.P2WPKH;
                }
                if (
                    scriptPubkey.length == 34 &&
                    scriptPubkey[0] == OP_1 &&
                    scriptPubkey[1] == OP_DATA_32
                ) {
                    return OutputType.P2TR;
                }
                if (
                    scriptPubkey.length == 34 &&
                    scriptPubkey[0] == OP_0 &&
                    scriptPubkey[1] == OP_DATA_32
                ) {
                    return OutputType.P2WSH;
                }
                return OutputType.UNSUPPORTED;
            }
            /// @notice Compute the dust limit for a given Bitcoin script public key
            /// @dev The dust limit is the minimum payment to an address that is considered
            ///      spendable under consensus rules. This function is based on Bitcoin Core's
            ///      implementation.
            /// @param scriptPubkey The Bitcoin script public key as a byte array
            /// @param dustFeeRate The current dust fee rate (in satoshis per 1000 bytes)
            /// @return dustLimit The calculated dust limit in satoshis
            /// @custom:reference https://github.com/bitcoin/bitcoin/blob/43740f4971f45cd5499470b6a085b3ecd8b96d28/src/policy/policy.cpp#L54
            function getDustLimitForOutput(
                OutputType outType,
                bytes calldata scriptPubkey,
                uint256 dustFeeRate
            ) internal pure returns (uint256 dustLimit) {
                uint256 spendCost = BASE_SPEND_COST;
                if (
                    outType == OutputType.P2TR ||
                    outType == OutputType.P2WPKH ||
                    outType == OutputType.P2WSH
                ) {
                    // witness v0 and v1 has a cheaper payment formula
                    spendCost += WITNESS_INPUT_SIZE;
                    // The current addition creates a discrepancy of 1, and our final value should be 98 bytes.
                    // Thus, we add 1 here.
                    spendCost += 1;
                } else {
                    spendCost += NON_WITNESS_INPUT_SIZE;
                }
                spendCost += scriptPubkey.length;
                // Calculate dust limit
                dustLimit = Math.ceilDiv(spendCost * dustFeeRate, 1000);
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
        import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
        /**
         * @title Library of utilities for making EIP1271-compliant signature checks.
         * @author Lombard.Finance
         * @notice The contracts is a part of Lombard.Finace protocol
         */
        library EIP1271SignatureUtils {
            // bytes4(keccak256("isValidSignature(bytes32,bytes)")
            bytes4 internal constant EIP1271_MAGICVALUE = 0x1626ba7e;
            bytes4 internal constant EIP1271_WRONGVALUE = 0xffffffff;
            /**
             * @notice Checks @param signature is a valid signature of @param digest from @param signer.
             * If the `signer` contains no code -- i.e. it is not (yet, at least) a contract address, then checks using standard ECDSA logic
             * Otherwise, passes on the signature to the signer to verify the signature and checks that it returns the `EIP1271_MAGICVALUE`.
             */
            function checkSignature(
                address signer,
                bytes32 digest,
                bytes memory signature
            ) internal view returns (bool) {
                if (signer.code.length != 0) {
                    if (
                        IERC1271(signer).isValidSignature(digest, signature) !=
                        EIP1271_MAGICVALUE
                    ) {
                        return false;
                    }
                } else {
                    if (ECDSA.recover(digest, signature) != signer) {
                        return false;
                    }
                }
                return true;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity 0.8.24;
        import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
        library FeeUtils {
            uint256 constant MAX_COMMISSION = 10000; // 100%
            error AmountTooSmallToPayRelativeFee();
            error BadCommission();
            function calcRelativeFee(
                uint256 amount,
                uint16 relativeComs
            ) internal pure returns (uint256) {
                return
                    Math.mulDiv(
                        amount,
                        relativeComs,
                        MAX_COMMISSION,
                        Math.Rounding.Ceil
                    );
            }
            function getRelativeFee(
                uint256 amount,
                uint16 relativeComs
            ) internal pure returns (uint256) {
                if (amount < relativeComs) revert AmountTooSmallToPayRelativeFee();
                return calcRelativeFee(amount, relativeComs);
            }
            function validateCommission(uint16 commission) internal pure {
                if (commission >= MAX_COMMISSION) revert BadCommission();
            }
        }