Sepolia Testnet

Token

ERC-20: Vote Maia - Burned Hermes: Aggregated Gov ... (vMAIA-bHERMES)
ERC-20

Overview

Max Total Supply

18,564.578596638692921314 vMAIA-bHERMES

Holders

587

Market

Onchain Market Cap

$0.00

Circulating Supply Market Cap

-

Other Info

Token Contract (WITH 18 Decimals)

Balance
0.04 vMAIA-bHERMES
0x0172fb8c846e311cee65bc27209a0b1afea494bc
Loading...
Loading
Loading...
Loading
Loading...
Loading

Click here to update the token information / general information
# Exchange Pair Price  24H Volume % Volume

Similar Match Source Code
This contract matches the deployed Bytecode of the Source Code for Contract 0x2bc7a31E...39465E976
The constructor portion of the code might be different and could alter the actual behaviour of the contract

Contract Name:
VoteMaia

Compiler Version
v0.8.19+commit.7dd6d404

Optimization Enabled:
Yes with 1000000 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity Standard Json-Input format)

File 1 of 48 : VoteMaia.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol";
import {SafeCastLib} from "lib/solady/src/utils/SafeCastLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {DateTimeLib} from "./libraries/DateTimeLib.sol";
import {ERC4626PartnerManager, PartnerManagerFactory} from "./tokens/ERC4626PartnerManager.sol";

/**
 * @title VoteMaia: Yield bearing, boosting, voting, and gauge enabled MAIA
 * @author Maia DAO (https://github.com/Maia-DAO)
 * @notice VoteMaia is an ERC-4626 compliant MAIA token which:
 *         distributes BurntHermes utility tokens (Weight, Governance) and Maia Governance
 *         in exchange for staking MAIA.
 *
 *         NOTE: Withdraw is only allowed once per month,
 *               during the 1st Tuesday (UTC+0) of the month that someone withdraws.
 */
contract VoteMaia is ERC4626PartnerManager {
    using SafeCastLib for uint256;
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                            VOTE MAIA STATE
    ///////////////////////////////////////////////////////////////*/

    uint128 private currentMonth;
    uint128 private unstakePeriodEnd;

    /**
     * @notice Initializes the VoteMaia token.
     * @param _factory The factory that keeps the registry for all partner tokens and vaults.
     * @param _bHermesRate The rate at which BurntHermes can be claimed.
     * @param _partnerAsset The asset that will be used to deposit to get VoteMaia.
     * @param _name The name of the token.
     * @param _symbol The symbol of the token.
     * @param _bHermes The address of the BurntHermes token.
     * @param _partnerVault The address of the partner vault.
     * @param _owner The owner of the token.
     */
    constructor(
        PartnerManagerFactory _factory,
        uint256 _bHermesRate,
        ERC20 _partnerAsset,
        string memory _name,
        string memory _symbol,
        address _bHermes,
        address _partnerVault,
        address _owner
    ) ERC4626PartnerManager(_factory, _bHermesRate, _partnerAsset, _name, _symbol, _bHermes, _partnerVault, _owner) {
        // Set the current month to the current month.
        currentMonth = DateTimeLib.getMonth(block.timestamp).toUint128();
    }

    /*///////////////////////////////////////////////////////////////
                         UTILITY TOKENS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @dev Boost can't be forfeit; does not fail.
    function forfeitBoost(uint256 amount) public override {}

    /*///////////////////////////////////////////////////////////////
                         UTILITY MANAGER LOGIC
    ///////////////////////////////////////////////////////////////*/

    function claimOutstanding() public override {
        /// @dev e.g. bHermesRate value 1100 if need to set 1.1X
        uint256 balance = balanceOf[msg.sender].mulWad(bHermesRate);
        /// @dev Never underflows since balandeOf >= userClaimed.
        unchecked {
            claimWeight(balance - userClaimedWeight[msg.sender]);
            claimGovernance(balance - userClaimedGovernance[msg.sender]);
            claimPartnerGovernance(balance - userClaimedPartnerGovernance[msg.sender]);
        }
    }

    function forfeitOutstanding() public override {
        forfeitWeight(userClaimedWeight[msg.sender]);
        forfeitGovernance(userClaimedGovernance[msg.sender]);
        forfeitPartnerGovernance(userClaimedPartnerGovernance[msg.sender]);
    }

    /*///////////////////////////////////////////////////////////////
                               MODIFIERS
    ///////////////////////////////////////////////////////////////*/

    /// @dev Boost can't be claimed; does not fail. It is all used by the partner vault.
    function claimBoost(uint256) public override {}

    /*//////////////////////////////////////////////////////////////
                    ER4626 WITHDRAWAL LIMIT LOGIC
    ///////////////////////////////////////////////////////////////*/

    function _checkIfWithdrawalIsAllowed() internal view returns (bool) {
        /// @dev Return true if unstake period has not ended yet.
        if (unstakePeriodEnd >= block.timestamp) return true;

        uint256 _currentMonth = DateTimeLib.getMonth(block.timestamp);
        if (_currentMonth == currentMonth) return false;

        (bool isTuesday,) = DateTimeLib.isTuesday(block.timestamp);
        return isTuesday;
    }

    /// @notice Returns the maximum amount of assets that can be withdrawn by a user.
    /// @dev Assumes that the user has already forfeited all utility tokens.
    function maxWithdraw(address user) public view virtual override returns (uint256) {
        return _checkIfWithdrawalIsAllowed() ? super.maxWithdraw(user) : 0;
    }

    /// @notice Returns the maximum amount of assets that can be redeemed by a user.
    /// @dev Assumes that the user has already forfeited all utility tokens.
    function maxRedeem(address user) public view virtual override returns (uint256) {
        return _checkIfWithdrawalIsAllowed() ? super.maxRedeem(user) : 0;
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HOOKS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Function that performs the necessary verifications before a user can withdraw from their VoteMaia position.
     *  Checks if we're inside the unstaked period, if so then the user can withdraw.
     * If we're not in the unstake period, then there will be checks to determine if this is the beginning of the month.
     */
    function beforeWithdraw(uint256, uint256) internal override {
        /// @dev Check if unstake period has not ended yet, continue if it is the case.
        if (unstakePeriodEnd >= block.timestamp) return;

        uint128 _currentMonth = DateTimeLib.getMonth(block.timestamp).toUint128();
        if (_currentMonth == currentMonth) revert UnstakePeriodNotLive();

        (bool isTuesday, uint256 _unstakePeriodStart) = DateTimeLib.isTuesday(block.timestamp);
        if (!isTuesday) revert UnstakePeriodNotLive();

        currentMonth = _currentMonth;
        unstakePeriodEnd = (_unstakePeriodStart + 1 days).toUint128();
    }

    /*///////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @dev Error thrown when trying to withdraw and it is not the first Tuesday of the month.
    error UnstakePeriodNotLive();
}

File 2 of 48 : EnumerableSet.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.0;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

File 3 of 48 : Ownable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The caller is not authorized to call the function.
    error Unauthorized();

    /// @dev The `newOwner` cannot be the zero address.
    error NewOwnerIsZeroAddress();

    /// @dev The `pendingOwner` does not have a valid handover request.
    error NoHandoverRequest();

    /// @dev Cannot double-initialize.
    error AlreadyInitialized();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                           EVENTS                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ownership is transferred from `oldOwner` to `newOwner`.
    /// This event is intentionally kept the same as OpenZeppelin's Ownable to be
    /// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
    /// despite it not being as lightweight as a single argument event.
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

    /// @dev An ownership handover to `pendingOwner` has been requested.
    event OwnershipHandoverRequested(address indexed pendingOwner);

    /// @dev The ownership handover to `pendingOwner` has been canceled.
    event OwnershipHandoverCanceled(address indexed pendingOwner);

    /// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
    uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
        0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;

    /// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
        0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;

    /// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
    uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
        0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The owner slot is given by:
    /// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
    /// It is intentionally chosen to be a high value
    /// to avoid collision with lower slots.
    /// The choice of manual storage layout is to enable compatibility
    /// with both regular and upgradeable contracts.
    bytes32 internal constant _OWNER_SLOT =
        0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;

    /// The ownership handover slot of `newOwner` is given by:
    /// ```
    ///     mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
    ///     let handoverSlot := keccak256(0x00, 0x20)
    /// ```
    /// It stores the expiry timestamp of the two-step ownership handover.
    uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     INTERNAL FUNCTIONS                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
    function _guardInitializeOwner() internal pure virtual returns (bool guard) {}

    /// @dev Initializes the owner directly without authorization guard.
    /// This function must be called upon initialization,
    /// regardless of whether the contract is upgradeable or not.
    /// This is to enable generalization to both regular and upgradeable contracts,
    /// and to save gas in case the initial owner is not the caller.
    /// For performance reasons, this function will not check if there
    /// is an existing owner.
    function _initializeOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                if sload(ownerSlot) {
                    mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
                    revert(0x1c, 0x04)
                }
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Store the new value.
                sstore(_OWNER_SLOT, newOwner)
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
            }
        }
    }

    /// @dev Sets the owner directly without authorization guard.
    function _setOwner(address newOwner) internal virtual {
        if (_guardInitializeOwner()) {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
            }
        } else {
            /// @solidity memory-safe-assembly
            assembly {
                let ownerSlot := _OWNER_SLOT
                // Clean the upper 96 bits.
                newOwner := shr(96, shl(96, newOwner))
                // Emit the {OwnershipTransferred} event.
                log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
                // Store the new value.
                sstore(ownerSlot, newOwner)
            }
        }
    }

    /// @dev Throws if the sender is not the owner.
    function _checkOwner() internal view virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // If the caller is not the stored owner, revert.
            if iszero(eq(caller(), sload(_OWNER_SLOT))) {
                mstore(0x00, 0x82b42900) // `Unauthorized()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Returns how long a two-step ownership handover is valid for in seconds.
    /// Override to return a different value if needed.
    /// Made internal to conserve bytecode. Wrap it in a public function if needed.
    function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
        return 48 * 3600;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  PUBLIC UPDATE FUNCTIONS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Allows the owner to transfer the ownership to `newOwner`.
    function transferOwnership(address newOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(shl(96, newOwner)) {
                mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
                revert(0x1c, 0x04)
            }
        }
        _setOwner(newOwner);
    }

    /// @dev Allows the owner to renounce their ownership.
    function renounceOwnership() public payable virtual onlyOwner {
        _setOwner(address(0));
    }

    /// @dev Request a two-step ownership handover to the caller.
    /// The request will automatically expire in 48 hours (172800 seconds) by default.
    function requestOwnershipHandover() public payable virtual {
        unchecked {
            uint256 expires = block.timestamp + _ownershipHandoverValidFor();
            /// @solidity memory-safe-assembly
            assembly {
                // Compute and set the handover slot to `expires`.
                mstore(0x0c, _HANDOVER_SLOT_SEED)
                mstore(0x00, caller())
                sstore(keccak256(0x0c, 0x20), expires)
                // Emit the {OwnershipHandoverRequested} event.
                log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
            }
        }
    }

    /// @dev Cancels the two-step ownership handover to the caller, if any.
    function cancelOwnershipHandover() public payable virtual {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, caller())
            sstore(keccak256(0x0c, 0x20), 0)
            // Emit the {OwnershipHandoverCanceled} event.
            log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
        }
    }

    /// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
    /// Reverts if there is no existing ownership handover requested by `pendingOwner`.
    function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and set the handover slot to 0.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            let handoverSlot := keccak256(0x0c, 0x20)
            // If the handover does not exist, or has expired.
            if gt(timestamp(), sload(handoverSlot)) {
                mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
                revert(0x1c, 0x04)
            }
            // Set the handover slot to 0.
            sstore(handoverSlot, 0)
        }
        _setOwner(pendingOwner);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   PUBLIC READ FUNCTIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the owner of the contract.
    function owner() public view virtual returns (address result) {
        /// @solidity memory-safe-assembly
        assembly {
            result := sload(_OWNER_SLOT)
        }
    }

    /// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
    function ownershipHandoverExpiresAt(address pendingOwner)
        public
        view
        virtual
        returns (uint256 result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Compute the handover slot.
            mstore(0x0c, _HANDOVER_SLOT_SEED)
            mstore(0x00, pendingOwner)
            // Load the handover slot.
            result := sload(keccak256(0x0c, 0x20))
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         MODIFIERS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Marks a function as only callable by the owner.
    modifier onlyOwner() virtual {
        _checkOwner();
        _;
    }
}

File 4 of 48 : FixedPointMathLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The operation failed, as the output exceeds the maximum value of uint256.
    error ExpOverflow();

    /// @dev The operation failed, as the output exceeds the maximum value of uint256.
    error FactorialOverflow();

    /// @dev The operation failed, due to an overflow.
    error RPowOverflow();

    /// @dev The mantissa is too big to fit.
    error MantissaOverflow();

    /// @dev The operation failed, due to an multiplication overflow.
    error MulWadFailed();

    /// @dev The operation failed, due to an multiplication overflow.
    error SMulWadFailed();

    /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
    error DivWadFailed();

    /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
    error SDivWadFailed();

    /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
    error MulDivFailed();

    /// @dev The division failed, as the denominator is zero.
    error DivFailed();

    /// @dev The full precision multiply-divide operation failed, either due
    /// to the result being larger than 256 bits, or a division by a zero.
    error FullMulDivFailed();

    /// @dev The output is undefined, as the input is less-than-or-equal to zero.
    error LnWadUndefined();

    /// @dev The input outside the acceptable domain.
    error OutOfDomain();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The scalar of ETH and most ERC20s.
    uint256 internal constant WAD = 1e18;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*              SIMPLIFIED FIXED POINT OPERATIONS             */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Equivalent to `(x * y) / WAD` rounded down.
    function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
            if mul(y, gt(x, div(not(0), y))) {
                mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := div(mul(x, y), WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded down.
    function sMulWad(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mul(x, y)
            // Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`.
            if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) {
                mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := sdiv(z, WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
    function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := div(mul(x, y), WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
    function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := sdiv(mul(x, y), WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded up.
    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
            if mul(y, gt(x, div(not(0), y))) {
                mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks.
    function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down.
    function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
            if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
                mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := div(mul(x, WAD), y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down.
    function sDivWad(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mul(x, WAD)
            // Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`.
            if iszero(and(iszero(iszero(y)), eq(sdiv(z, WAD), x))) {
                mstore(0x00, 0x5c43740d) // `SDivWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := sdiv(mul(x, WAD), y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
    function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := div(mul(x, WAD), y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
    function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := sdiv(mul(x, WAD), y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded up.
    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y != 0 && (WAD == 0 || x <= type(uint256).max / WAD))`.
            if iszero(mul(y, iszero(mul(WAD, gt(x, div(not(0), WAD)))))) {
                mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks.
    function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
        }
    }

    /// @dev Equivalent to `x` to the power of `y`.
    /// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
    function powWad(int256 x, int256 y) internal pure returns (int256) {
        // Using `ln(x)` means `x` must be greater than 0.
        return expWad((lnWad(x) * y) / int256(WAD));
    }

    /// @dev Returns `exp(x)`, denominated in `WAD`.
    /// Credit to Remco Bloemen under MIT license: https://2π.com/21/exp-ln
    function expWad(int256 x) internal pure returns (int256 r) {
        unchecked {
            // When the result is less than 0.5 we return zero.
            // This happens when `x <= floor(log(0.5e18) * 1e18) ≈ -42e18`.
            if (x <= -41446531673892822313) return r;

            /// @solidity memory-safe-assembly
            assembly {
                // When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
                // an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
                if iszero(slt(x, 135305999368893231589)) {
                    mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
                    revert(0x1c, 0x04)
                }
            }

            // `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
            // for more intermediate precision and a binary basis. This base conversion
            // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
            x = (x << 78) / 5 ** 18;

            // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
            // of two such that exp(x) = exp(x') * 2**k, where k is an integer.
            // Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
            int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
            x = x - k * 54916777467707473351141471128;

            // `k` is in the range `[-61, 195]`.

            // Evaluate using a (6, 7)-term rational approximation.
            // `p` is made monic, we'll multiply by a scale factor later.
            int256 y = x + 1346386616545796478920950773328;
            y = ((y * x) >> 96) + 57155421227552351082224309758442;
            int256 p = y + x - 94201549194550492254356042504812;
            p = ((p * y) >> 96) + 28719021644029726153956944680412240;
            p = p * x + (4385272521454847904659076985693276 << 96);

            // We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
            int256 q = x - 2855989394907223263936484059900;
            q = ((q * x) >> 96) + 50020603652535783019961831881945;
            q = ((q * x) >> 96) - 533845033583426703283633433725380;
            q = ((q * x) >> 96) + 3604857256930695427073651918091429;
            q = ((q * x) >> 96) - 14423608567350463180887372962807573;
            q = ((q * x) >> 96) + 26449188498355588339934803723976023;

            /// @solidity memory-safe-assembly
            assembly {
                // Div in assembly because solidity adds a zero check despite the unchecked.
                // The q polynomial won't have zeros in the domain as all its roots are complex.
                // No scaling is necessary because p is already `2**96` too large.
                r := sdiv(p, q)
            }

            // r should be in the range `(0.09, 0.25) * 2**96`.

            // We now need to multiply r by:
            // - The scale factor `s ≈ 6.031367120`.
            // - The `2**k` factor from the range reduction.
            // - The `1e18 / 2**96` factor for base conversion.
            // We do this all at once, with an intermediate result in `2**213`
            // basis, so the final right shift is always by a positive amount.
            r = int256(
                (uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
            );
        }
    }

    /// @dev Returns `ln(x)`, denominated in `WAD`.
    /// Credit to Remco Bloemen under MIT license: https://2π.com/21/exp-ln
    function lnWad(int256 x) internal pure returns (int256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            // We want to convert `x` from `10**18` fixed point to `2**96` fixed point.
            // We do this by multiplying by `2**96 / 10**18`. But since
            // `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here
            // and add `ln(2**96 / 10**18)` at the end.

            // Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
            r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffff, shr(r, x))))
            r := or(r, shl(3, lt(0xff, shr(r, x))))
            // We place the check here for more optimal stack operations.
            if iszero(sgt(x, 0)) {
                mstore(0x00, 0x1615e638) // `LnWadUndefined()`.
                revert(0x1c, 0x04)
            }
            // forgefmt: disable-next-item
            r := xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
                0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))

            // Reduce range of x to (1, 2) * 2**96
            // ln(2^k * x) = k * ln(2) + ln(x)
            x := shr(159, shl(r, x))

            // Evaluate using a (8, 8)-term rational approximation.
            // `p` is made monic, we will multiply by a scale factor later.
            // forgefmt: disable-next-item
            let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir.
                sar(96, mul(add(43456485725739037958740375743393,
                sar(96, mul(add(24828157081833163892658089445524,
                sar(96, mul(add(3273285459638523848632254066296,
                    x), x))), x))), x)), 11111509109440967052023855526967)
            p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857)
            p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526)
            p := sub(mul(p, x), shl(96, 795164235651350426258249787498))
            // We leave `p` in `2**192` basis so we don't need to scale it back up for the division.

            // `q` is monic by convention.
            let q := add(5573035233440673466300451813936, x)
            q := add(71694874799317883764090561454958, sar(96, mul(x, q)))
            q := add(283447036172924575727196451306956, sar(96, mul(x, q)))
            q := add(401686690394027663651624208769553, sar(96, mul(x, q)))
            q := add(204048457590392012362485061816622, sar(96, mul(x, q)))
            q := add(31853899698501571402653359427138, sar(96, mul(x, q)))
            q := add(909429971244387300277376558375, sar(96, mul(x, q)))

            // `p / q` is in the range `(0, 0.125) * 2**96`.

            // Finalization, we need to:
            // - Multiply by the scale factor `s = 5.549…`.
            // - Add `ln(2**96 / 10**18)`.
            // - Add `k * ln(2)`.
            // - Multiply by `10**18 / 2**96 = 5**18 >> 78`.

            // The q polynomial is known not to have zeros in the domain.
            // No scaling required because p is already `2**96` too large.
            p := sdiv(p, q)
            // Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`.
            p := mul(1677202110996718588342820967067443963516166, p)
            // Add `ln(2) * k * 5**18 * 2**192`.
            // forgefmt: disable-next-item
            p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p)
            // Add `ln(2**96 / 10**18) * 5**18 * 2**192`.
            p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p)
            // Base conversion: mul `2**18 / 2**192`.
            r := sar(174, p)
        }
    }

    /// @dev Returns `W_0(x)`, denominated in `WAD`.
    /// See: https://en.wikipedia.org/wiki/Lambert_W_function
    /// a.k.a. Product log function. This is an approximation of the principal branch.
    function lambertW0Wad(int256 x) internal pure returns (int256 w) {
        // forgefmt: disable-next-item
        unchecked {
            if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`.
            int256 wad = int256(WAD);
            int256 p = x;
            uint256 c; // Whether we need to avoid catastrophic cancellation.
            uint256 i = 4; // Number of iterations.
            if (w <= 0x1ffffffffffff) {
                if (-0x4000000000000 <= w) {
                    i = 1; // Inputs near zero only take one step to converge.
                } else if (w <= -0x3ffffffffffffff) {
                    i = 32; // Inputs near `-1/e` take very long to converge.
                }
            } else if (w >> 63 == 0) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Inline log2 for more performance, since the range is small.
                    let v := shr(49, w)
                    let l := shl(3, lt(0xff, v))
                    l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)),
                        0x0706060506020504060203020504030106050205030304010505030400000000)), 49)
                    w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13))
                    c := gt(l, 60)
                    i := add(2, add(gt(l, 53), c))
                }
            } else {
                int256 ll = lnWad(w = lnWad(w));
                /// @solidity memory-safe-assembly
                assembly {
                    // `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`.
                    w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll))
                    i := add(3, iszero(shr(68, x)))
                    c := iszero(shr(143, x))
                }
                if (c == 0) {
                    do { // If `x` is big, use Newton's so that intermediate values won't overflow.
                        int256 e = expWad(w);
                        /// @solidity memory-safe-assembly
                        assembly {
                            let t := mul(w, div(e, wad))
                            w := sub(w, sdiv(sub(t, x), div(add(e, t), wad)))
                        }
                        if (p <= w) break;
                        p = w;
                    } while (--i != 0);
                    /// @solidity memory-safe-assembly
                    assembly {
                        w := sub(w, sgt(w, 2))
                    }
                    return w;
                }
            }
            do { // Otherwise, use Halley's for faster convergence.
                int256 e = expWad(w);
                /// @solidity memory-safe-assembly
                assembly {
                    let t := add(w, wad)
                    let s := sub(mul(w, e), mul(x, wad))
                    w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t)))))
                }
                if (p <= w) break;
                p = w;
            } while (--i != c);
            /// @solidity memory-safe-assembly
            assembly {
                w := sub(w, sgt(w, 2))
            }
            // For certain ranges of `x`, we'll use the quadratic-rate recursive formula of
            // R. Iacono and J.P. Boyd for the last iteration, to avoid catastrophic cancellation.
            if (c != 0) {
                int256 t = w | 1;
                /// @solidity memory-safe-assembly
                assembly {
                    x := sdiv(mul(x, wad), t)
                }
                x = (t * (wad + lnWad(x)));
                /// @solidity memory-safe-assembly
                assembly {
                    w := sdiv(x, add(wad, t))
                }
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                  GENERAL NUMBER UTILITIES                  */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Calculates `floor(x * y / d)` with full precision.
    /// Throws if result overflows a uint256 or when `d` is zero.
    /// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv
    function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            for {} 1 {} {
                // 512-bit multiply `[p1 p0] = x * y`.
                // Compute the product mod `2**256` and mod `2**256 - 1`
                // then use the Chinese Remainder Theorem to reconstruct
                // the 512 bit result. The result is stored in two 256
                // variables such that `product = p1 * 2**256 + p0`.

                // Least significant 256 bits of the product.
                result := mul(x, y) // Temporarily use `result` as `p0` to save gas.
                let mm := mulmod(x, y, not(0))
                // Most significant 256 bits of the product.
                let p1 := sub(mm, add(result, lt(mm, result)))

                // Handle non-overflow cases, 256 by 256 division.
                if iszero(p1) {
                    if iszero(d) {
                        mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
                        revert(0x1c, 0x04)
                    }
                    result := div(result, d)
                    break
                }

                // Make sure the result is less than `2**256`. Also prevents `d == 0`.
                if iszero(gt(d, p1)) {
                    mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
                    revert(0x1c, 0x04)
                }

                /*------------------- 512 by 256 division --------------------*/

                // Make division exact by subtracting the remainder from `[p1 p0]`.
                // Compute remainder using mulmod.
                let r := mulmod(x, y, d)
                // `t` is the least significant bit of `d`.
                // Always greater or equal to 1.
                let t := and(d, sub(0, d))
                // Divide `d` by `t`, which is a power of two.
                d := div(d, t)
                // Invert `d mod 2**256`
                // Now that `d` is an odd number, it has an inverse
                // modulo `2**256` such that `d * inv = 1 mod 2**256`.
                // Compute the inverse by starting with a seed that is correct
                // correct for four bits. That is, `d * inv = 1 mod 2**4`.
                let inv := xor(2, mul(3, d))
                // Now use 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.
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64
                inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128
                result :=
                    mul(
                        // Divide [p1 p0] by the factors of two.
                        // Shift in bits from `p1` into `p0`. For this we need
                        // to flip `t` such that it is `2**256 / t`.
                        or(
                            mul(sub(p1, gt(r, result)), add(div(sub(0, t), t), 1)),
                            div(sub(result, r), t)
                        ),
                        // inverse mod 2**256
                        mul(inv, sub(2, mul(d, inv)))
                    )
                break
            }
        }
    }

    /// @dev Calculates `floor(x * y / d)` with full precision, rounded up.
    /// Throws if result overflows a uint256 or when `d` is zero.
    /// Credit to Uniswap-v3-core under MIT license:
    /// https://github.com/Uniswap/v3-core/blob/contracts/libraries/FullMath.sol
    function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 result) {
        result = fullMulDiv(x, y, d);
        /// @solidity memory-safe-assembly
        assembly {
            if mulmod(x, y, d) {
                result := add(result, 1)
                if iszero(result) {
                    mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
                    revert(0x1c, 0x04)
                }
            }
        }
    }

    /// @dev Returns `floor(x * y / d)`.
    /// Reverts if `x * y` overflows, or `d` is zero.
    function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
                mstore(0x00, 0xad251c27) // `MulDivFailed()`.
                revert(0x1c, 0x04)
            }
            z := div(mul(x, y), d)
        }
    }

    /// @dev Returns `ceil(x * y / d)`.
    /// Reverts if `x * y` overflows, or `d` is zero.
    function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
                mstore(0x00, 0xad251c27) // `MulDivFailed()`.
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(mul(x, y), d))), div(mul(x, y), d))
        }
    }

    /// @dev Returns `ceil(x / d)`.
    /// Reverts if `d` is zero.
    function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(d) {
                mstore(0x00, 0x65244e4e) // `DivFailed()`.
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(x, d))), div(x, d))
        }
    }

    /// @dev Returns `max(0, x - y)`.
    function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mul(gt(x, y), sub(x, y))
        }
    }

    /// @dev Exponentiate `x` to `y` by squaring, denominated in base `b`.
    /// Reverts if the computation overflows.
    function rpow(uint256 x, uint256 y, uint256 b) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mul(b, iszero(y)) // `0 ** 0 = 1`. Otherwise, `0 ** n = 0`.
            if x {
                z := xor(b, mul(xor(b, x), and(y, 1))) // `z = isEven(y) ? scale : x`
                let half := shr(1, b) // Divide `b` by 2.
                // Divide `y` by 2 every iteration.
                for { y := shr(1, y) } y { y := shr(1, y) } {
                    let xx := mul(x, x) // Store x squared.
                    let xxRound := add(xx, half) // Round to the nearest number.
                    // Revert if `xx + half` overflowed, or if `x ** 2` overflows.
                    if or(lt(xxRound, xx), shr(128, x)) {
                        mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
                        revert(0x1c, 0x04)
                    }
                    x := div(xxRound, b) // Set `x` to scaled `xxRound`.
                    // If `y` is odd:
                    if and(y, 1) {
                        let zx := mul(z, x) // Compute `z * x`.
                        let zxRound := add(zx, half) // Round to the nearest number.
                        // If `z * x` overflowed or `zx + half` overflowed:
                        if or(xor(div(zx, x), z), lt(zxRound, zx)) {
                            // Revert if `x` is non-zero.
                            if iszero(iszero(x)) {
                                mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
                                revert(0x1c, 0x04)
                            }
                        }
                        z := div(zxRound, b) // Return properly scaled `zxRound`.
                    }
                }
            }
        }
    }

    /// @dev Returns the square root of `x`.
    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`.
            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.

            // Let `y = x / 2**r`. We check `y >= 2**(k + 8)`
            // but shift right by `k` bits to ensure that if `x >= 256`, then `y >= 256`.
            let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffffff, shr(r, x))))
            z := shl(shr(1, r), 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(shr(r, x), 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
            z := sub(z, lt(div(x, z), z))
        }
    }

    /// @dev Returns the cube root of `x`.
    /// Credit to bout3fiddy and pcaversaccio under AGPLv3 license:
    /// https://github.com/pcaversaccio/snekmate/blob/main/src/utils/Math.vy
    function cbrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffff, shr(r, x))))
            r := or(r, shl(3, lt(0xff, shr(r, x))))

            z := div(shl(div(r, 3), shl(lt(0xf, shr(r, x)), 0xf)), xor(7, mod(r, 3)))

            z := div(add(add(div(x, mul(z, z)), z), z), 3)
            z := div(add(add(div(x, mul(z, z)), z), z), 3)
            z := div(add(add(div(x, mul(z, z)), z), z), 3)
            z := div(add(add(div(x, mul(z, z)), z), z), 3)
            z := div(add(add(div(x, mul(z, z)), z), z), 3)
            z := div(add(add(div(x, mul(z, z)), z), z), 3)
            z := div(add(add(div(x, mul(z, z)), z), z), 3)

            z := sub(z, lt(div(x, mul(z, z)), z))
        }
    }

    /// @dev Returns the square root of `x`, denominated in `WAD`.
    function sqrtWad(uint256 x) internal pure returns (uint256 z) {
        unchecked {
            z = 10 ** 9;
            if (x <= type(uint256).max / 10 ** 36 - 1) {
                x *= 10 ** 18;
                z = 1;
            }
            z *= sqrt(x);
        }
    }

    /// @dev Returns the cube root of `x`, denominated in `WAD`.
    function cbrtWad(uint256 x) internal pure returns (uint256 z) {
        unchecked {
            z = 10 ** 12;
            if (x <= (type(uint256).max / 10 ** 36) * 10 ** 18 - 1) {
                if (x >= type(uint256).max / 10 ** 36) {
                    x *= 10 ** 18;
                    z = 10 ** 6;
                } else {
                    x *= 10 ** 36;
                    z = 1;
                }
            }
            z *= cbrt(x);
        }
    }

    /// @dev Returns the factorial of `x`.
    function factorial(uint256 x) internal pure returns (uint256 result) {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(lt(x, 58)) {
                mstore(0x00, 0xaba0f2a2) // `FactorialOverflow()`.
                revert(0x1c, 0x04)
            }
            for { result := 1 } x { x := sub(x, 1) } { result := mul(result, x) }
        }
    }

    /// @dev Returns the log2 of `x`.
    /// Equivalent to computing the index of the most significant bit (MSB) of `x`.
    /// Returns 0 if `x` is zero.
    function log2(uint256 x) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffff, shr(r, x))))
            r := or(r, shl(3, lt(0xff, shr(r, x))))
            // forgefmt: disable-next-item
            r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
                0x0706060506020504060203020504030106050205030304010505030400000000))
        }
    }

    /// @dev Returns the log2 of `x`, rounded up.
    /// Returns 0 if `x` is zero.
    function log2Up(uint256 x) internal pure returns (uint256 r) {
        r = log2(x);
        /// @solidity memory-safe-assembly
        assembly {
            r := add(r, lt(shl(r, 1), x))
        }
    }

    /// @dev Returns the log10 of `x`.
    /// Returns 0 if `x` is zero.
    function log10(uint256 x) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(lt(x, 100000000000000000000000000000000000000)) {
                x := div(x, 100000000000000000000000000000000000000)
                r := 38
            }
            if iszero(lt(x, 100000000000000000000)) {
                x := div(x, 100000000000000000000)
                r := add(r, 20)
            }
            if iszero(lt(x, 10000000000)) {
                x := div(x, 10000000000)
                r := add(r, 10)
            }
            if iszero(lt(x, 100000)) {
                x := div(x, 100000)
                r := add(r, 5)
            }
            r := add(r, add(gt(x, 9), add(gt(x, 99), add(gt(x, 999), gt(x, 9999)))))
        }
    }

    /// @dev Returns the log10 of `x`, rounded up.
    /// Returns 0 if `x` is zero.
    function log10Up(uint256 x) internal pure returns (uint256 r) {
        r = log10(x);
        /// @solidity memory-safe-assembly
        assembly {
            r := add(r, lt(exp(10, r), x))
        }
    }

    /// @dev Returns the log256 of `x`.
    /// Returns 0 if `x` is zero.
    function log256(uint256 x) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffff, shr(r, x))))
            r := or(shr(3, r), lt(0xff, shr(r, x)))
        }
    }

    /// @dev Returns the log256 of `x`, rounded up.
    /// Returns 0 if `x` is zero.
    function log256Up(uint256 x) internal pure returns (uint256 r) {
        r = log256(x);
        /// @solidity memory-safe-assembly
        assembly {
            r := add(r, lt(shl(shl(3, r), 1), x))
        }
    }

    /// @dev Returns the scientific notation format `mantissa * 10 ** exponent` of `x`.
    /// Useful for compressing prices (e.g. using 25 bit mantissa and 7 bit exponent).
    function sci(uint256 x) internal pure returns (uint256 mantissa, uint256 exponent) {
        /// @solidity memory-safe-assembly
        assembly {
            mantissa := x
            if mantissa {
                if iszero(mod(mantissa, 1000000000000000000000000000000000)) {
                    mantissa := div(mantissa, 1000000000000000000000000000000000)
                    exponent := 33
                }
                if iszero(mod(mantissa, 10000000000000000000)) {
                    mantissa := div(mantissa, 10000000000000000000)
                    exponent := add(exponent, 19)
                }
                if iszero(mod(mantissa, 1000000000000)) {
                    mantissa := div(mantissa, 1000000000000)
                    exponent := add(exponent, 12)
                }
                if iszero(mod(mantissa, 1000000)) {
                    mantissa := div(mantissa, 1000000)
                    exponent := add(exponent, 6)
                }
                if iszero(mod(mantissa, 10000)) {
                    mantissa := div(mantissa, 10000)
                    exponent := add(exponent, 4)
                }
                if iszero(mod(mantissa, 100)) {
                    mantissa := div(mantissa, 100)
                    exponent := add(exponent, 2)
                }
                if iszero(mod(mantissa, 10)) {
                    mantissa := div(mantissa, 10)
                    exponent := add(exponent, 1)
                }
            }
        }
    }

    /// @dev Convenience function for packing `x` into a smaller number using `sci`.
    /// The `mantissa` will be in bits [7..255] (the upper 249 bits).
    /// The `exponent` will be in bits [0..6] (the lower 7 bits).
    /// Use `SafeCastLib` to safely ensure that the `packed` number is small
    /// enough to fit in the desired unsigned integer type:
    /// ```
    ///     uint32 packed = SafeCastLib.toUint32(FixedPointMathLib.packSci(777 ether));
    /// ```
    function packSci(uint256 x) internal pure returns (uint256 packed) {
        (x, packed) = sci(x); // Reuse for `mantissa` and `exponent`.
        /// @solidity memory-safe-assembly
        assembly {
            if shr(249, x) {
                mstore(0x00, 0xce30380c) // `MantissaOverflow()`.
                revert(0x1c, 0x04)
            }
            packed := or(shl(7, x), packed)
        }
    }

    /// @dev Convenience function for unpacking a packed number from `packSci`.
    function unpackSci(uint256 packed) internal pure returns (uint256 unpacked) {
        unchecked {
            unpacked = (packed >> 7) * 10 ** (packed & 0x7f);
        }
    }

    /// @dev Returns the average of `x` and `y`.
    function avg(uint256 x, uint256 y) internal pure returns (uint256 z) {
        unchecked {
            z = (x & y) + ((x ^ y) >> 1);
        }
    }

    /// @dev Returns the average of `x` and `y`.
    function avg(int256 x, int256 y) internal pure returns (int256 z) {
        unchecked {
            z = (x >> 1) + (y >> 1) + (((x & 1) + (y & 1)) >> 1);
        }
    }

    /// @dev Returns the absolute value of `x`.
    function abs(int256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(sub(0, shr(255, x)), add(sub(0, shr(255, x)), x))
        }
    }

    /// @dev Returns the absolute distance between `x` and `y`.
    function dist(int256 x, int256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(mul(xor(sub(y, x), sub(x, y)), sgt(x, y)), sub(y, x))
        }
    }

    /// @dev Returns the minimum of `x` and `y`.
    function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, y), lt(y, x)))
        }
    }

    /// @dev Returns the minimum of `x` and `y`.
    function min(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, y), slt(y, x)))
        }
    }

    /// @dev Returns the maximum of `x` and `y`.
    function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, y), gt(y, x)))
        }
    }

    /// @dev Returns the maximum of `x` and `y`.
    function max(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, y), sgt(y, x)))
        }
    }

    /// @dev Returns `x`, bounded to `minValue` and `maxValue`.
    function clamp(uint256 x, uint256 minValue, uint256 maxValue)
        internal
        pure
        returns (uint256 z)
    {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, minValue), gt(minValue, x)))
            z := xor(z, mul(xor(z, maxValue), lt(maxValue, z)))
        }
    }

    /// @dev Returns `x`, bounded to `minValue` and `maxValue`.
    function clamp(int256 x, int256 minValue, int256 maxValue) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := xor(x, mul(xor(x, minValue), sgt(minValue, x)))
            z := xor(z, mul(xor(z, maxValue), slt(maxValue, z)))
        }
    }

    /// @dev Returns greatest common divisor of `x` and `y`.
    function gcd(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            for { z := x } y {} {
                let t := y
                y := mod(z, y)
                z := t
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   RAW NUMBER OPERATIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns `x + y`, without checking for overflow.
    function rawAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
        unchecked {
            z = x + y;
        }
    }

    /// @dev Returns `x + y`, without checking for overflow.
    function rawAdd(int256 x, int256 y) internal pure returns (int256 z) {
        unchecked {
            z = x + y;
        }
    }

    /// @dev Returns `x - y`, without checking for underflow.
    function rawSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
        unchecked {
            z = x - y;
        }
    }

    /// @dev Returns `x - y`, without checking for underflow.
    function rawSub(int256 x, int256 y) internal pure returns (int256 z) {
        unchecked {
            z = x - y;
        }
    }

    /// @dev Returns `x * y`, without checking for overflow.
    function rawMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        unchecked {
            z = x * y;
        }
    }

    /// @dev Returns `x * y`, without checking for overflow.
    function rawMul(int256 x, int256 y) internal pure returns (int256 z) {
        unchecked {
            z = x * y;
        }
    }

    /// @dev Returns `x / y`, returning 0 if `y` is zero.
    function rawDiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := div(x, y)
        }
    }

    /// @dev Returns `x / y`, returning 0 if `y` is zero.
    function rawSDiv(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := sdiv(x, y)
        }
    }

    /// @dev Returns `x % y`, returning 0 if `y` is zero.
    function rawMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mod(x, y)
        }
    }

    /// @dev Returns `x % y`, returning 0 if `y` is zero.
    function rawSMod(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := smod(x, y)
        }
    }

    /// @dev Returns `(x + y) % d`, return 0 if `d` if zero.
    function rawAddMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := addmod(x, y, d)
        }
    }

    /// @dev Returns `(x * y) % d`, return 0 if `d` if zero.
    function rawMulMod(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mulmod(x, y, d)
        }
    }
}

File 5 of 48 : SafeCastLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe integer casting library that reverts on overflow.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeCastLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol)
library SafeCastLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    error Overflow();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*          UNSIGNED INTEGER SAFE CASTING OPERATIONS          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    function toUint8(uint256 x) internal pure returns (uint8) {
        if (x >= 1 << 8) _revertOverflow();
        return uint8(x);
    }

    function toUint16(uint256 x) internal pure returns (uint16) {
        if (x >= 1 << 16) _revertOverflow();
        return uint16(x);
    }

    function toUint24(uint256 x) internal pure returns (uint24) {
        if (x >= 1 << 24) _revertOverflow();
        return uint24(x);
    }

    function toUint32(uint256 x) internal pure returns (uint32) {
        if (x >= 1 << 32) _revertOverflow();
        return uint32(x);
    }

    function toUint40(uint256 x) internal pure returns (uint40) {
        if (x >= 1 << 40) _revertOverflow();
        return uint40(x);
    }

    function toUint48(uint256 x) internal pure returns (uint48) {
        if (x >= 1 << 48) _revertOverflow();
        return uint48(x);
    }

    function toUint56(uint256 x) internal pure returns (uint56) {
        if (x >= 1 << 56) _revertOverflow();
        return uint56(x);
    }

    function toUint64(uint256 x) internal pure returns (uint64) {
        if (x >= 1 << 64) _revertOverflow();
        return uint64(x);
    }

    function toUint72(uint256 x) internal pure returns (uint72) {
        if (x >= 1 << 72) _revertOverflow();
        return uint72(x);
    }

    function toUint80(uint256 x) internal pure returns (uint80) {
        if (x >= 1 << 80) _revertOverflow();
        return uint80(x);
    }

    function toUint88(uint256 x) internal pure returns (uint88) {
        if (x >= 1 << 88) _revertOverflow();
        return uint88(x);
    }

    function toUint96(uint256 x) internal pure returns (uint96) {
        if (x >= 1 << 96) _revertOverflow();
        return uint96(x);
    }

    function toUint104(uint256 x) internal pure returns (uint104) {
        if (x >= 1 << 104) _revertOverflow();
        return uint104(x);
    }

    function toUint112(uint256 x) internal pure returns (uint112) {
        if (x >= 1 << 112) _revertOverflow();
        return uint112(x);
    }

    function toUint120(uint256 x) internal pure returns (uint120) {
        if (x >= 1 << 120) _revertOverflow();
        return uint120(x);
    }

    function toUint128(uint256 x) internal pure returns (uint128) {
        if (x >= 1 << 128) _revertOverflow();
        return uint128(x);
    }

    function toUint136(uint256 x) internal pure returns (uint136) {
        if (x >= 1 << 136) _revertOverflow();
        return uint136(x);
    }

    function toUint144(uint256 x) internal pure returns (uint144) {
        if (x >= 1 << 144) _revertOverflow();
        return uint144(x);
    }

    function toUint152(uint256 x) internal pure returns (uint152) {
        if (x >= 1 << 152) _revertOverflow();
        return uint152(x);
    }

    function toUint160(uint256 x) internal pure returns (uint160) {
        if (x >= 1 << 160) _revertOverflow();
        return uint160(x);
    }

    function toUint168(uint256 x) internal pure returns (uint168) {
        if (x >= 1 << 168) _revertOverflow();
        return uint168(x);
    }

    function toUint176(uint256 x) internal pure returns (uint176) {
        if (x >= 1 << 176) _revertOverflow();
        return uint176(x);
    }

    function toUint184(uint256 x) internal pure returns (uint184) {
        if (x >= 1 << 184) _revertOverflow();
        return uint184(x);
    }

    function toUint192(uint256 x) internal pure returns (uint192) {
        if (x >= 1 << 192) _revertOverflow();
        return uint192(x);
    }

    function toUint200(uint256 x) internal pure returns (uint200) {
        if (x >= 1 << 200) _revertOverflow();
        return uint200(x);
    }

    function toUint208(uint256 x) internal pure returns (uint208) {
        if (x >= 1 << 208) _revertOverflow();
        return uint208(x);
    }

    function toUint216(uint256 x) internal pure returns (uint216) {
        if (x >= 1 << 216) _revertOverflow();
        return uint216(x);
    }

    function toUint224(uint256 x) internal pure returns (uint224) {
        if (x >= 1 << 224) _revertOverflow();
        return uint224(x);
    }

    function toUint232(uint256 x) internal pure returns (uint232) {
        if (x >= 1 << 232) _revertOverflow();
        return uint232(x);
    }

    function toUint240(uint256 x) internal pure returns (uint240) {
        if (x >= 1 << 240) _revertOverflow();
        return uint240(x);
    }

    function toUint248(uint256 x) internal pure returns (uint248) {
        if (x >= 1 << 248) _revertOverflow();
        return uint248(x);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*           SIGNED INTEGER SAFE CASTING OPERATIONS           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    function toInt8(int256 x) internal pure returns (int8) {
        int8 y = int8(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt16(int256 x) internal pure returns (int16) {
        int16 y = int16(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt24(int256 x) internal pure returns (int24) {
        int24 y = int24(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt32(int256 x) internal pure returns (int32) {
        int32 y = int32(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt40(int256 x) internal pure returns (int40) {
        int40 y = int40(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt48(int256 x) internal pure returns (int48) {
        int48 y = int48(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt56(int256 x) internal pure returns (int56) {
        int56 y = int56(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt64(int256 x) internal pure returns (int64) {
        int64 y = int64(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt72(int256 x) internal pure returns (int72) {
        int72 y = int72(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt80(int256 x) internal pure returns (int80) {
        int80 y = int80(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt88(int256 x) internal pure returns (int88) {
        int88 y = int88(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt96(int256 x) internal pure returns (int96) {
        int96 y = int96(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt104(int256 x) internal pure returns (int104) {
        int104 y = int104(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt112(int256 x) internal pure returns (int112) {
        int112 y = int112(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt120(int256 x) internal pure returns (int120) {
        int120 y = int120(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt128(int256 x) internal pure returns (int128) {
        int128 y = int128(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt136(int256 x) internal pure returns (int136) {
        int136 y = int136(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt144(int256 x) internal pure returns (int144) {
        int144 y = int144(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt152(int256 x) internal pure returns (int152) {
        int152 y = int152(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt160(int256 x) internal pure returns (int160) {
        int160 y = int160(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt168(int256 x) internal pure returns (int168) {
        int168 y = int168(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt176(int256 x) internal pure returns (int176) {
        int176 y = int176(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt184(int256 x) internal pure returns (int184) {
        int184 y = int184(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt192(int256 x) internal pure returns (int192) {
        int192 y = int192(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt200(int256 x) internal pure returns (int200) {
        int200 y = int200(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt208(int256 x) internal pure returns (int208) {
        int208 y = int208(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt216(int256 x) internal pure returns (int216) {
        int216 y = int216(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt224(int256 x) internal pure returns (int224) {
        int224 y = int224(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt232(int256 x) internal pure returns (int232) {
        int232 y = int232(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt240(int256 x) internal pure returns (int240) {
        int240 y = int240(x);
        if (x != y) _revertOverflow();
        return y;
    }

    function toInt248(int256 x) internal pure returns (int248) {
        int248 y = int248(x);
        if (x != y) _revertOverflow();
        return y;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*               OTHER SAFE CASTING OPERATIONS                */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    function toInt256(uint256 x) internal pure returns (int256) {
        if (x >= 1 << 255) _revertOverflow();
        return int256(x);
    }

    function toUint256(int256 x) internal pure returns (uint256) {
        if (x < 0) _revertOverflow();
        return uint256(x);
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      PRIVATE HELPERS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    function _revertOverflow() private pure {
        /// @solidity memory-safe-assembly
        assembly {
            // Store the function selector of `Overflow()`.
            mstore(0x00, 0x35278d12)
            // Revert with (offset, size).
            revert(0x1c, 0x04)
        }
    }
}

File 6 of 48 : SafeTransferLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
///   responsibility is delegated to the caller.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
    uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
    //
    // The regular variants:
    // - Forwards all remaining gas to the target.
    // - Reverts if the target reverts.
    // - Reverts if the current contract has insufficient balance.
    //
    // The force variants:
    // - Forwards with an optional gas stipend
    //   (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
    // - If the target reverts, or if the gas stipend is exhausted,
    //   creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
    //   Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
    // - Reverts if the current contract has insufficient balance.
    //
    // The try variants:
    // - Forwards with a mandatory gas stipend.
    // - Instead of reverting, returns whether the transfer succeeded.

    /// @dev Sends `amount` (in wei) ETH to `to`.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`.
    function safeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer all the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            if lt(selfbalance(), amount) {
                mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
                revert(0x1c, 0x04)
            }
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
    function forceSafeTransferAllETH(address to) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // forgefmt: disable-next-item
            if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
        }
    }

    /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
    function trySafeTransferAllETH(address to, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, amount) // Store the `amount` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have their entire balance approved for
    /// the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x2c, shl(96, from)) // Store the `from` argument.
            mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
            amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            // Read the balance, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, to) // Store the `to` argument.
            amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
            mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
            // Perform the transfer, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, reverting upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
    /// then retries the approval again (some tokens, e.g. USDT, requires this).
    /// Reverts upon failure.
    function safeApproveWithRetry(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
            // Perform the approval, retrying upon failure.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                    call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                )
            ) {
                mstore(0x34, 0) // Store 0 for the `amount`.
                mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
                pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
                mstore(0x34, amount) // Store back the original `amount`.
                // Retry the approval, reverting upon failure.
                if iszero(
                    and(
                        or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
                        call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
                    )
                ) {
                    mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, account) // Store the `account` argument.
            mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
            amount :=
                mul(
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
                    )
                )
        }
    }
}

File 7 of 48 : ERC20.sol
// 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);
    }
}

File 8 of 48 : ReentrancyGuard.sol
// 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;
    }
}

File 9 of 48 : ERC20Boost.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Ownable} from "lib/solady/src/auth/Ownable.sol";
import {SafeCastLib} from "lib/solady/src/utils/SafeCastLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {EnumerableSet} from "lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol";

import {IERC20Boost} from "./interfaces/IERC20Boost.sol";

/// @title An ERC20 with an embedded attachment mechanism to keep track of boost
///        allocations to gauges.
abstract contract ERC20Boost is ERC20, Ownable, IERC20Boost {
    using EnumerableSet for EnumerableSet.AddressSet;
    using SafeCastLib for *;

    /*///////////////////////////////////////////////////////////////
                            GAUGE STATE
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20Boost
    mapping(address user => mapping(address gauge => GaugeState userGaugeState)) public override getUserGaugeBoost;

    /// @inheritdoc IERC20Boost
    mapping(address user => uint256 boost) public override getUserBoost;

    mapping(address user => EnumerableSet.AddressSet userGaugeSet) internal _userGauges;

    EnumerableSet.AddressSet internal _gauges;

    // Store deprecated gauges in case a user needs to free dead boost
    EnumerableSet.AddressSet internal _deprecatedGauges;

    /*///////////////////////////////////////////////////////////////
                            VIEW HELPERS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20Boost
    function gauges() external view override returns (address[] memory) {
        return _gauges.values();
    }

    /// @inheritdoc IERC20Boost
    function gauges(uint256 offset, uint256 num) external view override returns (address[] memory values) {
        values = new address[](num);
        for (uint256 i = 0; i < num;) {
            unchecked {
                values[i] = _gauges.at(offset + i); // will revert if out of bounds
                i++;
            }
        }
    }

    /// @inheritdoc IERC20Boost
    function isGauge(address gauge) external view override returns (bool) {
        return _gauges.contains(gauge) && !_deprecatedGauges.contains(gauge);
    }

    /// @inheritdoc IERC20Boost
    function numGauges() external view override returns (uint256) {
        return _gauges.length();
    }

    /// @inheritdoc IERC20Boost
    function deprecatedGauges() external view override returns (address[] memory) {
        return _deprecatedGauges.values();
    }

    /// @inheritdoc IERC20Boost
    function numDeprecatedGauges() external view override returns (uint256) {
        return _deprecatedGauges.length();
    }

    /// @inheritdoc IERC20Boost
    function freeGaugeBoost(address user) public view override returns (uint256) {
        return balanceOf[user] - getUserBoost[user];
    }

    /// @inheritdoc IERC20Boost
    function userGauges(address user) external view override returns (address[] memory) {
        return _userGauges[user].values();
    }

    /// @inheritdoc IERC20Boost
    function isUserGauge(address user, address gauge) external view override returns (bool) {
        return _userGauges[user].contains(gauge);
    }

    /// @inheritdoc IERC20Boost
    function userGauges(address user, uint256 offset, uint256 num)
        external
        view
        override
        returns (address[] memory values)
    {
        values = new address[](num);
        EnumerableSet.AddressSet storage userGaugesSet = _userGauges[user];
        for (uint256 i = 0; i < num;) {
            unchecked {
                values[i] = userGaugesSet.at(offset + i); // will revert if out of bounds
                i++;
            }
        }
    }

    /// @inheritdoc IERC20Boost
    function numUserGauges(address user) external view override returns (uint256) {
        return _userGauges[user].length();
    }

    /*///////////////////////////////////////////////////////////////
                        GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20Boost
    function attach(address user) external override {
        if (!_gauges.contains(msg.sender) || _deprecatedGauges.contains(msg.sender)) {
            revert InvalidGauge();
        }

        // idempotent add
        if (!_userGauges[user].add(msg.sender)) revert GaugeAlreadyAttached();

        uint128 userGaugeBoost = balanceOf[user].toUint128();

        if (getUserBoost[user] < userGaugeBoost) {
            getUserBoost[user] = userGaugeBoost;
            emit UpdateUserBoost(user, userGaugeBoost);
        }

        uint256 _totalSupply = totalSupply;
        if (_totalSupply > 0) {
            GaugeState storage userBoost = getUserGaugeBoost[user][msg.sender];
            userBoost.userGaugeBoost = userGaugeBoost;
            userBoost.totalGaugeBoost = _totalSupply.toUint128();
        }

        emit Attach(user, msg.sender, userGaugeBoost);
    }

    /// @inheritdoc IERC20Boost
    function detach(address user) external {
        require(_userGauges[user].remove(msg.sender)); // Remove from set. Should never fail.
        delete getUserGaugeBoost[user][msg.sender];

        emit Detach(user, msg.sender);
    }

    /*///////////////////////////////////////////////////////////////
                        USER GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20Boost
    function updateUserBoost(address user) external override {
        uint256 userBoost;

        address[] memory gaugeList = _userGauges[user].values();

        uint256 length = gaugeList.length;
        for (uint256 i = 0; i < length;) {
            uint256 gaugeBoost = getUserGaugeBoost[user][gaugeList[i]].userGaugeBoost;

            if (userBoost < gaugeBoost) userBoost = gaugeBoost;

            unchecked {
                i++;
            }
        }
        getUserBoost[user] = userBoost;

        emit UpdateUserBoost(user, userBoost);
    }

    /// @inheritdoc IERC20Boost
    function decrementGaugeBoost(address gauge, uint256 boost) public override {
        GaugeState storage gaugeState = getUserGaugeBoost[msg.sender][gauge];
        uint256 _userGaugeBoost = gaugeState.userGaugeBoost;

        if (_deprecatedGauges.contains(gauge) || boost >= _userGaugeBoost) {
            require(_userGauges[msg.sender].remove(gauge)); // Remove from set. Should never fail.
            delete getUserGaugeBoost[msg.sender][gauge];

            emit Detach(msg.sender, gauge);
        } else {
            _userGaugeBoost = _userGaugeBoost - boost;
            gaugeState.userGaugeBoost = _userGaugeBoost.toUint128();

            emit DecrementUserGaugeBoost(msg.sender, gauge, _userGaugeBoost);
        }
    }

    /// @inheritdoc IERC20Boost
    function decrementGaugeAllBoost(address gauge) external override {
        require(_userGauges[msg.sender].remove(gauge)); // Remove from set. Should never fail.
        delete getUserGaugeBoost[msg.sender][gauge];

        emit Detach(msg.sender, gauge);
    }

    /// @inheritdoc IERC20Boost
    function decrementAllGaugesBoost(uint256 boost) external override {
        decrementGaugesBoostIndexed(boost, 0, _userGauges[msg.sender].length());
    }

    /// @inheritdoc IERC20Boost
    function decrementGaugesBoostIndexed(uint256 boost, uint256 offset, uint256 num) public override {
        EnumerableSet.AddressSet storage userGaugesSet = _userGauges[msg.sender];

        address[] memory gaugeList = userGaugesSet.values();

        uint256 length = gaugeList.length;
        for (uint256 i = 0; i < num && i < length;) {
            address gauge = gaugeList[offset + i];

            GaugeState storage gaugeState = getUserGaugeBoost[msg.sender][gauge];
            uint256 _userGaugeBoost = gaugeState.userGaugeBoost;

            if (_deprecatedGauges.contains(gauge) || boost >= _userGaugeBoost) {
                require(userGaugesSet.remove(gauge)); // Remove from set. Should never fail.
                delete getUserGaugeBoost[msg.sender][gauge];

                emit Detach(msg.sender, gauge);
            } else {
                _userGaugeBoost = _userGaugeBoost - boost;
                gaugeState.userGaugeBoost = _userGaugeBoost.toUint128();

                emit DecrementUserGaugeBoost(msg.sender, gauge, _userGaugeBoost);
            }

            unchecked {
                i++;
            }
        }
    }

    /// @inheritdoc IERC20Boost
    function decrementAllGaugesAllBoost() external override {
        EnumerableSet.AddressSet storage userGaugesSet = _userGauges[msg.sender];

        // Loop through all user gauges, live and deprecated
        address[] memory gaugeList = userGaugesSet.values();

        // Free gauges until through the entire list
        uint256 size = gaugeList.length;
        for (uint256 i = 0; i < size;) {
            address gauge = gaugeList[i];

            require(userGaugesSet.remove(gauge)); // Remove from set. Should never fail.
            delete getUserGaugeBoost[msg.sender][gauge];

            emit Detach(msg.sender, gauge);

            unchecked {
                i++;
            }
        }

        delete getUserBoost[msg.sender];

        emit UpdateUserBoost(msg.sender, 0);
    }

    /*///////////////////////////////////////////////////////////////
                        ADMIN GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20Boost
    function addGauge(address gauge) external override onlyOwner {
        _addGauge(gauge);
    }

    function _addGauge(address gauge) internal {
        // add and fail loud if zero address or already present and not deprecated
        if (gauge == address(0) || !(_gauges.add(gauge) || _deprecatedGauges.remove(gauge))) revert InvalidGauge();

        emit AddGauge(gauge);
    }

    /// @inheritdoc IERC20Boost
    function removeGauge(address gauge) external override onlyOwner {
        _removeGauge(gauge);
    }

    function _removeGauge(address gauge) internal {
        // add to deprecated and fail loud if not present
        if (!_deprecatedGauges.add(gauge)) revert InvalidGauge();

        emit RemoveGauge(gauge);
    }

    /// @inheritdoc IERC20Boost
    function replaceGauge(address oldGauge, address newGauge) external override onlyOwner {
        _removeGauge(oldGauge);
        _addGauge(newGauge);
    }

    /*///////////////////////////////////////////////////////////////
                             ERC20 LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// NOTE: any "removal" of tokens from a user requires notAttached < amount.

    /**
     * @notice Burns `amount` of tokens from `from` address.
     * @dev User must have enough free boost.
     * @param from The address to burn tokens from.
     * @param amount The amount of tokens to burn.
     */
    function _burn(address from, uint256 amount) internal override notAttached(from, amount) {
        super._burn(from, amount);
    }

    /**
     * @notice Transfers `amount` of tokens from `msg.sender` to `to` address.
     * @dev User must have enough free boost.
     * @param to the address to transfer to.
     * @param amount the amount to transfer.
     */
    function transfer(address to, uint256 amount) public override notAttached(msg.sender, amount) returns (bool) {
        return super.transfer(to, amount);
    }

    /**
     * @notice Transfers `amount` of tokens from `from` address to `to` address.
     * @dev User must have enough free boost.
     * @param from the address to transfer from.
     * @param to the address to transfer to.
     * @param amount the amount to transfer.
     */
    function transferFrom(address from, address to, uint256 amount)
        public
        override
        notAttached(from, amount)
        returns (bool)
    {
        if (from == msg.sender) return super.transfer(to, amount);

        return super.transferFrom(from, to, amount);
    }

    /*///////////////////////////////////////////////////////////////
                             MODIFIERS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Reverts if the user does not have enough free boost.
     * @param user The user address.
     * @param amount The amount of boost.
     */
    modifier notAttached(address user, uint256 amount) {
        if (freeGaugeBoost(user) < amount) revert AttachedBoost();
        _;
    }
}

File 10 of 48 : ERC20Gauges.sol
// SPDX-License-Identifier: MIT
// Gauge weight logic inspired by Tribe DAO Contracts (flywheel-v2/src/token/ERC20Gauges.sol)
pragma solidity ^0.8.0;

import {SafeCastLib} from "lib/solady/src/utils/SafeCastLib.sol";

import {ReentrancyGuard} from "lib/solmate/src/utils/ReentrancyGuard.sol";
import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {EnumerableSet} from "lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol";

import {IFlywheelBooster} from "src/rewards/interfaces/IFlywheelBooster.sol";

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

import {Errors} from "./interfaces/Errors.sol";
import {IERC20Gauges} from "./interfaces/IERC20Gauges.sol";

/// @title  An ERC20 with an embedded "Gauge" style vote with liquid weights
abstract contract ERC20Gauges is ERC20MultiVotes, ReentrancyGuard, IERC20Gauges {
    using EnumerableSet for EnumerableSet.AddressSet;
    using SafeCastLib for *;

    /**
     * @notice Construct a new ERC20Gauges
     * @param _flywheelBooster the flywheel booster contract to accrue bribes
     */
    constructor(address _flywheelBooster) {
        if (_flywheelBooster == address(0)) revert InvalidBooster();
        flywheelBooster = IFlywheelBooster(_flywheelBooster);

        emit SetFlywheelBooster(_flywheelBooster);
    }

    /*///////////////////////////////////////////////////////////////
                            GAUGE STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice 1 hours period at the end of a cycle where votes cannot increment
    uint256 private constant INCREMENT_FREEZE_WINDOW = 1 hours;

    /// @inheritdoc IERC20Gauges
    IFlywheelBooster public override flywheelBooster;

    /// @inheritdoc IERC20Gauges
    mapping(address user => mapping(address gauge => uint112 weight)) public override getUserGaugeWeight;

    /// @inheritdoc IERC20Gauges
    /// @dev NOTE this may contain weights for deprecated gauges
    mapping(address user => uint112 weight) public override getUserWeight;

    /// @notice a mapping from a gauge to the total weight allocated to it
    /// @dev NOTE this may contain weights for deprecated gauges
    mapping(address gauge => Weight gaugeWeight) internal _getGaugeWeight;

    /// @notice the total global allocated weight ONLY of live gauges
    Weight internal _totalWeight;

    mapping(address user => EnumerableSet.AddressSet userGaugeSet) internal _userGauges;

    EnumerableSet.AddressSet internal _gauges;

    // Store deprecated gauges in case a user needs to free dead weight
    EnumerableSet.AddressSet internal _deprecatedGauges;

    /*///////////////////////////////////////////////////////////////
                              VIEW HELPERS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20Gauges
    function getGaugeCycleEnd() external view override returns (uint32) {
        return _getGaugeCycleEnd();
    }

    function _getGaugeCycleEnd() internal view returns (uint32) {
        uint256 nowPlusOneCycle = block.timestamp + 1 weeks;
        unchecked {
            // cannot divide by zero and always <= nowPlusOneCycle so no overflow
            return ((nowPlusOneCycle / 1 weeks) * 1 weeks).toUint32();
        }
    }

    /// @inheritdoc IERC20Gauges
    function getGaugeWeight(address gauge) external view override returns (uint112) {
        return _getGaugeWeight[gauge].currentWeight;
    }

    /// @inheritdoc IERC20Gauges
    function getStoredGaugeWeight(address gauge) external view override returns (uint112) {
        if (_deprecatedGauges.contains(gauge)) return 0;
        return _getStoredWeight(_getGaugeWeight[gauge], _getGaugeCycleEnd());
    }

    function _getStoredWeight(Weight storage gaugeWeight, uint32 currentCycle) internal view returns (uint112) {
        return gaugeWeight.currentCycle < currentCycle ? gaugeWeight.currentWeight : gaugeWeight.storedWeight;
    }

    /// @inheritdoc IERC20Gauges
    function totalWeight() external view override returns (uint112) {
        return _totalWeight.currentWeight;
    }

    /// @inheritdoc IERC20Gauges
    function storedTotalWeight() external view override returns (uint112) {
        return _getStoredWeight(_totalWeight, _getGaugeCycleEnd());
    }

    /// @inheritdoc IERC20Gauges
    function gauges() external view override returns (address[] memory) {
        return _gauges.values();
    }

    /// @inheritdoc IERC20Gauges
    function gauges(uint256 offset, uint256 num) external view override returns (address[] memory values) {
        values = new address[](num);
        for (uint256 i = 0; i < num;) {
            unchecked {
                values[i] = _gauges.at(offset + i); // will revert if out of bounds
                i++;
            }
        }
    }

    /// @inheritdoc IERC20Gauges
    function isGauge(address gauge) external view override returns (bool) {
        return _gauges.contains(gauge) && !_deprecatedGauges.contains(gauge);
    }

    /// @inheritdoc IERC20Gauges
    function numGauges() external view override returns (uint256) {
        return _gauges.length();
    }

    /// @inheritdoc IERC20Gauges
    function deprecatedGauges() external view override returns (address[] memory) {
        return _deprecatedGauges.values();
    }

    /// @inheritdoc IERC20Gauges
    function numDeprecatedGauges() external view override returns (uint256) {
        return _deprecatedGauges.length();
    }

    /// @inheritdoc IERC20Gauges
    function userGauges(address user) external view override returns (address[] memory) {
        return _userGauges[user].values();
    }

    /// @inheritdoc IERC20Gauges
    function isUserGauge(address user, address gauge) external view override returns (bool) {
        return _userGauges[user].contains(gauge);
    }

    /// @inheritdoc IERC20Gauges
    function userGauges(address user, uint256 offset, uint256 num)
        external
        view
        override
        returns (address[] memory values)
    {
        values = new address[](num);
        EnumerableSet.AddressSet storage userGaugesSet = _userGauges[user];
        for (uint256 i = 0; i < num;) {
            unchecked {
                values[i] = userGaugesSet.at(offset + i); // will revert if out of bounds
                i++;
            }
        }
    }

    /// @inheritdoc IERC20Gauges
    function numUserGauges(address user) external view override returns (uint256) {
        return _userGauges[user].length();
    }

    /// @inheritdoc ERC20MultiVotes
    function userUnusedVotes(address user) public view override returns (uint256) {
        return super.userUnusedVotes(user) - getUserWeight[user];
    }

    /// @inheritdoc IERC20Gauges
    function calculateGaugeAllocation(address gauge, uint256 quantity) external view override returns (uint256) {
        if (_deprecatedGauges.contains(gauge)) return 0;
        uint32 currentCycle = _getGaugeCycleEnd();

        // quantity * gauge weight / total weight
        return (quantity * _getStoredWeight(_getGaugeWeight[gauge], currentCycle))
            / _getStoredWeight(_totalWeight, currentCycle);
    }

    /*///////////////////////////////////////////////////////////////
                        USER GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20Gauges
    function incrementGauge(address gauge, uint112 weight) external nonReentrant returns (uint112 newUserWeight) {
        uint32 currentCycle = _getGaugeCycleEnd();
        _incrementGaugeWeight(msg.sender, gauge, weight, currentCycle);
        return _incrementUserAndGlobalWeights(msg.sender, weight, currentCycle);
    }

    /**
     * @notice Increment the weight of a gauge for a user
     * @dev This function calls accrueBribes for the gauge to ensure the gauge handles the balance change.
     * @param user the user to increment the weight of
     * @param gauge the gauge to increment the weight of
     * @param weight the weight to increment by
     * @param cycle the cycle to increment the weight for
     */
    function _incrementGaugeWeight(address user, address gauge, uint112 weight, uint32 cycle) internal {
        if (!_gauges.contains(gauge) || _deprecatedGauges.contains(gauge)) revert InvalidGaugeError();
        unchecked {
            if (cycle - block.timestamp <= INCREMENT_FREEZE_WINDOW) revert IncrementFreezeError();
        }

        flywheelBooster.accrueBribesPositiveDelta(user, ERC20(gauge), weight);

        EnumerableSet.AddressSet storage userGaugeSet = _userGauges[user];

        // idempotent add
        if (userGaugeSet.add(gauge)) {
            if (userGaugeSet.length() > maxGauges) {
                if (!canContractExceedMaxGauges[user]) {
                    revert MaxGaugeError();
                }
            }
        }

        getUserGaugeWeight[user][gauge] += weight;

        _writeGaugeWeight(_getGaugeWeight[gauge], _add112, weight, cycle);

        emit IncrementGaugeWeight(user, gauge, weight);
    }

    /**
     * @notice Increment the weight of a gauge for a user and the total weight
     * @param user the user to increment the weight of
     * @param weight the weight to increment by
     * @param cycle the cycle to increment the weight for
     * @return newUserWeight the new user's weight
     */
    function _incrementUserAndGlobalWeights(address user, uint112 weight, uint32 cycle)
        internal
        returns (uint112 newUserWeight)
    {
        newUserWeight = getUserWeight[user] + weight;

        // new user weight must be less than or equal to the total user weight
        if (newUserWeight > getVotes(user)) revert OverWeightError();

        // Update gauge state
        getUserWeight[user] = newUserWeight;

        _writeGaugeWeight(_totalWeight, _add112, weight, cycle);
    }

    /// @inheritdoc IERC20Gauges
    function incrementGauges(address[] calldata gaugeList, uint112[] calldata weights)
        external
        nonReentrant
        returns (uint256 newUserWeight)
    {
        uint256 size = gaugeList.length;
        if (weights.length != size) revert SizeMismatchError();

        // store total in summary for a batch update on user/global state
        uint112 weightsSum;

        uint32 currentCycle = _getGaugeCycleEnd();

        // Update a gauge's specific state
        for (uint256 i = 0; i < size;) {
            address gauge = gaugeList[i];
            uint112 weight = weights[i];
            weightsSum += weight;

            _incrementGaugeWeight(msg.sender, gauge, weight, currentCycle);
            unchecked {
                i++;
            }
        }
        return _incrementUserAndGlobalWeights(msg.sender, weightsSum, currentCycle);
    }

    /// @inheritdoc IERC20Gauges
    function decrementGauge(address gauge, uint112 weight) external nonReentrant returns (uint112 newUserWeight) {
        uint32 currentCycle = _getGaugeCycleEnd();

        // All operations will revert on underflow, protecting against bad inputs
        _decrementGaugeWeight(msg.sender, gauge, weight, currentCycle);
        if (!_deprecatedGauges.contains(gauge)) {
            _writeGaugeWeight(_totalWeight, _subtract112, weight, currentCycle);
        }
        return _decrementUserWeights(msg.sender, weight);
    }

    /**
     * @notice Decrement the weight of a gauge for a user
     * @dev This function calls accrueBribes for the gauge to ensure the gauge handles the balance change.
     * @param user the user to decrement the weight of
     * @param gauge the gauge to decrement the weight of
     * @param weight the weight to decrement by
     * @param cycle the cycle to decrement the weight for
     */
    function _decrementGaugeWeight(address user, address gauge, uint112 weight, uint32 cycle) internal {
        if (!_gauges.contains(gauge)) revert InvalidGaugeError();

        uint112 oldWeight = getUserGaugeWeight[user][gauge];

        flywheelBooster.accrueBribesNegativeDelta(user, ERC20(gauge), weight);

        getUserGaugeWeight[user][gauge] = oldWeight - weight;
        if (oldWeight == weight) {
            // If removing all weight, remove gauge from user list.
            require(_userGauges[user].remove(gauge));
        }

        _writeGaugeWeight(_getGaugeWeight[gauge], _subtract112, weight, cycle);

        emit DecrementGaugeWeight(user, gauge, weight);
    }

    /**
     * @notice Decrement the weight of a gauge for a user and the total weight
     * @param user the user to decrement the weight of
     * @param weight the weight to decrement by
     * @return newUserWeight the new user's weight
     */
    function _decrementUserWeights(address user, uint112 weight) internal returns (uint112 newUserWeight) {
        newUserWeight = getUserWeight[user] - weight;
        getUserWeight[user] = newUserWeight;
    }

    /// @inheritdoc IERC20Gauges
    function decrementGauges(address[] calldata gaugeList, uint112[] calldata weights)
        external
        override
        nonReentrant
        returns (uint112 newUserWeight)
    {
        uint256 size = gaugeList.length;
        if (weights.length != size) revert SizeMismatchError();

        // store total in summary for the batch update on user and global state
        uint112 weightsSum;
        uint112 globalWeightsSum;

        uint32 currentCycle = _getGaugeCycleEnd();

        // Update the gauge's specific state
        // All operations will revert on underflow, protecting against bad inputs
        for (uint256 i = 0; i < size;) {
            address gauge = gaugeList[i];
            uint112 weight = weights[i];
            weightsSum += weight;
            if (!_deprecatedGauges.contains(gauge)) globalWeightsSum += weight;

            _decrementGaugeWeight(msg.sender, gauge, weight, currentCycle);
            unchecked {
                i++;
            }
        }
        _writeGaugeWeight(_totalWeight, _subtract112, globalWeightsSum, currentCycle);

        return _decrementUserWeights(msg.sender, weightsSum);
    }

    /**
     * @dev this function is the key to the entire contract.
     *  The storage weight it operates on is either a global or gauge-specific weight.
     *  The operation applied is either addition for incrementing gauges or subtraction for decrementing a gauge.
     * @param weight the weight to apply the operation to
     * @param op the operation to apply
     * @param delta the amount to apply the operation by
     * @param cycle the cycle to apply the operation for
     */
    function _writeGaugeWeight(
        Weight storage weight,
        function(uint112, uint112) view returns (uint112) op,
        uint112 delta,
        uint32 cycle
    ) private {
        uint112 currentWeight = weight.currentWeight;
        // If the last cycle of the weight is before the current cycle, use the current weight as the stored.
        uint112 stored = weight.currentCycle < cycle ? currentWeight : weight.storedWeight;
        uint112 newWeight = op(currentWeight, delta);

        weight.storedWeight = stored;
        weight.currentWeight = newWeight;
        weight.currentCycle = cycle;
    }

    function _add112(uint112 a, uint112 b) private pure returns (uint112) {
        return a + b;
    }

    function _subtract112(uint112 a, uint112 b) private pure returns (uint112) {
        return a - b;
    }

    /*///////////////////////////////////////////////////////////////
                        ADMIN GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20Gauges
    uint256 public override maxGauges;

    /// @inheritdoc IERC20Gauges
    mapping(address contractAddress => bool canExceedMaxGauges) public override canContractExceedMaxGauges;

    /// @inheritdoc IERC20Gauges
    function addGauge(address gauge) external override onlyOwner returns (uint112) {
        return _addGauge(gauge);
    }

    /**
     * @notice Add a gauge to the contract
     * @param gauge the gauge to add
     * @return weight the previous weight of the gauge, if it was already added
     */
    function _addGauge(address gauge) internal returns (uint112 weight) {
        // add and fail loud if zero address or already present and not deprecated
        if (gauge == address(0) || !(_gauges.add(gauge) || _deprecatedGauges.remove(gauge))) revert InvalidGaugeError();

        // Check if some previous weight exists and re-add to the total. Gauge and user weights are preserved.
        weight = _getGaugeWeight[gauge].currentWeight;
        if (weight > 0) {
            _writeGaugeWeight(_totalWeight, _add112, weight, _getGaugeCycleEnd());
        }

        emit AddGauge(gauge);
    }

    /// @inheritdoc IERC20Gauges
    function removeGauge(address gauge) external override onlyOwner {
        _removeGauge(gauge);
    }

    /**
     * @notice Remove a gauge from the contract
     * @param gauge the gauge to remove
     */
    function _removeGauge(address gauge) internal {
        // add to deprecated and fail loud if not present
        if (!_deprecatedGauges.add(gauge)) revert InvalidGaugeError();

        uint32 currentCycle = _getGaugeCycleEnd();

        // Remove weight from total but keep the gauge and user weights in storage in case the gauge is re-added.
        uint112 weight = _getGaugeWeight[gauge].currentWeight;
        if (weight > 0) {
            _writeGaugeWeight(_totalWeight, _subtract112, weight, currentCycle);
        }

        emit RemoveGauge(gauge);
    }

    /// @inheritdoc IERC20Gauges
    function replaceGauge(address oldGauge, address newGauge) external override onlyOwner {
        _removeGauge(oldGauge);
        _addGauge(newGauge);
    }

    /// @inheritdoc IERC20Gauges
    function setMaxGauges(uint256 newMax) external override onlyOwner {
        uint256 oldMax = maxGauges;
        maxGauges = newMax;

        emit MaxGaugesUpdate(oldMax, newMax);
    }

    /// @inheritdoc IERC20Gauges
    function setContractExceedMaxGauges(address account, bool canExceedMax) external override onlyOwner {
        if (canExceedMax) if (account.code.length == 0) revert Errors.NonContractError(); // can only approve contracts

        canContractExceedMaxGauges[account] = canExceedMax;

        emit CanContractExceedMaxGaugesUpdate(account, canExceedMax);
    }

    function setFlywheelBooster(address newFlywheelBooster) external onlyOwner {
        if (newFlywheelBooster == address(0)) revert InvalidBooster();
        flywheelBooster = IFlywheelBooster(newFlywheelBooster);

        emit SetFlywheelBooster(newFlywheelBooster);
    }

    /*///////////////////////////////////////////////////////////////
                             ERC20 LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// NOTE: any "removal" of tokens from a user requires userUnusedVotes < amount.
    /// _decrementWeightUntilFree is called as a greedy algorithm to free up weight.
    /// It may be more gas efficient to free weight before burning or transferring tokens.

    /**
     * @notice Burns `amount` of tokens from `from` address.
     * @dev Frees weights and votes with a greedy algorithm if needed to burn tokens
     * @param from The address to burn tokens from.
     * @param amount The amount of tokens to burn.
     */
    function _burn(address from, uint256 amount) internal virtual override {
        _decrementWeightUntilFree(from, amount);
        super._burn(from, amount);
    }

    /**
     * @notice Transfers `amount` of tokens from `msg.sender` to `to` address.
     * @dev Frees weights and votes with a greedy algorithm if needed to burn tokens
     * @param to the address to transfer to.
     * @param amount the amount to transfer.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        _decrementWeightUntilFree(msg.sender, amount);
        return super.transfer(to, amount);
    }

    /**
     * @notice Transfers `amount` of tokens from `from` address to `to` address.
     * @dev Frees weights and votes with a greedy algorithm if needed to burn tokens
     * @param from the address to transfer from.
     * @param to the address to transfer to.
     * @param amount the amount to transfer.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        _decrementWeightUntilFree(from, amount);

        if (from == msg.sender) return super.transfer(to, amount);

        return super.transferFrom(from, to, amount);
    }

    /**
     * @notice A greedy algorithm for freeing weight before a token burn/transfer
     * @dev Frees up entire gauges, so likely will free more than `weight`
     * @param user the user to free weight for
     * @param weight the weight to free
     */
    function _decrementWeightUntilFree(address user, uint256 weight) internal nonReentrant {
        uint256 userFreeWeight = freeVotes(user) + userUnusedVotes(user);

        // early return if already free
        if (userFreeWeight >= weight) return;

        uint32 currentCycle = _getGaugeCycleEnd();

        // cache totals for batch updates
        uint112 userFreed;
        uint112 totalFreed;

        // Loop through all user gauges, live and deprecated
        address[] memory gaugeList = _userGauges[user].values();

        // Free gauges through the entire list or until underweight
        uint256 size = gaugeList.length;
        for (uint256 i = 0; i < size && (userFreeWeight + userFreed) < weight;) {
            address gauge = gaugeList[i];
            uint112 userGaugeWeight = getUserGaugeWeight[user][gauge];
            if (userGaugeWeight != 0) {
                // If the gauge is live (not deprecated), include its weight in the total to remove
                if (!_deprecatedGauges.contains(gauge)) {
                    totalFreed += userGaugeWeight;
                }
                userFreed += userGaugeWeight;
                _decrementGaugeWeight(user, gauge, userGaugeWeight, currentCycle);
            }

            unchecked {
                i++;
            }
        }

        getUserWeight[user] = getUserWeight[user] - userFreed;
        _writeGaugeWeight(_totalWeight, _subtract112, totalFreed, currentCycle);
    }
}

File 11 of 48 : ERC20MultiVotes.sol
// SPDX-License-Identifier: MIT
// Voting logic inspired by OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/ERC20Votes.sol)

pragma solidity ^0.8.0;

import {Ownable} from "lib/solady/src/auth/Ownable.sol";
import {SafeCastLib} from "lib/solady/src/utils/SafeCastLib.sol";
import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {EnumerableSet} from "lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol";

import {IBaseV2Gauge} from "src/gauges/interfaces/IBaseV2Gauge.sol";

import {Errors} from "./interfaces/Errors.sol";
import {IERC20MultiVotes} from "./interfaces/IERC20MultiVotes.sol";

/// @title ERC20 Multi-Delegation Voting contract
abstract contract ERC20MultiVotes is ERC20, Ownable, IERC20MultiVotes {
    using EnumerableSet for EnumerableSet.AddressSet;
    using SafeCastLib for *;

    /*///////////////////////////////////////////////////////////////
                        VOTE CALCULATION LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice votes checkpoint list per user.
    mapping(address user => Checkpoint[] checkpointList) private _checkpoints;

    /// @inheritdoc IERC20MultiVotes
    function checkpoints(address account, uint32 pos) public view virtual override returns (Checkpoint memory) {
        return _checkpoints[account][pos];
    }

    /// @inheritdoc IERC20MultiVotes
    function numCheckpoints(address account) public view virtual override returns (uint32) {
        return _checkpoints[account].length.toUint32();
    }

    /// @inheritdoc IERC20MultiVotes
    function freeVotes(address account) public view virtual override returns (uint256) {
        return balanceOf[account] - userDelegatedVotes[account];
    }

    /// @inheritdoc IERC20MultiVotes
    function getVotes(address account) public view virtual override returns (uint256) {
        uint256 pos = _checkpoints[account].length;
        return pos == 0 ? 0 : _checkpoints[account][pos - 1].votes;
    }

    /// @inheritdoc IERC20MultiVotes
    function userUnusedVotes(address user) public view virtual override returns (uint256) {
        return getVotes(user);
    }

    /// @inheritdoc IERC20MultiVotes
    function getPriorVotes(address account, uint256 blockNumber) public view virtual override returns (uint256) {
        if (blockNumber >= block.number) revert BlockError();
        return _checkpointsLookup(_checkpoints[account], blockNumber);
    }

    /// @dev Lookup a value in a list of (sorted) checkpoints.
    function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256) {
        // We run a binary search to look for the earliest checkpoint taken after `blockNumber`.
        uint256 high = ckpts.length;
        uint256 low;
        while (low < high) {
            uint256 mid = _average(low, high);
            if (ckpts[mid].fromBlock > blockNumber) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        return high == 0 ? 0 : ckpts[high - 1].votes;
    }

    function _average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + ((a ^ b) >> 1);
    }

    /*///////////////////////////////////////////////////////////////
                        ADMIN OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC20MultiVotes
    uint256 public override maxDelegates;

    /// @inheritdoc IERC20MultiVotes
    mapping(address contractAddress => bool canExceedMaxGauges) public override canContractExceedMaxDelegates;

    /// @inheritdoc IERC20MultiVotes
    function setMaxDelegates(uint256 newMax) external override onlyOwner {
        uint256 oldMax = maxDelegates;
        maxDelegates = newMax;

        emit MaxDelegatesUpdate(oldMax, newMax);
    }

    /// @inheritdoc IERC20MultiVotes
    function setContractExceedMaxDelegates(address account, bool canExceedMax) external override onlyOwner {
        if (canExceedMax && account.code.length == 0) revert Errors.NonContractError(); // can only approve contracts

        canContractExceedMaxDelegates[account] = canExceedMax;

        emit CanContractExceedMaxDelegatesUpdate(account, canExceedMax);
    }

    /*///////////////////////////////////////////////////////////////
                        DELEGATION LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice How many votes a user has delegated to a delegatee.
    mapping(address user => mapping(address delegatee => uint256 votes)) private _delegatesVotesCount;

    /// @notice How many votes a user has delegated to him.
    mapping(address user => uint256 votes) public userDelegatedVotes;

    /// @notice The delegatees of a user.
    mapping(address user => EnumerableSet.AddressSet delegatesSet) private _delegates;

    /// @inheritdoc IERC20MultiVotes
    function delegatesVotesCount(address delegator, address delegatee) public view virtual override returns (uint256) {
        return _delegatesVotesCount[delegator][delegatee];
    }

    /// @inheritdoc IERC20MultiVotes
    function delegates(address delegator) public view override returns (address[] memory) {
        return _delegates[delegator].values();
    }

    /// @inheritdoc IERC20MultiVotes
    function delegateCount(address delegator) public view override returns (uint256) {
        return _delegates[delegator].length();
    }

    /// @inheritdoc IERC20MultiVotes
    function incrementDelegation(address delegatee, uint256 amount) public virtual override {
        _incrementDelegation(msg.sender, delegatee, amount);
    }

    /// @inheritdoc IERC20MultiVotes
    function undelegate(address delegatee, uint256 amount) public virtual override {
        _undelegate(msg.sender, delegatee, amount);
    }

    /// @inheritdoc IERC20MultiVotes
    function delegate(address newDelegatee) external virtual override {
        _delegate(msg.sender, newDelegatee);
    }

    /**
     * @notice Delegates all votes from `delegator` to `delegatee`
     * @dev Reverts if delegateCount > 1
     * @param delegator The address to delegate votes from
     * @param newDelegatee The address to delegate votes to
     */
    function _delegate(address delegator, address newDelegatee) internal virtual {
        uint256 count = delegateCount(delegator);

        // undefined behavior for delegateCount > 1
        if (count > 1) revert DelegationError();

        address oldDelegatee;
        // if already delegated, undelegate first
        if (count == 1) {
            oldDelegatee = _delegates[delegator].at(0);
            _undelegate(delegator, oldDelegatee, _delegatesVotesCount[delegator][oldDelegatee]);
        }

        // redelegate only if newDelegatee is not empty
        if (newDelegatee != address(0)) {
            _incrementDelegation(delegator, newDelegatee, freeVotes(delegator));
        }
        emit DelegateChanged(delegator, oldDelegatee, newDelegatee);
    }

    /**
     * @notice Delegates votes from `delegator` to `delegatee`
     * @dev Reverts if delegator is not approved and exceeds maxDelegates
     * @param delegator The address to delegate votes from
     * @param delegatee The address to delegate votes to
     * @param amount The amount of votes to delegate
     */
    function _incrementDelegation(address delegator, address delegatee, uint256 amount) internal virtual {
        // Require freeVotes exceed the delegation size
        if (delegatee == address(0) || freeVotes(delegator) < amount || amount == 0) revert DelegationError();

        // idempotent add
        if (_delegates[delegator].add(delegatee)) {
            if (delegateCount(delegator) > maxDelegates) {
                if (!canContractExceedMaxDelegates[delegator]) {
                    // if is a new delegate, exceeds max and is not approved to exceed, revert
                    revert DelegationError();
                }
            }
        }

        _delegatesVotesCount[delegator][delegatee] += amount;
        userDelegatedVotes[delegator] += amount;

        emit Delegation(delegator, delegatee, amount);
        _writeCheckpoint(delegatee, _add, amount);
    }

    /**
     * @notice Undelegates votes from `delegator` to `delegatee`
     * @dev Reverts if delegatee does not have enough free votes
     * @param delegator The address to undelegate votes from
     * @param delegatee The address to undelegate votes to
     * @param amount The amount of votes to undelegate
     */
    function _undelegate(address delegator, address delegatee, uint256 amount) internal virtual {
        /**
         * @dev delegatee needs to have sufficient free votes for delegator to undelegate.
         *         Delegatee needs to be trusted, can be either a contract or an EOA.
         *         If the delegatee does not have any free votes their vote delegators won't be able to undelegate.
         *         If it is a contract, a possible safety measure is to have an emergency clear votes.
         */
        if (userUnusedVotes(delegatee) < amount) revert UndelegationVoteError();

        uint256 newDelegates = _delegatesVotesCount[delegator][delegatee] - amount;

        if (newDelegates == 0) {
            require(_delegates[delegator].remove(delegatee));
        }

        _delegatesVotesCount[delegator][delegatee] = newDelegates;
        userDelegatedVotes[delegator] -= amount;

        emit Undelegation(delegator, delegatee, amount);
        _writeCheckpoint(delegatee, _subtract, amount);
    }

    /**
     * @notice Writes a checkpoint for `delegatee` with `delta` votes
     * @param delegatee The address to write a checkpoint for
     * @param op The operation to perform on the checkpoint
     * @param delta The difference in votes to write
     */
    function _writeCheckpoint(address delegatee, function(uint256, uint256) view returns (uint256) op, uint256 delta)
        private
    {
        Checkpoint[] storage ckpts = _checkpoints[delegatee];

        uint256 pos = ckpts.length;
        uint256 oldWeight = pos == 0 ? 0 : ckpts[pos - 1].votes;
        uint256 newWeight = op(oldWeight, delta);

        if (pos > 0 && ckpts[pos - 1].fromBlock == block.number) {
            ckpts[pos - 1].votes = newWeight.toUint224();
        } else {
            ckpts.push(Checkpoint({fromBlock: block.number.toUint32(), votes: newWeight.toUint224()}));
        }
        emit DelegateVotesChanged(delegatee, oldWeight, newWeight);
    }

    function _add(uint256 a, uint256 b) private pure returns (uint256) {
        return a + b;
    }

    function _subtract(uint256 a, uint256 b) private pure returns (uint256) {
        return a - b;
    }

    /*///////////////////////////////////////////////////////////////
                             ERC20 LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// NOTE: any "removal" of tokens from a user requires freeVotes(user) < amount.
    /// _decrementVotesUntilFree is called as a greedy algorithm to free up votes.
    /// It may be more gas efficient to free weight before burning or transferring tokens.

    /**
     * @notice Burns `amount` of tokens from `from` address.
     * @dev Frees votes with a greedy algorithm if needed to burn tokens
     * @param from The address to burn tokens from.
     * @param amount The amount of tokens to burn.
     */
    function _burn(address from, uint256 amount) internal virtual override {
        _decrementVotesUntilFree(from, amount);
        super._burn(from, amount);
    }

    /**
     * @notice Transfers `amount` of tokens from `msg.sender` to `to` address.
     * @dev Frees votes with a greedy algorithm if needed to burn tokens
     * @param to the address to transfer to.
     * @param amount the amount to transfer.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        _decrementVotesUntilFree(msg.sender, amount);
        return super.transfer(to, amount);
    }

    /**
     * @notice Transfers `amount` of tokens from `from` address to `to` address.
     * @dev Frees votes with a greedy algorithm if needed to burn tokens
     * @param from the address to transfer from.
     * @param to the address to transfer to.
     * @param amount the amount to transfer.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        _decrementVotesUntilFree(from, amount);

        if (from == msg.sender) return super.transfer(to, amount);

        return super.transferFrom(from, to, amount);
    }

    /**
     * @notice A greedy algorithm for freeing votes before a token burn/transfer
     * @dev Frees up entire delegates, so likely will free more than `votes`
     * @param user The address to free votes from.
     * @param votes The amount of votes to free.
     */
    function _decrementVotesUntilFree(address user, uint256 votes) internal {
        uint256 userFreeVotes = freeVotes(user);

        // early return if already free
        if (userFreeVotes >= votes) return;

        // cache total for batch updates
        uint256 totalFreed;

        EnumerableSet.AddressSet storage delegateSet = _delegates[user];

        // Loop through all delegates
        address[] memory delegateList = delegateSet.values();

        // Free gauges through the entire list or until underweight
        uint256 size = delegateList.length;
        for (uint256 i = 0; i < size && (userFreeVotes + totalFreed) < votes; i++) {
            address delegatee = delegateList[i];
            uint256 delegateVotes = _delegatesVotesCount[user][delegatee];
            // Minimum of votes delegated to delegatee and unused votes of delegatee
            uint256 votesToFree = FixedPointMathLib.min(delegateVotes, userUnusedVotes(delegatee));
            // Skip if votesToFree is zero
            if (votesToFree != 0) {
                totalFreed += votesToFree;

                if (delegateVotes == votesToFree) {
                    // If all votes are freed, remove delegatee from list
                    require(delegateSet.remove(delegatee)); // Remove from set. Should never fail.
                    delete _delegatesVotesCount[user][delegatee];
                } else {
                    // If not all votes are freed, update the votes count
                    _delegatesVotesCount[user][delegatee] -= votesToFree;
                }

                _writeCheckpoint(delegatee, _subtract, votesToFree);
                emit Undelegation(user, delegatee, votesToFree);
            }
        }

        if ((userFreeVotes + totalFreed) < votes) revert UndelegationVoteError();

        userDelegatedVotes[user] = userDelegatedVotes[user] - totalFreed;
    }

    /*///////////////////////////////////////////////////////////////
                             EIP-712 LOGIC
    ///////////////////////////////////////////////////////////////*/

    // keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
    bytes32 public constant DELEGATION_TYPEHASH = 0xe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf;

    function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) public {
        require(block.timestamp <= expiry, "ERC20MultiVotes: signature expired");
        address signer = ecrecover(
            keccak256(
                abi.encodePacked(
                    "\x19\x01", DOMAIN_SEPARATOR(), keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))
                )
            ),
            v,
            r,
            s
        );
        require(nonce == nonces[signer]++, "ERC20MultiVotes: invalid nonce");
        require(signer != address(0));
        _delegate(signer, delegatee);
    }

    // keccak256("Delegation(address delegatee,uint256 amount,uint256 nonce,uint256 expiry)");
    bytes32 public constant DELEGATION_AMOUNT_TYPEHASH =
        0x4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d4;

    function delegateAmountBySig(
        address delegatee,
        uint256 amount,
        uint256 nonce,
        uint256 expiry,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public {
        require(block.timestamp <= expiry, "ERC20MultiVotes: signature expired");
        address signer = ecrecover(
            keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR(),
                    keccak256(abi.encode(DELEGATION_AMOUNT_TYPEHASH, delegatee, amount, nonce, expiry))
                )
            ),
            v,
            r,
            s
        );
        require(nonce == nonces[signer]++, "ERC20MultiVotes: invalid nonce");
        require(signer != address(0));
        _incrementDelegation(signer, delegatee, amount);
    }
}

File 12 of 48 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title Shared Errors
 */
interface Errors {
    /// @notice thrown when attempting to approve an EOA that must be a contract
    error NonContractError();
}

File 13 of 48 : IERC20Boost.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title  An ERC20 with an embedded attachment mechanism to keep track of boost allocations to gauges
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice This contract is meant to be used to represent a token that can boost holders' rewards in other contracts.
 *          Holders can have their boost attached to gauges and cannot transfer their BurntHermes until they detach it.
 *          Only gauges can attach and detach boost from a user.
 *          The current user's boost and total supply are stored when attaching.
 *          The boost is then detached when the user removes their boost or when the gauge is removed.
 *          A "gauge" is represented by an address that distributes rewards to users periodically or continuously.
 *
 *          For example, gauges can be used to direct token emissions, similar to Curve or Hermes V1.
 *          Alternatively, gauges can be used to direct another quantity such as relative access to a line of credit.
 *          This contract should serve as reference for the amount of boost a user has allocated to a gauge.
 *          Then liquidity per user should be caculated by using this formula, from curve finance:
 *          min(UserLiquidity, (40 * UserLiquidity) + (60 * TotalLiquidity * UserBoostBalance / BoostTotal))
 *
 *          "Live" gauges are in the set.
 *          Users can only be attached to live gauges but can detach from live or deprecated gauges.
 *          Gauges can be deprecated and reinstated; and will maintain any non-removed boost from before.
 *
 *  @dev    SECURITY NOTES: decrementGaugeAllBoost can run out of gas.
 *          Gauges should be removed individually until decrementGaugeAllBoost can be called.
 *
 *          After having the boost attached:
 *           - getUserBoost() will return the maximum boost a user had allocated to all gauges.
 *             Which may be more than the current boost if there was boost removed and updateUserBoost() was not called.
 *
 *          Boost state is preserved on the gauge and user level even when a gauge is removed, in case it is re-added.
 */
interface IERC20Boost {
    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice User allocated boost to a gauge and BurntHermes total supply.
     * @param userGaugeBoost User allocated boost to a gauge.
     * @param totalGaugeBoost BurntHermes total supply when a user allocated the boost.
     */
    struct GaugeState {
        uint128 userGaugeBoost;
        uint128 totalGaugeBoost;
    }

    /*///////////////////////////////////////////////////////////////
                            GAUGE STATE
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice User allocated boost to a gauge and the BurntHermes total supply.
     * @param user User address.
     * @param gauge Gauge address.
     * @return userGaugeBoost User allocated boost to a gauge.
     * @return totalGaugeBoost The BurntHermes total supply when a user allocated the boost.
     */
    function getUserGaugeBoost(address user, address gauge)
        external
        view
        returns (uint128 userGaugeBoost, uint128 totalGaugeBoost);

    /*///////////////////////////////////////////////////////////////
                            VIEW HELPERS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Maximum boost a user had allocated to all gauges since he last called decrementAllGaugesAllBoost().
     * @param user User address.
     * @return boost Maximum user allocated boost.
     */
    function getUserBoost(address user) external view returns (uint256 boost);

    /**
     * @notice returns the set of live gauges
     */
    function gauges() external view returns (address[] memory);

    /**
     * @notice returns a paginated subset of live gauges
     *   @param offset the index of the first gauge element to read
     *   @param num the number of gauges to return
     */
    function gauges(uint256 offset, uint256 num) external view returns (address[] memory values);

    /**
     * @notice returns true if `gauge` is not in deprecated gauges
     */
    function isGauge(address gauge) external view returns (bool);

    /**
     * @notice returns the number of live gauges
     */
    function numGauges() external view returns (uint256);

    /**
     * @notice returns the set of previously live but now deprecated gauges
     */
    function deprecatedGauges() external view returns (address[] memory);

    /**
     * @notice returns the number of live gauges
     */
    function numDeprecatedGauges() external view returns (uint256);

    /**
     * @notice returns the set of gauges the user has allocated to, they may be live or deprecated.
     */
    function freeGaugeBoost(address user) external view returns (uint256);

    /**
     * @notice returns the set of gauges the user has allocated to, they may be live or deprecated.
     */
    function userGauges(address user) external view returns (address[] memory);

    /**
     * @notice returns true if `gauge` is in user gauges
     */
    function isUserGauge(address user, address gauge) external view returns (bool);

    /**
     *  @notice returns a paginated subset of gauges the user has allocated to, they may be live or deprecated.
     *  @param user the user to return gauges from.
     *  @param offset the index of the first gauge element to read.
     *  @param num the number of gauges to return.
     */
    function userGauges(address user, uint256 offset, uint256 num) external view returns (address[] memory values);

    /**
     * @notice returns the number of user gauges
     */
    function numUserGauges(address user) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice attach all user's boost to a gauge, only callable by the gauge.
     *  @param user the user to attach the gauge to.
     */
    function attach(address user) external;

    /**
     * @notice detach all user's boost from a gauge, only callable by the gauge.
     * @param user the user to detach the gauge from.
     */
    function detach(address user) external;

    /*///////////////////////////////////////////////////////////////
                        USER GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Update geUserBoost for a user, loop through all _userGauges
     * @param user the user to update the boost for.
     */
    function updateUserBoost(address user) external;

    /**
     * @notice Remove an amount of boost from a gauge
     * @param gauge the gauge to remove boost from.
     * @param boost the amount of boost to remove.
     */
    function decrementGaugeBoost(address gauge, uint256 boost) external;

    /**
     * @notice Remove all the boost from a gauge
     * @param gauge the gauge to remove boost from.
     */
    function decrementGaugeAllBoost(address gauge) external;

    /**
     * @notice Remove an amount of boost from all user gauges
     * @param boost the amount of boost to remove.
     */
    function decrementAllGaugesBoost(uint256 boost) external;

    /**
     * @notice Remove an amount of boost from all user gauges indexed
     * @param boost the amount of boost to remove.
     * @param offset the index of the first gauge element to read.
     * @param num the number of gauges to return.
     */
    function decrementGaugesBoostIndexed(uint256 boost, uint256 offset, uint256 num) external;

    /**
     * @notice Remove all the boost from all user gauges
     */
    function decrementAllGaugesAllBoost() external;

    /**
     * @notice add a new gauge. Requires auth by `authority`.
     */
    function addGauge(address gauge) external;

    /**
     * @notice remove a new gauge. Requires auth by `authority`.
     */
    function removeGauge(address gauge) external;

    /**
     * @notice replace a gauge. Requires auth by `authority`.
     */
    function replaceGauge(address oldGauge, address newGauge) external;

    /*///////////////////////////////////////////////////////////////
                            EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice emitted when adding a new gauge to the live set.
    event AddGauge(address indexed gauge);

    /// @notice emitted when removing a gauge from the live set.
    event RemoveGauge(address indexed gauge);

    /// @notice emmitted when a user attaches boost to a gauge.
    event Attach(address indexed user, address indexed gauge, uint256 indexed boost);

    /// @notice emmitted when a user detaches boost from a gauge.
    event Detach(address indexed user, address indexed gauge);

    /// @notice emmitted when a user updates their boost.
    event UpdateUserBoost(address indexed user, uint256 indexed updatedBoost);

    /// @notice emmitted when a user decrements their gauge boost.
    event DecrementUserGaugeBoost(address indexed user, address indexed gauge, uint256 indexed UpdatedBoost);

    /*///////////////////////////////////////////////////////////////
                            ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice thrown when trying to increment or remove a non-live gauge, or add a live gauge.
    error InvalidGauge();

    /// @notice thrown when a gauge tries to attach a position and already has one attached.
    error GaugeAlreadyAttached();

    /// @notice thrown when a gauge tries to transfer a position but does not have enough free balance.
    error AttachedBoost();
}

File 14 of 48 : IERC20Gauges.sol
// SPDX-License-Identifier: MIT
// Gauge weight logic inspired by Tribe DAO Contracts (flywheel-v2/src/token/ERC20Gauges.sol)
pragma solidity ^0.8.0;

import {IFlywheelBooster} from "src/rewards/interfaces/IFlywheelBooster.sol";

/**
 * @title  An ERC20 with an embedded "Gauge" style vote with liquid weights
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice This contract supports gauge-style votes with weights associated with resource allocation.
 *          Only after delegating to himself can a user allocate weight to a gauge.
 *          Holders can allocate weight in any proportion to supported gauges.
 *          A "gauge" is represented by an address that would receive the resources periodically or continuously.
 *
 *          For example, gauges can be used to direct token emissions, similar to Curve or Hermes V1.
 *          Alternatively, gauges can be used to direct another quantity such as relative access to a line of credit.
 *
 *          The contract's Ownable <https://github.com/Vectorized/solady/blob/main/src/auth/Ownable.sol>
 *          manages the gauge set and cap.
 *          "Live" gauges are in the set.
 *          Users can only add weight to live gauges but can remove weight from live or deprecated gauges.
 *          Gauges can be deprecated and reinstated; and will maintain any non-removed weight from before.
 *
 *  @dev    SECURITY NOTES: `maxGauges` is a critical variable to protect against gas DOS attacks upon token transfer.
 *          This must be low enough to allow complicated transactions to fit in a block.
 *
 *          Weight state is preserved on the gauge and user level even when a gauge is removed, in case it is re-added.
 *          This maintains the state efficiently, and global accounting is managed only on the `_totalWeight`
 */
interface IERC20Gauges {
    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice a struct representing a user's weight allocation to a gauge
     * @param storedWeight weight allocated to a gauge at the end of the last cycle
     * @param currentWeight current weight allocated to a gauge
     * @param currentCycle cycle in which the current weight was allocated
     */
    struct Weight {
        uint112 storedWeight;
        uint112 currentWeight;
        uint32 currentCycle;
    }

    /*///////////////////////////////////////////////////////////////
                            GAUGE STATE
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice returns the flywheel booster contract
     */
    function flywheelBooster() external view returns (IFlywheelBooster);

    /**
     * @notice a mapping from a user to their total allocated weight across all gauges
     */
    function getUserWeight(address) external view returns (uint112);

    /**
     * @notice a mapping from users to gauges to a user's allocated weight to that gauge
     */
    function getUserGaugeWeight(address, address) external view returns (uint112);

    /*///////////////////////////////////////////////////////////////
                              VIEW HELPERS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice returns the end of the current cycle.
     * @dev This is the next unix timestamp which evenly divides `gaugeCycleLength`
     */
    function getGaugeCycleEnd() external view returns (uint32);

    /**
     * @notice returns the current weight of a given gauge
     * @param gauge address of the gauge to get the weight from
     */
    function getGaugeWeight(address gauge) external view returns (uint112);

    /**
     * @notice returns the stored weight of a given gauge.
     * @dev This is the snapshotted weight as of the end of the last cycle.
     */
    function getStoredGaugeWeight(address gauge) external view returns (uint112);

    /**
     * @notice returns the current total allocated weight
     */
    function totalWeight() external view returns (uint112);

    /**
     * @notice returns the stored total allocated weight
     */
    function storedTotalWeight() external view returns (uint112);

    /**
     * @notice returns the set of live gauges
     */
    function gauges() external view returns (address[] memory);

    /**
     * @notice returns a paginated subset of live gauges
     *  @param offset the index of the first gauge element to read
     *  @param num the number of gauges to return
     */
    function gauges(uint256 offset, uint256 num) external view returns (address[] memory values);

    /**
     * @notice returns true if `gauge` is not in deprecated gauges
     */
    function isGauge(address gauge) external view returns (bool);

    /**
     * @notice returns the number of live gauges
     */
    function numGauges() external view returns (uint256);

    /**
     * @notice returns the set of previously live but now deprecated gauges
     */
    function deprecatedGauges() external view returns (address[] memory);

    /**
     * @notice returns the number of live gauges
     */
    function numDeprecatedGauges() external view returns (uint256);

    /**
     * @notice returns the set of gauges the user has allocated to, may be live or deprecated.
     */
    function userGauges(address user) external view returns (address[] memory);

    /**
     * @notice returns true if `gauge` is in user gauges
     */
    function isUserGauge(address user, address gauge) external view returns (bool);

    /**
     * @notice returns a paginated subset of gauges the user has allocated to, may be live or deprecated.
     *  @param user the user to return gauges from.
     *  @param offset the index of the first gauge element to read.
     *  @param num the number of gauges to return.
     */
    function userGauges(address user, uint256 offset, uint256 num) external view returns (address[] memory values);

    /**
     * @notice returns the number of user gauges
     */
    function numUserGauges(address user) external view returns (uint256);

    /**
     * @notice helper function for calculating the proportion of a `quantity` allocated to a gauge
     *  @dev Returns 0 if a gauge is not live, even if it has weight.
     *  @param gauge the gauge to calculate the allocation of
     *  @param quantity a representation of a resource to be shared among all gauges
     *  @return the proportion of `quantity` allocated to `gauge`.
     */
    function calculateGaugeAllocation(address gauge, uint256 quantity) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        USER GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice increment a gauge with some weight for the caller
     *  @param gauge the gauge to increment
     *  @param weight the amount of weight to increment on a gauge
     *  @return newUserWeight the new user weight
     */
    function incrementGauge(address gauge, uint112 weight) external returns (uint112 newUserWeight);

    /**
     * @notice increment a list of gauges with some weights for the caller
     *  @param gaugeList the gauges to increment
     *  @param weights the weights to increment by
     *  @return newUserWeight the new user weight
     */
    function incrementGauges(address[] memory gaugeList, uint112[] memory weights)
        external
        returns (uint256 newUserWeight);

    /**
     * @notice decrement a gauge with some weight for the caller
     *  @param gauge the gauge to decrement
     *  @param weight the amount of weight to decrement on a gauge
     *  @return newUserWeight the new user weight
     */
    function decrementGauge(address gauge, uint112 weight) external returns (uint112 newUserWeight);

    /**
     * @notice decrement a list of gauges with some weights for the caller
     *  @param gaugeList the gauges to decrement
     *  @param weights the list of weights to decrement on the gauges
     *  @return newUserWeight the new user weight
     */
    function decrementGauges(address[] memory gaugeList, uint112[] memory weights)
        external
        returns (uint112 newUserWeight);

    /*///////////////////////////////////////////////////////////////
                        ADMIN GAUGE OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice the default maximum amount of gauges a user can allocate to.
     * @dev Use `numUserGauges` to check this. If this number is ever lowered, or a contract has an override,
     *      then existing addresses MAY have more gauges allocated to them.
     */
    function maxGauges() external view returns (uint256);

    /**
     * @notice an approved list for contracts to go above the max gauge limit.
     */
    function canContractExceedMaxGauges(address) external view returns (bool);

    /**
     * @notice add a new gauge. Requires auth by `ownable`.
     */
    function addGauge(address gauge) external returns (uint112);

    /**
     * @notice remove a new gauge. Requires auth by `ownable`.
     */
    function removeGauge(address gauge) external;

    /**
     * @notice replace a gauge. Requires auth by `ownable`.
     */
    function replaceGauge(address oldGauge, address newGauge) external;

    /**
     * @notice set the new max gauges. Requires auth by `ownable`.
     * @dev Use `numUserGauges` to check this.
     *      If this is set to a lower number than the current max, users MAY have more gauges active than the max.
     */
    function setMaxGauges(uint256 newMax) external;

    /**
     * @notice set the canContractExceedMaxGauges flag for an account.
     */
    function setContractExceedMaxGauges(address account, bool canExceedMax) external;

    /*///////////////////////////////////////////////////////////////
                            EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice emitted when incrementing a gauge
    event IncrementGaugeWeight(address indexed user, address indexed gauge, uint256 indexed weight);

    /// @notice emitted when decrementing a gauge
    event DecrementGaugeWeight(address indexed user, address indexed gauge, uint256 indexed weight);

    /// @notice emitted when adding a new gauge to the live set.
    event AddGauge(address indexed gauge);

    /// @notice emitted when removing a gauge from the live set.
    event RemoveGauge(address indexed gauge);

    /// @notice emitted when updating the maximum number of gauges a user can delegate to.
    event MaxGaugesUpdate(uint256 indexed oldMaxGauges, uint256 indexed newMaxGauges);

    /// @notice emitted when changing a contract's approval to go over the max gauges.
    event CanContractExceedMaxGaugesUpdate(address indexed account, bool indexed canContractExceedMaxGauges);

    /// @notice emitted when changing a contract's approval to go over the max gauges.
    event SetFlywheelBooster(address indexed newFlywheelBooster);

    /*///////////////////////////////////////////////////////////////
                            ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice thrown when trying to increment/decrement a mismatched number of gauges and weights.
    error SizeMismatchError();

    /// @notice thrown when trying to increment over the max allowed gauges.
    error MaxGaugeError();

    /// @notice thrown when incrementing over a user's free weight.
    error OverWeightError();

    /// @notice thrown when incrementing during the frozen window.
    error IncrementFreezeError();

    /// @notice thrown when trying to increment or remove a non-live gauge, or add a live gauge.
    error InvalidGaugeError();

    /// @notice thrown when initializing the boost module with a zero address.
    error InvalidBooster();
}

File 15 of 48 : IERC20MultiVotes.sol
// SPDX-License-Identifier: MIT
// Voting logic inspired by OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/ERC20Votes.sol)
pragma solidity ^0.8.0;

/**
 * @title ERC20 Multi-Delegation Voting contract
 *  @notice an ERC20 extension that allows delegations to multiple delegatees up to a user's balance on a given block.
 */
interface IERC20MultiVotes {
    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice A checkpoint for marking the number of votes from a given block.
     * @param fromBlock the block number that the votes were delegated.
     * @param votes the number of votes delegated.
     */
    struct Checkpoint {
        uint32 fromBlock;
        uint224 votes;
    }

    /*///////////////////////////////////////////////////////////////
                        VOTE CALCULATION LOGIC
    ///////////////////////////////////////////////////////////////*/
    /**
     * @notice Get the `pos`-th checkpoint for `account`.
     */
    function checkpoints(address account, uint32 pos) external view returns (Checkpoint memory);

    /**
     * @notice Get number of checkpoints for `account`.
     */
    function numCheckpoints(address account) external view returns (uint32);

    /**
     * @notice Gets the amount of unallocated votes for `account`.
     * @param account the address to get free votes of.
     * @return the amount of unallocated votes.
     */
    function freeVotes(address account) external view returns (uint256);

    /**
     * @notice Gets the current votes balance for `account`.
     * @param account the address to get votes of.
     * @return the amount of votes.
     */
    function getVotes(address account) external view returns (uint256);

    /**
     * @notice helper function exposing the amount of weight available to allocate for a user
     */
    function userUnusedVotes(address user) external view returns (uint256);

    /**
     * @notice Retrieve the number of votes for `account` at the end of `blockNumber`.
     * @param account the address to get votes of.
     * @param blockNumber the block to calculate votes for.
     * @return the amount of votes.
     */
    function getPriorVotes(address account, uint256 blockNumber) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        ADMIN OPERATIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice the maximum amount of delegates for a user at a given time
     */
    function maxDelegates() external view returns (uint256);

    /**
     * @notice an approve list for contracts to go above the max delegate limit.
     */
    function canContractExceedMaxDelegates(address) external view returns (bool);

    /**
     * @notice set the new max delegates per user. Requires auth by `authority`.
     */
    function setMaxDelegates(uint256 newMax) external;

    /**
     * @notice set the canContractExceedMaxDelegates flag for an account.
     */
    function setContractExceedMaxDelegates(address account, bool canExceedMax) external;

    /**
     * @notice mapping from a delegator to the total number of delegated votes.
     */
    function userDelegatedVotes(address) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        DELEGATION LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Get the amount of votes currently delegated by `delegator` to `delegatee`.
     * @param delegator the account which is delegating votes to `delegatee`.
     * @param delegatee the account receiving votes from `delegator`.
     * @return the total amount of votes delegated to `delegatee` by `delegator`
     */
    function delegatesVotesCount(address delegator, address delegatee) external view returns (uint256);

    /**
     * @notice Get the list of delegates from `delegator`.
     * @param delegator the account which is delegating votes to delegates.
     * @return the list of delegated accounts.
     */
    function delegates(address delegator) external view returns (address[] memory);

    /**
     * @notice Get the number of delegates from `delegator`.
     * @param delegator the account which is delegating votes to delegates.
     * @return the number of delegated accounts.
     */
    function delegateCount(address delegator) external view returns (uint256);

    /**
     * @notice Delegate `amount` votes from the sender to `delegatee`.
     * @param delegatee the receivier of votes.
     * @param amount the amount of votes received.
     * @dev requires "freeVotes(msg.sender) > amount" and will not exceed max delegates
     */
    function incrementDelegation(address delegatee, uint256 amount) external;

    /**
     * @notice Undelegate `amount` votes from the sender from `delegatee`.
     * @param delegatee the receivier of undelegation.
     * @param amount the amount of votes taken away.
     */
    function undelegate(address delegatee, uint256 amount) external;

    /**
     * @notice Delegate all votes `newDelegatee`. First undelegates from an existing delegate. If `newDelegatee` is zero, only undelegates.
     * @param newDelegatee the receiver of votes.
     * @dev undefined for `delegateCount(msg.sender) > 1`
     * NOTE This is meant for backward compatibility with the `ERC20Votes` and `ERC20VotesComp` interfaces from OpenZeppelin.
     */
    function delegate(address newDelegatee) external;

    /*///////////////////////////////////////////////////////////////
                            EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice emitted when updating the maximum amount of delegates per user
    event MaxDelegatesUpdate(uint256 indexed oldMaxDelegates, uint256 indexed newMaxDelegates);

    /// @notice emitted when updating the canContractExceedMaxDelegates flag for an account
    event CanContractExceedMaxDelegatesUpdate(address indexed account, bool indexed canContractExceedMaxDelegates);

    /// @dev Emitted when a `delegator` delegates `amount` votes to `delegate`.
    event Delegation(address indexed delegator, address indexed delegate, uint256 indexed amount);

    /// @dev Emitted when a `delegator` undelegates `amount` votes from `delegate`.
    event Undelegation(address indexed delegator, address indexed delegate, uint256 indexed amount);

    /// @dev Emitted when a token transfer or delegate change results in changes to an account's voting power.
    event DelegateVotesChanged(address indexed delegate, uint256 indexed previousBalance, uint256 indexed newBalance);

    /// @notice An event thats emitted when an account changes its delegate
    /// @dev this is used for backward compatibility with OZ interfaces for ERC20Votes and ERC20VotesComp.
    event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);

    /*///////////////////////////////////////////////////////////////
                            ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice thrown when trying to read from an invalid block.
    error BlockError();

    /// @dev thrown when attempting to delegate more votes than an address has free, or exceeding the max delegates
    error DelegationError();

    /// @dev thrown when attempting to undelegate more votes than the delegatee has unused.
    error UndelegationVoteError();
}

File 16 of 48 : ERC4626.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol";
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {IERC4626} from "./interfaces/IERC4626.sol";

/// @title Minimal ERC4626 tokenized Vault implementation
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20, IERC4626 {
    using SafeTransferLib for address;
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                               IMMUTABLES
    ///////////////////////////////////////////////////////////////*/

    ERC20 public immutable asset;

    constructor(ERC20 _asset, string memory _name, string memory _symbol) ERC20(_name, _symbol, _asset.decimals()) {
        asset = _asset;
    }

    /*//////////////////////////////////////////////////////////////
                        DEPOSIT/WITHDRAWAL LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626
    function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
        // Check for rounding error since we round down in previewDeposit.
        require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");

        // Need to transfer before minting or ERC777s could reenter.
        address(asset).safeTransferFrom(msg.sender, address(this), assets);

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        afterDeposit(assets, shares);
    }

    /// @inheritdoc IERC4626
    function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
        assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.

        // Need to transfer before minting or ERC777s could reenter.
        address(asset).safeTransferFrom(msg.sender, address(this), assets);

        _mint(receiver, shares);

        emit Deposit(msg.sender, receiver, assets, shares);

        afterDeposit(assets, shares);
    }

    /// @inheritdoc IERC4626
    function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256 shares) {
        shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.

        if (msg.sender != owner) {
            uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.

            if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
        }

        beforeWithdraw(assets, shares);

        _burn(owner, shares);

        emit Withdraw(msg.sender, receiver, owner, assets, shares);

        address(asset).safeTransfer(receiver, assets);
    }

    /// @inheritdoc IERC4626
    function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256 assets) {
        if (msg.sender != owner) {
            uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.

            if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
        }

        // Check for rounding error since we round down in previewRedeem.
        require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");

        beforeWithdraw(assets, shares);

        _burn(owner, shares);

        emit Withdraw(msg.sender, receiver, owner, assets, shares);

        address(asset).safeTransfer(receiver, assets);
    }

    /*//////////////////////////////////////////////////////////////
                            ACCOUNTING LOGIC
    ///////////////////////////////////////////////////////////////*/

    function totalAssets() public view virtual returns (uint256);

    /// @inheritdoc IERC4626
    function convertToShares(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? assets : assets.mulDiv(supply, totalAssets());
    }

    /// @inheritdoc IERC4626
    function convertToAssets(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? shares : shares.mulDiv(totalAssets(), supply);
    }

    /// @inheritdoc IERC4626
    function previewDeposit(uint256 assets) public view virtual returns (uint256) {
        return convertToShares(assets);
    }

    /// @inheritdoc IERC4626
    function previewMint(uint256 shares) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
    }

    /// @inheritdoc IERC4626
    function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
        uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.

        return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
    }

    /// @inheritdoc IERC4626
    function previewRedeem(uint256 shares) public view virtual returns (uint256) {
        return convertToAssets(shares);
    }

    /*//////////////////////////////////////////////////////////////
                     DEPOSIT/WITHDRAWAL LIMIT LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626
    function maxDeposit(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /// @inheritdoc IERC4626
    function maxMint(address) public view virtual returns (uint256) {
        return type(uint256).max;
    }

    /// @inheritdoc IERC4626
    function maxWithdraw(address owner) public view virtual returns (uint256) {
        return convertToAssets(balanceOf[owner]);
    }

    /// @inheritdoc IERC4626
    function maxRedeem(address owner) public view virtual returns (uint256) {
        return balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HOOKS LOGIC
    ///////////////////////////////////////////////////////////////*/

    function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}

    function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}

File 17 of 48 : ERC4626DepositOnly.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC4626, ERC4626, ERC20} from "./ERC4626.sol";

/// @title Minimal Deposit Only ERC4626 tokenized Vault implementation
/// @author Maia DAO (https://github.com/Maia-DAO)
abstract contract ERC4626DepositOnly is ERC4626 {
    /*//////////////////////////////////////////////////////////////
                               IMMUTABLES
    ///////////////////////////////////////////////////////////////*/

    constructor(ERC20 _asset, string memory _name, string memory _symbol) ERC4626(_asset, _name, _symbol) {}

    /*//////////////////////////////////////////////////////////////
                        DEPOSIT/WITHDRAWAL LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626
    function withdraw(uint256, address, address) public pure override returns (uint256) {
        revert DepositOnly();
    }

    /// @inheritdoc IERC4626
    function redeem(uint256, address, address) public pure override returns (uint256) {
        revert DepositOnly();
    }

    /*//////////////////////////////////////////////////////////////
                            ACCOUNTING LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626
    function previewWithdraw(uint256) public pure override returns (uint256) {
        revert DepositOnly();
    }

    /// @inheritdoc IERC4626
    function previewRedeem(uint256) public pure override returns (uint256) {
        revert DepositOnly();
    }

    /*//////////////////////////////////////////////////////////////
                     DEPOSIT/WITHDRAWAL LIMIT LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626
    function maxWithdraw(address) public pure override returns (uint256) {
        return 0;
    }

    /// @inheritdoc IERC4626
    function maxRedeem(address) public pure override returns (uint256) {
        return 0;
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HOOKS LOGIC
    ///////////////////////////////////////////////////////////////*/

    function beforeWithdraw(uint256, uint256) internal override {}

    /*//////////////////////////////////////////////////////////////
                                ERROR
    ///////////////////////////////////////////////////////////////*/

    error DepositOnly();
}

File 18 of 48 : IERC4626.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC4626 {
    /**
     * @notice Deposit assets into the Vault.
     * @param assets The amount of assets to deposit.
     * @param receiver The address to receive the shares.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @notice Mint shares from the Vault.
     * @param shares The amount of shares to mint.
     * @param receiver The address to receive the shares.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @notice Withdraw assets from the Vault.
     * @param assets The amount of assets to withdraw.
     * @param receiver The address to receive the assets.
     * @param owner The address to receive the shares.
     */
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /**
     * @notice  Redeem shares from the Vault.
     * @param shares The amount of shares to redeem.
     * @param receiver The address to receive the assets.
     * @param owner The address to receive the shares.
     */
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

    /**
     * @notice Calculates the amount of shares that would be received for a given amount of assets.
     * @param assets The amount of assets to deposit.
     */
    function convertToShares(uint256 assets) external view returns (uint256);

    /**
     * @notice  Calculates the amount of assets that would be received for a given amount of shares.
     * @param shares The amount of shares to redeem.
     */
    function convertToAssets(uint256 shares) external view returns (uint256);

    /**
     * @notice Preview the amount of shares that would be received for a given amount of assets.
     */
    function previewDeposit(uint256 assets) external view returns (uint256);

    /**
     * @notice Previews the amount of assets that would be received for minting a given amount of shares
     * @param shares The amount of shares to mint
     */
    function previewMint(uint256 shares) external view returns (uint256);

    /**
     * @notice Previews the amount of shares that would be received for a withdraw of a given amount of assets.
     * @param assets The amount of assets to withdraw.
     */
    function previewWithdraw(uint256 assets) external view returns (uint256);

    /**
     * @notice Previews the amount of assets that would be received for a redeem of a given amount of shares.
     */
    function previewRedeem(uint256 shares) external view returns (uint256);

    /**
     * @notice Returns the max amount of assets that can be deposited into the Vault.
     */
    function maxDeposit(address) external view returns (uint256);

    /**
     * @notice Returns the max amount of shares that can be minted from the Vault.
     */
    function maxMint(address) external view returns (uint256);

    /**
     * @notice Returns the max amount of assets that can be withdrawn from the Vault.
     */
    function maxWithdraw(address owner) external view returns (uint256);

    /**
     * @notice Returns the max amount of shares that can be redeemed from the Vault.
     */
    function maxRedeem(address owner) external view returns (uint256);

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    ///////////////////////////////////////////////////////////////*/

    event Deposit(address indexed caller, address indexed owner, uint256 indexed assets, uint256 shares);

    event Withdraw(
        address indexed caller, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
    );
}

File 19 of 48 : IBaseV2Gauge.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FlywheelCore} from "src/rewards/FlywheelCoreStrategy.sol";
import {FlywheelGaugeRewards} from "src/rewards/rewards/FlywheelGaugeRewards.sol";
import {MultiRewardsDepot} from "src/rewards/depots/MultiRewardsDepot.sol";

/**
 * @title Base V2 Gauge
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Handles rewards for distribution, boost attaching/detaching and
 *          accruing bribes for a given strategy.
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⠒⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠑⠦⣄⠀⠀⢀⣠⠖⢶⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⢤⡈⠳⣦⡀⠀⠀⠀⠀⠀⠀⠒⢦⣀⠀⠀⠈⢱⠖⠉⠀⠀⠀⠳⡄⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣌⣻⣦⡀⠀⠀⠀⠀⠀⠀⠈⠳⢄⢠⡏⠀⠀⠐⠀⠀⠀⠘⡀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠙⢿⣿⣄⠈⠓⢄⠀⠀⠀⠀⠈⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⢀⡴⠁⠀⠀⠀⡠⠂⠀⠀⠀⡾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢦⠀⠀⠀⠀⠀⠑⢄⠀⠀⠙⢿⣦⠀⠀⠑⢄⠀⠀⢰⠃⠀⠀⠀⠀⢀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⢠⠞⠀⠀⠀⠀⢰⠃⠀⠀⠀⠀⠧⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠀⠈⢧⠀⠀⠀⠀⠀⠈⠳⣄⠀⠀⠙⢷⡀⠀⠀⠙⢦⡘⢦⠀⠀⠀⠺⢿⠇⢀⠀⢸⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⢠⠎⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⡆⡀⠀⠀⠀⠈⣦⠀⠀⠀⠀⠀⠀⠈⢦⡀⠀⠀⠙⢦⡀⠀⠀⠑⢾⡇⠀⠀⠀⠈⢠⡟⠁⢸⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠁⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⢳⠀⢣⣧⠀⠀⠀⠀⠘⡆⠑⡀⠀⠀⠀⠀⠐⡝⢄⠀⠀⠀⠹⢆⠀⠀⢈⡷⠀⠀⠀⢠⡟⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⢸⣦⠀⠀⠀⠀⠀⢸⡄⢸⢹⣄⣆⠀⠀⠀⠸⡄⠹⡄⠀⠀⠀⠀⠈⢎⢧⡀⠀⠀⠈⠳⣤⡿⠛⠳⡀⠀⡉⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢸⠇⠀⠀⠀⡇⠀⢸⢸⠀⠀⠀⠀⠀⠘⣿⠀⣿⣿⡙⡄⠀⠀⠀⠹⡄⠘⡄⠀⠀⠀⠀⠈⢧⡱⡄⠀⠀⠀⠛⢷⣦⣤⣽⣶⣶⣶⣦⣤⣸⣀⡀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀⠀⠀⠀⡇⠀⢸⡸⡆⠀⠀⠀⠀⠀⣿⣇⣿⣿⣷⣽⡖⠒⠒⠊⢻⡉⠹⡏⠒⠂⠀⠀⠀⠳⡜⣦⡀⠀⠀⠀⠹⣿⡟⠋⣿⡻⣯⣍⠉⡉⠛⠶⣄⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⡇⡀⠸⡇⣧⢂⠀⠀⠀⢰⡸⣿⡿⢻⡇⠁⠹⣆⠀⠀⠈⢷⡀⠹⡾⣆⠀⠀⠀⠀⠙⣎⠟⣦⣀⣀⣴⣿⠀⣼⣿⢷⣌⠻⢷⣦⣄⣸⠇⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⢹⣇⠀⣧⢹⡟⡄⠀⠀⠀⣿⢿⠀⡀⢻⡀⠘⣎⢇⢀⣀⣨⣿⣦⠹⣾⣧⡀⠀⠀⣀⣨⠶⠾⣿⣿⣿⣿⣶⡿⣼⡞⠙⢷⣵⡈⠉⠉⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢸⢠⣠⠤⠒⢺⣿⠒⢿⡼⣿⣳⡀⠀⣠⠋⢿⠆⠰⡄⢳⣤⠼⣿⣯⣶⠿⠿⠿⠿⢿⣿⣷⣶⣿⠁⠀⠀⠀⣻⣿⣿⣿⣿⣷⣿⢡⢇⣾⢻⣿⣶⣄⡀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⢰⣼⢾⡀⠀⠀⠸⣿⡇⠈⣧⢹⡛⢣⡴⠃⠀⠘⣿⡀⣨⠾⣿⣾⠟⢩⣷⣿⣶⣤⠀⠀⠈⢿⡿⣿⠀⠀⠀⠀⢹⢿⣿⣿⣿⣿⠋⢻⠏⢹⣸⠁⠈⠛⠿⣶⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣇⠀⠀⠀⣿⢹⡄⢻⣧⢷⠛⣧⠀⠀⠀⠈⣿⣧⣾⣿⠁⠀⣾⣿⣿⣷⣾⡇⠀⠀⡜⠀⢸⠀⠀⠀⠀⢸⡄⢻⣿⣿⠋⠀⢸⠀⠀⣿⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⢸⣄⠠⣶⣻⣖⣷⡘⣇⠈⢧⠘⣷⠶⠒⠊⠙⣿⠟⠁⠀⠀⢹⡿⣿⣿⢿⠇⠀⠐⠁⠀⢼⠀⠀⠀⠀⡼⢸⠻⣿⣧⣀⠀⠀⢀⣼⢹⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿⣿⣿⠟⠛⠛⣿⣿⣿⣦⡈⠠⠘⠆⠀⠀⠈⠁⠀⠀⠀⠀⠈⠛⠶⠞⠋⠀⠀⠀⡀⢠⡏⠀⠀⠀⢠⡇⣼⢸⣿⡟⠉⢣⣠⢾⡇⢸⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⢀⣨⢿⡿⣟⢿⡄⠀⠀⣿⣿⣯⣿⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⣼⡿⢱⣿⣿⠁⠀⠈⡇⢸⡇⢠⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⢋⠁⠈⡇⠘⣆⠑⢄⠀⠘⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠇⠀⠀⢠⢿⣷⣿⣿⣿⡄⠀⠰⠃⣼⡇⢸⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⢻⠀⢹⣆⠀⠁⢤⡌⠓⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⠀⠀⢠⡏⢸⣿⣿⣿⡿⠻⡄⣠⣾⣿⡇⢸⠦⠀⠀⠀ ⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠈⡇⠀⢸⡆⢸⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⡏⠀⢀⡟⠀⣾⣿⣿⣿⠀⠀⣽⠁⢸⣿⣷⢸⡇⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⢸⡇⢸⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⠁⢠⡟⠀⢰⣿⣿⣿⣿⠀⢠⠇⢠⣾⡇⢿⡈⡇⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠈⠃⢸⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⡏⢀⠎⠀⠀⡼⣽⠋⠁⠈⢀⣾⣴⣿⣿⡇⠸⡇⢱⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⢠⡇⠀⠀⠀⢸⣿⠑⢹⣿⣄⠀⠀⠀⠀⠀⠀⠀⠀⠐⠂⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⠋⢠⠟⠀⠀⣸⣷⠃⠀⢀⡞⠁⢸⣿⣿⣿⣧⠀⢿⣸⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⢻⠃⠀⠀⠀⣿⡇⠀⣼⣿⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⣿⣿⠇⡴⠃⠀⠀⣰⣟⡏⠀⡠⠋⢀⣠⣿⣿⡏⠈⣿⡇⠘⣏⣆⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⢀⡾⠀⠀⠀⢠⣿⠀⠀⣿⣿⡿⢹⣿⡿⠷⣶⣤⣀⡀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣷⢧⡞⠁⠀⠀⣸⣿⠼⠷⢶⣶⣾⣿⡿⣿⡿⠀⠀⠘⣷⠀⠸⣿⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠊⠀⠀⠀⠀⣼⠇⠀⣸⣿⡿⢡⣿⡟⠀⣠⣾⣿⣿⣿⣷⣦⣤⣀⣠⣾⣿⣿⣿⣿⣛⣋⣀⣀⣠⢞⡟⠀⣀⡠⢾⣿⣿⡟⠀⢿⣧⠀⠀⠀⠘⡆⠀⠙⡇⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⡟⠀⢠⣿⠟⢀⣿⡟⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠉⠁⠀⠉⠉⠉⠑⠻⢭⡉⠉⠀⢸⡆⢿⡗⠀⠈⢿⡀⠀⠀⠀⠹⡄⠀⢱⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢀⡼⠁⠀⢀⡞⠉⢠⡿⣌⣴⣿⣿⣿⣿⣿⣿⣿⣿⡿⠛⠋⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠲⣄⢸⡇⠘⡇⠀⠀⠈⢧⠀⠀⠀⠀⢱⡀⠈⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⣠⠞⠁⠀⣠⠏⠀⣰⣿⣿⣿⡿⠟⠿⠛⣩⣾⡿⠛⠁⠀⢀⣼⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⡇⠀⠸⡄⠀⠀⠈⢇⠀⠀⠀⠀⢻⡀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⣠⠇⣠⣾⣿⠿⠛⠁⢀⣠⣴⣿⠟⠁⠀⠀⠀⢰⠋⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⡀⠀⠹⡄⠀⠀⠘⢆⠀⠀⠀⠀⠳⡀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣫⡾⠟⠋⢁⣀⣤⣶⣿⡟⠋⠀⠀⣀⣠⣤⣾⡿⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡄⠀⢹⡄⠀⠀⠈⢧⡀⠀⠀⠀⠙⣔⡄⠀
 */
interface IBaseV2Gauge {
    /*///////////////////////////////////////////////////////////////
                            GAUGE STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice the reward token paid
    function rewardToken() external returns (address);

    /// @notice the flywheel core contract
    function flywheelGaugeRewards() external returns (FlywheelGaugeRewards);

    /// @notice the gauge's strategy contract
    function strategy() external returns (address);

    /// @notice the gauge's rewards depot
    function multiRewardsDepot() external returns (MultiRewardsDepot);

    /*///////////////////////////////////////////////////////////////
                        GAUGE ACTIONS    
    ///////////////////////////////////////////////////////////////*/

    /// @notice function responsible for updating current epoch
    /// @dev should be called once per week, or any outstanding rewards will be kept for next cycle
    function newEpoch() external;

    /// @notice attaches a gauge to a user
    /// @dev only the strategy can call this function
    function attachUser(address user) external;

    /// @notice detaches a gauge to a users
    /// @dev only the strategy can call this function
    function detachUser(address user) external;

    /*///////////////////////////////////////////////////////////////
                            EVENTS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Emitted when weekly emissions are distributed
     * @param amount amount of tokens distributed
     */
    event Distribute(uint256 indexed amount);

    /*///////////////////////////////////////////////////////////////
                            ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice thrown when caller is not the strategy
    error StrategyError();
}

File 20 of 48 : BurntHermes.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {ERC4626DepositOnly} from "src/erc-4626/ERC4626DepositOnly.sol";

import {DeployBurntHermesBoost} from "./tokens/bHermesBoost.sol";
import {bHermesGauges} from "./tokens/bHermesGauges.sol";
import {bHermesVotes} from "./tokens/bHermesVotes.sol";
import {UtilityManager} from "./UtilityManager.sol";

/**
 * @title BurntHermes: Yield bearing, boosting, voting, and gauge enabled Hermes
 *  @notice BurntHermes is a deposit only ERC-4626 for HERMES tokens which:
 *          mints BurntHermes utility tokens (Weight, Boost, Governance)
 *          in exchange for burning HERMES.
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⣀⣀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣇⠸⣿⣿⠇⣸⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀
 *  ⢀⣠⣴⣶⠿⠋⣩⡿⣿⡿⠻⣿⡇⢠⡄⢸⣿⠟⢿⣿⢿⣍⠙⠿⣶⣦⣄⡀⠀
 *  ⠀⠉⠉⠁⠶⠟⠋⠀⠉⠀⢀⣈⣁⡈⢁⣈⣁⡀⠀⠉⠀⠙⠻⠶⠈⠉⠉⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⡿⠛⢁⡈⠛⢿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠿⣿⣦⣤⣈⠁⢠⣴⣿⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠻⢿⣿⣦⡉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢷⣦⣈⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣴⠦⠈⠙⠿⣦⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣤⡈⠁⢤⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠷⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠑⢶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠁⢰⡆⠈⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⠈⣡⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *
 *      ⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⠀⣿⡿⠛⠛⢿⣿⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⠀⢿⠁⠀⠀⠈⡿⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⢀⣤⣄
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣀⣀⣀⣸⣿⣿
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⢸⣿⣿
 *      ⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠉⠁
 *      ⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀
 *      ⢀⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⡀
 *      ⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿
 */
contract BurntHermes is UtilityManager, ERC4626DepositOnly {
    using SafeTransferLib for address;

    constructor(ERC20 _hermes, address _owner, address _flywheelBooster)
        UtilityManager(
            address(new bHermesGauges(_owner, _flywheelBooster)),
            address(DeployBurntHermesBoost.deploy(_owner)),
            address(new bHermesVotes(_owner))
        )
        ERC4626DepositOnly(_hermes, "Burnt Hermes: Gov + Yield + Boost", "bHERMES")
    {}

    /*///////////////////////////////////////////////////////////////
                            MODIFIERS
    ///////////////////////////////////////////////////////////////*/

    /// @dev Checks available weight allows for the call.
    modifier checkWeight(uint256 amount) override {
        if (balanceOf[msg.sender] < amount + userClaimedWeight[msg.sender]) {
            revert InsufficientShares();
        }
        _;
    }

    /// @dev Checks available boost allows for the call.
    modifier checkBoost(uint256 amount) override {
        if (balanceOf[msg.sender] < amount + userClaimedBoost[msg.sender]) {
            revert InsufficientShares();
        }
        _;
    }

    /// @dev Checks available governance allows for the call.
    modifier checkGovernance(uint256 amount) override {
        if (balanceOf[msg.sender] < amount + userClaimedGovernance[msg.sender]) {
            revert InsufficientShares();
        }
        _;
    }

    /*///////////////////////////////////////////////////////////////
                            UTILITY MANAGER LOGIC
    ///////////////////////////////////////////////////////////////*/

    function claimOutstanding() external virtual {
        uint256 balance = balanceOf[msg.sender];
        /// @dev Never underflows since balandeOf >= userClaimed.
        unchecked {
            claimWeight(balance - userClaimedWeight[msg.sender]);
            claimBoost(balance - userClaimedBoost[msg.sender]);
            claimGovernance(balance - userClaimedGovernance[msg.sender]);
        }
    }

    /*///////////////////////////////////////////////////////////////
                            ERC4626 LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Computes the amounts of tokens available in the contract.
     * @dev Front-running first deposit vulnerability is not an
     *      issue since in the initial state:
     *      total assets (~90,000,000 ether) are larger than the
     *      underlying's remaining circulating supply (~30,000,000 ether).
     */
    function totalAssets() public view virtual override returns (uint256) {
        return address(asset).balanceOf(address(this));
    }

    /*///////////////////////////////////////////////////////////////
                             ERC20 LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Mint new BurntHermes and its underlying tokens: governance, boost and gauge tokens
     * @param to address to mint new tokens for
     * @param amount amounts of new tokens to mint
     */
    function _mint(address to, uint256 amount) internal virtual override {
        gaugeWeight.mint(address(this), amount);
        gaugeBoost.mint(address(this), amount);
        governance.mint(address(this), amount);
        super._mint(to, amount);
    }

    /**
     * @notice Transfer BurntHermes and its underlying tokens.
     * @param to address to transfer the tokens to
     * @param amount amounts of tokens to transfer
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        uint256 userBalance = balanceOf[msg.sender];

        if (
            userBalance - userClaimedWeight[msg.sender] < amount || userBalance - userClaimedBoost[msg.sender] < amount
                || userBalance - userClaimedGovernance[msg.sender] < amount
        ) revert InsufficientUnderlying();

        return super.transfer(to, amount);
    }

    /**
     * @notice Transfer BurntHermes and its underlying tokens from a specific account
     * @param from address to transfer the tokens from
     * @param to address to transfer the tokens to
     * @param amount amounts of tokens to transfer
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        uint256 userBalance = balanceOf[from];

        if (
            userBalance - userClaimedWeight[from] < amount || userBalance - userClaimedBoost[from] < amount
                || userBalance - userClaimedGovernance[from] < amount
        ) revert InsufficientUnderlying();

        return super.transferFrom(from, to, amount);
    }

    /*///////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/
    /// @notice Insufficient Underlying assets in the vault for transfer.
    error InsufficientUnderlying();
}

File 21 of 48 : IBaseV2Minter.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import {ERC4626} from "src/erc-4626/ERC4626.sol";

import {HERMES} from "src/hermes/tokens/HERMES.sol";

import {IRewardsStream} from "src/rewards/interfaces/IFlywheelGaugeRewards.sol";
import {FlywheelGaugeRewards} from "src/rewards/rewards/FlywheelGaugeRewards.sol";

/**
 * @title Base V2 Minter
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Codifies the minting rules as per b(3,3), abstracted from the token to support
 *          any ERC4626 with any token that allows minting. Responsible for minting new tokens.
 */
interface IBaseV2Minter is IRewardsStream {
    /*//////////////////////////////////////////////////////////////
                         MINTER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice Underlying token that the contract has minting powers over.
    function underlying() external view returns (address);

    /// @notice ERC4626 vault that receives emissions via rebases,
    ///         which later will be distributed to the depositors.
    function vault() external view returns (ERC4626);

    /// @notice Holds the rewards for the current cycle and distributes them to the gauges.
    function flywheelGaugeRewards() external view returns (FlywheelGaugeRewards);

    /// @notice Represents the address of the DAO.
    function dao() external view returns (address);

    /// @notice Represents the percentage of the emissions that will be sent to the DAO.
    function daoShare() external view returns (uint96);

    /// @notice Represents the percentage of the circulating supply
    ///         that will be distributed every epoch as rewards
    function tailEmission() external view returns (uint96);

    /// @notice Represents the weekly emissions.
    function weekly() external view returns (uint256);

    /// @notice Represents the timestamp of the beginning of the new cycle.
    function activePeriod() external view returns (uint256);

    /**
     * @notice Initializes contract state. Called once when the contract is
     *         deployed to initialize the contract state.
     * @param _flywheelGaugeRewards address of the flywheel gauge rewards contract.
     */
    function initialize(FlywheelGaugeRewards _flywheelGaugeRewards) external;

    /*//////////////////////////////////////////////////////////////
                         ADMIN LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Changes the current tail emissions.
     * @param _tailEmission amount to set as the tail emission
     */
    function setTailEmission(uint96 _tailEmission) external;

    /**
     * @notice Sets the address of the DAO.
     * @param _dao address of the DAO.
     */
    function setDao(address _dao) external;

    /**
     * @notice Sets the share of the DAO rewards.
     * @param _daoShare share of the DAO rewards.
     */
    function setDaoShare(uint96 _daoShare) external;

    /*//////////////////////////////////////////////////////////////
                         EMISSION LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice Calculates circulating supply as total token supply - locked supply
    function circulatingSupply() external view returns (uint256);

    /// @notice Calculates tail end (infinity) emissions, starts set as 2% of total supply.
    function weeklyEmission() external view returns (uint256);

    /**
     * @notice Calculate inflation and adjust burn balances accordingly.
     * @param _minted Amount of minted bHermes
     */
    function calculateGrowth(uint256 _minted) external view returns (uint256);

    /**
     * @notice Updates critical information surrounding emissions, such as
     *         the weekly emissions, and mints the tokens for the previous week rewards.
     *         Update period can only be called once per cycle (1 week)
     */
    function updatePeriod() external;

    /**
     * @notice Distributes the weekly emissions to flywheelGaugeRewards contract.
     * @return totalQueuedForCycle represents the amounts of rewards to be distributed.
     */
    function getRewards() external returns (uint256 totalQueuedForCycle);

    /*///////////////////////////////////////////////////////////////
                                EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice Emitted when weekly emissions are minted.
    event Mint(uint256 indexed weekly, uint256 indexed circulatingSupply, uint256 indexed growth, uint256 daoShare);

    /// @notice Emitted when the DAO address changes.
    event ChangedDao(address indexed dao);

    /// @notice Emitted when the DAO share changes.
    event ChangedDaoShare(uint256 indexed daoShare);

    /// @notice Emitted when the tail emission changes.
    event ChangedTailEmission(uint256 indexed tailEmission);

    /*///////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @dev Throws when the caller of `getRewards()` is not the flywheelGaugeRewards contract.
    error NotFlywheelGaugeRewards();

    /// @dev Throws when the caller of `intialize()` is not the initializer contract.
    error NotInitializer();

    /// @dev Throws when new tail emission is higher than 10%.
    error TailEmissionTooHigh();

    /// @dev Throws when the new dao share is higher than 30%.
    error DaoShareTooHigh();

    /// @dev Throws when the updating dao share without a dao set.
    error DaoRewardsAreDisabled();
}

File 22 of 48 : IbHermesUnderlying.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title BurntHermes Underlying
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Represents the underlying position of the BurntHermes token.
 *   ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⣀⣀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣇⠸⣿⣿⠇⣸⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀
 *  ⢀⣠⣴⣶⠿⠋⣩⡿⣿⡿⠻⣿⡇⢠⡄⢸⣿⠟⢿⣿⢿⣍⠙⠿⣶⣦⣄⡀⠀
 *  ⠀⠉⠉⠁⠶⠟⠋⠀⠉⠀⢀⣈⣁⡈⢁⣈⣁⡀⠀⠉⠀⠙⠻⠶⠈⠉⠉⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⡿⠛⢁⡈⠛⢿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠿⣿⣦⣤⣈⠁⢠⣴⣿⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠻⢿⣿⣦⡉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢷⣦⣈⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣴⠦⠈⠙⠿⣦⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣤⡈⠁⢤⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠷⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠑⢶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠁⢰⡆⠈⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⠈⣡⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *
 *      ⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⠀⣿⡿⠛⠛⢿⣿⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⠀⢿⠁⠀⠀⠈⡿⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⢀⣤⣄
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣀⣀⣀⣸⣿⣿
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
 *      ⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⢸⣿⣿
 *      ⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠉⠁
 *      ⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀
 *      ⢀⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⡀
 *      ⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿
 */
interface IbHermesUnderlying {
    /// @notice thrown when minter is not BurntHermes contract.
    error NotbHermes();

    /**
     * @notice
     */
    function bHermes() external view returns (address);

    /**
     * @notice Mints new BurntHermes underlying tokens to a specific account.
     * @param to account to transfer BurntHermes underlying tokens to
     * @param amount amount of tokens to mint.
     */
    function mint(address to, uint256 amount) external;
}

File 23 of 48 : IUtilityManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {bHermesBoost} from "../tokens/bHermesBoost.sol";
import {bHermesGauges} from "../tokens/bHermesGauges.sol";
import {bHermesVotes as ERC20Votes} from "../tokens/bHermesVotes.sol";

/**
 * @title Utility Tokens Manager Contract
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice When implemented, this contract allows for the management
 *          of BurntHermes utility tokens.
 */
interface IUtilityManager {
    /*//////////////////////////////////////////////////////////////
                         UTILITY MANAGER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice BurntHermes Underlying Token responsible for allocating gauge weights.
    function gaugeWeight() external view returns (bHermesGauges);

    /// @notice BurntHermes Underlying Token for user boost accounting.
    function gaugeBoost() external view returns (bHermesBoost);

    /// @notice BurntHermes Underlying Token which grants governance rights.
    function governance() external view returns (ERC20Votes);

    /// @notice Mapping of different user's BurntHermes Gauge Weight withdrawn from vault.
    function userClaimedWeight(address) external view returns (uint256);

    /// @notice Mapping of different user's BurntHermes Boost withdrawn from vault.
    function userClaimedBoost(address) external view returns (uint256);

    /// @notice Mapping of different user's BurntHermes Governance withdrawn from vault.
    function userClaimedGovernance(address) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        UTILITY TOKENS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice Forfeits all claimed utility tokens.
    function forfeitOutstanding() external;

    /// @notice Forfeits the same amounts of multiple utility tokens.
    function forfeitMultiple(uint256 amount) external;

    /// @notice Forfeits multiple amounts of multiple utility tokens.
    function forfeitMultipleAmounts(uint256 weight, uint256 boost, uint256 _governance) external;

    /// @notice Forfeits amounts of weight utility token.
    /// @param amount The amount to send to partner manager
    function forfeitWeight(uint256 amount) external;

    /// @notice Forfeits amounts of boost utility token.
    /// @param amount The amount to send to partner manager
    function forfeitBoost(uint256 amount) external;

    /// @notice Forfeits amounts of governance utility token.
    /// @param amount The amount to send to partner manager
    function forfeitGovernance(uint256 amount) external;

    /// @notice Claims the same amounts of multiple utility tokens.
    function claimMultiple(uint256 amount) external;

    /// @notice Claims multiple amounts of multiple utility tokens.
    function claimMultipleAmounts(uint256 weight, uint256 boost, uint256 _governance) external;

    /// @notice Claims amounts of weight utility token.
    /// @param amount The amount to send to partner manager
    function claimWeight(uint256 amount) external;

    /// @notice Claims amounts of boost utility token.
    /// @param amount The amount to send to partner manager
    function claimBoost(uint256 amount) external;

    /// @notice Claims amounts of governance utility token.
    /// @param amount The amount to send to partner manager
    function claimGovernance(uint256 amount) external;

    /*///////////////////////////////////////////////////////////////
                                EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice Emitted when a user forfeits weight.
    event ForfeitWeight(address indexed user, uint256 indexed amount);

    /// @notice Emitted when a user forfeits boost.
    event ForfeitBoost(address indexed user, uint256 indexed amount);

    /// @notice Emitted when a user forfeits governance.
    event ForfeitGovernance(address indexed user, uint256 indexed amount);

    /// @notice Emitted when a user claims weight.
    event ClaimWeight(address indexed user, uint256 indexed amount);

    /// @notice Emitted when a user claims boost.
    event ClaimBoost(address indexed user, uint256 indexed amount);

    /// @notice Emitted when a user claims governance.
    event ClaimGovernance(address indexed user, uint256 indexed amount);

    /*///////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice Insufficient vault shares for action.
    error InsufficientShares();
}

File 24 of 48 : bHermesBoost.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {ERC20Boost} from "src/erc-20/ERC20Boost.sol";

import {IbHermesUnderlying} from "../interfaces/IbHermesUnderlying.sol";

/// @title Library for bHermesBoost deployment
library DeployBurntHermesBoost {
    function deploy(address _owner) external returns (bHermesBoost) {
        return new bHermesBoost(_owner);
    }
}

/**
 * @title bHermesBoost: Earns rights to boosted Hermes yield
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice An ERC20 with an embedded attachment mechanism to
 *          keep track of boost allocations to gauges.
 */
contract bHermesBoost is ERC20Boost, IbHermesUnderlying {
    /// @inheritdoc IbHermesUnderlying
    address public immutable override bHermes;

    constructor(address _owner) ERC20("BurntHermes Boost", "bHERMES-B", 18) {
        _initializeOwner(_owner);
        bHermes = msg.sender;
    }

    /// @inheritdoc IbHermesUnderlying
    function mint(address to, uint256 amount) external override onlybHermes {
        _mint(to, amount);
    }

    modifier onlybHermes() {
        if (msg.sender != bHermes) revert NotbHermes();
        _;
    }
}

File 25 of 48 : bHermesGauges.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {ERC20Gauges} from "src/erc-20/ERC20Gauges.sol";

import {IbHermesUnderlying} from "../interfaces/IbHermesUnderlying.sol";

/**
 * @title bHermesGauges: Directs Hermes emissions and receives fees/bribes
 * @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Represents the underlying emission direction power of a BurntHermes token.
 *          bHermesGauges is an ERC-4626 compliant BurntHermes token which:
 *          votes on bribes rewards allocation for Hermes gauges in a
 *          manipulation-resistant manner.
 *
 *          The BurntHermes owner/authority ONLY control the maximum number
 *          and approved overrides of gauges and delegates, as well as the live gauge list.
 */
contract bHermesGauges is ERC20Gauges, IbHermesUnderlying {
    /// @inheritdoc IbHermesUnderlying
    address public immutable bHermes;

    constructor(address _owner, address _flywheelBooster)
        ERC20Gauges(_flywheelBooster)
        ERC20("BurntHermes Gauges", "bHERMES-G", 18)
    {
        _initializeOwner(_owner);
        bHermes = msg.sender;
    }

    /// @inheritdoc IbHermesUnderlying
    function mint(address to, uint256 amount) external onlybHermes {
        _mint(to, amount);
    }

    modifier onlybHermes() {
        if (msg.sender != bHermes) revert NotbHermes();
        _;
    }
}

File 26 of 48 : bHermesVotes.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {ERC20MultiVotes} from "src/erc-20/ERC20MultiVotes.sol";

import {IbHermesUnderlying} from "../interfaces/IbHermesUnderlying.sol";

/**
 * @title bHermesVotes: Have power over Hermes' governance
 * @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Represents the underlying governance power of a BurntHermes token.
 */
contract bHermesVotes is ERC20MultiVotes, IbHermesUnderlying {
    /// @inheritdoc IbHermesUnderlying
    address public immutable override bHermes;

    constructor(address _owner) ERC20("BurntHermes Votes", "bHERMES-V", 18) {
        _initializeOwner(_owner);
        bHermes = msg.sender;
    }

    /// @inheritdoc IbHermesUnderlying
    function mint(address to, uint256 amount) external override onlybHermes {
        _mint(to, amount);
    }

    /**
     * @notice Burns Burnt Hermes gauge tokens
     * @param from account to burn tokens from
     * @param amount amount of tokens to burn
     */
    function burn(address from, uint256 amount) external onlybHermes {
        _burn(from, amount);
    }

    modifier onlybHermes() {
        if (msg.sender != bHermes) revert NotbHermes();
        _;
    }
}

File 27 of 48 : HERMES.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import {Ownable} from "lib/solady/src/auth/Ownable.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

/**
 * @title Hermes ERC20 token - Native token for the Hermes Incentive System
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Native token for the Hermes Incentive System.
 *
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣶⣾⡿⢻⣿⣿⣿⣟⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣟⡿⣷⣿⣿⢤⣾⣿⣻⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣽⣿⣿⣿⣿⡿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣻⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡛⠛⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⣴⣯⣛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⠟⠋⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣍⣻⣿⣿⣿⣿⣿⢧⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⡏⠀⠀⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣻⠾⠿⠻⣿⠇⣿⣿⡿⠟⢠⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⡇⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⣻⣿⣧⣤⣽⣤⣯⣿⣾⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⡇⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⡵⠾⢿⣿⡿⢟⣿⣿⣿⣿⣿⣿⡟⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⡿⣿⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣼⣯⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢳⠁⠀⠀⠈⠻⣿⣿⣿⣿⣿⡿⢋⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⣸⢳⣿⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣏⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⢠⣿⣿⣿⣿⡿⣽⣿⣿⣿⣿⣿⡋⣰⡟⣸⣿⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢰⡟⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠻⣿⣷⣤⣤⣀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣟⣫⣾⣿⣿⣿⣿⣿⡟⣱⠟⢰⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⢀⡿⢱⣿⣿⢯⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣰⡇⠉⠉⠉⠁⠀⠀⠀⠀⠀⣠⠄⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⡋⣴⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⢀⣾⢡⣿⣿⡿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢻⡇⡏⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣦⣤⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⡁⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⢀⡾⢃⣾⣿⣿⠗⣮⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣼⡇⡇⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣿⣿⣿⡏⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⢀⡿⢃⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡁⠸⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⡇⢻⣿⣿⣿⣿⣿⣿⣿⣿⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⣠⡿⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣷⠀⠀⠀⠀⠀⢀⣀⣠⣶⣿⣿⣿⣿⣿⣷⢟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⣀⣀⠀⠀⠀
 * ⠀⠀⣰⠟⣠⣿⣿⣿⣿⣿⣿⣿⣿⠿⠟⠋⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠸⣿⣿⣷⣶⣶⣶⣿⣿⣿⠿⠿⠿⢿⣿⣿⣿⣿⡾⣞⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣭⣟⣛⣛
 * ⢠⡾⠋⣴⣿⣿⣿⣿⡿⠟⠛⠯⣵⡦⣄⣸⣿⣿⣿⣿⣿⣿⡟⣿⣿⣿⣿⣿⣇⠈⢿⡈⢻⣿⢿⡋⠑⣷⠀⠀⠀⠀⠹⣿⣿⣿⣿⣽⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
 * ⢟⣥⣾⣿⣿⣿⠋⠁⠀⠀⠀⠀⠀⠙⢮⣿⣿⢻⣿⣿⣿⣿⡇⠈⡿⣿⣿⣿⣿⣷⠤⠵⠶⠊⠑⣮⠶⠥⠀⠀⠀⠀⠀⢻⡎⠿⣿⣏⣿⣮⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
 * ⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⢀⡞⢹⣿⢸⣿⣿⣿⣿⣿⠶⢅⣽⣿⣿⣿⣿⣷⣤⡴⠖⠋⠁⠀⠀⠀⠀⠀⠀⠀⠘⣿⡀⠙⣿⣿⣿⣿⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
 * ⣿⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠀⡞⢻⡼⣿⣿⣿⣿⣿⣧⠀⠈⢿⣿⣿⣿⣿⣿⣿⣦⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣇⠀⠘⣿⣿⣿⣿⣿⣟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
 * ⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⢀⡾⠁⣼⠇⠈⣧⢹⣿⣿⣿⣿⣿⡆⠀⠀⠻⣧⠙⢿⣿⣿⣿⣿⣿⣿⣶⣤⣀⠀⠀⠀⣀⣠⠽⣆⠀⠘⣿⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
 * ⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⣠⣾⠁⢀⣿⠀⣴⡿⣿⢿⣿⣿⣿⣿⣿⡀⠀⠀⢙⣷⣄⣉⢿⣿⣿⣿⣿⣿⣿⣿⣷⣾⡛⢋⠀⠀⢻⣧⠀⠈⢿⣷⡙⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡛⠿
 * ⣿⠃⠀⠀⠀⠀⠀⠀⢀⡴⣹⠃⠀⣸⣹⠀⢻⢇⠘⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⢀⣻⣷⣄⡈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣠⠶⠚⠛⣦⣴⣦⡹⣿⣎⠻⣮⡛⠿⣿⣿⣿⣿⣿⣿⣷
 * ⠇⠀⠀⠀⠀⠀⢀⡴⠋⣰⠃⠀⠀⡇⣿⠀⣾⢸⡄⠈⢿⣿⣿⣿⣿⣿⣿⣦⣠⠟⠀⢸⡇⢙⡳⢦⣽⡛⠻⢿⣿⣿⣿⣿⣿⣶⣺⡆⢹⣿⣿⣿⣿⣿⣶⡈⠻⣦⡈⠻⢿⣿⣿⣿⣿
 * ⠀⠀⠀⠀⠀⣠⠏⠀⣰⠃⠀⠀⣸⠀⠸⡄⣿⠈⡇⠀⠀⢿⢿⣿⣿⣿⣿⣿⣯⣀⢰⡋⠀⣟⣉⣉⣿⣿⣶⣾⠉⢻⣿⣿⣿⣿⣿⡇⢸⣿⣿⠻⣿⣿⣟⢿⣷⡈⠻⣦⣠⣿⣿⣿⣿
 * ⠀⠀⠀⠀⡜⠁⠀⣰⠃⠀⠀⠀⡿⠀⠀⢣⡟⠀⡗⠀⠀⠸⡆⠙⢿⣿⡻⣿⣿⣿⣿⣗⡒⠚⠛⠓⠋⠉⢁⣈⣳⣾⠋⠙⢿⣿⣿⣿⣼⣿⣿⣇⠈⢿⣿⣧⣽⣿⣆⢨⣿⣿⣿⠋⠚
 * ⠀⠀⠀⣾⠁⠀⢰⠇⠀⠀⠀⠀⡇⠀⠀⣸⣷⡞⠁⠀⠀⠀⣧⠀⠀⢽⡿⣯⠙⣿⣿⣿⣿⣦⣴⠒⠒⠋⠉⠉⠀⠙⣦⡀⠈⢹⣿⣿⣿⢿⣿⣿⣆⠀⢙⣿⣿⣿⢻⣿⣿⣟⢧⠀⠀
 * ⠀⠀⠀⡇⠀⠉⡅⢾⡡⢖⣈⡀⣷⣶⠾⣟⠋⠙⢦⡀⠀⠀⢸⠀⠀⠈⠷⠾⢿⣇⠀⠙⠿⣿⣿⣷⣄⣤⡤⠴⠶⠚⠛⣿⠉⠉⠹⣿⣿⢸⣿⣿⣘⣶⣾⣿⣿⣿⣿⣿⡟⠈⠈⢷⣠
 * ⠀⠀⠀⣇⠀⠀⠇⠀⠀⠀⠐⠯⠿⡄⠀⣿⡷⠀⠀⠈⠓⢤⣈⣧⣀⣀⣀⣠⠤⠿⣿⠛⠛⠉⠻⣿⣿⣧⡀⠀⠀⠀⠀⠘⣧⠀⠀⢻⣿⢸⣿⣿⡟⢁⣿⣿⣇⣿⣿⡿⡆⣀⣴⣿⡟
 * ⠀⠀⠀⠘⠄⠀⠀⠀⠀⠀⠀⠀⠀⠳⣴⡗⠈⠀⠀⠀⠀⠀⠙⢦⣄⠀⠀⠀⠀⠀⠘⡆⠀⠀⠀⢻⣿⣿⣿⣄⠀⠀⠀⠀⢫⠃⠀⢸⣿⣿⣿⣿⣧⢸⣿⣿⢿⣿⡟⣸⣿⡿⠋⠀⣇
 */
contract HERMES is ERC20, Ownable {
    constructor(address _owner) ERC20("Hermes", "HERMES", 18) {
        _initializeOwner(_owner);
    }

    /*///////////////////////////////////////////////////////////////
                        ERC20 LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Responsible for minting new hermes tokens.
     * @dev Checks if the sender is an allowed minter.
     * @param account account to mint tokens to.
     * @param amount amount of hermes to mint.
     */
    function mint(address account, uint256 amount) external onlyOwner {
        _mint(account, amount);
    }
}

File 28 of 48 : UtilityManager.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {bHermesBoost} from "./tokens/bHermesBoost.sol";
import {bHermesGauges} from "./tokens/bHermesGauges.sol";
import {bHermesVotes as ERC20Votes} from "./tokens/bHermesVotes.sol";

import {IUtilityManager} from "./interfaces/IUtilityManager.sol";

/// @title Utility Tokens Manager Contract
abstract contract UtilityManager is IUtilityManager {
    using SafeTransferLib for address;

    /*//////////////////////////////////////////////////////////////
                         UTILITY MANAGER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IUtilityManager
    bHermesGauges public immutable override gaugeWeight;
    /// @inheritdoc IUtilityManager
    bHermesBoost public immutable override gaugeBoost;
    /// @inheritdoc IUtilityManager
    ERC20Votes public immutable override governance;

    /// @inheritdoc IUtilityManager
    mapping(address user => uint256 claimedWeight) public override userClaimedWeight;
    /// @inheritdoc IUtilityManager
    mapping(address user => uint256 claimedBoost) public override userClaimedBoost;
    /// @inheritdoc IUtilityManager
    mapping(address user => uint256 claimedGovernance) public override userClaimedGovernance;

    /**
     * @notice Constructs the UtilityManager contract.
     * @param _gaugeWeight The address of the bHermesGauges contract.
     * @param _gaugeBoost The address of the bHermesBoost contract.
     * @param _governance The address of the bHermesVotes contract.
     */
    constructor(address _gaugeWeight, address _gaugeBoost, address _governance) {
        gaugeWeight = bHermesGauges(_gaugeWeight);
        gaugeBoost = bHermesBoost(_gaugeBoost);
        governance = ERC20Votes(_governance);
    }

    /*///////////////////////////////////////////////////////////////
                        UTILITY TOKENS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IUtilityManager
    function forfeitOutstanding() public virtual override {
        forfeitWeight(userClaimedWeight[msg.sender]);
        forfeitBoost(userClaimedBoost[msg.sender]);
        forfeitGovernance(userClaimedGovernance[msg.sender]);
    }

    /// @inheritdoc IUtilityManager
    function forfeitMultiple(uint256 amount) public virtual override {
        forfeitWeight(amount);
        forfeitBoost(amount);
        forfeitGovernance(amount);
    }

    /// @inheritdoc IUtilityManager
    function forfeitMultipleAmounts(uint256 weight, uint256 boost, uint256 _governance) public virtual override {
        forfeitWeight(weight);
        forfeitBoost(boost);
        forfeitGovernance(_governance);
    }

    /// @inheritdoc IUtilityManager
    function forfeitWeight(uint256 amount) public virtual override {
        if (amount == 0) return;
        userClaimedWeight[msg.sender] -= amount;
        address(gaugeWeight).safeTransferFrom(msg.sender, address(this), amount);

        emit ForfeitWeight(msg.sender, amount);
    }

    /// @inheritdoc IUtilityManager
    function forfeitBoost(uint256 amount) public virtual override {
        if (amount == 0) return;
        userClaimedBoost[msg.sender] -= amount;
        address(gaugeBoost).safeTransferFrom(msg.sender, address(this), amount);

        emit ForfeitBoost(msg.sender, amount);
    }

    /// @inheritdoc IUtilityManager
    function forfeitGovernance(uint256 amount) public virtual override {
        if (amount == 0) return;
        userClaimedGovernance[msg.sender] -= amount;
        address(governance).safeTransferFrom(msg.sender, address(this), amount);

        emit ForfeitGovernance(msg.sender, amount);
    }

    /// @inheritdoc IUtilityManager
    function claimMultiple(uint256 amount) public virtual override {
        claimWeight(amount);
        claimBoost(amount);
        claimGovernance(amount);
    }

    /// @inheritdoc IUtilityManager
    function claimMultipleAmounts(uint256 weight, uint256 boost, uint256 _governance) public virtual override {
        claimWeight(weight);
        claimBoost(boost);
        claimGovernance(_governance);
    }

    /// @inheritdoc IUtilityManager
    function claimWeight(uint256 amount) public virtual override checkWeight(amount) {
        if (amount == 0) return;
        userClaimedWeight[msg.sender] += amount;
        address(gaugeWeight).safeTransfer(msg.sender, amount);

        emit ClaimWeight(msg.sender, amount);
    }

    /// @inheritdoc IUtilityManager
    function claimBoost(uint256 amount) public virtual override checkBoost(amount) {
        if (amount == 0) return;
        userClaimedBoost[msg.sender] += amount;
        address(gaugeBoost).safeTransfer(msg.sender, amount);

        emit ClaimBoost(msg.sender, amount);
    }

    /// @inheritdoc IUtilityManager
    function claimGovernance(uint256 amount) public virtual override checkGovernance(amount) {
        if (amount == 0) return;
        userClaimedGovernance[msg.sender] += amount;
        address(governance).safeTransfer(msg.sender, amount);

        emit ClaimGovernance(msg.sender, amount);
    }

    /*///////////////////////////////////////////////////////////////
                            MODIFIERS
    ///////////////////////////////////////////////////////////////*/

    /// @dev Checks available weight allows for call.
    modifier checkWeight(uint256 amount) virtual;

    /// @dev Checks available boost allows for call.
    modifier checkBoost(uint256 amount) virtual;

    /// @dev Checks available governance allows for call.
    modifier checkGovernance(uint256 amount) virtual;
}

File 29 of 48 : PartnerManagerFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Ownable} from "lib/solady/src/auth/Ownable.sol";

import {IBaseVault} from "../interfaces/IBaseVault.sol";
import {IPartnerManagerFactory} from "../interfaces/IPartnerManagerFactory.sol";
import {ERC4626PartnerManager as PartnerManager} from "../tokens/ERC4626PartnerManager.sol";

/// @title Factory for managing PartnerManagers
contract PartnerManagerFactory is Ownable, IPartnerManagerFactory {
    /*//////////////////////////////////////////////////////////////
                         PARTNER MANAGER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IPartnerManagerFactory
    address public immutable override bHermes;

    /// @inheritdoc IPartnerManagerFactory
    PartnerManager[] public override partners;

    /// @inheritdoc IPartnerManagerFactory
    IBaseVault[] public override vaults;

    /// @inheritdoc IPartnerManagerFactory
    mapping(PartnerManager partner => uint256 partnerId) public override partnerIds;

    /// @inheritdoc IPartnerManagerFactory
    mapping(IBaseVault vault => uint256 vaultId) public override vaultIds;

    /**
     * @notice Initializes the contract with the owner and BurntHermes token.
     * @param _bHermes The address of the BurntHermes token.
     * @param _owner The owner of the contract.
     */
    constructor(address _bHermes, address _owner) {
        _initializeOwner(_owner);
        bHermes = _bHermes;
        partners.push(PartnerManager(address(0)));
        vaults.push(IBaseVault(address(0)));
    }

    /// @notice Function being overridden to prevent mistakenly renouncing ownership.
    function renounceOwnership() public payable override {
        revert RenounceOwnershipNotAllowed();
    }

    /// @inheritdoc IPartnerManagerFactory
    function getPartners() external view override returns (PartnerManager[] memory) {
        return partners;
    }

    /// @inheritdoc IPartnerManagerFactory
    function getVaults() external view override returns (IBaseVault[] memory) {
        return vaults;
    }

    /*//////////////////////////////////////////////////////////////
                        NEW PARTNER LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IPartnerManagerFactory
    function addPartner(PartnerManager newPartnerManager) external override onlyOwner {
        if (partners[partnerIds[newPartnerManager]] == newPartnerManager) revert InvalidPartnerManager();
        uint256 id = partners.length;
        partners.push(newPartnerManager);
        partnerIds[newPartnerManager] = id;

        emit AddedPartner(newPartnerManager, id);
    }

    /// @inheritdoc IPartnerManagerFactory
    function addVault(IBaseVault newVault) external override onlyOwner {
        if (vaults[vaultIds[newVault]] == newVault) revert InvalidVault();
        uint256 id = vaults.length;
        vaults.push(newVault);
        vaultIds[newVault] = id;

        emit AddedVault(newVault, id);
    }

    /*//////////////////////////////////////////////////////////////
                        MIGRATION LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IPartnerManagerFactory
    function removePartner(PartnerManager partnerManager) external override onlyOwner {
        if (partners[partnerIds[partnerManager]] != partnerManager) revert InvalidPartnerManager();
        delete partners[partnerIds[partnerManager]];
        delete partnerIds[partnerManager];

        emit RemovedPartner(partnerManager);
    }

    /// @inheritdoc IPartnerManagerFactory
    function removeVault(IBaseVault vault) external override onlyOwner {
        if (vaults[vaultIds[vault]] != vault) revert InvalidVault();
        delete vaults[vaultIds[vault]];
        delete vaultIds[vault];

        emit RemovedVault(vault);
    }
}

File 30 of 48 : IBaseVault.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title Base Vault Contract.
 * @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice This contract allows for the management of BurntHermes utility tokens.
 *          Should be able to retrieve applied tokens at any time and transfer
 *          back to its owner(s).
 *
 *          NOTE: When added to a partner manager, the vault should use any
 *          utility tokens that are forfeited to it after calling `applyAll()`.
 *          Should be able to retrieve applied tokens at any time and transfer
 *          back to the vault when `clearAll()` is called.
 */
interface IBaseVault {
    function applyWeight() external;

    function applyBoost() external;

    function applyGovernance() external;

    function applyAll() external;

    function clearWeight(uint256 amount) external;

    function clearBoost(uint256 amount) external;

    function clearGovernance(uint256 amount) external;

    function clearAll() external;
}

File 31 of 48 : IERC4626PartnerManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {BurntHermes} from "src/hermes/BurntHermes.sol";

import {PartnerManagerFactory} from "../factories/PartnerManagerFactory.sol";

/**
 * @title Yield bearing, boosting, voting, and gauge enabled Partner Token
 * @author Maia DAO (https://github.com/Maia-DAO)
 * @notice Partner Manager is an ERC-4626 compliant Partner token which:
 *          distributes BurntHermes utility tokens (Weight, Boost, Governance)
 *          in exchange for staking Partner tokens.
 */
interface IERC4626PartnerManager {
    /*//////////////////////////////////////////////////////////////
                         PARTNER MANAGER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice The partner manager factory.
    function factory() external view returns (PartnerManagerFactory);

    /// @notice The BurntHermes token.
    function bHermes() external view returns (BurntHermes);

    /// @notice The BurntHermes rate is used to determine how much hermes
    ///         can be claimed by one share.
    function bHermesRate() external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                            UTILITY MANAGER LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice Updates the BurntHermes underlying balance.
    /// @dev Claims all outstanding underlying BurntHermes utility tokens for this contract.
    function updateUnderlyingBalance() external;

    /// @notice Claims all outstanding underlying BurntHermes utility tokens for msg.sender.
    function claimOutstanding() external;

    /*///////////////////////////////////////////////////////////////
                             MIGRATION LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Migrates assets to new Partner Vault.
     * @dev Must be a Vault recognized by PartnerManagerFactory.
     * @param newPartnerVault destination Partner Vault.
     */
    function migratePartnerVault(address newPartnerVault) external;

    /*//////////////////////////////////////////////////////////////
                            ADMIN LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Allows owner to raise the conversion rate used for deposit.
     *         Conversion rate can only be raised. Sets the ratio
     *         between pbHermes<>BurntHermes. If the ratio is 1 it means that
     *         1 $pbHermes has 1 $BurntHermes worth of voting power.
     * @param newRate new BurntHermes to pbHermes conversion rate. represents
     *                   the value that correlates partnerToken with BurntHermes voting power.
     * @dev Maximum increase of conversion rate up to:
     *         `bHermesToken.balanceOf(address(this)).divWad(_totalSupply)`.
     */
    function increaseConversionRate(uint256 newRate) external;

    /*///////////////////////////////////////////////////////////////
                                EVENTS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Emitted when a user's rewards accrue to a given strategy.
     *   @param user the user of the rewards
     *   @param rewardsDelta how many new rewards accrued to the user
     *   @param rewardsIndex market index for rewards per token accrued
     */
    event AccrueRewards(address indexed user, uint256 indexed rewardsDelta, uint256 indexed rewardsIndex);

    /**
     *   @notice Emitted when a user claims accrued rewards.
     *   @param user the user of the rewards
     *   @param amount the amount of rewards claimed
     */
    event ClaimRewards(address indexed user, uint256 indexed amount);

    /**
     *   @notice Emitted when a partner vault is migrated.
     *   @param oldPartnerVault the old partner vault
     *   @param newPartnerVault the new partner vault
     */
    event MigratePartnerVault(address indexed oldPartnerVault, address indexed newPartnerVault);

    /*///////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @dev throws when trying to migrate to an invalid partner vault.
    error UnrecognizedVault();

    /// @dev throws when trying to migrate to a new vault with funds still in the old vault.
    error UserFundsExistInOldVault();

    /// @dev throws when trying to new bHermesRate is smaller than the last one.
    error InvalidRate();

    /// @dev throws when trying to increase bHermesRate to an invalid value.
    error InsufficientBacking();

    /// @dev throws when a user does not have not enough claimed balance for transfer.
    error InsufficientUnderlying();

    /// @dev throws when trying to mint more than the contract can support.
    error ExceedsMaxDeposit();
}

File 32 of 48 : IPartnerManagerFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IBaseVault} from "../interfaces/IBaseVault.sol";
import {ERC4626PartnerManager as PartnerManager} from "../tokens/ERC4626PartnerManager.sol";

/**
 * @title Factory for managing PartnerManagers
 * @author Maia DAO (https://github.com/Maia-DAO)
 * @notice This contract is used to manage the list of partners and vaults.
 */
interface IPartnerManagerFactory {
    /*//////////////////////////////////////////////////////////////
                            PARTNER MANAGER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice The BurntHermes token.
    function bHermes() external view returns (address);

    /// @notice Returns the partner manager at the given index.
    function partners(uint256) external view returns (PartnerManager);

    /// @notice Returns the vault at the given index.
    function vaults(uint256) external view returns (IBaseVault);

    /// @notice Returns the partner's list index for the given partner manager.
    function partnerIds(PartnerManager) external view returns (uint256);

    /// @notice Returns the vault's list index for the given vault.
    function vaultIds(IBaseVault) external view returns (uint256);

    /// @notice Used to get all partners managers created
    function getPartners() external view returns (PartnerManager[] memory);

    /// @notice Used to get all vaults created
    function getVaults() external view returns (IBaseVault[] memory);

    /*//////////////////////////////////////////////////////////////
                        NEW PARTNER LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice Used to add a new partner manager to the list of partners.
    function addPartner(PartnerManager newPartnerManager) external;

    /// @notice Used to add a new vault to the list of vaults.
    function addVault(IBaseVault newVault) external;

    /*//////////////////////////////////////////////////////////////
                        MIGRATION LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice Used to remove a partner manager from the list of partners.
    function removePartner(PartnerManager partnerManager) external;

    /// @notice Used to remove a vault from the list of vaults.
    function removeVault(IBaseVault vault) external;

    /*//////////////////////////////////////////////////////////////
                            EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice Emitted when a new partner manager is added.
    event AddedPartner(PartnerManager indexed partnerManager, uint256 indexed id);

    /// @notice Emitted when a new vault is added.
    event AddedVault(IBaseVault indexed vault, uint256 indexed id);

    /// @notice Emitted when a partner manager is removed.
    event RemovedPartner(PartnerManager indexed partnerManager);

    /// @notice Emitted when a vault is removed.
    event RemovedVault(IBaseVault indexed vault);

    /*//////////////////////////////////////////////////////////////
                            ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice Error emitted when the owner tries to renounce ownership.
    error RenounceOwnershipNotAllowed();

    /// @notice Error thrown when the partner manager is not found.
    error InvalidPartnerManager();

    /// @notice Error thrown when the vault is not found.
    error InvalidVault();
}

File 33 of 48 : IPartnerUtilityManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {bHermesVotes as ERC20Votes} from "src/hermes/tokens/bHermesVotes.sol";

/**
 * @title Partner Utility Tokens Manager Contract.
 * @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice When implemented, this contract allows for the partner
 *          management of BurntHermes utility tokens.
 */
interface IPartnerUtilityManager {
    /*//////////////////////////////////////////////////////////////
                         UTILITY MANAGER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice address applying unused utility tokens.
    function partnerVault() external view returns (address);

    /// @notice Partner Underlying Token which grants governance rights.
    function partnerGovernance() external view returns (ERC20Votes);

    /// @notice Mapping of different user's Partner Governance withdrawn from vault.
    function userClaimedPartnerGovernance(address) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        UTILITY TOKENS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice Forfeits multiple amounts of multiple utility tokens.
    function forfeitMultipleAmounts(uint256 weight, uint256 boost, uint256 _governance, uint256 partnerGovernance)
        external;

    /// @notice Forfeits amounts of partner governance utility token.
    /// @param amount The amount to send to partner manager
    function forfeitPartnerGovernance(uint256 amount) external;

    /// @notice Claims multiple amounts of multiple utility tokens.
    function claimMultipleAmounts(uint256 weight, uint256 boost, uint256 _governance, uint256 partnerGovernance)
        external;

    /// @notice Claims amounts of partner governance utility token.
    /// @param amount The amount to send to partner manager
    function claimPartnerGovernance(uint256 amount) external;

    /*///////////////////////////////////////////////////////////////
                                EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice Emitted when a user claims partner governance.
    event ClaimPartnerGovernance(address indexed user, uint256 indexed amount);
}

File 34 of 48 : DateTimeLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @title Library to check if it is the first Tuesday of a month.
/// @notice Library for date time operations.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/DateTimeLib.sol)
///
/// Conventions:
/// --------------------------------------------------------------------+
/// Unit      | Range                | Notes                            |
/// --------------------------------------------------------------------|
/// timestamp | 0..0x1e18549868c76ff | Unix timestamp.                  |
/// epochDay  | 0..0x16d3e098039     | Days since 1970-01-01.           |
/// year      | 1970..0xffffffff     | Gregorian calendar year.         |
/// month     | 1..12                | Gregorian calendar month.        |
/// day       | 1..31                | Gregorian calendar day of month. |
/// weekday   | 1..7                 | The day of the week (1-indexed). |
/// --------------------------------------------------------------------+
/// All timestamps of days are rounded down to 00:00:00 UTC.
library DateTimeLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // Weekdays are 1-indexed for a traditional rustic feel.

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                    DATE TIME OPERATIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns (`month`) from the number of days since 1970-01-01.
    /// See: https://howardhinnant.github.io/date_algorithms.html
    /// Note: Inputs outside the supported ranges result in undefined behavior.
    /// Use {isSupportedDays} to check if the inputs is supported.
    function getMonth(uint256 timestamp) internal pure returns (uint256 month) {
        uint256 epochDay = timestamp / 1 days;

        assembly ("memory-safe") {
            epochDay := add(epochDay, 719468)
            let doe := mod(epochDay, 146097)
            let yoe := div(sub(sub(add(doe, div(doe, 36524)), div(doe, 1460)), eq(doe, 146096)), 365)
            let doy := sub(doe, sub(add(mul(365, yoe), shr(2, yoe)), div(yoe, 100)))
            let mp := div(add(mul(5, doy), 2), 153)
            month := sub(add(mp, 3), mul(gt(mp, 9), 12))
        }
    }

    /// @dev Returns the weekday from the unix timestamp.
    /// Monday: 1, Tuesday: 2, ....., Sunday: 7.
    function isTuesday(uint256 timestamp) internal pure returns (bool result, uint256 startOfDay) {
        unchecked {
            uint256 day = timestamp / 1 days;
            startOfDay = day * 1 days;
            result = ((day + 3) % 7) + 1 == 2;
        }
    }
}

File 35 of 48 : PartnerUtilityManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";

import {ERC20Votes, UtilityManager, IUtilityManager} from "src/hermes/UtilityManager.sol";

import {IBaseVault} from "./interfaces/IBaseVault.sol";
import {IPartnerUtilityManager} from "./interfaces/IPartnerUtilityManager.sol";

/// @title Partner Utility Tokens Manager Contract
abstract contract PartnerUtilityManager is UtilityManager, IPartnerUtilityManager {
    using SafeTransferLib for address;

    /*//////////////////////////////////////////////////////////////
                         UTILITY MANAGER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IPartnerUtilityManager
    address public override partnerVault;

    /// @inheritdoc IPartnerUtilityManager
    ERC20Votes public immutable override partnerGovernance;

    /// @inheritdoc IPartnerUtilityManager
    mapping(address user => uint256 claimedPartnerGovernance) public override userClaimedPartnerGovernance;

    /**
     * @notice Constructs the Utility Manager Contract.
     * @param _gaugeWeight The address of the weight gauge.
     * @param _gaugeBoost The address of the boost gauge.
     * @param _governance The address of the governance token.
     * @param _partnerGovernance The address of the partner governance token.
     * @param _partnerVault The address of the partner vault.
     */
    constructor(
        address _gaugeWeight,
        address _gaugeBoost,
        address _governance,
        address _partnerGovernance,
        address _partnerVault
    ) UtilityManager(_gaugeWeight, _gaugeBoost, _governance) {
        partnerGovernance = ERC20Votes(_partnerGovernance);
        partnerVault = _partnerVault;

        address(gaugeWeight).safeApprove(partnerVault, type(uint256).max);
        address(gaugeBoost).safeApprove(partnerVault, type(uint256).max);
        address(governance).safeApprove(partnerVault, type(uint256).max);
    }

    /*///////////////////////////////////////////////////////////////
                        UTILITY TOKENS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IUtilityManager
    function forfeitMultiple(uint256 amount) public virtual override {
        forfeitWeight(amount);
        forfeitBoost(amount);
        forfeitGovernance(amount);
        forfeitPartnerGovernance(amount);
    }

    /// @inheritdoc IPartnerUtilityManager
    function forfeitMultipleAmounts(uint256 weight, uint256 boost, uint256 _governance, uint256 _partnerGovernance)
        public
        virtual
        override
    {
        forfeitWeight(weight);
        forfeitBoost(boost);
        forfeitGovernance(_governance);
        forfeitPartnerGovernance(_partnerGovernance);
    }

    /// @inheritdoc IUtilityManager
    function forfeitWeight(uint256 amount) public virtual override {
        super.forfeitWeight(amount);

        // Save partnerVault to memory
        address _partnerVault = partnerVault;

        /// @dev Vault applies outstanding weight.
        if (_partnerVault != address(0)) {
            IBaseVault(_partnerVault).applyWeight();
        }
    }

    /// @inheritdoc IUtilityManager
    function forfeitBoost(uint256 amount) public virtual override {
        super.forfeitBoost(amount);

        // Save partnerVault to memory
        address _partnerVault = partnerVault;

        /// @dev Vault applies outstanding boost.
        if (_partnerVault != address(0)) {
            IBaseVault(_partnerVault).applyBoost();
        }
    }

    /// @inheritdoc IUtilityManager
    function forfeitGovernance(uint256 amount) public virtual override {
        super.forfeitGovernance(amount);

        // Save partnerVault to memory
        address _partnerVault = partnerVault;

        /// @dev Vault applies outstanding governance.
        if (_partnerVault != address(0)) {
            IBaseVault(_partnerVault).applyGovernance();
        }
    }

    /// @inheritdoc IPartnerUtilityManager
    function forfeitPartnerGovernance(uint256 amount) public override {
        userClaimedPartnerGovernance[msg.sender] -= amount;
        /// @dev partnerGovernance is kept in this contract and not sent to vaults to avoid governance attacks.
        address(partnerGovernance).safeTransferFrom(msg.sender, address(this), amount);
    }

    /// @inheritdoc IUtilityManager
    function claimMultiple(uint256 amount) public virtual override {
        claimWeight(amount);
        claimBoost(amount);
        claimGovernance(amount);
        claimPartnerGovernance(amount);
    }

    /// @inheritdoc IPartnerUtilityManager
    function claimMultipleAmounts(uint256 weight, uint256 boost, uint256 _governance, uint256 _partnerGovernance)
        public
        virtual
        override
    {
        claimWeight(weight);
        claimBoost(boost);
        claimGovernance(_governance);
        claimPartnerGovernance(_partnerGovernance);
    }

    /// @inheritdoc IUtilityManager
    function claimWeight(uint256 amount) public virtual override checkWeight(amount) {
        uint256 weightAvailable = address(gaugeWeight).balanceOf(address(this));
        /// @dev Must transfer weight amount to this manager address.
        if (weightAvailable < amount) {
            IBaseVault(partnerVault).clearWeight(amount - weightAvailable);
        }

        super.claimWeight(amount);
    }

    /// @inheritdoc IUtilityManager
    function claimBoost(uint256 amount) public virtual override checkBoost(amount) {
        uint256 boostAvailable = address(gaugeBoost).balanceOf(address(this));
        /// @dev Must transfer boost amount to this manager address.
        if (boostAvailable < amount) IBaseVault(partnerVault).clearBoost(amount - boostAvailable);

        super.claimBoost(amount);
    }

    /// @inheritdoc IUtilityManager
    function claimGovernance(uint256 amount) public virtual override checkGovernance(amount) {
        uint256 governanceAvailable = address(governance).balanceOf(address(this));
        /// @dev Must transfer governance amount to this manager address.
        if (governanceAvailable < amount) {
            IBaseVault(partnerVault).clearGovernance(amount - governanceAvailable);
        }

        super.claimGovernance(amount);
    }

    /// @inheritdoc IPartnerUtilityManager
    function claimPartnerGovernance(uint256 amount) public override checkPartnerGovernance(amount) {
        if (amount == 0) return;
        userClaimedPartnerGovernance[msg.sender] += amount;
        address(partnerGovernance).safeTransfer(msg.sender, amount);

        emit ClaimPartnerGovernance(msg.sender, amount);
    }

    /*///////////////////////////////////////////////////////////////
                            MODIFIERS
    ///////////////////////////////////////////////////////////////*/

    /// @dev Checks available governance allows for call.
    modifier checkPartnerGovernance(uint256 amount) virtual;
}

File 36 of 48 : ERC4626PartnerManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {FixedPointMathLib} from "lib/solady/src/utils/FixedPointMathLib.sol";
import {Ownable} from "lib/solady/src/auth/Ownable.sol";
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {ERC4626} from "src/erc-4626/ERC4626.sol";

import {BurntHermes} from "src/hermes/BurntHermes.sol";
import {bHermesVotes as ERC20MultiVotes} from "src/hermes/tokens/bHermesVotes.sol";

import {PartnerManagerFactory} from "../factories/PartnerManagerFactory.sol";
import {IBaseVault} from "../interfaces/IBaseVault.sol";
import {PartnerUtilityManager} from "../PartnerUtilityManager.sol";

import {IERC4626PartnerManager} from "../interfaces/IERC4626PartnerManager.sol";

/// @title Yield bearing, boosting, voting, and gauge enabled Partner Token
abstract contract ERC4626PartnerManager is PartnerUtilityManager, Ownable, ERC4626, IERC4626PartnerManager {
    using SafeTransferLib for address;
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                         PARTNER MANAGER STATE
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626PartnerManager
    PartnerManagerFactory public immutable override factory;

    /// @inheritdoc IERC4626PartnerManager
    BurntHermes public immutable override bHermes;

    /// @inheritdoc IERC4626PartnerManager
    uint256 public override bHermesRate;

    /**
     * @notice Initializes the ERC4626PartnerManager token.
     * @param _factory The partner manager factory.
     * @param _bHermesRate The rate at which BurntHermes underlying's can be claimed.
     * @param _partnerAsset The asset that will be used to deposit to get partner tokens.
     * @param _name The name of the token.
     * @param _symbol The symbol of the token.
     * @param _bHermes The address of the BurntHermes token.
     * @param _partnerVault The address of the partner vault.
     * @param _owner The owner of this contract.
     */
    constructor(
        PartnerManagerFactory _factory,
        uint256 _bHermesRate,
        ERC20 _partnerAsset,
        string memory _name,
        string memory _symbol,
        address _bHermes,
        address _partnerVault,
        address _owner
    )
        PartnerUtilityManager(
            address(BurntHermes(_bHermes).gaugeWeight()),
            address(BurntHermes(_bHermes).gaugeBoost()),
            address(BurntHermes(_bHermes).governance()),
            address(new ERC20MultiVotes(_owner)),
            _partnerVault
        )
        ERC4626(
            _partnerAsset,
            string.concat(_name, " - Burned Hermes: Aggregated Gov + Yield + Boost"),
            string.concat(_symbol, "-bHERMES")
        )
    {
        _initializeOwner(_owner);
        factory = _factory;
        bHermesRate = _bHermesRate;
        bHermes = BurntHermes(_bHermes);
    }

    /*///////////////////////////////////////////////////////////////
                            UTILITY MANAGER LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626PartnerManager
    function updateUnderlyingBalance() public virtual override {
        bHermes.claimOutstanding();
    }

    /// @inheritdoc IERC4626PartnerManager
    function claimOutstanding() public virtual override {
        /// @dev e.g. bHermesRate value 1100 if need to set 1.1X
        uint256 balance = balanceOf[msg.sender].mulWad(bHermesRate);
        /// @dev Never underflows since balandeOf >= userClaimed.
        unchecked {
            claimWeight(balance - userClaimedWeight[msg.sender]);
            claimBoost(balance - userClaimedBoost[msg.sender]);
            claimGovernance(balance - userClaimedGovernance[msg.sender]);
            claimPartnerGovernance(balance - userClaimedPartnerGovernance[msg.sender]);
        }
    }

    /*//////////////////////////////////////////////////////////////
                        ERC4626 ACCOUNTING LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice Compute the amount of tokens available in contract.
    /// @dev 1:1 with underlying asset.
    function totalAssets() public view override returns (uint256) {
        return totalSupply;
    }

    /**
     * @notice Computes and returns the amount of shares from a given amount of assets.
     * @param assets amount of assets to convert to shares
     */
    function convertToShares(uint256 assets) public view virtual override returns (uint256) {
        return assets;
    }

    /**
     * @notice Computes and returns the amount of assets from a given amount of shares.
     * @param shares amount of shares to convert to assets
     */
    function convertToAssets(uint256 shares) public view virtual override returns (uint256) {
        return shares;
    }

    /**
     * @notice Simulates the amount of shares that the assets deposited are worth.
     * @param assets amount of assets to simulate the deposit.
     */
    function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
        return assets;
    }

    /**
     * @notice Calculates the amount of shares that the assets deposited are worth.
     */
    function previewMint(uint256 shares) public view virtual override returns (uint256) {
        return shares;
    }

    /**
     * @notice Previews the amount of assets to be withdrawn from a given amount of shares.
     */
    function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
        return assets;
    }

    /**
     * @notice Previews the amount of assets to be redeemed from a given amount of shares.
     * @param shares amount of shares to convert to assets.
     */
    function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
        return shares;
    }

    /*//////////////////////////////////////////////////////////////
                    ER4626 DEPOSIT/WITHDRAWAL LIMIT LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice Returns the maximum amount of assets that can be deposited by a user.
    /// @dev Returns the remaining balance of the BurntHermes divided by the bHermesRate.
    function maxDeposit(address) public view virtual override returns (uint256) {
        return address(bHermes).balanceOf(address(this)).divWad(bHermesRate) - totalSupply;
    }

    /// @notice Returns the maximum amount of shares that can be minted by a user.
    /// @dev Returns the remaining balance of the BurntHermes divided by the bHermesRate.
    function maxMint(address) public view virtual override returns (uint256) {
        return address(bHermes).balanceOf(address(this)).divWad(bHermesRate) - totalSupply;
    }

    /// @notice Returns the maximum amount of assets that can be withdrawn by a user.
    /// @dev Assumes that the user has already forfeited all utility tokens.
    function maxWithdraw(address user) public view virtual override returns (uint256) {
        uint256 claimed;

        if (userClaimedWeight[user] > claimed) claimed = userClaimedWeight[user];
        if (userClaimedBoost[user] > claimed) claimed = userClaimedBoost[user];
        if (userClaimedGovernance[user] > claimed) claimed = userClaimedGovernance[user];
        if (userClaimedPartnerGovernance[user] > claimed) claimed = userClaimedPartnerGovernance[user];

        return balanceOf[user] - claimed.divWadUp(bHermesRate);
    }

    /// @notice Returns the maximum amount of assets that can be redeemed by a user.
    /// @dev Assumes that the user has already forfeited all utility tokens.
    function maxRedeem(address user) public view virtual override returns (uint256) {
        uint256 claimed;

        if (userClaimedWeight[user] > claimed) claimed = userClaimedWeight[user];
        if (userClaimedBoost[user] > claimed) claimed = userClaimedBoost[user];
        if (userClaimedGovernance[user] > claimed) claimed = userClaimedGovernance[user];
        if (userClaimedPartnerGovernance[user] > claimed) claimed = userClaimedPartnerGovernance[user];

        return balanceOf[user] - claimed.divWadUp(bHermesRate);
    }

    /*///////////////////////////////////////////////////////////////
                             MIGRATION LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626PartnerManager
    function migratePartnerVault(address newPartnerVault) external override onlyOwner {
        if (newPartnerVault != address(0)) {
            if (factory.vaultIds(IBaseVault(newPartnerVault)) == 0) revert UnrecognizedVault();
        }

        address oldPartnerVault = partnerVault;
        if (oldPartnerVault != address(0)) IBaseVault(oldPartnerVault).clearAll();
        bHermes.claimOutstanding();

        uint256 bHermesBalance = address(bHermes).balanceOf(address(this));
        if (address(gaugeWeight).balanceOf(address(this)) < bHermesBalance) revert UserFundsExistInOldVault();
        if (address(gaugeBoost).balanceOf(address(this)) < bHermesBalance) revert UserFundsExistInOldVault();
        if (address(governance).balanceOf(address(this)) < bHermesBalance) revert UserFundsExistInOldVault();

        if (oldPartnerVault != address(0)) {
            address(gaugeWeight).safeApprove(oldPartnerVault, 0);
            address(gaugeBoost).safeApprove(oldPartnerVault, 0);
            address(governance).safeApprove(oldPartnerVault, 0);
        }

        if (newPartnerVault != address(0)) {
            address(gaugeWeight).safeApprove(newPartnerVault, type(uint256).max);
            address(gaugeBoost).safeApprove(newPartnerVault, type(uint256).max);
            address(governance).safeApprove(newPartnerVault, type(uint256).max);

            IBaseVault(newPartnerVault).applyAll();
        }

        partnerVault = newPartnerVault;

        emit MigratePartnerVault(address(this), newPartnerVault);
    }

    /*//////////////////////////////////////////////////////////////
                            ADMIN LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC4626PartnerManager
    function increaseConversionRate(uint256 newRate) external override onlyOwner {
        uint256 _bHermesRate = bHermesRate;
        if (newRate <= _bHermesRate) revert InvalidRate();

        uint256 _totalSupply = totalSupply;
        if (newRate > address(bHermes).balanceOf(address(this)).divWad(_totalSupply)) {
            revert InsufficientBacking();
        }

        bHermesRate = newRate;

        partnerGovernance.mint(address(this), _totalSupply.mulWad(newRate - _bHermesRate));

        bHermes.claimOutstanding();
    }

    /*///////////////////////////////////////////////////////////////
                             ERC20 LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Mints new partner bHermes tokens to a specific address.
     * @param to address to mints tokens to.
     * @param amount amount of tokens to mint.
     */
    function _mint(address to, uint256 amount) internal virtual override {
        if (amount > maxMint(to)) revert ExceedsMaxDeposit();
        bHermes.claimOutstanding();

        ERC20MultiVotes(partnerGovernance).mint(address(this), amount.mulWad(bHermesRate));
        super._mint(to, amount);
    }

    /**
     * @notice Burns (or unstakes) the VoteMaia token in exchange for the underlying
     *         Partner tokens, performing changes around BurntHermes tokens.
     * @param from account to burn the partner manager from
     * @param amount amounts of VoteMaia to burn
     */
    function _burn(address from, uint256 amount) internal virtual override checkTransfer(from, amount) {
        ERC20MultiVotes(partnerGovernance).burn(address(this), amount.mulWad(bHermesRate));
        super._burn(from, amount);
    }

    /**
     * @notice Transfer partner manager to a specific address.
     * @param to address to transfer the tokens to.
     * @param amount amounts of tokens to transfer.
     */
    function transfer(address to, uint256 amount)
        public
        virtual
        override
        checkTransfer(msg.sender, amount)
        returns (bool)
    {
        return super.transfer(to, amount);
    }

    /**
     * @notice Transfer tokens from a given address.
     * @param from address to transfer the tokens from.
     * @param to address to transfer the tokens to.
     * @param amount amounts of tokens to transfer.
     */
    function transferFrom(address from, address to, uint256 amount)
        public
        virtual
        override
        checkTransfer(from, amount)
        returns (bool)
    {
        return super.transferFrom(from, to, amount);
    }

    /*///////////////////////////////////////////////////////////////
                            MODIFIERS
    ///////////////////////////////////////////////////////////////*/

    /// @dev Checks available weight allows for call.
    modifier checkWeight(uint256 amount) virtual override {
        if (balanceOf[msg.sender].mulWad(bHermesRate) < amount + userClaimedWeight[msg.sender]) {
            revert InsufficientShares();
        }
        _;
    }

    /// @dev Checks available boost allows for call.
    modifier checkBoost(uint256 amount) virtual override {
        if (balanceOf[msg.sender].mulWad(bHermesRate) < amount + userClaimedBoost[msg.sender]) {
            revert InsufficientShares();
        }
        _;
    }

    /// @dev Checks available governance allows for call.
    modifier checkGovernance(uint256 amount) virtual override {
        if (balanceOf[msg.sender].mulWad(bHermesRate) < amount + userClaimedGovernance[msg.sender]) {
            revert InsufficientShares();
        }
        _;
    }

    /// @dev Checks available partner governance allows for call.
    modifier checkPartnerGovernance(uint256 amount) virtual override {
        if (balanceOf[msg.sender].mulWad(bHermesRate) < amount + userClaimedPartnerGovernance[msg.sender]) {
            revert InsufficientShares();
        }
        _;
    }

    modifier checkTransfer(address from, uint256 amount) virtual {
        uint256 userBalance = (balanceOf[from] - amount).mulWad(bHermesRate);

        if (
            userBalance < userClaimedWeight[from] || userBalance < userClaimedBoost[from]
                || userBalance < userClaimedGovernance[from] || userBalance < userClaimedPartnerGovernance[from]
        ) revert InsufficientUnderlying();

        _;
    }
}

File 37 of 48 : FlywheelCore.sol
// SPDX-License-Identifier: MIT
// Rewards logic inspired by Tribe DAO Contracts (flywheel-v2/src/FlywheelCore.sol)
pragma solidity ^0.8.0;

import {Ownable} from "lib/solady/src/auth/Ownable.sol";
import {SafeCastLib} from "lib/solady/src/utils/SafeCastLib.sol";
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {IFlywheelBooster} from "../interfaces/IFlywheelBooster.sol";
import {IFlywheelCore} from "../interfaces/IFlywheelCore.sol";

/// @title Flywheel Core Incentives Manager
abstract contract FlywheelCore is Ownable, IFlywheelCore {
    using SafeTransferLib for address;
    using SafeCastLib for uint256;

    /*///////////////////////////////////////////////////////////////
                        FLYWHEEL CORE STATE
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IFlywheelCore
    address public immutable override rewardToken;

    /// @inheritdoc IFlywheelCore
    ERC20[] public override allStrategies;

    /// @inheritdoc IFlywheelCore
    mapping(ERC20 strategy => uint256 strategyIds) public override strategyIds;

    /// @inheritdoc IFlywheelCore
    address public override flywheelRewards;

    /// @inheritdoc IFlywheelCore
    IFlywheelBooster public override flywheelBooster;

    /**
     * @notice Flywheel Core constructor.
     *  @param _rewardToken the reward token
     *  @param _flywheelRewards the flywheel rewards contract
     *  @param _flywheelBooster the flywheel booster contract
     *  @param _owner the owner of this contract
     */
    constructor(address _rewardToken, address _flywheelRewards, IFlywheelBooster _flywheelBooster, address _owner) {
        _initializeOwner(_owner);
        rewardToken = _rewardToken;
        flywheelRewards = _flywheelRewards;
        flywheelBooster = _flywheelBooster;
    }

    /// @inheritdoc IFlywheelCore
    function getAllStrategies() external view returns (ERC20[] memory) {
        return allStrategies;
    }

    /*///////////////////////////////////////////////////////////////
                        ACCRUE/CLAIM LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IFlywheelCore
    mapping(address user => uint256 userRewards) public override rewardsAccrued;

    /// @inheritdoc IFlywheelCore
    function accrue(address user) external override returns (uint256) {
        return _accrue(ERC20(msg.sender), user);
    }

    /// @inheritdoc IFlywheelCore
    function accrue(ERC20 strategy, address user) external override returns (uint256) {
        return _accrue(strategy, user);
    }

    function _accrue(ERC20 strategy, address user) internal returns (uint256) {
        uint256 index = strategyIndex[strategy];

        if (index == 0) return 0;

        index = accrueStrategy(strategy, index);
        return accrueUser(strategy, user, index);
    }

    /// @inheritdoc IFlywheelCore
    function accrue(ERC20 strategy, address user, address secondUser) public override returns (uint256, uint256) {
        uint256 index = strategyIndex[strategy];

        if (index == 0) return (0, 0);

        index = accrueStrategy(strategy, index);
        return (accrueUser(strategy, user, index), accrueUser(strategy, secondUser, index));
    }

    /// @inheritdoc IFlywheelCore
    function claimRewards(address user) external override {
        uint256 accrued = rewardsAccrued[user];

        if (accrued != 0) {
            delete rewardsAccrued[user];

            rewardToken.safeTransferFrom(flywheelRewards, user, accrued);

            emit ClaimRewards(user, accrued);
        }
    }

    /*///////////////////////////////////////////////////////////////
                          ADMIN LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IFlywheelCore
    function addStrategyForRewards(ERC20 strategy) external override onlyOwner {
        _addStrategyForRewards(strategy);
    }

    function _addStrategyForRewards(ERC20 strategy) internal {
        require(strategyIndex[strategy] == 0, "strategy");
        strategyIndex[strategy] = ONE;

        strategyIds[strategy] = allStrategies.length;
        allStrategies.push(strategy);
        emit AddStrategy(address(strategy));
    }

    /// @inheritdoc IFlywheelCore
    function setFlywheelRewards(address newFlywheelRewards) external override onlyOwner {
        uint256 oldRewardBalance = rewardToken.balanceOf(flywheelRewards);
        if (oldRewardBalance > 0 && flywheelRewards != address(0)) {
            rewardToken.safeTransferFrom(flywheelRewards, newFlywheelRewards, oldRewardBalance);
        }

        flywheelRewards = newFlywheelRewards;

        emit FlywheelRewardsUpdate(newFlywheelRewards);
    }

    /// @inheritdoc IFlywheelCore
    function setBooster(IFlywheelBooster newBooster) external override onlyOwner {
        flywheelBooster = newBooster;

        emit FlywheelBoosterUpdate(address(newBooster));
    }

    /*///////////////////////////////////////////////////////////////
                    INTERNAL ACCOUNTING LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice the fixed point factor of flywheel
    uint256 private constant ONE = 1e18;

    /// @inheritdoc IFlywheelCore
    mapping(ERC20 strategy => uint256 index) public override strategyIndex;

    /// @inheritdoc IFlywheelCore
    mapping(ERC20 strategy => mapping(address user => uint256 index)) public override userIndex;

    /// @notice accumulate global rewards on a strategy
    function accrueStrategy(ERC20 strategy, uint256 state) private returns (uint256 rewardsIndex) {
        // calculate accrued rewards through rewards module
        uint256 strategyRewardsAccrued = _getAccruedRewards(strategy);

        rewardsIndex = state;
        if (strategyRewardsAccrued > 0) {
            // use the booster or token supply to calculate the reward index denominator
            uint256 supplyTokens = address(flywheelBooster) != address(0)
                ? flywheelBooster.boostedTotalSupply(strategy)
                : strategy.totalSupply();

            uint256 deltaIndex;

            if (supplyTokens > 0) {
                unchecked {
                    deltaIndex = (strategyRewardsAccrued * ONE) / supplyTokens;
                }
            }

            // accumulate rewards per token onto the index, multiplied by a fixed-point factor
            rewardsIndex += deltaIndex;
            strategyIndex[strategy] = rewardsIndex;
        }
    }

    /// @notice accumulate rewards on a strategy for a specific user
    function accrueUser(ERC20 strategy, address user, uint256 index) private returns (uint256) {
        // load indices
        uint256 supplierIndex = userIndex[strategy][user];

        // sync user index to global
        userIndex[strategy][user] = index;

        // if user hasn't yet accrued rewards, grant them interest from the strategy beginning if they have a balance
        // zero balances will have no effect other than syncing to global index
        if (supplierIndex == 0) {
            supplierIndex = ONE;
        }

        uint256 deltaIndex = index - supplierIndex;
        // use the booster or token balance to calculate reward balance multiplier
        uint256 supplierTokens = address(flywheelBooster) != address(0)
            ? flywheelBooster.boostedBalanceOf(strategy, user)
            : strategy.balanceOf(user);

        // accumulate rewards by multiplying user tokens by rewardsPerToken index and adding on unclaimed
        uint256 supplierDelta = (supplierTokens * deltaIndex) / ONE;
        uint256 supplierAccrued = rewardsAccrued[user] + supplierDelta;

        rewardsAccrued[user] = supplierAccrued;

        emit AccrueRewards(strategy, user, supplierDelta, index);

        return supplierAccrued;
    }

    function _getAccruedRewards(ERC20 strategy) internal virtual returns (uint256);
}

File 38 of 48 : MultiRewardsDepot.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Ownable} from "lib/solady/src/auth/Ownable.sol";

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

import {IMultiRewardsDepot} from "../interfaces/IMultiRewardsDepot.sol";

/// @title Multiple Rewards Depot - Contract for multiple reward token storage
contract MultiRewardsDepot is Ownable, RewardsDepot, IMultiRewardsDepot {
    /*///////////////////////////////////////////////////////////////
                        REWARDS DEPOT STATE
    ///////////////////////////////////////////////////////////////*/

    /// @dev _assets[rewardsContracts] => asset (reward Token)
    mapping(address rewardsContracts => address rewardToken) private _assets;

    /// @notice _isAsset[asset] => true/false
    mapping(address rewardToken => address rewardsContracts) private _rewardsContracts;

    /**
     * @notice MultiRewardsDepot constructor
     *  @param _owner owner of the contract
     */
    constructor(address _owner) {
        _initializeOwner(_owner);
    }

    /*///////////////////////////////////////////////////////////////
                        GET REWARDS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IMultiRewardsDepot
    function getRewards() external override(RewardsDepot, IMultiRewardsDepot) returns (uint256) {
        address asset = _assets[msg.sender];
        if (asset == address(0)) revert FlywheelRewardsError();

        return transferRewards(asset, msg.sender);
    }

    /*///////////////////////////////////////////////////////////////
                            ADMIN FUNCTIONS
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IMultiRewardsDepot
    function addAsset(address rewardsContract, address asset) external onlyOwner {
        if (_rewardsContracts[asset] != address(0)) revert ErrorAddingAsset();
        _rewardsContracts[asset] = rewardsContract;
        _assets[rewardsContract] = asset;

        emit AssetAdded(rewardsContract, asset);
    }
}

File 39 of 48 : RewardsDepot.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";

import {IRewardsDepot} from "../interfaces/IRewardsDepot.sol";

/// @title Rewards Depot - Base contract for reward token storage
abstract contract RewardsDepot is IRewardsDepot {
    using SafeTransferLib for address;

    ///  @inheritdoc IRewardsDepot
    function getRewards() external virtual override returns (uint256);

    /// @notice Transfer balance of token to rewards contract
    function transferRewards(address _asset, address _rewardsContract) internal returns (uint256 balance) {
        balance = _asset.balanceOf(address(this));
        _asset.safeTransfer(_rewardsContract, balance);
    }
}

File 40 of 48 : FlywheelCoreStrategy.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {FlywheelCore as Core} from "./base/FlywheelCore.sol";

import {IFlywheelBooster} from "./interfaces/IFlywheelBooster.sol";
import {IFlywheelAcummulatedRewards} from "./interfaces/IFlywheelAcummulatedRewards.sol";
import {IFlywheelRewards} from "./interfaces/IFlywheelRewards.sol";

/**
 * @title Flywheel Core Incentives Manager
 *  @notice Flywheel is a general framework for managing token incentives.
 *          It takes reward streams to various *strategies* such as staking LP tokens
 *          and divides them among *users* of those strategies.
 *
 *          The Core contract maintains three important pieces of state:
 *           - the rewards index which determines how many rewards are owed per token per strategy.
 *           - User indexes track how far behind the strategy they are to lazily calculate all catch-up rewards.
 *           - the accrued (unclaimed) rewards per user.
 *           - references to the booster and rewards module described below.
 *
 *          Core does not manage any tokens directly. The rewards module maintains token balances,
 *          and approves core to pull transfer them to users when they claim.
 *
 *          SECURITY NOTE: For maximum accuracy and to avoid exploits:
 *          Rewards accrual should be notified atomically through the accrue hook.
 *          Accrue should be called any time tokens are transferred, minted, or burned.
 */
contract FlywheelCore is Core {
    constructor(
        address _rewardToken,
        IFlywheelRewards _flywheelRewards,
        IFlywheelBooster _flywheelBooster,
        address _owner
    ) Core(_rewardToken, address(_flywheelRewards), _flywheelBooster, _owner) {}

    function _getAccruedRewards(ERC20 strategy) internal override returns (uint256) {
        return IFlywheelAcummulatedRewards(flywheelRewards).getAccruedRewards(strategy);
    }
}

File 41 of 48 : IFlywheelAcummulatedRewards.sol
// SPDX-License-Identifier: MIT
// Rewards logic inspired by Tribe DAO Contracts (flywheel-v2/src/rewards/FlywheelDynamicRewards.sol)
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

/**
 * @title Flywheel Accumulated Rewards
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice This contract is responsible for strategy rewards management.
 *          Once every week all the rewards can be accrued
 *          from the strategy's corresponding rewards depot for subsequent distribution.
 *
 *          The reward depot serves as a pool of rewards.
 *
 *          The getNextCycleRewards() hook should also transfer the next cycle's rewards to this contract
 *          to ensure proper accounting.
 */
interface IFlywheelAcummulatedRewards {
    /*//////////////////////////////////////////////////////////////
                        REWARDS CONTRACT STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice end of current active rewards cycle's UNIX timestamp.
    function endCycles(ERC20 strategy) external view returns (uint256);

    /*//////////////////////////////////////////////////////////////
                        FLYWHEEL CORE FUNCTIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice calculate and transfer accrued rewards to flywheel core
     *  @param strategy the strategy to accrue rewards for
     *  @return amount amounts of tokens accrued and transferred
     */
    function getAccruedRewards(ERC20 strategy) external returns (uint256 amount);

    /*//////////////////////////////////////////////////////////////
                                EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice emitted every time a new rewards cycle begins
    event NewRewardsCycle(uint256 indexed reward);
}

File 42 of 48 : IFlywheelBooster.sol
// SPDX-License-Identifier: MIT
// Rewards logic inspired by Tribe DAO Contracts (flywheel-v2/src/rewards/IFlywheelBooster.sol)
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {FlywheelCore} from "src/rewards/FlywheelCoreStrategy.sol";

/**
 * @title Balance Booster Module for Flywheel
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Flywheel is a general framework for managing token incentives.
 *          It takes reward streams to various *strategies* such as staking LP tokens
 *          and divides them among *users* of those strategies.
 *
 *          The Booster module is an optional module for virtually boosting or otherwise transforming user balances.
 *          If a booster is not configured, the strategies ERC-20 balanceOf/totalSupply will be used instead.
 *
 *          Boosting logic can be associated with referrals, vote-escrow, or other strategies.
 *
 *          SECURITY NOTE: Similar to how Core needs to be notified any time the strategy user composition changes,
 *          the booster would need to be notified of any conditions which change the boosted balances atomically.
 *          This prevents gaming of the reward calculation function by using manipulated balances when accruing.
 *
 *          NOTE: Gets total and user voting power allocated to each strategy.
 *
 *          ⣿⡇⣿⣿⣿⠛⠁⣴⣿⡿⠿⠧⠹⠿⠘⣿⣿⣿⡇⢸⡻⣿⣿⣿⣿⣿⣿⣿
 *          ⢹⡇⣿⣿⣿⠄⣞⣯⣷⣾⣿⣿⣧⡹⡆⡀⠉⢹⡌⠐⢿⣿⣿⣿⡞⣿⣿⣿
 *          ⣾⡇⣿⣿⡇⣾⣿⣿⣿⣿⣿⣿⣿⣿⣄⢻⣦⡀⠁⢸⡌⠻⣿⣿⣿⡽⣿⣿
 *          ⡇⣿⠹⣿⡇⡟⠛⣉⠁⠉⠉⠻⡿⣿⣿⣿⣿⣿⣦⣄⡉⠂⠈⠙⢿⣿⣝⣿
 *          ⠤⢿⡄⠹⣧⣷⣸⡇⠄⠄⠲⢰⣌⣾⣿⣿⣿⣿⣿⣿⣶⣤⣤⡀⠄⠈⠻⢮
 *          ⠄⢸⣧⠄⢘⢻⣿⡇⢀⣀⠄⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⡀⠄⢀
 *          ⠄⠈⣿⡆⢸⣿⣿⣿⣬⣭⣴⣿⣿⣿⣿⣿⣿⣿⣯⠝⠛⠛⠙⢿⡿⠃⠄⢸
 *          ⠄⠄⢿⣿⡀⣿⣿⣿⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⣿⡾⠁⢠⡇⢀
 *          ⠄⠄⢸⣿⡇⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣏⣫⣻⡟⢀⠄⣿⣷⣾
 *          ⠄⠄⢸⣿⡇⠄⠈⠙⠿⣿⣿⣿⣮⣿⣿⣿⣿⣿⣿⣿⣿⡿⢠⠊⢀⡇⣿⣿
 *          ⠒⠤⠄⣿⡇⢀⡲⠄⠄⠈⠙⠻⢿⣿⣿⠿⠿⠟⠛⠋⠁⣰⠇⠄⢸⣿⣿⣿
 *          ⠄⠄⠄⣿⡇⢬⡻⡇⡄⠄⠄⠄⡰⢖⠔⠉⠄⠄⠄⠄⣼⠏⠄⠄⢸⣿⣿⣿
 *          ⠄⠄⠄⣿⡇⠄⠙⢌⢷⣆⡀⡾⡣⠃⠄⠄⠄⠄⠄⣼⡟⠄⠄⠄⠄⢿⣿⣿
 */
interface IFlywheelBooster {
    /*///////////////////////////////////////////////////////////////
                        FLYWHEEL BOOSTER STATE
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Gets the flywheel bribe at index to accrue rewards for a given user per strategy.
     *   @param user the user to get bribes for
     *   @param strategy the strategy to get bribes for
     *   @return flywheel bribe to accrue rewards for a given user for a strategy
     */
    function userGaugeFlywheels(address user, ERC20 strategy, uint256 index) external returns (FlywheelCore flywheel);

    /**
     * @notice Gets the index + 1 of the flywheel in userGaugeFlywheels array. 0 means not opted in.
     *   @param user the user to get the flywheel index for
     *   @param strategy the strategy to get the flywheel index for
     *   @param flywheel the flywheel to get the index for
     *   @return id the index + 1 of the flywheel in userGaugeFlywheels array. 0 means not opted in.
     */
    function userGaugeflywheelId(address user, ERC20 strategy, FlywheelCore flywheel) external returns (uint256 id);

    /**
     * @notice Gets the gauge weight for a given strategy.
     *   @param strategy the strategy to get the gauge weight for
     *   @param flywheel the flywheel to get the gauge weight for
     *   @return gaugeWeight the gauge weight for a given strategy
     */
    function flywheelStrategyGaugeWeight(ERC20 strategy, FlywheelCore flywheel)
        external
        returns (uint256 gaugeWeight);

    /*///////////////////////////////////////////////////////////////
                            VIEW HELPERS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Gets the flywheel bribes to accrue rewards for a given user per strategy.
     *   @param user the user to get bribes for
     *   @param strategy the strategy to get bribes for
     *   @return flywheel bribes to accrue rewards for a given user per strategy
     */
    function getUserGaugeFlywheels(address user, ERC20 strategy) external returns (FlywheelCore[] memory flywheel);

    /**
     * @notice calculate the boosted supply of a strategy.
     *   @param strategy the strategy to calculate boosted supply of
     *   @return the boosted supply
     */
    function boostedTotalSupply(ERC20 strategy) external view returns (uint256);

    /**
     * @notice Calculate the boosted balance of a user in a given strategy.
     *   @param strategy the strategy to calculate boosted balance of
     *   @param user the user to calculate boosted balance of
     *   @return the boosted balance
     */
    function boostedBalanceOf(ERC20 strategy, address user) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        USER BRIBE OPT-IN/OPT-OUT
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice opt-in to a flywheel for a strategy
     *   @param strategy the strategy to opt-in to
     *   @param flywheel the flywheel to opt-in to
     */
    function optIn(ERC20 strategy, FlywheelCore flywheel) external;

    /**
     * @notice opt-out of a flywheel for a strategy
     *   @param strategy the strategy to opt-out of
     *   @param flywheel the flywheel to opt-out of
     *   @param accrue whether or not to accrue rewards before opting out
     */
    function optOut(ERC20 strategy, FlywheelCore flywheel, bool accrue) external;

    /*///////////////////////////////////////////////////////////////
                       bHERMES GAUGE WEIGHT ACCRUAL
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice accrue gauge weight for a user for a strategy before increasing their gauge weight
     *   @param user the user to accrue gauge weight for
     *   @param strategy the strategy to accrue gauge weight for
     *   @param delta the amount of gauge weight to accrue
     */
    function accrueBribesPositiveDelta(address user, ERC20 strategy, uint256 delta) external;

    /**
     * @notice accrue gauge weight for a user for a strategy before decreasing their gauge weight
     *   @param user the user to accrue gauge weight for
     *   @param strategy the strategy to accrue gauge weight for
     *   @param delta the amount of gauge weight to accrue
     */
    function accrueBribesNegativeDelta(address user, ERC20 strategy, uint256 delta) external;

    /*///////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice error thrown when a user is already opted into a flywheel for a strategy
    error AlreadyOptedIn();

    /// @notice error thrown when a user tries to opt out of a flywheel they are not opted in to
    error NotOptedIn();

    /// @notice Throws when trying to opt-in to a strategy that is not a gauge.
    error InvalidGauge();

    /// @notice Throws when trying to opt-in to an invalid flywheel.
    error InvalidFlywheel();
}

File 43 of 48 : IFlywheelCore.sol
// SPDX-License-Identifier: MIT
// Rewards logic inspired by Tribe DAO Contracts (flywheel-v2/src/FlywheelCore.sol)
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {IFlywheelBooster} from "../interfaces/IFlywheelBooster.sol";

/**
 * @title Flywheel Core Incentives Manager
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Flywheel is a general framework for managing token incentives.
 *          It takes reward streams to various *strategies* such as staking LP tokens
 *          and divides them among *users* of those strategies.
 *
 *          The Core contract maintains three important pieces of state:
 *           - The rewards index which determines how many rewards are owed per token per strategy.
 *           - User indexes track how far behind the strategy they are to lazily calculate all catch-up rewards.
 *           - The accrued (unclaimed) rewards per user.
 *           - References to the booster and rewards module are described below.
 *
 *          Core does not manage any tokens directly. The rewards module maintains token balances,
 *          and approves core to pull and transfer them to users when they claim.
 *
 *          SECURITY NOTE: For maximum accuracy and to avoid exploits:
 *          Rewards accrual should be notified atomically through the accrue hook.
 *          Accrue should be called any time tokens are transferred, minted, or burned.
 */
interface IFlywheelCore {
    /*///////////////////////////////////////////////////////////////
                        FLYWHEEL CORE STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice The token to reward
    function rewardToken() external view returns (address);

    /// @notice append-only list of strategies added
    function allStrategies(uint256) external view returns (ERC20);

    /// @notice The strategy index in allStrategies
    function strategyIds(ERC20) external view returns (uint256);

    /// @notice the rewards contract for managing streams
    function flywheelRewards() external view returns (address);

    /// @notice optional booster module for calculating virtual balances on strategies
    function flywheelBooster() external view returns (IFlywheelBooster);

    /// @notice The accrued but not yet transferred rewards for each user
    function rewardsAccrued(address) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        ACCRUE/CLAIM LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice accrue rewards for a single user for msg.sender
     *   @param user the user to be accrued
     *   @return the cumulative amount of rewards accrued to user (including prior)
     */
    function accrue(address user) external returns (uint256);

    /**
     * @notice accrue rewards for a single user on a strategy
     *   @param strategy the strategy to accrue a user's rewards on
     *   @param user the user to be accrued
     *   @return the cumulative amount of rewards accrued to user (including prior)
     */
    function accrue(ERC20 strategy, address user) external returns (uint256);

    /**
     * @notice accrue rewards for a two users on a strategy
     *   @param strategy the strategy to accrue a user's rewards on
     *   @param user the first user to be accrued
     *   @param secondUser the second user to be accrued
     *   @return first cumulative amount of rewards accrued to the first user (including prior)
     *   @return second cumulative amount of rewards accrued to the second user (including prior)
     */
    function accrue(ERC20 strategy, address user, address secondUser) external returns (uint256, uint256);

    /**
     * @notice claim rewards for a given user
     *   @param user the user claiming rewards
     *   @dev this function is public, and all rewards transfer to the user
     */
    function claimRewards(address user) external;

    /*///////////////////////////////////////////////////////////////
                          ADMIN LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice initialize a new strategy
    function addStrategyForRewards(ERC20 strategy) external;

    /// @notice Returns all strategies added to flywheel.
    function getAllStrategies() external view returns (ERC20[] memory);

    /// @notice swap out the flywheel rewards contract
    function setFlywheelRewards(address newFlywheelRewards) external;

    /// @notice swap out the flywheel booster contract
    function setBooster(IFlywheelBooster newBooster) external;

    /*///////////////////////////////////////////////////////////////
                    INTERNAL ACCOUNTING LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @notice The last updated index per strategy
    function strategyIndex(ERC20) external view returns (uint256);

    /// @notice The last updated index per strategy
    function userIndex(ERC20, address) external view returns (uint256);

    /*///////////////////////////////////////////////////////////////
                        EVENTS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Emitted when a user's rewards accrue to a given strategy.
     *   @param strategy the updated rewards strategy
     *   @param user the user of the rewards
     *   @param rewardsDelta how many new rewards accrued to the user
     *   @param rewardsIndex the market index for rewards per token accrued
     */
    event AccrueRewards(
        ERC20 indexed strategy, address indexed user, uint256 indexed rewardsDelta, uint256 rewardsIndex
    );

    /**
     * @notice Emitted when a user claims accrued rewards.
     *   @param user the user of the rewards
     *   @param amount the amount of rewards claimed
     */
    event ClaimRewards(address indexed user, uint256 indexed amount);

    /**
     * @notice Emitted when a new strategy is added to flywheel by the admin
     *   @param newStrategy the new added strategy
     */
    event AddStrategy(address indexed newStrategy);

    /**
     * @notice Emitted when the rewards module changes
     *   @param newFlywheelRewards the new rewards module
     */
    event FlywheelRewardsUpdate(address indexed newFlywheelRewards);

    /**
     * @notice Emitted when the booster module changes
     *   @param newBooster the new booster module
     */
    event FlywheelBoosterUpdate(address indexed newBooster);
}

File 44 of 48 : IFlywheelGaugeRewards.sol
// SPDX-License-Identifier: MIT
// Rewards logic inspired by Tribe DAO Contracts (flywheel-v2/src/rewards/FlywheelGaugeRewards.sol)
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {ERC20Gauges} from "src/erc-20/ERC20Gauges.sol";

import {FlywheelCore} from "../base/FlywheelCore.sol";

/// @notice a contract that streams reward tokens to the FlywheelRewards module
interface IRewardsStream {
    /// @notice read and transfer reward token chunk to FlywheelRewards module
    function getRewards() external returns (uint256);
}

/**
 * @title Flywheel Gauge Reward Stream
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Distributes rewards from a stream based on gauge weights
 *
 *  The contract assumes an arbitrary stream of rewards `S` of the rewardToken.
 *  It chunks the rewards into cycles of length `l` of 1 week.
 *
 *  The allocation function for each cycle A(g, S) proportions the stream to each gauge
 *  such that SUM(A(g, S)) over all gauges <= S.
 *  NOTE it should be approximately S, but may be less due to truncation.
 *
 *  Rewards are accumulated every time a new rewards cycle begins,
 *  and all prior rewards are cached in the previous cycle.
 *
 *  When the Flywheel Core requests accrued rewards for a specific gauge:
 *  1. All prior rewards before this cycle are distributed
 *  2. Rewards for the current cycle are distributed proportionally to the remaining time in the cycle.
 *     If `e` is the cycle end, `t` is the min of e and current timestamp, and `p` is the prior updated time:
 *     For `A` accrued rewards over the cycle, distribute `min(A * (t-p)/(e-p), A)`.
 */
interface IFlywheelGaugeRewards {
    /// @notice rewards queued from prior and current cycles
    struct QueuedRewards {
        uint112 priorCycleRewards;
        uint112 cycleRewards;
        uint32 storedCycle;
    }

    /*//////////////////////////////////////////////////////////////
                        REWARDS CONTRACT STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice the gauge token for determining gauge allocations of the rewards stream
    function gaugeToken() external view returns (ERC20Gauges);

    /// @notice the rewards token for this flywheel rewards contract
    function rewardToken() external view returns (address);

    /// @notice the start of the current cycle
    function gaugeCycle() external view returns (uint32);

    /// @notice mapping from gauges to queued rewards
    function gaugeQueuedRewards(ERC20)
        external
        view
        returns (uint112 priorCycleRewards, uint112 cycleRewards, uint32 storedCycle);

    /*//////////////////////////////////////////////////////////////
                        GAUGE REWARDS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Iterates over all live gauges and queues up the rewards for the cycle
     * @return totalQueuedForCycle the max amount of rewards to be distributed over the cycle
     */
    function queueRewardsForCycle() external returns (uint256 totalQueuedForCycle);

    /// @notice Iterates over all live gauges and queues up the rewards for the cycle
    function queueRewardsForCyclePaginated(uint256 numRewards) external;

    /*//////////////////////////////////////////////////////////////
                        FLYWHEEL CORE FUNCTIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice calculate and transfer accrued rewards to flywheel core
     *  @dev msg.sender is the gauge to accrue rewards for
     * @return amount amounts of tokens accrued and transferred
     */
    function getAccruedRewards() external returns (uint256 amount);

    /*//////////////////////////////////////////////////////////////
                                EVENTS
    ///////////////////////////////////////////////////////////////*/

    /// @notice emitted when a cycle has completely queued and started
    event CycleStart(uint256 indexed rewardAmount);

    /// @notice emitted when a single gauge is queued.
    /// @dev May be emitted before the cycle starts if the queue is done via pagination.
    event QueueRewards(address indexed gauge, uint256 indexed rewardAmount);

    /*//////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice thrown when trying to queue a new cycle during an old one.
    error CycleError();

    /// @notice thrown when trying to queue with 0 gauges
    error EmptyGaugesError();
}

File 45 of 48 : IFlywheelRewards.sol
// SPDX-License-Identifier: MIT
// Rewards logic inspired by Tribe DAO Contracts (flywheel-v2/src/rewards/BaseFlywheelRewards.sol)
pragma solidity ^0.8.0;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {FlywheelCore} from "../base/FlywheelCore.sol";

/**
 * @title Rewards Module for Flywheel
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Flywheel is a general framework for managing token incentives.
 *          It takes reward streams to various *strategies* such as staking LP tokens
 *          and divides them among *users* of those strategies.
 *
 *          The Rewards module is responsible for:
 *             - Determining the ongoing reward amounts to entire strategies
 *               (core handles the logic for dividing among users)
 *             - Holding rewards that are yet to be claimed
 *
 *          The reward stream can follow arbitrary logic
 *          as long as the reward amount passed to flywheel core has been sent to this contract.
 *
 *          Different module strategies include:
 *             - A static reward rate per second
 *             - A decaying reward rate
 *             - A dynamic just-in-time reward stream
 *             - Liquid governance reward delegation (Curve Gauge style)
 *
 *          SECURITY NOTE: The rewards strategy should be smooth and continuous,
 *          to prevent gaming the reward distribution by frontrunning.
 */
interface IFlywheelRewards {
    /*//////////////////////////////////////////////////////////////
                        REWARDS CONTRACT STATE
    ///////////////////////////////////////////////////////////////*/

    /// @notice returns the reward token associated with flywheel core.
    function rewardToken() external view returns (address);

    /// @notice return the flywheel core address
    function flywheel() external view returns (FlywheelCore);

    /*///////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice thrown when msg.sender is not the flywheel
    error FlywheelError();
}

File 46 of 48 : IMultiRewardsDepot.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title Multiple Rewards Depot
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Holds multiple rewards to be distributed to Flywheel Rewards distributor contracts.
 *          When `getRewards()` is called by an added flywheel rewards, it transfers
 *          its balance of the associated assets to its flywheel rewards contract.
 *
 *             ⠉⠉⠉⠉⠉⠉⠉⠉⢉⠟⣩⠋⠉⠉⢩⠟⠉⣹⠋⡽⠉⠉⠉⠉⠉⠉⠉⠉⡏⠉⠉⠉⠉⠉⠉⠹⠹⡉⠉⠉⢙⠻⡍⠉⠉⠹⡍⠉⠉⠉
 *         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡟⠀⠀⠀⠀⠀⠀⠀⠈⢻⡽⣄⠀⠀⠀⠙⣄⠀⠻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢯⣎⢧⡀⠀⠀⠘⢦⠀⢹⡄⠀⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠀⢀⣀⣠⣤⣶⠾⠷⠞⢿⡏⠻⣄⠀⠀⠈⢧⡀⢻⡄⠀⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⡾⠟⠛⠉⠁⠀⠀⠀⠀⠈⢳⡀⠈⢳⡀⠀⠀⠻⣄⢹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠸⠉⢹⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠙⢦⡀⠀⠘⢦⣻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⠀⠀⠀⠀⠀⡆⠀⠀⢸⡀⠀⠀⠀⠀⠀⠀⠀⠈⡇⠀⠀⠈⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⣄⠀⠀⠙⢦⡀⠀⠉⢻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢸⣟⠀⠀⠀⠀⣷⢣⣀⣀⠘⣧⠀⠀⠀⣶⠀⠀⠀⠀⢹⡄⠀⠀⠸⡆⠀⠀⠀⣀⣀⡤⠴⠶⠶⠶⠶⠘⠦⠤⣀⡀⠉⠳⢤⡀⢳⡀⠀⠀⠀⠀⠀⠀⠀⠀⡼
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢸⡿⡆⠀⠀⠀⠸⣾⠉⠉⠁⢿⡆⠀⠀⠘⢧⠀⠀⠀⠀⢳⡀⠀⠀⢳⡴⠚⠉⢀⣀⢀⣠⣶⣶⣿⣿⣿⣿⣧⣤⣀⣀⠀⠀⠈⠓⢧⡀⠀⠀⠀⠀⠀⠀⢰⡁
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠠⣧⢿⡆⠀⠀⠀⡜⣇⠀⠀⠘⣿⣄⡀⠀⠈⢣⡀⠀⠀⠀⢻⣆⠀⠈⢷⡀⣺⢿⣾⣿⡿⠛⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠈⢷⠀⠀⠀⠀⠀⢀⠿⠃
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⢳⡀⠀⠀⢧⠈⢦⡀⠀⠘⣏⠛⣦⣀⣀⣙⠦⠤⢤⠤⠽⠗⠀⠀⢸⣭⣾⡿⠋⠀⣤⣤⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⢸⠀⠀⠀⠀⠀⢀⣀⠀
 * ⠀⠀⠀⠀⠀⣦⠀⠀⠀⠘⡆⠀⢳⠲⣄⢘⣷⠬⣥⣄⡀⠈⠂⠀⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠘⠛⠙⠃⠀⠀⣼⣿⣿⣿⡿⠿⠁⠛⢛⣿⣿⣿⣿⡟⣿⢺⠀⠀⠀⠀⠀⢸⣿⡇
 * ⠸⡄⢆⠀⠀⠈⣷⣄⠀⠀⠹⡆⢀⡴⠚⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠺⣿⣿⡿⠿⠀⠀⠀⠘⠿⢿⣿⣿⠇⠟⢨⠀⡄⠀⠀⠀⠀⢻⣷
 * ⢰⣳⡸⣧⡀⠀⠘⣿⣶⣄⣀⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⡀⢀⠀⢀⠐⠞⢀⣼⠿⠃⠀⠀⢸⣼⠁⠀⠀⠀⠀⠈⠏
 * ⠈⢇⠹⡟⠙⢦⡀⠘⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⠿⠤⠴⠾⠟⠛⠉⠁⠀⠀⠀⠀⢸⠃⠀⠀⠀⠀⠀⢸⠀
 * ⢃⡘⣆⠘⣦⡀⠋⠀⠈⠛⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠏⠀⠀⠀⠀⠀⠀⠟⠁
 * ⠀⣷⡘⣆⠈⢷⣄⡀⠀⠐⣽⡄⠀⠀⠀⢀⣠⣾⣿⣶⣶⣶⠶⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠏⠀⠀⠀⠀⠀⠀⡀⠀⠀
 * ⠀⠉⢳⡘⢆⠈⢦⢁⡤⢄⡀⢷⡀⢀⢰⣿⡿⠟⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⡄⠀⠀
 * ⠀⠀⢸⠛⣌⠇⠀⢻⠀⠀⠙⢎⢷⣀⡿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣇
 * ⠀⠀⠀⠀⠈⢳⣄⡘⡆⠀⠀⠘⢧⡩⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠏⠈
 * ⠀⠀⠀⠀⠀⠀⡟⢽⣧⠀⠀⠀⠈⢿⣮⣳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⡏⠀⠀
 * ⠀⠀⠀⠀⠀⣸⡇⠈⠹⣇⠀⠀⠀⠘⣿⡀⠈⠙⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⠀⠀⠀
 * ⠀⠀⠀⠀⠀⣿⠁⠀⠀⢿⡀⠀⠀⠀⠹⡷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣴⣶⣾⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⠃⠀⠀⠀
 * ⢷⡇⠀⠀⢠⠇⠀⠀⡄⢀⣇⠀⠀⠀⠀⢷⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡤⠤⠖⣚⣯⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⡟⠀⠀⠀⠀
 * ⠀⠙⣦⠀⡜⠀⠀⢸⠁⣸⣻⡄⠀⠀⠀⠸⣇⢹⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣤⣖⣞⣩⣥⣴⠶⠾⠟⠋⠄⠀⠀⠈⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⡿⠀⠀⠀⠀⠀
 * ⠀⠀⠈⢳⡄⠀⢀⡟⢠⡇⠙⣇⠀⠀⠀⠀⢻⡀⠘⢧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⡏⢻⣇⠀⠀⠀⠀⠀⠀⣠⠞⡽⠁⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⠃⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠙⣆⡘⠀⡞⠀⠀⢿⡀⠀⠀⠀⠀⣧⠀⠀⣿⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠦⡙⢦⣀⣀⡠⠴⢊⡡⠞⠁⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀
 * ⢄⠀⠀⠀⠀⠈⠳⣼⠄⠀⢀⣼⣧⠀⠀⠀⠀⢸⣆⢠⡧⣼⠉⠳⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠒⠶⠖⠊⣉⡀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠠
 * ⠀⠳⡄⠀⠀⠀⠀⠙⢧⡀⢠⡿⢻⡀⠀⠀⠀⠀⢻⣤⠼⠿⠤⢤⣄⣈⡿⠲⠤⣄⣀⡤⠖⢶⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠂
 * ⠀⠀⠙⣄⠀⠀⠀⠀⠈⢳⣼⠃⢠⡇⠀⠀⠀⠀⠘⡇⠀⠀⠀⠀⠀⢉⡓⣶⣴⠞⠉⠀⢀⢻⣧⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠙⣧⠀⠀⠀⠀⠀⠹⣦⣶⢿⣦⠀⠀⠀⠀⠹⡄⠀⠀⠀⠀⣰⣿⡟⠁⠀⠀⢠⢿⣟⠛⠛⠛⠛⠒⠦⣤⣄⡀⠀⠀⢀⣠⣴⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠐⠋⣧⠀⠀⠀⠀⠀⠈⠧⣼⢹⠀⠀⠀⠀⠀⢱⡀⠀⠀⢰⣿⡟⠀⠀⠀⢀⢏⣿⡙⠲⢦⣄⣀⡀⠀⠀⠀⣿⠋⠉⠹⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⡐⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⡨⣉⡀⠈⠀⠀⠀⠀⢷⡀⠀⣾⡿⠀⠀⠀⠀⡞⣾⣿⣿⣷⣶⣤⣤⣭⣽⣶⣿⡏⠀⠀⠀⠹⢿⣿⣿⣿⠿⠋⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⢀⣀⡞⢻⠃⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⣰⡿⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠘⠛⢧⣄⡀⠀⠀⢀⣶⠞⠋⠀⠀⠀⠀⠀⠀⠀
 */
interface IMultiRewardsDepot {
    /*///////////////////////////////////////////////////////////////
                        GET REWARDS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /**
     *  @notice returns available reward amount and transfer them to rewardsContract.
     *  @dev msg.sender needs to be an added Flywheel Rewards distributor contract.
     *       Transfers all associated assets to msg.sender.
     *  @return balance available reward amount for strategy.
     */
    function getRewards() external returns (uint256 balance);

    /*///////////////////////////////////////////////////////////////
                            ADMIN FUNCTIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     *  @notice Adds an asset to be distributed by a given contract.
     *  @param rewardsContract address of the rewards contract to attach the asset to.
     *  @param asset address of the asset to be distributed.
     */
    function addAsset(address rewardsContract, address asset) external;

    /*//////////////////////////////////////////////////////////////
                                EVENTS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Emitted when a new asset and rewards contract are added.
     * @param rewardsContract address of the rewards contract.
     * @param asset address of the asset to be distributed.
     */
    event AssetAdded(address indexed rewardsContract, address indexed asset);

    /*///////////////////////////////////////////////////////////////
                                ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice Error thrown when trying to add existing flywheel rewards or assets.
    error ErrorAddingAsset();
}

File 47 of 48 : IRewardsDepot.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title Rewards Depot
 *  @author Maia DAO (https://github.com/Maia-DAO)
 *  @notice Holds rewards to be distributed by a Flywheel Rewards distributor contract.
 *          The `getRewards()` hook should transfer all available rewards to a
 *          flywheel rewards contract to ensure proper accounting.
 *             ⠉⠉⠉⠉⠉⠉⠉⠉⢉⠟⣩⠋⠉⠉⢩⠟⠉⣹⠋⡽⠉⠉⠉⠉⠉⠉⠉⠉⡏⠉⠉⠉⠉⠉⠉⠹⠹⡉⠉⠉⢙⠻⡍⠉⠉⠹⡍⠉⠉⠉
 *         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡟⠀⠀⠀⠀⠀⠀⠀⠈⢻⡽⣄⠀⠀⠀⠙⣄⠀⠻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢯⣎⢧⡀⠀⠀⠘⢦⠀⢹⡄⠀⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠀⢀⣀⣠⣤⣶⠾⠷⠞⢿⡏⠻⣄⠀⠀⠈⢧⡀⢻⡄⠀⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⡾⠟⠛⠉⠁⠀⠀⠀⠀⠈⢳⡀⠈⢳⡀⠀⠀⠻⣄⢹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠸⠉⢹⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠙⢦⡀⠀⠘⢦⣻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⠀⠀⠀⠀⠀⡆⠀⠀⢸⡀⠀⠀⠀⠀⠀⠀⠀⠈⡇⠀⠀⠈⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⣄⠀⠀⠙⢦⡀⠀⠉⢻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢸⣟⠀⠀⠀⠀⣷⢣⣀⣀⠘⣧⠀⠀⠀⣶⠀⠀⠀⠀⢹⡄⠀⠀⠸⡆⠀⠀⠀⣀⣀⡤⠴⠶⠶⠶⠶⠘⠦⠤⣀⡀⠉⠳⢤⡀⢳⡀⠀⠀⠀⠀⠀⠀⠀⠀⡼
 * ⠀⠀⠀⠀⠀⠀⠀⠀⢸⡿⡆⠀⠀⠀⠸⣾⠉⠉⠁⢿⡆⠀⠀⠘⢧⠀⠀⠀⠀⢳⡀⠀⠀⢳⡴⠚⠉⢀⣀⢀⣠⣶⣶⣿⣿⣿⣿⣧⣤⣀⣀⠀⠀⠈⠓⢧⡀⠀⠀⠀⠀⠀⠀⢰⡁
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠠⣧⢿⡆⠀⠀⠀⡜⣇⠀⠀⠘⣿⣄⡀⠀⠈⢣⡀⠀⠀⠀⢻⣆⠀⠈⢷⡀⣺⢿⣾⣿⡿⠛⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠈⢷⠀⠀⠀⠀⠀⢀⠿⠃
 * ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⢳⡀⠀⠀⢧⠈⢦⡀⠀⠘⣏⠛⣦⣀⣀⣙⠦⠤⢤⠤⠽⠗⠀⠀⢸⣭⣾⡿⠋⠀⣤⣤⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⢸⠀⠀⠀⠀⠀⢀⣀⠀
 * ⠀⠀⠀⠀⠀⣦⠀⠀⠀⠘⡆⠀⢳⠲⣄⢘⣷⠬⣥⣄⡀⠈⠂⠀⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠘⠛⠙⠃⠀⠀⣼⣿⣿⣿⡿⠿⠁⠛⢛⣿⣿⣿⣿⡟⣿⢺⠀⠀⠀⠀⠀⢸⣿⡇
 * ⠸⡄⢆⠀⠀⠈⣷⣄⠀⠀⠹⡆⢀⡴⠚⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠺⣿⣿⡿⠿⠀⠀⠀⠘⠿⢿⣿⣿⠇⠟⢨⠀⡄⠀⠀⠀⠀⢻⣷
 * ⢰⣳⡸⣧⡀⠀⠘⣿⣶⣄⣀⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⡀⢀⠀⢀⠐⠞⢀⣼⠿⠃⠀⠀⢸⣼⠁⠀⠀⠀⠀⠈⠏
 * ⠈⢇⠹⡟⠙⢦⡀⠘⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⠿⠤⠴⠾⠟⠛⠉⠁⠀⠀⠀⠀⢸⠃⠀⠀⠀⠀⠀⢸⠀
 * ⢃⡘⣆⠘⣦⡀⠋⠀⠈⠛⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠏⠀⠀⠀⠀⠀⠀⠟⠁
 * ⠀⣷⡘⣆⠈⢷⣄⡀⠀⠐⣽⡄⠀⠀⠀⢀⣠⣾⣿⣶⣶⣶⠶⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠏⠀⠀⠀⠀⠀⠀⡀⠀⠀
 * ⠀⠉⢳⡘⢆⠈⢦⢁⡤⢄⡀⢷⡀⢀⢰⣿⡿⠟⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⡄⠀⠀
 * ⠀⠀⢸⠛⣌⠇⠀⢻⠀⠀⠙⢎⢷⣀⡿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣇
 * ⠀⠀⠀⠀⠈⢳⣄⡘⡆⠀⠀⠘⢧⡩⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠏⠈
 * ⠀⠀⠀⠀⠀⠀⡟⢽⣧⠀⠀⠀⠈⢿⣮⣳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⡏⠀⠀
 * ⠀⠀⠀⠀⠀⣸⡇⠈⠹⣇⠀⠀⠀⠘⣿⡀⠈⠙⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⠀⠀⠀
 * ⠀⠀⠀⠀⠀⣿⠁⠀⠀⢿⡀⠀⠀⠀⠹⡷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣴⣶⣾⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⠃⠀⠀⠀
 * ⢷⡇⠀⠀⢠⠇⠀⠀⡄⢀⣇⠀⠀⠀⠀⢷⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡤⠤⠖⣚⣯⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⡟⠀⠀⠀⠀
 * ⠀⠙⣦⠀⡜⠀⠀⢸⠁⣸⣻⡄⠀⠀⠀⠸⣇⢹⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣤⣖⣞⣩⣥⣴⠶⠾⠟⠋⠄⠀⠀⠈⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⡿⠀⠀⠀⠀⠀
 * ⠀⠀⠈⢳⡄⠀⢀⡟⢠⡇⠙⣇⠀⠀⠀⠀⢻⡀⠘⢧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⡏⢻⣇⠀⠀⠀⠀⠀⠀⣠⠞⡽⠁⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⠃⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠙⣆⡘⠀⡞⠀⠀⢿⡀⠀⠀⠀⠀⣧⠀⠀⣿⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠦⡙⢦⣀⣀⡠⠴⢊⡡⠞⠁⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀
 * ⢄⠀⠀⠀⠀⠈⠳⣼⠄⠀⢀⣼⣧⠀⠀⠀⠀⢸⣆⢠⡧⣼⠉⠳⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠒⠶⠖⠊⣉⡀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠠
 * ⠀⠳⡄⠀⠀⠀⠀⠙⢧⡀⢠⡿⢻⡀⠀⠀⠀⠀⢻⣤⠼⠿⠤⢤⣄⣈⡿⠲⠤⣄⣀⡤⠖⢶⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠂
 * ⠀⠀⠙⣄⠀⠀⠀⠀⠈⢳⣼⠃⢠⡇⠀⠀⠀⠀⠘⡇⠀⠀⠀⠀⠀⢉⡓⣶⣴⠞⠉⠀⢀⢻⣧⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠙⣧⠀⠀⠀⠀⠀⠹⣦⣶⢿⣦⠀⠀⠀⠀⠹⡄⠀⠀⠀⠀⣰⣿⡟⠁⠀⠀⢠⢿⣟⠛⠛⠛⠛⠒⠦⣤⣄⡀⠀⠀⢀⣠⣴⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠐⠋⣧⠀⠀⠀⠀⠀⠈⠧⣼⢹⠀⠀⠀⠀⠀⢱⡀⠀⠀⢰⣿⡟⠀⠀⠀⢀⢏⣿⡙⠲⢦⣄⣀⡀⠀⠀⠀⣿⠋⠉⠹⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⡐⠀⠀⠀⠀
 * ⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⡨⣉⡀⠈⠀⠀⠀⠀⢷⡀⠀⣾⡿⠀⠀⠀⠀⡞⣾⣿⣿⣷⣶⣤⣤⣭⣽⣶⣿⡏⠀⠀⠀⠹⢿⣿⣿⣿⠿⠋⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⠀⠀
 * ⠀⠀⠀⠀⢀⣀⡞⢻⠃⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⣰⡿⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠘⠛⢧⣄⡀⠀⠀⢀⣶⠞⠋⠀⠀⠀⠀⠀⠀⠀
 */
interface IRewardsDepot {
    /**
     * @notice returns available reward amount and transfer them to rewardsContract.
     *  @dev Can only be called by Flywheel Rewards distributor contract.
     *  @return balance available reward amount for strategy.
     */
    function getRewards() external returns (uint256 balance);

    /*///////////////////////////////////////////////////////////////
                            ERRORS
    ///////////////////////////////////////////////////////////////*/

    /// @notice thrown when msg.sender is not the flywheel rewards
    error FlywheelRewardsError();
}

File 48 of 48 : FlywheelGaugeRewards.sol
// SPDX-License-Identifier: MIT
// Rewards logic inspired by Tribe DAO Contracts (flywheel-v2/src/rewards/FlywheelGaugeRewards.sol)
pragma solidity ^0.8.0;

import {SafeCastLib} from "lib/solady/src/utils/SafeCastLib.sol";
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

import {ERC20Gauges} from "src/erc-20/ERC20Gauges.sol";

import {IFlywheelGaugeRewards} from "../interfaces/IFlywheelGaugeRewards.sol";

import {IBaseV2Minter} from "src/hermes/interfaces/IBaseV2Minter.sol";

/// @title Flywheel Gauge Reward Stream
contract FlywheelGaugeRewards is IFlywheelGaugeRewards {
    using SafeTransferLib for address;
    using SafeCastLib for uint256;

    /*//////////////////////////////////////////////////////////////
                        REWARDS CONTRACT STATE
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IFlywheelGaugeRewards
    ERC20Gauges public immutable override gaugeToken;

    /// @notice the minter contract, is a rewardsStream to collect rewards from
    IBaseV2Minter public immutable minter;

    /// @inheritdoc IFlywheelGaugeRewards
    address public immutable override rewardToken;

    /// @inheritdoc IFlywheelGaugeRewards
    uint32 public override gaugeCycle;

    /// @notice the start of the next cycle being partially queued
    uint32 internal nextCycle;

    // rewards that made it into a partial queue but didn't get completed
    uint112 internal nextCycleQueuedRewards;

    // the offset during pagination of the queue
    uint32 internal paginationOffset;

    /// @inheritdoc IFlywheelGaugeRewards
    mapping(ERC20 gauge => QueuedRewards rewards) public override gaugeQueuedRewards;

    constructor(address _rewardToken, ERC20Gauges _gaugeToken, IBaseV2Minter _minter) {
        rewardToken = _rewardToken;

        // seed initial gaugeCycle
        gaugeCycle = ((block.timestamp / 1 weeks) * 1 weeks).toUint32();

        gaugeToken = _gaugeToken;

        minter = _minter;
    }

    /*//////////////////////////////////////////////////////////////
                        GAUGE REWARDS LOGIC
    ///////////////////////////////////////////////////////////////*/

    /// @inheritdoc IFlywheelGaugeRewards
    function queueRewardsForCycle() external override returns (uint256 totalQueuedForCycle) {
        /// @dev Update minter cycle and queue rewards if needed.
        /// This will make this call fail if it is a new epoch,
        /// because the minter calls this function, the first call would fail with "CycleError()".
        /// Should be called through Minter to kick off the new epoch.
        minter.updatePeriod();

        // next cycle is always the next even divisor of the cycle length above the current block timestamp.
        uint32 currentCycle = (block.timestamp.toUint32() / 1 weeks) * 1 weeks;
        uint32 lastCycle = gaugeCycle;

        // ensure new cycle has begun
        if (currentCycle <= lastCycle) revert CycleError();

        gaugeCycle = currentCycle;

        // queue the rewards stream and sanity check the tokens were received
        uint256 balanceBefore = rewardToken.balanceOf(address(this));
        totalQueuedForCycle = minter.getRewards();
        require(rewardToken.balanceOf(address(this)) - balanceBefore >= totalQueuedForCycle);

        // include uncompleted cycle
        totalQueuedForCycle += nextCycleQueuedRewards;

        // iterate over all gauges and update the rewards allocations
        address[] memory gauges = gaugeToken.gauges();

        _queueRewards(gauges, currentCycle, lastCycle, totalQueuedForCycle);

        delete nextCycleQueuedRewards;
        delete paginationOffset;

        emit CycleStart(totalQueuedForCycle);
    }

    /// @inheritdoc IFlywheelGaugeRewards
    function queueRewardsForCyclePaginated(uint256 numRewards) external override {
        /// @dev Update minter cycle and queue rewards if needed.
        /// This will make this call fail if it is a new epoch,
        /// because the minter calls this function, the first call would fail with "CycleError()".
        /// Should be called through Minter to kick off the new epoch.
        minter.updatePeriod();

        // next cycle is always the next even divisor of the cycle length above the current block timestamp.
        uint32 currentCycle = (block.timestamp.toUint32() / 1 weeks) * 1 weeks;
        uint32 lastCycle = gaugeCycle;

        // ensure new cycle has begun
        if (currentCycle <= lastCycle) revert CycleError();

        uint32 offset;

        if (currentCycle > nextCycle) {
            nextCycle = currentCycle;
            paginationOffset = 0;
            offset = 0;
        } else {
            offset = paginationOffset;
        }

        uint112 queued;

        // Important to only calculate the reward amount once
        /// to prevent each page from having a different reward amount
        if (offset == 0) {
            // queue the rewards stream and sanity check the tokens were received
            uint256 balanceBefore = rewardToken.balanceOf(address(this));
            uint256 newRewards = minter.getRewards();
            require(rewardToken.balanceOf(address(this)) - balanceBefore >= newRewards);
            // safe cast
            require(newRewards <= type(uint112).max);
            // In case a previous incomplete cycle had rewards, add on
            queued = nextCycleQueuedRewards + uint112(newRewards);
            nextCycleQueuedRewards = queued;
        } else {
            queued = nextCycleQueuedRewards;
        }

        uint256 remaining = gaugeToken.numGauges() - offset;

        // Important to do non-strict inequality
        // to include the case where the numRewards is just enough to complete the cycle
        if (remaining <= numRewards) {
            numRewards = remaining;
            gaugeCycle = currentCycle;
            nextCycleQueuedRewards = 0;
            paginationOffset = 0;
            emit CycleStart(queued);
        } else {
            paginationOffset = offset + numRewards.toUint32();
        }

        // iterate over all gauges and update the rewards allocations
        address[] memory gauges = gaugeToken.gauges(offset, numRewards);

        _queueRewards(gauges, currentCycle, lastCycle, queued);
    }

    /*//////////////////////////////////////////////////////////////
                        FLYWHEEL CORE FUNCTIONS
    ///////////////////////////////////////////////////////////////*/

    /**
     * @notice Queues the rewards for the next cycle for each given gauge.
     * @param gauges array of gauges addresses to queue rewards for.
     * @param currentCycle timestamp representing the beginning of the new cycle.
     * @param lastCycle timestamp representing the end of the of the last cycle.
     * @param totalQueuedForCycle total number of rewards queued for the next cycle.
     */
    function _queueRewards(address[] memory gauges, uint32 currentCycle, uint32 lastCycle, uint256 totalQueuedForCycle)
        internal
    {
        uint256 size = gauges.length;

        if (size == 0) revert EmptyGaugesError();

        for (uint256 i = 0; i < size;) {
            ERC20 gauge = ERC20(gauges[i]);

            QueuedRewards storage queuedRewards = gaugeQueuedRewards[gauge];

            // Cycle queue already started
            require(queuedRewards.storedCycle < currentCycle);
            assert(queuedRewards.storedCycle == 0 || queuedRewards.storedCycle >= lastCycle);

            uint112 completedRewards = queuedRewards.storedCycle == lastCycle ? queuedRewards.cycleRewards : 0;
            uint256 nextRewards = gaugeToken.calculateGaugeAllocation(address(gauge), totalQueuedForCycle);
            require(nextRewards <= type(uint112).max); // safe cast

            queuedRewards.priorCycleRewards = queuedRewards.priorCycleRewards + completedRewards;
            queuedRewards.cycleRewards = uint112(nextRewards);
            queuedRewards.storedCycle = currentCycle;

            emit QueueRewards(address(gauge), nextRewards);

            unchecked {
                ++i;
            }
        }
    }

    /// @inheritdoc IFlywheelGaugeRewards
    function getAccruedRewards() external override returns (uint256 accruedRewards) {
        /// @dev Update minter cycle and queue rewards if needed.
        minter.updatePeriod();

        QueuedRewards storage queuedRewards = gaugeQueuedRewards[ERC20(msg.sender)];

        uint32 cycle = gaugeCycle;
        bool incompleteCycle = queuedRewards.storedCycle > cycle;

        // no rewards
        if (queuedRewards.priorCycleRewards == 0) {
            // no rewards
            if (queuedRewards.cycleRewards == 0) return 0;
            if (incompleteCycle) return 0;
        }

        // if stored cycle != 0 it must be >= the last queued cycle
        assert(queuedRewards.storedCycle >= cycle);

        // always accrue prior rewards
        accruedRewards = queuedRewards.priorCycleRewards;
        uint112 cycleRewardsNext = queuedRewards.cycleRewards;

        if (incompleteCycle) {
            // If current cycle queue incomplete, do nothing to current cycle rewards or accrued
        } else {
            accruedRewards += cycleRewardsNext;
            cycleRewardsNext = 0;
        }

        queuedRewards.priorCycleRewards = 0;
        queuedRewards.cycleRewards = cycleRewardsNext;
        queuedRewards.storedCycle = queuedRewards.storedCycle;

        if (accruedRewards > 0) rewardToken.safeTransfer(msg.sender, accruedRewards);
    }
}

Settings
{
  "viaIR": true,
  "optimizer": {
    "enabled": true,
    "runs": 1000000
  },
  "metadata": {
    "bytecodeHash": "none"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract ABI

[{"inputs":[{"internalType":"contract PartnerManagerFactory","name":"_factory","type":"address"},{"internalType":"uint256","name":"_bHermesRate","type":"uint256"},{"internalType":"contract ERC20","name":"_partnerAsset","type":"address"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"address","name":"_bHermes","type":"address"},{"internalType":"address","name":"_partnerVault","type":"address"},{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"ExceedsMaxDeposit","type":"error"},{"inputs":[],"name":"InsufficientBacking","type":"error"},{"inputs":[],"name":"InsufficientShares","type":"error"},{"inputs":[],"name":"InsufficientUnderlying","type":"error"},{"inputs":[],"name":"InvalidRate","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnrecognizedVault","type":"error"},{"inputs":[],"name":"UnstakePeriodNotLive","type":"error"},{"inputs":[],"name":"UserFundsExistInOldVault","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"rewardsDelta","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"rewardsIndex","type":"uint256"}],"name":"AccrueRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ClaimBoost","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ClaimGovernance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ClaimPartnerGovernance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ClaimRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ClaimWeight","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ForfeitBoost","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ForfeitGovernance","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ForfeitWeight","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldPartnerVault","type":"address"},{"indexed":true,"internalType":"address","name":"newPartnerVault","type":"address"}],"name":"MigratePartnerVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bHermes","outputs":[{"internalType":"contract BurntHermes","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bHermesRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"claimBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimMultiple","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"uint256","name":"boost","type":"uint256"},{"internalType":"uint256","name":"_governance","type":"uint256"},{"internalType":"uint256","name":"_partnerGovernance","type":"uint256"}],"name":"claimMultipleAmounts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"uint256","name":"boost","type":"uint256"},{"internalType":"uint256","name":"_governance","type":"uint256"}],"name":"claimMultipleAmounts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimOutstanding","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimPartnerGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"claimWeight","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract PartnerManagerFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"forfeitBoost","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"forfeitGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"forfeitMultiple","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"uint256","name":"boost","type":"uint256"},{"internalType":"uint256","name":"_governance","type":"uint256"}],"name":"forfeitMultipleAmounts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"uint256","name":"boost","type":"uint256"},{"internalType":"uint256","name":"_governance","type":"uint256"},{"internalType":"uint256","name":"_partnerGovernance","type":"uint256"}],"name":"forfeitMultipleAmounts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"forfeitOutstanding","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"forfeitPartnerGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"forfeitWeight","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gaugeBoost","outputs":[{"internalType":"contract bHermesBoost","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gaugeWeight","outputs":[{"internalType":"contract bHermesGauges","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"contract bHermesVotes","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newRate","type":"uint256"}],"name":"increaseConversionRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newPartnerVault","type":"address"}],"name":"migratePartnerVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"partnerGovernance","outputs":[{"internalType":"contract bHermesVotes","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"partnerVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"updateUnderlyingBalance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userClaimedBoost","outputs":[{"internalType":"uint256","name":"claimedBoost","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userClaimedGovernance","outputs":[{"internalType":"uint256","name":"claimedGovernance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userClaimedPartnerGovernance","outputs":[{"internalType":"uint256","name":"claimedPartnerGovernance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userClaimedWeight","outputs":[{"internalType":"uint256","name":"claimedWeight","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]

6101c0806040523462000a505762008586803803809162000021828562000a55565b83398101906101008183031262000a50578051906001600160a01b038216820362000a5057602081015160408201519092906001600160a01b038116810362000a505760608301516001600160401b03811162000a5057856200008691850162000a9e565b608084015190956001600160401b03821162000a5057620000a991850162000a9e565b92620000b860a0820162000af9565b93620000d560e0620000cd60c0850162000af9565b930162000af9565b9662000146605060405183620000f682955180926020808601910162000a79565b81017f202d204275726e6564204865726d65733a204167677265676174656420476f7660208201526f080ac8165a595b19080ac8109bdbdcdd60821b604082015203603081018452018262000a55565b620001886028604051846200016682965180926020808601910162000a79565b8101672d624845524d455360c01b602082015203600881018552018362000a55565b604051631f4c030360e21b81526020816004816001600160a01b038b165afa908115620009655760009162000a07575b50604051630f6c11c760e31b8152936020856004816001600160a01b038c165afa9485156200096557600095620009ba575b50604051635aa6e67560e01b81526020816004816001600160a01b038d165afa908115620009655760009162000971575b506040516001600160401b03613682820190811190821117620006bf5761368262004ec482396001600160a01b038c166136828201908152819003602001906000f0908115620009655760405163313ce56760e01b8152966020886004816001600160a01b038d165afa978815620009655760009862000914575b506001600160a01b03948516608081905290851660a05290841660c05290831660e052600380546001600160a01b03191693909216928317909155620002dd919062000b4b565b60a051600354620002fb916001600160a01b03918216911662000b4b565b60c05160035462000319916001600160a01b03918216911662000b4b565b8051906001600160401b038211620006bf5781906200033a60055462000b0e565b601f8111620008be575b50602090601f8311600114620008405760009262000834575b50508160011b916000199060031b1c1916176005555b8051906001600160401b038211620006bf5781906200039460065462000b0e565b601f8111620007d3575b50602090601f8311600114620007555760009262000749575b50508160011b916000199060031b1c1916176006555b610100524661012052604051600554816000620003ea8362000b0e565b8083529260018116908115620007285750600114620006d5575b620004129250038262000a55565b60208151910120936040519460208601907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f825260408701527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608701524660808701523060a087015260a0865260c086019580871060018060401b03881117620006bf5760408790525190206101409081526101609283526001600160a01b03909616638b78c6d81981905560007f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a3610180928352600b556101a09260018060a01b0316835260036099600262023ab1620afa6c620151804204010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c6009821102900301600160801b811015620006b157600c80546001600160801b0319166001600160801b039290921691909117905561432b948562000b9986396080518581816106ea015281816116e101528181611ab901528181611fac0152818161235b01528181612b88015281816130a2015281816139810152613be8015260a0518581816117b10152611aed015260c051858181610beb01528181611b2001528181611e4a01528181612055015281816123fb01528181612c280152818161314201528181613a7c0152613c72015260e0518581816105cd01528181610efd015281816126c301528181613b0101528181613cfc01526141f10152610100518561283b015261012051856136030152518461362a01525183818161100b015281816115600152818161191f01526125b4015251828181610e470152611d6301525181818161039901528181611a46015281816122780152818161267901528181613f6101526141880152f35b6335278d126000526004601cfd5b634e487b7160e01b600052604160045260246000fd5b50600560009081529091600080516020620085468339815191525b8183106200070b575050906020620004129282010162000404565b6020919350806001915483858801015201910190918392620006f0565b602092506200041294915060ff191682840152151560051b82010162000404565b015190503880620003b7565b6006600090815293506000805160206200856683398151915291905b601f1984168510620007b7576001945083601f198116106200079d575b505050811b01600655620003cd565b015160001960f88460031b161c191690553880806200078e565b8181015183556020948501946001909301929091019062000771565b600660005290915060008051602062008566833981519152601f840160051c8101602085106200082c575b90849392915b601f830160051c820181106200081c5750506200039e565b6000815585945060010162000804565b5080620007fe565b0151905038806200035d565b6005600090815293506000805160206200854683398151915291905b601f1984168510620008a2576001945083601f1981161062000888575b505050811b0160055562000373565b015160001960f88460031b161c1916905538808062000879565b818101518355602094850194600190930192909101906200085c565b90915060056000526020600020601f840160051c8101602085106200090c575b90849392915b601f830160051c82018110620008fc57505062000344565b60008155859450600101620008e4565b5080620008de565b6020989198813d6020116200095c575b81620009336020938362000a55565b810103126200095857519060ff8216820362000955575096620002dd62000296565b80fd5b5080fd5b3d915062000924565b6040513d6000823e3d90fd5b6020813d602011620009b1575b816200098d6020938362000a55565b81010312620009585751906001600160a01b0382168203620009555750386200021b565b3d91506200097e565b6020959195813d602011620009fe575b81620009d96020938362000a55565b81010312620009585751906001600160a01b03821682036200095557509338620001ea565b3d9150620009ca565b6020813d60201162000a47575b8162000a236020938362000a55565b81010312620009585751906001600160a01b038216820362000955575038620001b8565b3d915062000a14565b600080fd5b601f909101601f19168101906001600160401b03821190821017620006bf57604052565b60005b83811062000a8d5750506000910152565b818101518382015260200162000a7c565b81601f8201121562000a505780516001600160401b038111620006bf576040519262000ad5601f8301601f19166020018562000a55565b8184526020828401011162000a505762000af6916020808501910162000a79565b90565b51906001600160a01b038216820362000a5057565b90600182811c9216801562000b40575b602083101462000b2a57565b634e487b7160e01b600052602260045260246000fd5b91607f169162000b1e565b60209060109260145260001960345260446000938480936f095ea7b300000000000000000000000082525af13d15600183511417161562000b8b57603452565b633e3f8f7390526004601cfdfe6040608081526004908136101561001557600080fd5b600091823560e01c9081630100170a1461302057816301e1d11414612a9f57816306fdde0314612f5757816307a2d13a146105785781630826a55d14612ef6578163095ea7b314612e585781630a28a477146105785781630e215e6214612e1d5781631099f88814612de2578163118917e314612d8f57816313f2e64714612b24578163152ca92714612adc57816318160ddd14612a9f57816323b872dd146128e657816324932de3146128c8578163256929621461285f578163313ce5671461280357816332564c15146126135781633644e515146125d857816338d52e0f14612569578163402d267d14610df757816344dba573146122f75781634cdad50614610578578163502d29671461057d578382635217237b1461223057508163521fb1fd14611f0d57816354d1f13d14611ea957816355f7be3a14611e6e5781635aa6e67514611dff5781635f2078eb146119cc5781636ca0b414146119a15781636e553f65146118b757816370a0823114611855578163715018a6146117d55781637b608e38146117665781637bf01826146117055781637d300c0c146116965781637ecebe00146116345781638da5cb5b146115c257816394bf804d1461150357816395d89b41146113e55781639f14cf2514611376578163a9059cbb14611227578163b3d7f6b914610578578163b460af941461117e578163ba08765214610e6b578163c45a015514610dfc578163c63d75b614610df7578163c6e6f59214610578578163caedd78e14610d95578163ce96cb7714610802578163ce99dfe614610d2b578163d0d6091514610cc9578163d29a41aa14610b68578163d505accf14610864578163d905777e14610802578163dc918f0c14610668578163dd62ed3e146105f1578163e01bfb0214610582578163eb5a25ca1461057d578163ef8b30f714610578578163f04e283e146104ae578163f2fde38b146103f957508063f5a76217146103bd578063fa7e38da1461034f5763fee81cf4146102fb57600080fd5b3461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602091610335613431565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602090600b549051908152f35b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761042d613431565b906104366135c6565b8160601b156104a3575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a35580f35b637448fbae8352601cfd5b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610575576104e1613431565b6104e96135c6565b63389a75e1600c528082526020600c20928354421161056a57508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a35580f35b636f5e88188352601cfd5b80fd5b6133f1565b613535565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50503461034b57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209161062c613431565b82610635613454565b9273ffffffffffffffffffffffffffffffffffffffff809316815260098652209116600052825280600020549051908152f35b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5780359133845260086020526106b481852054600b5490613b33565b338552846020526106c88286205485613b26565b11610759578373ffffffffffffffffffffffffffffffffffffffff61070f30827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b9085821061077f575b505050338452600860205261073381852054600b5490613b33565b338552846020526107478286205485613b26565b11610759578361075684613bab565b80f35b517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61078e90600354169186613836565b813b156107fe57829160248392865194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528a8401525af180156107f4576107dc575b80610718565b6107e590613307565b6107f05783386107d6565b8380fd5b83513d84823e3d90fd5b8280fd5b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209161083e613431565b610846613d49565b1561085d576108559150613fde565b905b51908152f35b5090610857565b9050346107fe5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5761089d613431565b6108a5613454565b9260443590606435936084359360ff8516809503610b6457428610610b07576108cc6135fe565b9673ffffffffffffffffffffffffffffffffffffffff80921696878a52602096600a8852858b20998a549a60018c019055865192858a8501957f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c987528c8a870152169b8c606086015289608086015260a085015260c084015260c0835260e0830167ffffffffffffffff9484821086831117610ad957818952845190206101008501927f190100000000000000000000000000000000000000000000000000000000000084526101028601526101228501526042815261016084019481861090861117610aab57848852519020835261018082015260a4356101a082015260c4356101c0909101528880528590899060809060015afa15610aa1578751169081151580610a98575b15610a3c5750907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92593929187526009835280872086600052835281816000205551908152a380f35b606490858451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b508582146109f4565b82513d89823e3d90fd5b6041877f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b6041887f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b50602060649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b8780fd5b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe578035913384526008602052610bb481852054600b5490613b33565b3385526002602052610bc98286205485613b26565b11610759578373ffffffffffffffffffffffffffffffffffffffff610c1030827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b90858210610c58575b5050503384526008602052610c3481852054600b5490613b33565b3385526002602052610c498286205485613b26565b11610759578361075684613c35565b610c6790600354169186613836565b813b156107fe57829160248392865194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528a8401525af180156107f457610cb5575b80610c19565b610cbe90613307565b6107f0578338610caf565b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff610d1b613431565b1681526001845220549051908152f35b919050346107fe57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe576107569133845283602052610d73828520546138bb565b3384526002602052610d87828520546139ce565b338452602052822054613ac9565b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff610de7613431565b1681526002845220549051908152f35b6134eb565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b83833461034b57610e7b36613566565b73ffffffffffffffffffffffffffffffffffffffff939293809116803303611110575b84156110b357610eac613df0565b80865260209560088752610ec38686832054613836565b610ed0600b548092613b33565b8383528289528683205481109081156110a2575b8115611091575b8115611081575b5061105957610f24847f0000000000000000000000000000000000000000000000000000000000000000169188613b33565b98813b156107fe5786517f9dc29fac00000000000000000000000000000000000000000000000000000000815230918101918252602082019a909a52979896979596958291879182908490829060400103925af1801561104d57908794939291611030575b610857955081815260088952868120610fa3868254613836565b90558460075403600755817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8a8951888152a385518481528489820152828416907ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db883392a47f000000000000000000000000000000000000000000000000000000000000000016613872565b919350919361103f8291613307565b610575579183918693610f89565b508551903d90823e3d90fd5b8886517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b905089895286832054118a610ef2565b60028a528784205481109150610eeb565b60018a528784205481109150610ee4565b60648760208651917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600b60248201527f5a45524f5f4153534554530000000000000000000000000000000000000000006044820152fd5b8086526009602052838620336000526020528360002054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611157575b5050610e9e565b61116091613836565b81875260096020528487203360005260205284600020558785611150565b83833461034b5761118e36613566565b73ffffffffffffffffffffffffffffffffffffffff9392938091168033036111b9575b610eac613df0565b8086526009602052838620336000526020528360002054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611200575b50506111b1565b61120991613836565b818752600960205284872033600052602052846000205587856111f9565b82843461057557817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755761125f613431565b602435903383526020946008865261128661127d8487872054613836565b600b5490613b33565b338552848752858520548110908115611365575b8115611354575b8115611344575b5061131d575073ffffffffffffffffffffffffffffffffffffffff8491338552600887528285206112da858254613836565b90551692838152600886522081815401905582519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef843392a35160018152f35b84517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b90508187528585205411876112a8565b6002885286862054811091506112a1565b60018852868620548110915061129a565b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5780359133845260086020526113c281852054600b5490613b33565b338552826020526113d68286205485613b26565b11610759578361075684613cbf565b82843461057557807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508051600091600654611427816132b4565b808452906001908181169081156114bd5750600114611460575b50506114528261145c94038361334a565b519182918261338b565b0390f35b6006600090815294507ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f5b8286106114a5575050509181016020019161145282611441565b8054602087870181019190915290950194810161148b565b61145c965085925060209150927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00611452941682840152151560051b8201019450611441565b9050346107fe57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5760209250359081611542613454565b73ffffffffffffffffffffffffffffffffffffffff611585833033847f0000000000000000000000000000000000000000000000000000000000000000166137dd565b61158f838361415d565b83519183835216907fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7863392a451908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff611686613431565b168152600a845220549051908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe57602092829173ffffffffffffffffffffffffffffffffffffffff611758613431565b168252845220549051908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610575576118076135c6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff6118a7613431565b1681526008845220549051908152f35b9050823461057557827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508035906118f4613454565b9082156119445750918160209373ffffffffffffffffffffffffffffffffffffffff611585833033847f0000000000000000000000000000000000000000000000000000000000000000166137dd565b60649060208551917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600b60248201527f5a45524f5f5348415245530000000000000000000000000000000000000000006044820152fd5b8334610575576107566119c26119c76119b936613477565b949150916138bb565b6139ce565b613ac9565b8391503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57611a06613431565b90611a0f6135c6565b73ffffffffffffffffffffffffffffffffffffffff93848316928315159081611d30575b8660035416918215159788611cc9575b807f000000000000000000000000000000000000000000000000000000000000000016803b15611cbb5785517f521fb1fd0000000000000000000000000000000000000000000000000000000081528981898183865af18015611cbf57908a91611ca7575b5050611ab5903090613b7c565b91817f0000000000000000000000000000000000000000000000000000000000000000169483611ae53088613b7c565b10611c5757827f0000000000000000000000000000000000000000000000000000000000000000169284611b193086613b7c565b10611c7f577f00000000000000000000000000000000000000000000000000000000000000001693611b4b3086613b7c565b10611c5757899a999899611c33575b50611bb4575b8688807fffffffffffffffffffffffff00000000000000000000000000000000000000006003541617600355307f889ad99844dae1714b73856de24207bc777a6e82bc70da589759c5eb669773c98380a380f35b82611bc691611bc682611bcb97614100565b614100565b833b156107fe5782815180937f2bbfaae50000000000000000000000000000000000000000000000000000000082528183885af1908115611c2a5750611c16575b8080808080611b60565b611c1f90613307565b61034b578183611c0c565b513d84823e3d90fd5b80611c41611c5192886140b6565b611c4b81856140b6565b846140b6565b8a611b5a565b8787517fa1fb25d1000000000000000000000000000000000000000000000000000000008152fd5b8888517fa1fb25d1000000000000000000000000000000000000000000000000000000008152fd5b611cb090613307565b611cbb57888b611aa8565b8880fd5b87513d8c823e3d90fd5b833b15610b645784517febb689a10000000000000000000000000000000000000000000000000000000081528881888183895af18015611d2657908991611d12575b5050611a43565b611d1b90613307565b610b6457878a611d0b565b86513d8b823e3d90fd5b82517f41849afc00000000000000000000000000000000000000000000000000000000815285858201526020816024818b7f0000000000000000000000000000000000000000000000000000000000000000165afa908115611df5578791611dc0575b50611a33575050517f97e8471e000000000000000000000000000000000000000000000000000000008152fd5b90506020813d8211611ded575b81611dda6020938361334a565b81010312611de9575188611d93565b8680fd5b3d9150611dcd565b84513d89823e3d90fd5b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761075690356139ce565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b9050346107fe57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe57338352600891602083815281852054611f59600b548092613b33565b90338752868352611f768488205483039187855285892054613b33565b338852878452611f898589205483613b26565b116120f05773ffffffffffffffffffffffffffffffffffffffff9087611fd130847f000000000000000000000000000000000000000000000000000000000000000016613b7c565b8281106121bc575b5050338852868452611ff185892054600b5490613b33565b338952888552612004868a205483613b26565b116121945761201290613bab565b338752600283528387205482039086845261203385892054600b5490613b33565b33895260028552612047868a205484613b26565b1161219457879061207a30827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b90838210612119575b50505033875285835261209c84882054600b5490613b33565b338852600284526120b08589205483613b26565b116120f0576120be90613c35565b338652838252828620549003938152826120de83872054600b5490613b33565b91338752526113d68286205485613b26565b505050517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61212890600354169184613836565b813b156107fe57829160248392895194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528d8401525af1801561218a57612176575b80612083565b61217f90613307565b611de9578638612170565b86513d84823e3d90fd5b8585517f39996567000000000000000000000000000000000000000000000000000000008152fd5b6121cb84600354169184613836565b813b156107fe57829060248a838b5195869485937f4066800c0000000000000000000000000000000000000000000000000000000085528401525af180156122265715611fd95761221b90613307565b610b64578738611fd9565b87513d84823e3d90fd5b809184346122f357827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126122f35773ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001691823b156122ee57839283918351809581937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af1908115611c2a57506122de5750f35b6122e790613307565b6105755780f35b505050fd5b5050fd5b9050346107fe57612307366134b3565b939190503385526020906008825261232583872054600b5490613b33565b3387528683526123388488205483613b26565b116124d65773ffffffffffffffffffffffffffffffffffffffff908661238030847f000000000000000000000000000000000000000000000000000000000000000016613b7c565b8281106124fe575b5050338752600883526123a184882054600b5490613b33565b3388528784526123b48589205483613b26565b116120f0576123c290613bab565b338652600882526123d983872054600b5490613b33565b338752600283526123ed8488205487613b26565b116124d657859061242030827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b90868210612457575b50505033855260088152600261244583872054600b5490613b33565b9133875252610c498286205485613b26565b61246690600354169187613836565b813b156107fe57829160248392875194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528b8401525af180156124cc576124b4575b80612429565b6124bd90613307565b6124c85784386124ae565b8480fd5b84513d84823e3d90fd5b5050517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61250d84600354169184613836565b813b156107fe57829160248392895194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528d8401525af1801561218a57156123885761255e90613307565b611de9578638612388565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020906108556135fe565b8391503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578035926126506135c6565b600b5491828511156127dc576007549473ffffffffffffffffffffffffffffffffffffffff93847f000000000000000000000000000000000000000000000000000000000000000016946126ad886126a83089613b7c565b613f9b565b83116127b457966126e96126ef92889985600b557f00000000000000000000000000000000000000000000000000000000000000001694613836565b90613b33565b90803b156127b05783517f40c10f19000000000000000000000000000000000000000000000000000000008152308482019081526020810193909352918691839182908490829060400103925af180156127a657908591612792575b5050823b156122ee57839283918351809581937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af1908115611c2a57506122de5750f35b61279b90613307565b6122ee57838661274b565b83513d87823e3d90fd5b8580fd5b8385517f40db7f87000000000000000000000000000000000000000000000000000000008152fd5b90517f6a43f8d1000000000000000000000000000000000000000000000000000000008152fd5b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b8334610575576107566119c26128dd366134b3565b929190506138bb565b8284346105755760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755761291f613431565b612927613454565b906044359073ffffffffffffffffffffffffffffffffffffffff809116928385526020966008885261295f61127d8589892054613836565b858752868952878720548110908115612a8e575b8115612a7d575b8115612a6d575b50612a465750918587927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef94868852600985528288203360005285528260002054847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612a21575b505086885260088552828820612a02858254613836565b9055169586815260088452208181540190558551908152a35160018152f35b612a2a91613836565b8789526009865283892033600052865283600020558a846129eb565b86517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b9050818952878720541189612981565b60028a52888820548110915061297a565b60018a528888205481109150612973565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020906007549051908152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576107569035612b1b816138bb565b6119c7816139ce565b9050346107fe57612b3436613477565b9492915033865260209160088352612b5284882054600b5490613b33565b338852878452612b658589205483613b26565b116120f05773ffffffffffffffffffffffffffffffffffffffff9087612bad30847f000000000000000000000000000000000000000000000000000000000000000016613b7c565b828110612d25575b505033885260088452612bce85892054600b5490613b33565b338952888552612be1868a205483613b26565b1161219457612bef90613bab565b33875260088352612c0684882054600b5490613b33565b33885260028452612c1a8589205484613b26565b116120f0578690612c4d30827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b90838210612caa575b50505033865260088252612c7083872054600b5490613b33565b33875260028352612c848488205483613b26565b116124d657612c9290613c35565b33855260088152826120de83872054600b5490613b33565b612cb990600354169184613836565b813b156107fe57829160248392885194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528c8401525af18015612d1b57612d07575b80612c56565b612d1090613307565b6127b0578538612d01565b85513d84823e3d90fd5b612d3484600354169184613836565b813b156107fe57829060248a838b5195869485937f4066800c0000000000000000000000000000000000000000000000000000000085528401525af180156122265715612bb557612d8490613307565b610b64578738612bb5565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209073ffffffffffffffffffffffffffffffffffffffff600354169051908152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576107569035613ac9565b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761075690356138bb565b50503461034b57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602091612e93613431565b9073ffffffffffffffffffffffffffffffffffffffff8360243592338152600987522092169182600052845280836000205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff612f48613431565b16815280845220549051908152f35b82843461057557807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508051600091600554612f99816132b4565b808452906001908181169081156114bd5750600114612fc35750506114528261145c94038361334a565b6005600090815294507f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05b828610613008575050509181016020019161145282611441565b80546020878701810191909152909501948101612fee565b9050346107fe57602091827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107f0578135923385526008815261306d82862054600b5490613b33565b3386528582526130808387205486613b26565b116131ac5773ffffffffffffffffffffffffffffffffffffffff856130c730837f000000000000000000000000000000000000000000000000000000000000000016613b7c565b868110613244575b5050338652600882526130e883872054600b5490613b33565b3387528683526130fb8488205487613b26565b116124d65761310985613bab565b3386526008825261312083872054600b5490613b33565b338752600283526131348488205487613b26565b116124d657859061316730827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b908682106131d3575b5050503385526008815261318a82862054600b5490613b33565b3386526002825261319e8387205486613b26565b116131ac57612c9284613c35565b50517f39996567000000000000000000000000000000000000000000000000000000008152fd5b6131e290600354169187613836565b813b156107fe57829160248392875194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528b8401525af180156124cc57613230575b80613170565b61323990613307565b6124c857843861322a565b61325383600354169188613836565b90803b156107fe5760248392875194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528b8401525af18015611df5576132a1575b86906130cf565b6132ad90969196613307565b943861329a565b90600182811c921680156132fd575b60208310146132ce57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f16916132c3565b67ffffffffffffffff811161331b57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761331b57604052565b60208082528251818301819052939260005b8581106133dd575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b81810183015184820160400152820161339d565b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c5760206040516004358152f35b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361342c57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361342c57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc608091011261342c5760043590602435906044359060643590565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc606091011261342c57600435906024359060443590565b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c57613522613431565b50602061352d613f40565b604051908152f35b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c57005b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc606091011261342c576004359073ffffffffffffffffffffffffffffffffffffffff90602435828116810361342c5791604435908116810361342c5790565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739275433036135f057565b6382b429006000526004601cfd5b6000467f00000000000000000000000000000000000000000000000000000000000000000361364c57507f000000000000000000000000000000000000000000000000000000000000000090565b604051600554829161365d826132b4565b80825281602094858201946001908782821691826000146137a1575050600114613747575b5061368f9250038261334a565b51902091604051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f845260408301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c082019082821067ffffffffffffffff83111761371a575060405251902090565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526041600452fd5b6005885286915087907f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05b85831061378957505061368f935082010138613682565b80548388018501528694508893909201918101613772565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016885261368f95151560051b85010192503891506136829050565b601c600060649281946020966040519860605260405260601b602c526f23b872dd000000000000000000000000600c525af13d15600160005114171615613828576000606052604052565b637939f4246000526004601cfd5b9190820391821161384357565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60109260209260145260345260446000938480936fa9059cbb00000000000000000000000082525af13d1560018351141716156138ae57603452565b6390b8ec1890526004601cfd5b6138c490613943565b73ffffffffffffffffffffffffffffffffffffffff60035416806138e6575b50565b803b1561342c57600080916004604051809481937fcd2137850000000000000000000000000000000000000000000000000000000083525af180156139375761392c5750565b61393590613307565b565b6040513d6000823e3d90fd5b80156138e3573360005260006020526040600020613962828254613836565b90556139a681303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166137dd565b337f1c4d85c1e7d2103e7da3a4aee8d1d10c48c372a6eee0d52d9197e23a2aea63cd600080a3565b6139d790613a3e565b73ffffffffffffffffffffffffffffffffffffffff60035416806139f85750565b803b1561342c57600080916004604051809481937fc255fb1b0000000000000000000000000000000000000000000000000000000083525af180156139375761392c5750565b80156138e3573360005260026020526040600020613a5d828254613836565b9055613aa181303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166137dd565b337f3b7581e5f2644158be06ded43f7757416c423fe79298c94db0ef737427dae656600080a3565b613935903360005260046020526040600020613ae6828254613836565b9055303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166137dd565b9190820180921161384357565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482118102613b6e57670de0b6b3a764000091020490565b63bac65e5b6000526004601cfd5b602460106020939284936014526f70a082310000000000000000000000006000525afa601f3d11166020510290565b80156138e3573360005260006020526040600020613bca828254613b26565b9055613c0d813373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016613872565b337f24ab920816cdd8b858bbc7ab3116e7b4717a8f407c9fe1278ea6bf0e85ebe03b600080a3565b80156138e3573360005260026020526040600020613c54828254613b26565b9055613c97813373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016613872565b337f990e925c0c406b5aceff4657f74f5b071516036c6c2c46455080a33125e7e6b3600080a3565b80156138e3573360005260046020526040600020613cde828254613b26565b9055613d21813373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016613872565b337f7ff1bba75e07cab48d0e7ad8ce556a153ac5751934be86ed272c67b85ff73e47600080a3565b600c54428160801c1015613dea576fffffffffffffffffffffffffffffffff613dbc426099600262023ab1620afa6c6201518060039504010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c600982110290030190565b911614613de557613de142600260016007600362015180809504948502940106011491565b5090565b600090565b50600190565b600c54428160801c10156138e3576fffffffffffffffffffffffffffffffff80613e6c613e67426099600262023ab1620afa6c6201518060039504010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c600982110290030190565b613f04565b1691168114613eda57613e9342600260016007600362015180809504948502940106011491565b9015613eda5762015180810180911161384357613ed07fffffffffffffffffffffffffffffffff0000000000000000000000000000000091613f04565b60801b1617600c55565b60046040517fb9c67ec5000000000000000000000000000000000000000000000000000000008152fd5b700100000000000000000000000000000000811015613f32576fffffffffffffffffffffffffffffffff1690565b6335278d126000526004601cfd5b613f98613f8f613f863073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016613b7c565b600b5490613f9b565b60075490613836565b90565b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111820215830215613fd057020490565b637c5f487d6000526004601cfd5b73ffffffffffffffffffffffffffffffffffffffff600091168152806020526040812054806140ae575b5060016020526040600020548181116140a6575b50600260205260406000205481811161409e575b506004602052604060002054818111614096575b506008602052604060002054600b54670de0b6b3a76400007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218411810215820215613fd057613f98930290808204910615150190613836565b905038614044565b905038614030565b90503861401c565b905038614008565b6020906010926014526044600093848093816034526f095ea7b300000000000000000000000082525af13d1560018351141716156140f357603452565b633e3f8f7390526004601cfd5b6020906010926014527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60345260446000938480936f095ea7b300000000000000000000000082525af13d1560018351141716156140f357603452565b90614166613f40565b9060009181116142f45773ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001693843b156107f057838060409660048851809481937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af180156142d7576142e1575b50817f00000000000000000000000000000000000000000000000000000000000000001661421d600b5485613b33565b813b156127b05786517f40c10f1900000000000000000000000000000000000000000000000000000000815230600482015260248101919091529085908290604490829084905af180156142d7579085916142c3575b5050916020917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef936142a783600754613b26565b60075516948585526008835280852082815401905551908152a3565b6142cc90613307565b6107f0578338614273565b86513d87823e3d90fd5b6142ed90949194613307565b92386141ed565b60046040517fe4bac01b000000000000000000000000000000000000000000000000000000008152fdfea164736f6c6343000813000a61010060409080825234620004a65780620036828038038091620000248285620004c7565b8339602092839181010312620004a657516001600160a01b0381169190829003620004a6578251926200005784620004ab565b60118452704275726e744865726d657320566f74657360781b828501528051916200008283620004ab565b6009835268312422a926a2a996ab60b91b8184015284516001600160401b0395909390868511620004905760009480620000bd8754620004eb565b92601f938481116200043f575b508590848311600114620003d7578892620003cb575b50508160011b916000199060031b1c19161785555b815190878211620003b7578190600193620001118554620004eb565b82811162000362575b5085918311600114620002fe578792620002f2575b5050600019600383901b1c191690821b1781555b60126080524660a052825184549181866200015e85620004eb565b9283835286830195878282169182600014620002d257505060011462000292575b506200018e92500382620004c7565b519020918051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452818301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c0820195828710908711176200027e5785905251902060c05281638b78c6d819557f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a33360e052613159908162000529823960805181611817015260a05181611ef7015260c05181611f1e015260e05181818161029c01528181610def015261172d0152f35b634e487b7160e01b84526041600452602484fd5b8691508880528189209089915b858310620002b95750506200018e9350820101386200017f565b805483880185015286945088939092019181016200029f565b60ff191688526200018e95151560051b85010192503891506200017f9050565b0151905038806200012f565b8488528588208594509190601f198416895b888282106200034b575050841162000331575b505050811b01815562000143565b015160001960f88460031b161c1916905538808062000323565b838501518655889790950194938401930162000310565b909192508488528588208380860160051c820192888710620003ad575b91869588929594930160051c01915b8281106200039e5750506200011a565b8a81558695508791016200038e565b925081926200037f565b634e487b7160e01b86526041600452602486fd5b015190503880620000e0565b8880528689209250601f198416895b88828210620004285750509084600195949392106200040e575b505050811b018555620000f5565b015160001960f88460031b161c1916905538808062000400565b6001859682939686015181550195019301620003e6565b9091508780528588208480850160051c82019288861062000486575b9085949392910160051c01905b818110620004775750620000ca565b89815584935060010162000468565b925081926200045b565b634e487b7160e01b600052604160045260246000fd5b600080fd5b604081019081106001600160401b038211176200049057604052565b601f909101601f19168101906001600160401b038211908210176200049057604052565b90600182811c921680156200051d575b60208310146200050757565b634e487b7160e01b600052602260045260246000fd5b91607f1691620004fb56fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314611c3a5750816307f6365814611bc6578163095ea7b314611b2b57816318160ddd14611aee578163189aa7bf14611a8c578163239cbb811461198a57816323b872dd1461193a57816325692962146118d15781632641fe2114611878578163289c26f71461183b578163313ce567146117df5781633644e515146117a457816340c10f19146116d75781634d99dd161461169357816354d1f13d1461162f578163587cde1e1461155c5781635c19a95c1461151b5781636b578185146114b35781636fcfff451461144357816370a08231146113e1578163715018a6146113625781637757dc5814611300578163782d6fe1146111325781637c7b78e1146110ee5781637ecebe001461108c5781638da5cb5b1461101a578163951e26ec14610fb357816395d89b4114610e985781639ab24eb014610cf45781639dc29fac14610d99578163a9059cbb14610d3f578163af959b0514610cf4578163c3cda52014610ae7578163d505accf146107da578163dd62ed3e14610762578163e7a324dc14610709578163f04e283e14610643578163f1127ed814610571578163f14b34a61461052a578163f2fde38b1461047657508063f870efb4146102c0578063fa7e38da146102525763fee81cf4146101fe57600080fd5b3461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602091610238611e5f565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576102f8611e5f565b60243590604435926064359360208661030f611eaa565b61031b8842111561305c565b610323611ef2565b6103d06103fc8751868101907f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d4825273ffffffffffffffffffffffffffffffffffffffff9c8d8c168b8301528c606083015289608083015260a082015260a0815261038d81611d9c565b5190208851928391888301958690916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611db8565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa1561046c5761045790865195861692838852600560205287209081549161044f83612df1565b9055146130e7565b1561046857610465926123ab565b80f35b8380fd5b81513d87823e3d90fd5b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576104aa611e5f565b906104b3611eba565b8160601b1561051f575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b637448fbae8352601cfd5b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e57610465610564611e5f565b60243590336123ab565b80fd5b82843461056e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e576105a9611e5f565b63ffffffff9160243591838316830361024e5791848273ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff95610616956020855161060381611d51565b8281520152168152600660205220612095565b5091602084519361062685611d51565b5491821693848152019060201c8152835192835251166020820152f35b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e57610676611e5f565b61067e611eba565b63389a75e1600c528082526020600c2092835442116106fe57508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b636f5e88188352601cfd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf8152f35b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657602092829161079f611e5f565b6107a7611e87565b9173ffffffffffffffffffffffffffffffffffffffff8092168452865283832091168252845220549051908152f35b8280fd5b83833461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57610813611e5f565b61081b611e87565b6044356064359361082a611eaa565b93428610610a8a5761083a611ef2565b9473ffffffffffffffffffffffffffffffffffffffff8092169586895260209560058752848a209889549960018b01905585519085898301937f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c985528b89850152169a8b606084015288608084015260a083015260c082015260c0815260e081019181831067ffffffffffffffff841117610a5e579161096e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff208d95936109418c9896858c52825190206101008301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00810184520182611db8565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa15610a54578651169687151580610a4b575b156109f05786977f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259596975283528087208688528352818188205551908152a380f35b8360649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b508488146109ad565b81513d88823e3d90fd5b60248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b60648860208451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b919050346107d65760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657610b21611e5f565b90602435604435936064359060ff82168203610cf057610b438642111561305c565b610b4b611ef2565b908451602081017fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf815273ffffffffffffffffffffffffffffffffffffffff988989168884015286606084015260808301526080825260a082019282841067ffffffffffffffff851117610cc4575091610c597fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff608b9593610c2c60209896858c528251902060c08301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40810184520182611db8565b519020855190815260ff919091166020820152608435604082015260a435606082015281805260809060015afa15610cba57610cac90855194851692838752600560205286209081549161044f83612df1565b156107d6576104659161225e565b81513d86823e3d90fd5b8a60416024927f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8680fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d38610d33611e5f565b612177565b9051908152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d90610d7d611e5f565b60243590610d8b8233612e1e565b612c81565b90519015158152f35b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657610dd1611e5f565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f0000000000000000000000000000000000000000000000000000000000000000163303610e715750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9282610e48889796602095612e1e565b169384865260038352808620610e5f838254612100565b9055816002540360025551908152a380f35b84517f4d316367000000000000000000000000000000000000000000000000000000008152fd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57805190826001805491610eda83611cfe565b80865292828116908115610f6d5750600114610f11575b505050610f0382610f0d940383611db8565b5191829182611df9565b0390f35b94508085527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828610610f5557505050610f03826020610f0d9582010194610ef1565b80546020878701810191909152909501948101610f38565b610f0d975086935060209250610f039491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b82010194610ef1565b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760ff8160209373ffffffffffffffffffffffffffffffffffffffff611007611e5f565b1681526008855220541690519015158152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff6110de611e5f565b1681526005845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d3861112d611e5f565b61213c565b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d65761116a611e5f565b906024803592438410156112d85773ffffffffffffffffffffffffffffffffffffffff16855260066020528385209182549386905b858210611267575050836111da57505050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff602092915b5191168152f35b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84969293940195861161123e575050506112346020937bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92612095565b5054831c916111d3565b6011907f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b909460019061127c818818831c828916612251565b918363ffffffff61128d858a612095565b50541611156112a0575050945b9061119f565b90965081018091111561129a5783886011857f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8285517f3f8d3c1c000000000000000000000000000000000000000000000000000000008152fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611352611e5f565b168152600b845220549051908152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e57611394611eba565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611433611e5f565b1681526003845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5763ffffffff6114ab8260209473ffffffffffffffffffffffffffffffffffffffff61149d611e5f565b1681526006865220546120dc565b915191168152f35b83903461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57356114ed611eba565b600754816007557f9960c7dba5c668f2dcce571ead061f33d2e4174c892c8eb86b4b34529bb7271e8380a380f35b833461056e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e57610465611556611e5f565b3361225e565b50503461024e576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d65773ffffffffffffffffffffffffffffffffffffffff92836115ad611e5f565b168152600b825282812091835190818185549182815201908195855282852090855b81811061161b57505050826115e5910383611db8565b8451948186019282875251809352850193925b8281106116055785850386f35b83518716855293810193928101926001016115f8565b8254845292840192600192830192016115cf565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e5763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e576104656116cd611e5f565b6024359033612899565b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d65761170f611e5f565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f0000000000000000000000000000000000000000000000000000000000000000163303610e715750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef926020926117878896600254612251565b60025516948585526003835280852082815401905551908152a380f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d38611ef2565b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906007549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d48152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e5763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b50503461024e5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d90611979611e5f565b611981611e87565b60443591612cf9565b919050346107d657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d6576119c3611e5f565b6024359283151593848103611a88576119da611eba565b80611a7f575b611a58575073ffffffffffffffffffffffffffffffffffffffff1690818452600860205283207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541660ff84161790557ff250dd6faf51f88e0d298800d22453f75bd1af207056ddd9a4fb55f1408376fb8380a380f35b82517f270de3fd000000000000000000000000000000000000000000000000000000008152fd5b50813b156119e0565b8580fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611ade611e5f565b168152600a845220549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906002549051908152f35b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657602092611b66611e5f565b9183602435928392338252875273ffffffffffffffffffffffffffffffffffffffff8282209516948582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5780602092611c02611e5f565b611c0a611e87565b73ffffffffffffffffffffffffffffffffffffffff91821683526009865283832091168252845220549051908152f35b849084346107d657827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657828054611c7781611cfe565b80855291600191808316908115610f6d5750600114611ca257505050610f0382610f0d940383611db8565b80809650527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828610611ce657505050610f03826020610f0d9582010194610ef1565b80546020878701810191909152909501948101611cc9565b90600182811c92168015611d47575b6020831014611d1857565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611d0d565b6040810190811067ffffffffffffffff821117611d6d57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60c0810190811067ffffffffffffffff821117611d6d57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117611d6d57604052565b60208082528251818301819052939260005b858110611e4b575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b818101830151848201604001528201611e0b565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203611e8257565b600080fd5b6024359073ffffffffffffffffffffffffffffffffffffffff82168203611e8257565b6084359060ff82168203611e8257565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927543303611ee457565b6382b429006000526004601cfd5b6000467f000000000000000000000000000000000000000000000000000000000000000003611f4057507f000000000000000000000000000000000000000000000000000000000000000090565b604051815491908181611f5285611cfe565b9182825260209586830195600191888382169182600014612056575050600114611ffd575b5050611f8592500382611db8565b51902090604051908101917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f835260408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a08152611ff781611d9c565b51902090565b908792508180527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b85831061203e575050611f8593508201013880611f77565b80548388018501528694508893909201918101612026565b91509350611f859592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009150168652151560051b8201013880611f77565b80548210156120ad5760005260206000200190600090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6401000000008110156120f25763ffffffff1690565b6335278d126000526004601cfd5b9190820391821161210d57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff166000526003602052612174604060002054600a60205260406000205490612100565b90565b73ffffffffffffffffffffffffffffffffffffffff16600090815260066020526040812080549190826121c857507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff91501690565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830192831161222457507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9161221b91612095565b505460201c1690565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526011600452fd5b9190820180921161210d57565b9073ffffffffffffffffffffffffffffffffffffffff9081831690600092828452600b6020526040842054600181116123815760018591146122e9575b82827f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f941696876122cf575b5050169280a4565b6122e2916122dc8261213c565b916123ab565b38806122c7565b5090828452600b6020526040842080541561235457907f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f92918552816020862054169084865260096020526040862082875260205261234d60408720548389612899565b925061229b565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526032600452fd5b60046040517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b919073ffffffffffffffffffffffffffffffffffffffff80911692831580156127c7575b80156127bf575b61238157169160008381526020600b815283836040966123f8828987206127d8565b612767575b80855260098452878520828652845287852061241a848254612251565b9055808552600a8452878520612431848254612251565b90557f96eafeca8c3c21ab2fa4a636b93ba20c9e22e3d222d92c6530fedc29a53671ee8580a48282526006815284822090815490811596876000146126fc5784905b61249d7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8093169889612251565b98158061268b575b15612578575050506124b686612c3b565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161254b5791612511612547927fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7249594612095565b509063ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000083549260201b169116179055565b80a4565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b91939092612585436120dc565b91846125908b612c3b565b94519661259c88611d51565b63ffffffff80951688528701941684526801000000000000000082101561265e57906125cd91600182018155612095565b9490946126325751915163ffffffff9190921616911660201b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000161790557fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72490612547565b6024867f4e487b7100000000000000000000000000000000000000000000000000000000815280600452fd5b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84018481116126cf576126c563ffffffff9187612095565b50541643146124a5565b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830183811161273a576127309085612095565b5054821c90612473565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b808552600b84528785205460075410156123fd579150506008825260ff868420541615612796578385916123fd565b600486517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b5082156123d6565b50826127d28261213c565b106123cf565b9190600183016000908282528060205260408220541560001461289357845494680100000000000000008610156128665783612856612821886001604098999a01855584612095565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b9055549382526020522055600190565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b50925050565b929190816128a682612177565b1061296b5773ffffffffffffffffffffffffffffffffffffffff809416936000948086526020600981526040908188209385169384895281526128ec86838a2054612100565b8015612950575b978392600a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa9797739361294e9a9b8a9897855260098252838520888652825283852055858452528120612945868254612100565b905580a4612ac1565b565b838952600b825261296385848b20612995565b6128f3578880fd5b60046040517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b90600182019060009281845282602052604084205490811515600014612aba577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff918281018181116126cf57825490848201918211612a8d57808203612a58575b50505080548015612a2b57820191612a0e8383612095565b909182549160031b1b191690555582526020526040812055600190565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b612a78612a686128219386612095565b90549060031b1c92839286612095565b905586528460205260408620553880806129f6565b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b5050505090565b73ffffffffffffffffffffffffffffffffffffffff1660008181526006602052604081208054801594939291908515612bfd57825b612b207bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8092169687612100565b961580612b8c575b15612b3757506124b686612c3b565b90612b41436120dc565b612b4a88612c3b565b9160405194612b5886611d51565b63ffffffff80931686528460208701941684526801000000000000000082101561265e57906125cd91600182018155612095565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201828111612bd057612bc663ffffffff9185612095565b5054164314612b28565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811161254b57612c319083612095565b505460201c612af6565b7c01000000000000000000000000000000000000000000000000000000008110156120f2577bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff903360005260036020526040600020612cb0848254612100565b9055169081600052600360205260406000208181540190556040519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60203392a3600190565b9190612d058284612e1e565b73ffffffffffffffffffffffffffffffffffffffff80931691338314612de557907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91600094848652602092600484526040918291828920338a52865282892054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612dc2575b505087895260038652828920612da6868254612100565b90551696878152600385522082815401905551908152a3600190565b612dcb91612100565b888a5260048752838a20338b528752838a20553885612d8f565b91506121749250612c81565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461210d5760010190565b91612e288361213c565b91808310156130565791909260009073ffffffffffffffffffffffffffffffffffffffff809116808352602095600b875260409283852091845190818a85549182815201908589528b892090895b8d828210613042575050505082612e8e910383611db8565b81519887985b8a8a1080613030575b15612fc85783518a10156120ad57848c8b60051b860101511699876000528c60098091528d8a600020908d600052528d8a600020549c8d91612ede82612177565b838110908418028084189303612f14575b50505050612f069192939495969798999a50612df1565b989796959493929190612e94565b9091612f2a8180959798999a9b9c9d9e9f612251565b9e03612f9e5750612f3b818b612995565b15611e82578e612f06948c60005281528c60002090826000525260008c8120555b612f668282612ac1565b8a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa979773600080a48a99989796959493929138808f612eef565b612f06948c60005281528c6000209082600052528b600020612fc1838254612100565b9055612f5c565b92509498509895949650612fdf9250849150612251565b106130075790612ffc600a92856000528383528460002054612100565b936000525260002055565b600483517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b508161303c8a83612251565b10612e9d565b835485529093019260019283019201612e76565b50915050565b1561306357565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332304d756c7469566f7465733a207369676e617475726520657870697260448201527f65640000000000000000000000000000000000000000000000000000000000006064820152fd5b156130ee57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f45524332304d756c7469566f7465733a20696e76616c6964206e6f6e636500006044820152fdfea164736f6c6343000813000a036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0f652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f000000000000000000000000c2080ca61d32663fb698321def53c038efc729640000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c466af7ff16ef0f1a7fa4e23e095e47a4058d7910000000000000000000000000000000000000000000000000000000000000009566f7465204d61696100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005764d414941000000000000000000000000000000000000000000000000000000

Deployed Bytecode

0x6040608081526004908136101561001557600080fd5b600091823560e01c9081630100170a1461302057816301e1d11414612a9f57816306fdde0314612f5757816307a2d13a146105785781630826a55d14612ef6578163095ea7b314612e585781630a28a477146105785781630e215e6214612e1d5781631099f88814612de2578163118917e314612d8f57816313f2e64714612b24578163152ca92714612adc57816318160ddd14612a9f57816323b872dd146128e657816324932de3146128c8578163256929621461285f578163313ce5671461280357816332564c15146126135781633644e515146125d857816338d52e0f14612569578163402d267d14610df757816344dba573146122f75781634cdad50614610578578163502d29671461057d578382635217237b1461223057508163521fb1fd14611f0d57816354d1f13d14611ea957816355f7be3a14611e6e5781635aa6e67514611dff5781635f2078eb146119cc5781636ca0b414146119a15781636e553f65146118b757816370a0823114611855578163715018a6146117d55781637b608e38146117665781637bf01826146117055781637d300c0c146116965781637ecebe00146116345781638da5cb5b146115c257816394bf804d1461150357816395d89b41146113e55781639f14cf2514611376578163a9059cbb14611227578163b3d7f6b914610578578163b460af941461117e578163ba08765214610e6b578163c45a015514610dfc578163c63d75b614610df7578163c6e6f59214610578578163caedd78e14610d95578163ce96cb7714610802578163ce99dfe614610d2b578163d0d6091514610cc9578163d29a41aa14610b68578163d505accf14610864578163d905777e14610802578163dc918f0c14610668578163dd62ed3e146105f1578163e01bfb0214610582578163eb5a25ca1461057d578163ef8b30f714610578578163f04e283e146104ae578163f2fde38b146103f957508063f5a76217146103bd578063fa7e38da1461034f5763fee81cf4146102fb57600080fd5b3461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602091610335613431565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b3168152f35b503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602090600b549051908152f35b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761042d613431565b906104366135c6565b8160601b156104a3575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a35580f35b637448fbae8352601cfd5b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610575576104e1613431565b6104e96135c6565b63389a75e1600c528082526020600c20928354421161056a57508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a35580f35b636f5e88188352601cfd5b80fd5b6133f1565b613535565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f168152f35b50503461034b57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209161062c613431565b82610635613454565b9273ffffffffffffffffffffffffffffffffffffffff809316815260098652209116600052825280600020549051908152f35b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5780359133845260086020526106b481852054600b5490613b33565b338552846020526106c88286205485613b26565b11610759578373ffffffffffffffffffffffffffffffffffffffff61070f30827f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b9085821061077f575b505050338452600860205261073381852054600b5490613b33565b338552846020526107478286205485613b26565b11610759578361075684613bab565b80f35b517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61078e90600354169186613836565b813b156107fe57829160248392865194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528a8401525af180156107f4576107dc575b80610718565b6107e590613307565b6107f05783386107d6565b8380fd5b83513d84823e3d90fd5b8280fd5b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209161083e613431565b610846613d49565b1561085d576108559150613fde565b905b51908152f35b5090610857565b9050346107fe5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5761089d613431565b6108a5613454565b9260443590606435936084359360ff8516809503610b6457428610610b07576108cc6135fe565b9673ffffffffffffffffffffffffffffffffffffffff80921696878a52602096600a8852858b20998a549a60018c019055865192858a8501957f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c987528c8a870152169b8c606086015289608086015260a085015260c084015260c0835260e0830167ffffffffffffffff9484821086831117610ad957818952845190206101008501927f190100000000000000000000000000000000000000000000000000000000000084526101028601526101228501526042815261016084019481861090861117610aab57848852519020835261018082015260a4356101a082015260c4356101c0909101528880528590899060809060015afa15610aa1578751169081151580610a98575b15610a3c5750907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92593929187526009835280872086600052835281816000205551908152a380f35b606490858451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b508582146109f4565b82513d89823e3d90fd5b6041877f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b6041887f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b50602060649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b8780fd5b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe578035913384526008602052610bb481852054600b5490613b33565b3385526002602052610bc98286205485613b26565b11610759578373ffffffffffffffffffffffffffffffffffffffff610c1030827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b90858210610c58575b5050503384526008602052610c3481852054600b5490613b33565b3385526002602052610c498286205485613b26565b11610759578361075684613c35565b610c6790600354169186613836565b813b156107fe57829160248392865194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528a8401525af180156107f457610cb5575b80610c19565b610cbe90613307565b6107f0578338610caf565b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff610d1b613431565b1681526001845220549051908152f35b919050346107fe57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe576107569133845283602052610d73828520546138bb565b3384526002602052610d87828520546139ce565b338452602052822054613ac9565b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff610de7613431565b1681526002845220549051908152f35b6134eb565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c2080ca61d32663fb698321def53c038efc72964168152f35b83833461034b57610e7b36613566565b73ffffffffffffffffffffffffffffffffffffffff939293809116803303611110575b84156110b357610eac613df0565b80865260209560088752610ec38686832054613836565b610ed0600b548092613b33565b8383528289528683205481109081156110a2575b8115611091575b8115611081575b5061105957610f24847f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f169188613b33565b98813b156107fe5786517f9dc29fac00000000000000000000000000000000000000000000000000000000815230918101918252602082019a909a52979896979596958291879182908490829060400103925af1801561104d57908794939291611030575b610857955081815260088952868120610fa3868254613836565b90558460075403600755817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8a8951888152a385518481528489820152828416907ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db883392a47f0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e16613872565b919350919361103f8291613307565b610575579183918693610f89565b508551903d90823e3d90fd5b8886517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b905089895286832054118a610ef2565b60028a528784205481109150610eeb565b60018a528784205481109150610ee4565b60648760208651917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600b60248201527f5a45524f5f4153534554530000000000000000000000000000000000000000006044820152fd5b8086526009602052838620336000526020528360002054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611157575b5050610e9e565b61116091613836565b81875260096020528487203360005260205284600020558785611150565b83833461034b5761118e36613566565b73ffffffffffffffffffffffffffffffffffffffff9392938091168033036111b9575b610eac613df0565b8086526009602052838620336000526020528360002054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611200575b50506111b1565b61120991613836565b818752600960205284872033600052602052846000205587856111f9565b82843461057557817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755761125f613431565b602435903383526020946008865261128661127d8487872054613836565b600b5490613b33565b338552848752858520548110908115611365575b8115611354575b8115611344575b5061131d575073ffffffffffffffffffffffffffffffffffffffff8491338552600887528285206112da858254613836565b90551692838152600886522081815401905582519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef843392a35160018152f35b84517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b90508187528585205411876112a8565b6002885286862054811091506112a1565b60018852868620548110915061129a565b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5780359133845260086020526113c281852054600b5490613b33565b338552826020526113d68286205485613b26565b11610759578361075684613cbf565b82843461057557807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508051600091600654611427816132b4565b808452906001908181169081156114bd5750600114611460575b50506114528261145c94038361334a565b519182918261338b565b0390f35b6006600090815294507ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f5b8286106114a5575050509181016020019161145282611441565b8054602087870181019190915290950194810161148b565b61145c965085925060209150927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00611452941682840152151560051b8201019450611441565b9050346107fe57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5760209250359081611542613454565b73ffffffffffffffffffffffffffffffffffffffff611585833033847f0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e166137dd565b61158f838361415d565b83519183835216907fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7863392a451908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff611686613431565b168152600a845220549051908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb168152f35b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe57602092829173ffffffffffffffffffffffffffffffffffffffff611758613431565b168252845220549051908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000008265aa10ee11f57ed01bf8ece5ba57ef75ed36dc168152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610575576118076135c6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff6118a7613431565b1681526008845220549051908152f35b9050823461057557827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508035906118f4613454565b9082156119445750918160209373ffffffffffffffffffffffffffffffffffffffff611585833033847f0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e166137dd565b60649060208551917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600b60248201527f5a45524f5f5348415245530000000000000000000000000000000000000000006044820152fd5b8334610575576107566119c26119c76119b936613477565b949150916138bb565b6139ce565b613ac9565b8391503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57611a06613431565b90611a0f6135c6565b73ffffffffffffffffffffffffffffffffffffffff93848316928315159081611d30575b8660035416918215159788611cc9575b807f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b316803b15611cbb5785517f521fb1fd0000000000000000000000000000000000000000000000000000000081528981898183865af18015611cbf57908a91611ca7575b5050611ab5903090613b7c565b91817f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb169483611ae53088613b7c565b10611c5757827f0000000000000000000000008265aa10ee11f57ed01bf8ece5ba57ef75ed36dc169284611b193086613b7c565b10611c7f577f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d1693611b4b3086613b7c565b10611c5757899a999899611c33575b50611bb4575b8688807fffffffffffffffffffffffff00000000000000000000000000000000000000006003541617600355307f889ad99844dae1714b73856de24207bc777a6e82bc70da589759c5eb669773c98380a380f35b82611bc691611bc682611bcb97614100565b614100565b833b156107fe5782815180937f2bbfaae50000000000000000000000000000000000000000000000000000000082528183885af1908115611c2a5750611c16575b8080808080611b60565b611c1f90613307565b61034b578183611c0c565b513d84823e3d90fd5b80611c41611c5192886140b6565b611c4b81856140b6565b846140b6565b8a611b5a565b8787517fa1fb25d1000000000000000000000000000000000000000000000000000000008152fd5b8888517fa1fb25d1000000000000000000000000000000000000000000000000000000008152fd5b611cb090613307565b611cbb57888b611aa8565b8880fd5b87513d8c823e3d90fd5b833b15610b645784517febb689a10000000000000000000000000000000000000000000000000000000081528881888183895af18015611d2657908991611d12575b5050611a43565b611d1b90613307565b610b6457878a611d0b565b86513d8b823e3d90fd5b82517f41849afc00000000000000000000000000000000000000000000000000000000815285858201526020816024818b7f000000000000000000000000c2080ca61d32663fb698321def53c038efc72964165afa908115611df5578791611dc0575b50611a33575050517f97e8471e000000000000000000000000000000000000000000000000000000008152fd5b90506020813d8211611ded575b81611dda6020938361334a565b81010312611de9575188611d93565b8680fd5b3d9150611dcd565b84513d89823e3d90fd5b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d168152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761075690356139ce565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b9050346107fe57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe57338352600891602083815281852054611f59600b548092613b33565b90338752868352611f768488205483039187855285892054613b33565b338852878452611f898589205483613b26565b116120f05773ffffffffffffffffffffffffffffffffffffffff9087611fd130847f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b8281106121bc575b5050338852868452611ff185892054600b5490613b33565b338952888552612004868a205483613b26565b116121945761201290613bab565b338752600283528387205482039086845261203385892054600b5490613b33565b33895260028552612047868a205484613b26565b1161219457879061207a30827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b90838210612119575b50505033875285835261209c84882054600b5490613b33565b338852600284526120b08589205483613b26565b116120f0576120be90613c35565b338652838252828620549003938152826120de83872054600b5490613b33565b91338752526113d68286205485613b26565b505050517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61212890600354169184613836565b813b156107fe57829160248392895194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528d8401525af1801561218a57612176575b80612083565b61217f90613307565b611de9578638612170565b86513d84823e3d90fd5b8585517f39996567000000000000000000000000000000000000000000000000000000008152fd5b6121cb84600354169184613836565b813b156107fe57829060248a838b5195869485937f4066800c0000000000000000000000000000000000000000000000000000000085528401525af180156122265715611fd95761221b90613307565b610b64578738611fd9565b87513d84823e3d90fd5b809184346122f357827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126122f35773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b31691823b156122ee57839283918351809581937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af1908115611c2a57506122de5750f35b6122e790613307565b6105755780f35b505050fd5b5050fd5b9050346107fe57612307366134b3565b939190503385526020906008825261232583872054600b5490613b33565b3387528683526123388488205483613b26565b116124d65773ffffffffffffffffffffffffffffffffffffffff908661238030847f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b8281106124fe575b5050338752600883526123a184882054600b5490613b33565b3388528784526123b48589205483613b26565b116120f0576123c290613bab565b338652600882526123d983872054600b5490613b33565b338752600283526123ed8488205487613b26565b116124d657859061242030827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b90868210612457575b50505033855260088152600261244583872054600b5490613b33565b9133875252610c498286205485613b26565b61246690600354169187613836565b813b156107fe57829160248392875194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528b8401525af180156124cc576124b4575b80612429565b6124bd90613307565b6124c85784386124ae565b8480fd5b84513d84823e3d90fd5b5050517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61250d84600354169184613836565b813b156107fe57829160248392895194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528d8401525af1801561218a57156123885761255e90613307565b611de9578638612388565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e168152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020906108556135fe565b8391503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578035926126506135c6565b600b5491828511156127dc576007549473ffffffffffffffffffffffffffffffffffffffff93847f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b316946126ad886126a83089613b7c565b613f9b565b83116127b457966126e96126ef92889985600b557f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f1694613836565b90613b33565b90803b156127b05783517f40c10f19000000000000000000000000000000000000000000000000000000008152308482019081526020810193909352918691839182908490829060400103925af180156127a657908591612792575b5050823b156122ee57839283918351809581937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af1908115611c2a57506122de5750f35b61279b90613307565b6122ee57838661274b565b83513d87823e3d90fd5b8580fd5b8385517f40db7f87000000000000000000000000000000000000000000000000000000008152fd5b90517f6a43f8d1000000000000000000000000000000000000000000000000000000008152fd5b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905160ff7f0000000000000000000000000000000000000000000000000000000000000012168152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b8334610575576107566119c26128dd366134b3565b929190506138bb565b8284346105755760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755761291f613431565b612927613454565b906044359073ffffffffffffffffffffffffffffffffffffffff809116928385526020966008885261295f61127d8589892054613836565b858752868952878720548110908115612a8e575b8115612a7d575b8115612a6d575b50612a465750918587927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef94868852600985528288203360005285528260002054847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612a21575b505086885260088552828820612a02858254613836565b9055169586815260088452208181540190558551908152a35160018152f35b612a2a91613836565b8789526009865283892033600052865283600020558a846129eb565b86517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b9050818952878720541189612981565b60028a52888820548110915061297a565b60018a528888205481109150612973565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020906007549051908152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576107569035612b1b816138bb565b6119c7816139ce565b9050346107fe57612b3436613477565b9492915033865260209160088352612b5284882054600b5490613b33565b338852878452612b658589205483613b26565b116120f05773ffffffffffffffffffffffffffffffffffffffff9087612bad30847f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b828110612d25575b505033885260088452612bce85892054600b5490613b33565b338952888552612be1868a205483613b26565b1161219457612bef90613bab565b33875260088352612c0684882054600b5490613b33565b33885260028452612c1a8589205484613b26565b116120f0578690612c4d30827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b90838210612caa575b50505033865260088252612c7083872054600b5490613b33565b33875260028352612c848488205483613b26565b116124d657612c9290613c35565b33855260088152826120de83872054600b5490613b33565b612cb990600354169184613836565b813b156107fe57829160248392885194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528c8401525af18015612d1b57612d07575b80612c56565b612d1090613307565b6127b0578538612d01565b85513d84823e3d90fd5b612d3484600354169184613836565b813b156107fe57829060248a838b5195869485937f4066800c0000000000000000000000000000000000000000000000000000000085528401525af180156122265715612bb557612d8490613307565b610b64578738612bb5565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209073ffffffffffffffffffffffffffffffffffffffff600354169051908152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576107569035613ac9565b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761075690356138bb565b50503461034b57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602091612e93613431565b9073ffffffffffffffffffffffffffffffffffffffff8360243592338152600987522092169182600052845280836000205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff612f48613431565b16815280845220549051908152f35b82843461057557807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508051600091600554612f99816132b4565b808452906001908181169081156114bd5750600114612fc35750506114528261145c94038361334a565b6005600090815294507f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05b828610613008575050509181016020019161145282611441565b80546020878701810191909152909501948101612fee565b9050346107fe57602091827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107f0578135923385526008815261306d82862054600b5490613b33565b3386528582526130808387205486613b26565b116131ac5773ffffffffffffffffffffffffffffffffffffffff856130c730837f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b868110613244575b5050338652600882526130e883872054600b5490613b33565b3387528683526130fb8488205487613b26565b116124d65761310985613bab565b3386526008825261312083872054600b5490613b33565b338752600283526131348488205487613b26565b116124d657859061316730827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b908682106131d3575b5050503385526008815261318a82862054600b5490613b33565b3386526002825261319e8387205486613b26565b116131ac57612c9284613c35565b50517f39996567000000000000000000000000000000000000000000000000000000008152fd5b6131e290600354169187613836565b813b156107fe57829160248392875194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528b8401525af180156124cc57613230575b80613170565b61323990613307565b6124c857843861322a565b61325383600354169188613836565b90803b156107fe5760248392875194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528b8401525af18015611df5576132a1575b86906130cf565b6132ad90969196613307565b943861329a565b90600182811c921680156132fd575b60208310146132ce57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f16916132c3565b67ffffffffffffffff811161331b57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761331b57604052565b60208082528251818301819052939260005b8581106133dd575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b81810183015184820160400152820161339d565b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c5760206040516004358152f35b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361342c57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361342c57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc608091011261342c5760043590602435906044359060643590565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc606091011261342c57600435906024359060443590565b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c57613522613431565b50602061352d613f40565b604051908152f35b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c57005b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc606091011261342c576004359073ffffffffffffffffffffffffffffffffffffffff90602435828116810361342c5791604435908116810361342c5790565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739275433036135f057565b6382b429006000526004601cfd5b6000467f0000000000000000000000000000000000000000000000000000000000aa36a70361364c57507f34888eff16829c3e9bd226cb95537287dad600fd4244732f95a9e11b9263ca8090565b604051600554829161365d826132b4565b80825281602094858201946001908782821691826000146137a1575050600114613747575b5061368f9250038261334a565b51902091604051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f845260408301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c082019082821067ffffffffffffffff83111761371a575060405251902090565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526041600452fd5b6005885286915087907f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05b85831061378957505061368f935082010138613682565b80548388018501528694508893909201918101613772565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016885261368f95151560051b85010192503891506136829050565b601c600060649281946020966040519860605260405260601b602c526f23b872dd000000000000000000000000600c525af13d15600160005114171615613828576000606052604052565b637939f4246000526004601cfd5b9190820391821161384357565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60109260209260145260345260446000938480936fa9059cbb00000000000000000000000082525af13d1560018351141716156138ae57603452565b6390b8ec1890526004601cfd5b6138c490613943565b73ffffffffffffffffffffffffffffffffffffffff60035416806138e6575b50565b803b1561342c57600080916004604051809481937fcd2137850000000000000000000000000000000000000000000000000000000083525af180156139375761392c5750565b61393590613307565b565b6040513d6000823e3d90fd5b80156138e3573360005260006020526040600020613962828254613836565b90556139a681303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb166137dd565b337f1c4d85c1e7d2103e7da3a4aee8d1d10c48c372a6eee0d52d9197e23a2aea63cd600080a3565b6139d790613a3e565b73ffffffffffffffffffffffffffffffffffffffff60035416806139f85750565b803b1561342c57600080916004604051809481937fc255fb1b0000000000000000000000000000000000000000000000000000000083525af180156139375761392c5750565b80156138e3573360005260026020526040600020613a5d828254613836565b9055613aa181303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d166137dd565b337f3b7581e5f2644158be06ded43f7757416c423fe79298c94db0ef737427dae656600080a3565b613935903360005260046020526040600020613ae6828254613836565b9055303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f166137dd565b9190820180921161384357565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482118102613b6e57670de0b6b3a764000091020490565b63bac65e5b6000526004601cfd5b602460106020939284936014526f70a082310000000000000000000000006000525afa601f3d11166020510290565b80156138e3573360005260006020526040600020613bca828254613b26565b9055613c0d813373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613872565b337f24ab920816cdd8b858bbc7ab3116e7b4717a8f407c9fe1278ea6bf0e85ebe03b600080a3565b80156138e3573360005260026020526040600020613c54828254613b26565b9055613c97813373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613872565b337f990e925c0c406b5aceff4657f74f5b071516036c6c2c46455080a33125e7e6b3600080a3565b80156138e3573360005260046020526040600020613cde828254613b26565b9055613d21813373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f16613872565b337f7ff1bba75e07cab48d0e7ad8ce556a153ac5751934be86ed272c67b85ff73e47600080a3565b600c54428160801c1015613dea576fffffffffffffffffffffffffffffffff613dbc426099600262023ab1620afa6c6201518060039504010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c600982110290030190565b911614613de557613de142600260016007600362015180809504948502940106011491565b5090565b600090565b50600190565b600c54428160801c10156138e3576fffffffffffffffffffffffffffffffff80613e6c613e67426099600262023ab1620afa6c6201518060039504010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c600982110290030190565b613f04565b1691168114613eda57613e9342600260016007600362015180809504948502940106011491565b9015613eda5762015180810180911161384357613ed07fffffffffffffffffffffffffffffffff0000000000000000000000000000000091613f04565b60801b1617600c55565b60046040517fb9c67ec5000000000000000000000000000000000000000000000000000000008152fd5b700100000000000000000000000000000000811015613f32576fffffffffffffffffffffffffffffffff1690565b6335278d126000526004601cfd5b613f98613f8f613f863073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b316613b7c565b600b5490613f9b565b60075490613836565b90565b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111820215830215613fd057020490565b637c5f487d6000526004601cfd5b73ffffffffffffffffffffffffffffffffffffffff600091168152806020526040812054806140ae575b5060016020526040600020548181116140a6575b50600260205260406000205481811161409e575b506004602052604060002054818111614096575b506008602052604060002054600b54670de0b6b3a76400007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218411810215820215613fd057613f98930290808204910615150190613836565b905038614044565b905038614030565b90503861401c565b905038614008565b6020906010926014526044600093848093816034526f095ea7b300000000000000000000000082525af13d1560018351141716156140f357603452565b633e3f8f7390526004601cfd5b6020906010926014527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60345260446000938480936f095ea7b300000000000000000000000082525af13d1560018351141716156140f357603452565b90614166613f40565b9060009181116142f45773ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b31693843b156107f057838060409660048851809481937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af180156142d7576142e1575b50817f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f1661421d600b5485613b33565b813b156127b05786517f40c10f1900000000000000000000000000000000000000000000000000000000815230600482015260248101919091529085908290604490829084905af180156142d7579085916142c3575b5050916020917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef936142a783600754613b26565b60075516948585526008835280852082815401905551908152a3565b6142cc90613307565b6107f0578338614273565b86513d87823e3d90fd5b6142ed90949194613307565b92386141ed565b60046040517fe4bac01b000000000000000000000000000000000000000000000000000000008152fdfea164736f6c6343000813000a

[ Download: CSV Export  ]
[ Download: CSV Export  ]

A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.