Source Code
Overview
ETH Balance
0 ETH
Token Holdings
More Info
ContractCreator
TokenTracker
Multichain Info
N/A
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block |
From
|
To
|
|||
---|---|---|---|---|---|---|
5741655 | 274 days ago | Contract Creation | 0 ETH |
Loading...
Loading
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)
// 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(); }
// 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; } }
// 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(); _; } }
// 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) } } }
// 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) } } }
// 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) ) ) } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Gas optimized reentrancy protection for smart contracts. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) abstract contract ReentrancyGuard { uint256 private locked = 1; modifier nonReentrant() virtual { require(locked == 1, "REENTRANCY"); locked = 2; _; locked = 1; } }
// SPDX-License-Identifier: 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(); _; } }
// 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); } }
// 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); } }
// 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(); }
// 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(); }
// 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(); }
// 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(); }
// 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 {} }
// 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(); }
// 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 ); }
// 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(); }
// 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(); }
// 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(); }
// 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; }
// 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(); }
// 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(); _; } }
// 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(); _; } }
// 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(); _; } }
// 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); } }
// 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; }
// 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); } }
// 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; }
// 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(); }
// 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(); }
// 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); }
// 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; } } }
// 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; }
// 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(); _; } }
// 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); }
// 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); } }
// 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); } }
// 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); } }
// 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); }
// 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(); }
// 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); }
// 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(); }
// 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(); }
// 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(); }
// 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(); }
// 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); } }
{ "viaIR": true, "optimizer": { "enabled": true, "runs": 1000000 }, "metadata": { "bytecodeHash": "none" }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": {} }
[{"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"}]
Contract Creation Code
6101c0806040523462000a505762008586803803809162000021828562000a55565b83398101906101008183031262000a50578051906001600160a01b038216820362000a5057602081015160408201519092906001600160a01b038116810362000a505760608301516001600160401b03811162000a5057856200008691850162000a9e565b608084015190956001600160401b03821162000a5057620000a991850162000a9e565b92620000b860a0820162000af9565b93620000d560e0620000cd60c0850162000af9565b930162000af9565b9662000146605060405183620000f682955180926020808601910162000a79565b81017f202d204275726e6564204865726d65733a204167677265676174656420476f7660208201526f080ac8165a595b19080ac8109bdbdcdd60821b604082015203603081018452018262000a55565b620001886028604051846200016682965180926020808601910162000a79565b8101672d624845524d455360c01b602082015203600881018552018362000a55565b604051631f4c030360e21b81526020816004816001600160a01b038b165afa908115620009655760009162000a07575b50604051630f6c11c760e31b8152936020856004816001600160a01b038c165afa9485156200096557600095620009ba575b50604051635aa6e67560e01b81526020816004816001600160a01b038d165afa908115620009655760009162000971575b506040516001600160401b03613682820190811190821117620006bf5761368262004ec482396001600160a01b038c166136828201908152819003602001906000f0908115620009655760405163313ce56760e01b8152966020886004816001600160a01b038d165afa978815620009655760009862000914575b506001600160a01b03948516608081905290851660a05290841660c05290831660e052600380546001600160a01b03191693909216928317909155620002dd919062000b4b565b60a051600354620002fb916001600160a01b03918216911662000b4b565b60c05160035462000319916001600160a01b03918216911662000b4b565b8051906001600160401b038211620006bf5781906200033a60055462000b0e565b601f8111620008be575b50602090601f8311600114620008405760009262000834575b50508160011b916000199060031b1c1916176005555b8051906001600160401b038211620006bf5781906200039460065462000b0e565b601f8111620007d3575b50602090601f8311600114620007555760009262000749575b50508160011b916000199060031b1c1916176006555b610100524661012052604051600554816000620003ea8362000b0e565b8083529260018116908115620007285750600114620006d5575b620004129250038262000a55565b60208151910120936040519460208601907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f825260408701527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608701524660808701523060a087015260a0865260c086019580871060018060401b03881117620006bf5760408790525190206101409081526101609283526001600160a01b03909616638b78c6d81981905560007f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a3610180928352600b556101a09260018060a01b0316835260036099600262023ab1620afa6c620151804204010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c6009821102900301600160801b811015620006b157600c80546001600160801b0319166001600160801b039290921691909117905561432b948562000b9986396080518581816106ea015281816116e101528181611ab901528181611fac0152818161235b01528181612b88015281816130a2015281816139810152613be8015260a0518581816117b10152611aed015260c051858181610beb01528181611b2001528181611e4a01528181612055015281816123fb01528181612c280152818161314201528181613a7c0152613c72015260e0518581816105cd01528181610efd015281816126c301528181613b0101528181613cfc01526141f10152610100518561283b015261012051856136030152518461362a01525183818161100b015281816115600152818161191f01526125b4015251828181610e470152611d6301525181818161039901528181611a46015281816122780152818161267901528181613f6101526141880152f35b6335278d126000526004601cfd5b634e487b7160e01b600052604160045260246000fd5b50600560009081529091600080516020620085468339815191525b8183106200070b575050906020620004129282010162000404565b6020919350806001915483858801015201910190918392620006f0565b602092506200041294915060ff191682840152151560051b82010162000404565b015190503880620003b7565b6006600090815293506000805160206200856683398151915291905b601f1984168510620007b7576001945083601f198116106200079d575b505050811b01600655620003cd565b015160001960f88460031b161c191690553880806200078e565b8181015183556020948501946001909301929091019062000771565b600660005290915060008051602062008566833981519152601f840160051c8101602085106200082c575b90849392915b601f830160051c820181106200081c5750506200039e565b6000815585945060010162000804565b5080620007fe565b0151905038806200035d565b6005600090815293506000805160206200854683398151915291905b601f1984168510620008a2576001945083601f1981161062000888575b505050811b0160055562000373565b015160001960f88460031b161c1916905538808062000879565b818101518355602094850194600190930192909101906200085c565b90915060056000526020600020601f840160051c8101602085106200090c575b90849392915b601f830160051c82018110620008fc57505062000344565b60008155859450600101620008e4565b5080620008de565b6020989198813d6020116200095c575b81620009336020938362000a55565b810103126200095857519060ff8216820362000955575096620002dd62000296565b80fd5b5080fd5b3d915062000924565b6040513d6000823e3d90fd5b6020813d602011620009b1575b816200098d6020938362000a55565b81010312620009585751906001600160a01b0382168203620009555750386200021b565b3d91506200097e565b6020959195813d602011620009fe575b81620009d96020938362000a55565b81010312620009585751906001600160a01b03821682036200095557509338620001ea565b3d9150620009ca565b6020813d60201162000a47575b8162000a236020938362000a55565b81010312620009585751906001600160a01b038216820362000955575038620001b8565b3d915062000a14565b600080fd5b601f909101601f19168101906001600160401b03821190821017620006bf57604052565b60005b83811062000a8d5750506000910152565b818101518382015260200162000a7c565b81601f8201121562000a505780516001600160401b038111620006bf576040519262000ad5601f8301601f19166020018562000a55565b8184526020828401011162000a505762000af6916020808501910162000a79565b90565b51906001600160a01b038216820362000a5057565b90600182811c9216801562000b40575b602083101462000b2a57565b634e487b7160e01b600052602260045260246000fd5b91607f169162000b1e565b60209060109260145260001960345260446000938480936f095ea7b300000000000000000000000082525af13d15600183511417161562000b8b57603452565b633e3f8f7390526004601cfdfe6040608081526004908136101561001557600080fd5b600091823560e01c9081630100170a1461302057816301e1d11414612a9f57816306fdde0314612f5757816307a2d13a146105785781630826a55d14612ef6578163095ea7b314612e585781630a28a477146105785781630e215e6214612e1d5781631099f88814612de2578163118917e314612d8f57816313f2e64714612b24578163152ca92714612adc57816318160ddd14612a9f57816323b872dd146128e657816324932de3146128c8578163256929621461285f578163313ce5671461280357816332564c15146126135781633644e515146125d857816338d52e0f14612569578163402d267d14610df757816344dba573146122f75781634cdad50614610578578163502d29671461057d578382635217237b1461223057508163521fb1fd14611f0d57816354d1f13d14611ea957816355f7be3a14611e6e5781635aa6e67514611dff5781635f2078eb146119cc5781636ca0b414146119a15781636e553f65146118b757816370a0823114611855578163715018a6146117d55781637b608e38146117665781637bf01826146117055781637d300c0c146116965781637ecebe00146116345781638da5cb5b146115c257816394bf804d1461150357816395d89b41146113e55781639f14cf2514611376578163a9059cbb14611227578163b3d7f6b914610578578163b460af941461117e578163ba08765214610e6b578163c45a015514610dfc578163c63d75b614610df7578163c6e6f59214610578578163caedd78e14610d95578163ce96cb7714610802578163ce99dfe614610d2b578163d0d6091514610cc9578163d29a41aa14610b68578163d505accf14610864578163d905777e14610802578163dc918f0c14610668578163dd62ed3e146105f1578163e01bfb0214610582578163eb5a25ca1461057d578163ef8b30f714610578578163f04e283e146104ae578163f2fde38b146103f957508063f5a76217146103bd578063fa7e38da1461034f5763fee81cf4146102fb57600080fd5b3461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602091610335613431565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602090600b549051908152f35b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761042d613431565b906104366135c6565b8160601b156104a3575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a35580f35b637448fbae8352601cfd5b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610575576104e1613431565b6104e96135c6565b63389a75e1600c528082526020600c20928354421161056a57508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a35580f35b636f5e88188352601cfd5b80fd5b6133f1565b613535565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50503461034b57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209161062c613431565b82610635613454565b9273ffffffffffffffffffffffffffffffffffffffff809316815260098652209116600052825280600020549051908152f35b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5780359133845260086020526106b481852054600b5490613b33565b338552846020526106c88286205485613b26565b11610759578373ffffffffffffffffffffffffffffffffffffffff61070f30827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b9085821061077f575b505050338452600860205261073381852054600b5490613b33565b338552846020526107478286205485613b26565b11610759578361075684613bab565b80f35b517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61078e90600354169186613836565b813b156107fe57829160248392865194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528a8401525af180156107f4576107dc575b80610718565b6107e590613307565b6107f05783386107d6565b8380fd5b83513d84823e3d90fd5b8280fd5b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209161083e613431565b610846613d49565b1561085d576108559150613fde565b905b51908152f35b5090610857565b9050346107fe5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5761089d613431565b6108a5613454565b9260443590606435936084359360ff8516809503610b6457428610610b07576108cc6135fe565b9673ffffffffffffffffffffffffffffffffffffffff80921696878a52602096600a8852858b20998a549a60018c019055865192858a8501957f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c987528c8a870152169b8c606086015289608086015260a085015260c084015260c0835260e0830167ffffffffffffffff9484821086831117610ad957818952845190206101008501927f190100000000000000000000000000000000000000000000000000000000000084526101028601526101228501526042815261016084019481861090861117610aab57848852519020835261018082015260a4356101a082015260c4356101c0909101528880528590899060809060015afa15610aa1578751169081151580610a98575b15610a3c5750907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92593929187526009835280872086600052835281816000205551908152a380f35b606490858451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b508582146109f4565b82513d89823e3d90fd5b6041877f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b6041887f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b50602060649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b8780fd5b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe578035913384526008602052610bb481852054600b5490613b33565b3385526002602052610bc98286205485613b26565b11610759578373ffffffffffffffffffffffffffffffffffffffff610c1030827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b90858210610c58575b5050503384526008602052610c3481852054600b5490613b33565b3385526002602052610c498286205485613b26565b11610759578361075684613c35565b610c6790600354169186613836565b813b156107fe57829160248392865194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528a8401525af180156107f457610cb5575b80610c19565b610cbe90613307565b6107f0578338610caf565b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff610d1b613431565b1681526001845220549051908152f35b919050346107fe57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe576107569133845283602052610d73828520546138bb565b3384526002602052610d87828520546139ce565b338452602052822054613ac9565b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff610de7613431565b1681526002845220549051908152f35b6134eb565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b83833461034b57610e7b36613566565b73ffffffffffffffffffffffffffffffffffffffff939293809116803303611110575b84156110b357610eac613df0565b80865260209560088752610ec38686832054613836565b610ed0600b548092613b33565b8383528289528683205481109081156110a2575b8115611091575b8115611081575b5061105957610f24847f0000000000000000000000000000000000000000000000000000000000000000169188613b33565b98813b156107fe5786517f9dc29fac00000000000000000000000000000000000000000000000000000000815230918101918252602082019a909a52979896979596958291879182908490829060400103925af1801561104d57908794939291611030575b610857955081815260088952868120610fa3868254613836565b90558460075403600755817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8a8951888152a385518481528489820152828416907ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db883392a47f000000000000000000000000000000000000000000000000000000000000000016613872565b919350919361103f8291613307565b610575579183918693610f89565b508551903d90823e3d90fd5b8886517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b905089895286832054118a610ef2565b60028a528784205481109150610eeb565b60018a528784205481109150610ee4565b60648760208651917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600b60248201527f5a45524f5f4153534554530000000000000000000000000000000000000000006044820152fd5b8086526009602052838620336000526020528360002054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611157575b5050610e9e565b61116091613836565b81875260096020528487203360005260205284600020558785611150565b83833461034b5761118e36613566565b73ffffffffffffffffffffffffffffffffffffffff9392938091168033036111b9575b610eac613df0565b8086526009602052838620336000526020528360002054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611200575b50506111b1565b61120991613836565b818752600960205284872033600052602052846000205587856111f9565b82843461057557817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755761125f613431565b602435903383526020946008865261128661127d8487872054613836565b600b5490613b33565b338552848752858520548110908115611365575b8115611354575b8115611344575b5061131d575073ffffffffffffffffffffffffffffffffffffffff8491338552600887528285206112da858254613836565b90551692838152600886522081815401905582519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef843392a35160018152f35b84517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b90508187528585205411876112a8565b6002885286862054811091506112a1565b60018852868620548110915061129a565b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5780359133845260086020526113c281852054600b5490613b33565b338552826020526113d68286205485613b26565b11610759578361075684613cbf565b82843461057557807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508051600091600654611427816132b4565b808452906001908181169081156114bd5750600114611460575b50506114528261145c94038361334a565b519182918261338b565b0390f35b6006600090815294507ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f5b8286106114a5575050509181016020019161145282611441565b8054602087870181019190915290950194810161148b565b61145c965085925060209150927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00611452941682840152151560051b8201019450611441565b9050346107fe57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5760209250359081611542613454565b73ffffffffffffffffffffffffffffffffffffffff611585833033847f0000000000000000000000000000000000000000000000000000000000000000166137dd565b61158f838361415d565b83519183835216907fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7863392a451908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff611686613431565b168152600a845220549051908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe57602092829173ffffffffffffffffffffffffffffffffffffffff611758613431565b168252845220549051908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610575576118076135c6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff6118a7613431565b1681526008845220549051908152f35b9050823461057557827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508035906118f4613454565b9082156119445750918160209373ffffffffffffffffffffffffffffffffffffffff611585833033847f0000000000000000000000000000000000000000000000000000000000000000166137dd565b60649060208551917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600b60248201527f5a45524f5f5348415245530000000000000000000000000000000000000000006044820152fd5b8334610575576107566119c26119c76119b936613477565b949150916138bb565b6139ce565b613ac9565b8391503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57611a06613431565b90611a0f6135c6565b73ffffffffffffffffffffffffffffffffffffffff93848316928315159081611d30575b8660035416918215159788611cc9575b807f000000000000000000000000000000000000000000000000000000000000000016803b15611cbb5785517f521fb1fd0000000000000000000000000000000000000000000000000000000081528981898183865af18015611cbf57908a91611ca7575b5050611ab5903090613b7c565b91817f0000000000000000000000000000000000000000000000000000000000000000169483611ae53088613b7c565b10611c5757827f0000000000000000000000000000000000000000000000000000000000000000169284611b193086613b7c565b10611c7f577f00000000000000000000000000000000000000000000000000000000000000001693611b4b3086613b7c565b10611c5757899a999899611c33575b50611bb4575b8688807fffffffffffffffffffffffff00000000000000000000000000000000000000006003541617600355307f889ad99844dae1714b73856de24207bc777a6e82bc70da589759c5eb669773c98380a380f35b82611bc691611bc682611bcb97614100565b614100565b833b156107fe5782815180937f2bbfaae50000000000000000000000000000000000000000000000000000000082528183885af1908115611c2a5750611c16575b8080808080611b60565b611c1f90613307565b61034b578183611c0c565b513d84823e3d90fd5b80611c41611c5192886140b6565b611c4b81856140b6565b846140b6565b8a611b5a565b8787517fa1fb25d1000000000000000000000000000000000000000000000000000000008152fd5b8888517fa1fb25d1000000000000000000000000000000000000000000000000000000008152fd5b611cb090613307565b611cbb57888b611aa8565b8880fd5b87513d8c823e3d90fd5b833b15610b645784517febb689a10000000000000000000000000000000000000000000000000000000081528881888183895af18015611d2657908991611d12575b5050611a43565b611d1b90613307565b610b6457878a611d0b565b86513d8b823e3d90fd5b82517f41849afc00000000000000000000000000000000000000000000000000000000815285858201526020816024818b7f0000000000000000000000000000000000000000000000000000000000000000165afa908115611df5578791611dc0575b50611a33575050517f97e8471e000000000000000000000000000000000000000000000000000000008152fd5b90506020813d8211611ded575b81611dda6020938361334a565b81010312611de9575188611d93565b8680fd5b3d9150611dcd565b84513d89823e3d90fd5b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761075690356139ce565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b9050346107fe57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe57338352600891602083815281852054611f59600b548092613b33565b90338752868352611f768488205483039187855285892054613b33565b338852878452611f898589205483613b26565b116120f05773ffffffffffffffffffffffffffffffffffffffff9087611fd130847f000000000000000000000000000000000000000000000000000000000000000016613b7c565b8281106121bc575b5050338852868452611ff185892054600b5490613b33565b338952888552612004868a205483613b26565b116121945761201290613bab565b338752600283528387205482039086845261203385892054600b5490613b33565b33895260028552612047868a205484613b26565b1161219457879061207a30827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b90838210612119575b50505033875285835261209c84882054600b5490613b33565b338852600284526120b08589205483613b26565b116120f0576120be90613c35565b338652838252828620549003938152826120de83872054600b5490613b33565b91338752526113d68286205485613b26565b505050517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61212890600354169184613836565b813b156107fe57829160248392895194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528d8401525af1801561218a57612176575b80612083565b61217f90613307565b611de9578638612170565b86513d84823e3d90fd5b8585517f39996567000000000000000000000000000000000000000000000000000000008152fd5b6121cb84600354169184613836565b813b156107fe57829060248a838b5195869485937f4066800c0000000000000000000000000000000000000000000000000000000085528401525af180156122265715611fd95761221b90613307565b610b64578738611fd9565b87513d84823e3d90fd5b809184346122f357827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126122f35773ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001691823b156122ee57839283918351809581937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af1908115611c2a57506122de5750f35b6122e790613307565b6105755780f35b505050fd5b5050fd5b9050346107fe57612307366134b3565b939190503385526020906008825261232583872054600b5490613b33565b3387528683526123388488205483613b26565b116124d65773ffffffffffffffffffffffffffffffffffffffff908661238030847f000000000000000000000000000000000000000000000000000000000000000016613b7c565b8281106124fe575b5050338752600883526123a184882054600b5490613b33565b3388528784526123b48589205483613b26565b116120f0576123c290613bab565b338652600882526123d983872054600b5490613b33565b338752600283526123ed8488205487613b26565b116124d657859061242030827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b90868210612457575b50505033855260088152600261244583872054600b5490613b33565b9133875252610c498286205485613b26565b61246690600354169187613836565b813b156107fe57829160248392875194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528b8401525af180156124cc576124b4575b80612429565b6124bd90613307565b6124c85784386124ae565b8480fd5b84513d84823e3d90fd5b5050517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61250d84600354169184613836565b813b156107fe57829160248392895194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528d8401525af1801561218a57156123885761255e90613307565b611de9578638612388565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020906108556135fe565b8391503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578035926126506135c6565b600b5491828511156127dc576007549473ffffffffffffffffffffffffffffffffffffffff93847f000000000000000000000000000000000000000000000000000000000000000016946126ad886126a83089613b7c565b613f9b565b83116127b457966126e96126ef92889985600b557f00000000000000000000000000000000000000000000000000000000000000001694613836565b90613b33565b90803b156127b05783517f40c10f19000000000000000000000000000000000000000000000000000000008152308482019081526020810193909352918691839182908490829060400103925af180156127a657908591612792575b5050823b156122ee57839283918351809581937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af1908115611c2a57506122de5750f35b61279b90613307565b6122ee57838661274b565b83513d87823e3d90fd5b8580fd5b8385517f40db7f87000000000000000000000000000000000000000000000000000000008152fd5b90517f6a43f8d1000000000000000000000000000000000000000000000000000000008152fd5b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b8334610575576107566119c26128dd366134b3565b929190506138bb565b8284346105755760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755761291f613431565b612927613454565b906044359073ffffffffffffffffffffffffffffffffffffffff809116928385526020966008885261295f61127d8589892054613836565b858752868952878720548110908115612a8e575b8115612a7d575b8115612a6d575b50612a465750918587927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef94868852600985528288203360005285528260002054847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612a21575b505086885260088552828820612a02858254613836565b9055169586815260088452208181540190558551908152a35160018152f35b612a2a91613836565b8789526009865283892033600052865283600020558a846129eb565b86517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b9050818952878720541189612981565b60028a52888820548110915061297a565b60018a528888205481109150612973565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020906007549051908152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576107569035612b1b816138bb565b6119c7816139ce565b9050346107fe57612b3436613477565b9492915033865260209160088352612b5284882054600b5490613b33565b338852878452612b658589205483613b26565b116120f05773ffffffffffffffffffffffffffffffffffffffff9087612bad30847f000000000000000000000000000000000000000000000000000000000000000016613b7c565b828110612d25575b505033885260088452612bce85892054600b5490613b33565b338952888552612be1868a205483613b26565b1161219457612bef90613bab565b33875260088352612c0684882054600b5490613b33565b33885260028452612c1a8589205484613b26565b116120f0578690612c4d30827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b90838210612caa575b50505033865260088252612c7083872054600b5490613b33565b33875260028352612c848488205483613b26565b116124d657612c9290613c35565b33855260088152826120de83872054600b5490613b33565b612cb990600354169184613836565b813b156107fe57829160248392885194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528c8401525af18015612d1b57612d07575b80612c56565b612d1090613307565b6127b0578538612d01565b85513d84823e3d90fd5b612d3484600354169184613836565b813b156107fe57829060248a838b5195869485937f4066800c0000000000000000000000000000000000000000000000000000000085528401525af180156122265715612bb557612d8490613307565b610b64578738612bb5565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209073ffffffffffffffffffffffffffffffffffffffff600354169051908152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576107569035613ac9565b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761075690356138bb565b50503461034b57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602091612e93613431565b9073ffffffffffffffffffffffffffffffffffffffff8360243592338152600987522092169182600052845280836000205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff612f48613431565b16815280845220549051908152f35b82843461057557807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508051600091600554612f99816132b4565b808452906001908181169081156114bd5750600114612fc35750506114528261145c94038361334a565b6005600090815294507f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05b828610613008575050509181016020019161145282611441565b80546020878701810191909152909501948101612fee565b9050346107fe57602091827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107f0578135923385526008815261306d82862054600b5490613b33565b3386528582526130808387205486613b26565b116131ac5773ffffffffffffffffffffffffffffffffffffffff856130c730837f000000000000000000000000000000000000000000000000000000000000000016613b7c565b868110613244575b5050338652600882526130e883872054600b5490613b33565b3387528683526130fb8488205487613b26565b116124d65761310985613bab565b3386526008825261312083872054600b5490613b33565b338752600283526131348488205487613b26565b116124d657859061316730827f000000000000000000000000000000000000000000000000000000000000000016613b7c565b908682106131d3575b5050503385526008815261318a82862054600b5490613b33565b3386526002825261319e8387205486613b26565b116131ac57612c9284613c35565b50517f39996567000000000000000000000000000000000000000000000000000000008152fd5b6131e290600354169187613836565b813b156107fe57829160248392875194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528b8401525af180156124cc57613230575b80613170565b61323990613307565b6124c857843861322a565b61325383600354169188613836565b90803b156107fe5760248392875194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528b8401525af18015611df5576132a1575b86906130cf565b6132ad90969196613307565b943861329a565b90600182811c921680156132fd575b60208310146132ce57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f16916132c3565b67ffffffffffffffff811161331b57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761331b57604052565b60208082528251818301819052939260005b8581106133dd575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b81810183015184820160400152820161339d565b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c5760206040516004358152f35b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361342c57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361342c57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc608091011261342c5760043590602435906044359060643590565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc606091011261342c57600435906024359060443590565b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c57613522613431565b50602061352d613f40565b604051908152f35b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c57005b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc606091011261342c576004359073ffffffffffffffffffffffffffffffffffffffff90602435828116810361342c5791604435908116810361342c5790565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739275433036135f057565b6382b429006000526004601cfd5b6000467f00000000000000000000000000000000000000000000000000000000000000000361364c57507f000000000000000000000000000000000000000000000000000000000000000090565b604051600554829161365d826132b4565b80825281602094858201946001908782821691826000146137a1575050600114613747575b5061368f9250038261334a565b51902091604051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f845260408301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c082019082821067ffffffffffffffff83111761371a575060405251902090565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526041600452fd5b6005885286915087907f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05b85831061378957505061368f935082010138613682565b80548388018501528694508893909201918101613772565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016885261368f95151560051b85010192503891506136829050565b601c600060649281946020966040519860605260405260601b602c526f23b872dd000000000000000000000000600c525af13d15600160005114171615613828576000606052604052565b637939f4246000526004601cfd5b9190820391821161384357565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60109260209260145260345260446000938480936fa9059cbb00000000000000000000000082525af13d1560018351141716156138ae57603452565b6390b8ec1890526004601cfd5b6138c490613943565b73ffffffffffffffffffffffffffffffffffffffff60035416806138e6575b50565b803b1561342c57600080916004604051809481937fcd2137850000000000000000000000000000000000000000000000000000000083525af180156139375761392c5750565b61393590613307565b565b6040513d6000823e3d90fd5b80156138e3573360005260006020526040600020613962828254613836565b90556139a681303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166137dd565b337f1c4d85c1e7d2103e7da3a4aee8d1d10c48c372a6eee0d52d9197e23a2aea63cd600080a3565b6139d790613a3e565b73ffffffffffffffffffffffffffffffffffffffff60035416806139f85750565b803b1561342c57600080916004604051809481937fc255fb1b0000000000000000000000000000000000000000000000000000000083525af180156139375761392c5750565b80156138e3573360005260026020526040600020613a5d828254613836565b9055613aa181303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166137dd565b337f3b7581e5f2644158be06ded43f7757416c423fe79298c94db0ef737427dae656600080a3565b613935903360005260046020526040600020613ae6828254613836565b9055303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166137dd565b9190820180921161384357565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482118102613b6e57670de0b6b3a764000091020490565b63bac65e5b6000526004601cfd5b602460106020939284936014526f70a082310000000000000000000000006000525afa601f3d11166020510290565b80156138e3573360005260006020526040600020613bca828254613b26565b9055613c0d813373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016613872565b337f24ab920816cdd8b858bbc7ab3116e7b4717a8f407c9fe1278ea6bf0e85ebe03b600080a3565b80156138e3573360005260026020526040600020613c54828254613b26565b9055613c97813373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016613872565b337f990e925c0c406b5aceff4657f74f5b071516036c6c2c46455080a33125e7e6b3600080a3565b80156138e3573360005260046020526040600020613cde828254613b26565b9055613d21813373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016613872565b337f7ff1bba75e07cab48d0e7ad8ce556a153ac5751934be86ed272c67b85ff73e47600080a3565b600c54428160801c1015613dea576fffffffffffffffffffffffffffffffff613dbc426099600262023ab1620afa6c6201518060039504010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c600982110290030190565b911614613de557613de142600260016007600362015180809504948502940106011491565b5090565b600090565b50600190565b600c54428160801c10156138e3576fffffffffffffffffffffffffffffffff80613e6c613e67426099600262023ab1620afa6c6201518060039504010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c600982110290030190565b613f04565b1691168114613eda57613e9342600260016007600362015180809504948502940106011491565b9015613eda5762015180810180911161384357613ed07fffffffffffffffffffffffffffffffff0000000000000000000000000000000091613f04565b60801b1617600c55565b60046040517fb9c67ec5000000000000000000000000000000000000000000000000000000008152fd5b700100000000000000000000000000000000811015613f32576fffffffffffffffffffffffffffffffff1690565b6335278d126000526004601cfd5b613f98613f8f613f863073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016613b7c565b600b5490613f9b565b60075490613836565b90565b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111820215830215613fd057020490565b637c5f487d6000526004601cfd5b73ffffffffffffffffffffffffffffffffffffffff600091168152806020526040812054806140ae575b5060016020526040600020548181116140a6575b50600260205260406000205481811161409e575b506004602052604060002054818111614096575b506008602052604060002054600b54670de0b6b3a76400007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218411810215820215613fd057613f98930290808204910615150190613836565b905038614044565b905038614030565b90503861401c565b905038614008565b6020906010926014526044600093848093816034526f095ea7b300000000000000000000000082525af13d1560018351141716156140f357603452565b633e3f8f7390526004601cfd5b6020906010926014527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60345260446000938480936f095ea7b300000000000000000000000082525af13d1560018351141716156140f357603452565b90614166613f40565b9060009181116142f45773ffffffffffffffffffffffffffffffffffffffff807f00000000000000000000000000000000000000000000000000000000000000001693843b156107f057838060409660048851809481937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af180156142d7576142e1575b50817f00000000000000000000000000000000000000000000000000000000000000001661421d600b5485613b33565b813b156127b05786517f40c10f1900000000000000000000000000000000000000000000000000000000815230600482015260248101919091529085908290604490829084905af180156142d7579085916142c3575b5050916020917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef936142a783600754613b26565b60075516948585526008835280852082815401905551908152a3565b6142cc90613307565b6107f0578338614273565b86513d87823e3d90fd5b6142ed90949194613307565b92386141ed565b60046040517fe4bac01b000000000000000000000000000000000000000000000000000000008152fdfea164736f6c6343000813000a61010060409080825234620004a65780620036828038038091620000248285620004c7565b8339602092839181010312620004a657516001600160a01b0381169190829003620004a6578251926200005784620004ab565b60118452704275726e744865726d657320566f74657360781b828501528051916200008283620004ab565b6009835268312422a926a2a996ab60b91b8184015284516001600160401b0395909390868511620004905760009480620000bd8754620004eb565b92601f938481116200043f575b508590848311600114620003d7578892620003cb575b50508160011b916000199060031b1c19161785555b815190878211620003b7578190600193620001118554620004eb565b82811162000362575b5085918311600114620002fe578792620002f2575b5050600019600383901b1c191690821b1781555b60126080524660a052825184549181866200015e85620004eb565b9283835286830195878282169182600014620002d257505060011462000292575b506200018e92500382620004c7565b519020918051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452818301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c0820195828710908711176200027e5785905251902060c05281638b78c6d819557f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a33360e052613159908162000529823960805181611817015260a05181611ef7015260c05181611f1e015260e05181818161029c01528181610def015261172d0152f35b634e487b7160e01b84526041600452602484fd5b8691508880528189209089915b858310620002b95750506200018e9350820101386200017f565b805483880185015286945088939092019181016200029f565b60ff191688526200018e95151560051b85010192503891506200017f9050565b0151905038806200012f565b8488528588208594509190601f198416895b888282106200034b575050841162000331575b505050811b01815562000143565b015160001960f88460031b161c1916905538808062000323565b838501518655889790950194938401930162000310565b909192508488528588208380860160051c820192888710620003ad575b91869588929594930160051c01915b8281106200039e5750506200011a565b8a81558695508791016200038e565b925081926200037f565b634e487b7160e01b86526041600452602486fd5b015190503880620000e0565b8880528689209250601f198416895b88828210620004285750509084600195949392106200040e575b505050811b018555620000f5565b015160001960f88460031b161c1916905538808062000400565b6001859682939686015181550195019301620003e6565b9091508780528588208480850160051c82019288861062000486575b9085949392910160051c01905b818110620004775750620000ca565b89815584935060010162000468565b925081926200045b565b634e487b7160e01b600052604160045260246000fd5b600080fd5b604081019081106001600160401b038211176200049057604052565b601f909101601f19168101906001600160401b038211908210176200049057604052565b90600182811c921680156200051d575b60208310146200050757565b634e487b7160e01b600052602260045260246000fd5b91607f1691620004fb56fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314611c3a5750816307f6365814611bc6578163095ea7b314611b2b57816318160ddd14611aee578163189aa7bf14611a8c578163239cbb811461198a57816323b872dd1461193a57816325692962146118d15781632641fe2114611878578163289c26f71461183b578163313ce567146117df5781633644e515146117a457816340c10f19146116d75781634d99dd161461169357816354d1f13d1461162f578163587cde1e1461155c5781635c19a95c1461151b5781636b578185146114b35781636fcfff451461144357816370a08231146113e1578163715018a6146113625781637757dc5814611300578163782d6fe1146111325781637c7b78e1146110ee5781637ecebe001461108c5781638da5cb5b1461101a578163951e26ec14610fb357816395d89b4114610e985781639ab24eb014610cf45781639dc29fac14610d99578163a9059cbb14610d3f578163af959b0514610cf4578163c3cda52014610ae7578163d505accf146107da578163dd62ed3e14610762578163e7a324dc14610709578163f04e283e14610643578163f1127ed814610571578163f14b34a61461052a578163f2fde38b1461047657508063f870efb4146102c0578063fa7e38da146102525763fee81cf4146101fe57600080fd5b3461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602091610238611e5f565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b503461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576102f8611e5f565b60243590604435926064359360208661030f611eaa565b61031b8842111561305c565b610323611ef2565b6103d06103fc8751868101907f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d4825273ffffffffffffffffffffffffffffffffffffffff9c8d8c168b8301528c606083015289608083015260a082015260a0815261038d81611d9c565b5190208851928391888301958690916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611db8565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa1561046c5761045790865195861692838852600560205287209081549161044f83612df1565b9055146130e7565b1561046857610465926123ab565b80f35b8380fd5b81513d87823e3d90fd5b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576104aa611e5f565b906104b3611eba565b8160601b1561051f575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b637448fbae8352601cfd5b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e57610465610564611e5f565b60243590336123ab565b80fd5b82843461056e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e576105a9611e5f565b63ffffffff9160243591838316830361024e5791848273ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff95610616956020855161060381611d51565b8281520152168152600660205220612095565b5091602084519361062685611d51565b5491821693848152019060201c8152835192835251166020820152f35b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e57610676611e5f565b61067e611eba565b63389a75e1600c528082526020600c2092835442116106fe57508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b636f5e88188352601cfd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf8152f35b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657602092829161079f611e5f565b6107a7611e87565b9173ffffffffffffffffffffffffffffffffffffffff8092168452865283832091168252845220549051908152f35b8280fd5b83833461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57610813611e5f565b61081b611e87565b6044356064359361082a611eaa565b93428610610a8a5761083a611ef2565b9473ffffffffffffffffffffffffffffffffffffffff8092169586895260209560058752848a209889549960018b01905585519085898301937f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c985528b89850152169a8b606084015288608084015260a083015260c082015260c0815260e081019181831067ffffffffffffffff841117610a5e579161096e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff208d95936109418c9896858c52825190206101008301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00810184520182611db8565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa15610a54578651169687151580610a4b575b156109f05786977f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259596975283528087208688528352818188205551908152a380f35b8360649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b508488146109ad565b81513d88823e3d90fd5b60248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b60648860208451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b919050346107d65760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657610b21611e5f565b90602435604435936064359060ff82168203610cf057610b438642111561305c565b610b4b611ef2565b908451602081017fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf815273ffffffffffffffffffffffffffffffffffffffff988989168884015286606084015260808301526080825260a082019282841067ffffffffffffffff851117610cc4575091610c597fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff608b9593610c2c60209896858c528251902060c08301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40810184520182611db8565b519020855190815260ff919091166020820152608435604082015260a435606082015281805260809060015afa15610cba57610cac90855194851692838752600560205286209081549161044f83612df1565b156107d6576104659161225e565b81513d86823e3d90fd5b8a60416024927f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8680fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d38610d33611e5f565b612177565b9051908152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d90610d7d611e5f565b60243590610d8b8233612e1e565b612c81565b90519015158152f35b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657610dd1611e5f565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f0000000000000000000000000000000000000000000000000000000000000000163303610e715750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9282610e48889796602095612e1e565b169384865260038352808620610e5f838254612100565b9055816002540360025551908152a380f35b84517f4d316367000000000000000000000000000000000000000000000000000000008152fd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57805190826001805491610eda83611cfe565b80865292828116908115610f6d5750600114610f11575b505050610f0382610f0d940383611db8565b5191829182611df9565b0390f35b94508085527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828610610f5557505050610f03826020610f0d9582010194610ef1565b80546020878701810191909152909501948101610f38565b610f0d975086935060209250610f039491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b82010194610ef1565b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760ff8160209373ffffffffffffffffffffffffffffffffffffffff611007611e5f565b1681526008855220541690519015158152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff6110de611e5f565b1681526005845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d3861112d611e5f565b61213c565b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d65761116a611e5f565b906024803592438410156112d85773ffffffffffffffffffffffffffffffffffffffff16855260066020528385209182549386905b858210611267575050836111da57505050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff602092915b5191168152f35b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84969293940195861161123e575050506112346020937bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92612095565b5054831c916111d3565b6011907f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b909460019061127c818818831c828916612251565b918363ffffffff61128d858a612095565b50541611156112a0575050945b9061119f565b90965081018091111561129a5783886011857f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8285517f3f8d3c1c000000000000000000000000000000000000000000000000000000008152fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611352611e5f565b168152600b845220549051908152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e57611394611eba565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611433611e5f565b1681526003845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5763ffffffff6114ab8260209473ffffffffffffffffffffffffffffffffffffffff61149d611e5f565b1681526006865220546120dc565b915191168152f35b83903461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57356114ed611eba565b600754816007557f9960c7dba5c668f2dcce571ead061f33d2e4174c892c8eb86b4b34529bb7271e8380a380f35b833461056e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e57610465611556611e5f565b3361225e565b50503461024e576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d65773ffffffffffffffffffffffffffffffffffffffff92836115ad611e5f565b168152600b825282812091835190818185549182815201908195855282852090855b81811061161b57505050826115e5910383611db8565b8451948186019282875251809352850193925b8281106116055785850386f35b83518716855293810193928101926001016115f8565b8254845292840192600192830192016115cf565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e5763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e576104656116cd611e5f565b6024359033612899565b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d65761170f611e5f565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f0000000000000000000000000000000000000000000000000000000000000000163303610e715750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef926020926117878896600254612251565b60025516948585526003835280852082815401905551908152a380f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d38611ef2565b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906007549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d48152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261056e5763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b50503461024e5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d90611979611e5f565b611981611e87565b60443591612cf9565b919050346107d657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d6576119c3611e5f565b6024359283151593848103611a88576119da611eba565b80611a7f575b611a58575073ffffffffffffffffffffffffffffffffffffffff1690818452600860205283207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541660ff84161790557ff250dd6faf51f88e0d298800d22453f75bd1af207056ddd9a4fb55f1408376fb8380a380f35b82517f270de3fd000000000000000000000000000000000000000000000000000000008152fd5b50813b156119e0565b8580fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611ade611e5f565b168152600a845220549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906002549051908152f35b9050346107d657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657602092611b66611e5f565b9183602435928392338252875273ffffffffffffffffffffffffffffffffffffffff8282209516948582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5780602092611c02611e5f565b611c0a611e87565b73ffffffffffffffffffffffffffffffffffffffff91821683526009865283832091168252845220549051908152f35b849084346107d657827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107d657828054611c7781611cfe565b80855291600191808316908115610f6d5750600114611ca257505050610f0382610f0d940383611db8565b80809650527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828610611ce657505050610f03826020610f0d9582010194610ef1565b80546020878701810191909152909501948101611cc9565b90600182811c92168015611d47575b6020831014611d1857565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611d0d565b6040810190811067ffffffffffffffff821117611d6d57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60c0810190811067ffffffffffffffff821117611d6d57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117611d6d57604052565b60208082528251818301819052939260005b858110611e4b575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b818101830151848201604001528201611e0b565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203611e8257565b600080fd5b6024359073ffffffffffffffffffffffffffffffffffffffff82168203611e8257565b6084359060ff82168203611e8257565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927543303611ee457565b6382b429006000526004601cfd5b6000467f000000000000000000000000000000000000000000000000000000000000000003611f4057507f000000000000000000000000000000000000000000000000000000000000000090565b604051815491908181611f5285611cfe565b9182825260209586830195600191888382169182600014612056575050600114611ffd575b5050611f8592500382611db8565b51902090604051908101917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f835260408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a08152611ff781611d9c565b51902090565b908792508180527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b85831061203e575050611f8593508201013880611f77565b80548388018501528694508893909201918101612026565b91509350611f859592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009150168652151560051b8201013880611f77565b80548210156120ad5760005260206000200190600090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6401000000008110156120f25763ffffffff1690565b6335278d126000526004601cfd5b9190820391821161210d57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff166000526003602052612174604060002054600a60205260406000205490612100565b90565b73ffffffffffffffffffffffffffffffffffffffff16600090815260066020526040812080549190826121c857507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff91501690565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830192831161222457507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9161221b91612095565b505460201c1690565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526011600452fd5b9190820180921161210d57565b9073ffffffffffffffffffffffffffffffffffffffff9081831690600092828452600b6020526040842054600181116123815760018591146122e9575b82827f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f941696876122cf575b5050169280a4565b6122e2916122dc8261213c565b916123ab565b38806122c7565b5090828452600b6020526040842080541561235457907f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f92918552816020862054169084865260096020526040862082875260205261234d60408720548389612899565b925061229b565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526032600452fd5b60046040517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b919073ffffffffffffffffffffffffffffffffffffffff80911692831580156127c7575b80156127bf575b61238157169160008381526020600b815283836040966123f8828987206127d8565b612767575b80855260098452878520828652845287852061241a848254612251565b9055808552600a8452878520612431848254612251565b90557f96eafeca8c3c21ab2fa4a636b93ba20c9e22e3d222d92c6530fedc29a53671ee8580a48282526006815284822090815490811596876000146126fc5784905b61249d7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8093169889612251565b98158061268b575b15612578575050506124b686612c3b565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161254b5791612511612547927fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7249594612095565b509063ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000083549260201b169116179055565b80a4565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b91939092612585436120dc565b91846125908b612c3b565b94519661259c88611d51565b63ffffffff80951688528701941684526801000000000000000082101561265e57906125cd91600182018155612095565b9490946126325751915163ffffffff9190921616911660201b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000161790557fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72490612547565b6024867f4e487b7100000000000000000000000000000000000000000000000000000000815280600452fd5b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84018481116126cf576126c563ffffffff9187612095565b50541643146124a5565b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830183811161273a576127309085612095565b5054821c90612473565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b808552600b84528785205460075410156123fd579150506008825260ff868420541615612796578385916123fd565b600486517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b5082156123d6565b50826127d28261213c565b106123cf565b9190600183016000908282528060205260408220541560001461289357845494680100000000000000008610156128665783612856612821886001604098999a01855584612095565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b9055549382526020522055600190565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b50925050565b929190816128a682612177565b1061296b5773ffffffffffffffffffffffffffffffffffffffff809416936000948086526020600981526040908188209385169384895281526128ec86838a2054612100565b8015612950575b978392600a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa9797739361294e9a9b8a9897855260098252838520888652825283852055858452528120612945868254612100565b905580a4612ac1565b565b838952600b825261296385848b20612995565b6128f3578880fd5b60046040517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b90600182019060009281845282602052604084205490811515600014612aba577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff918281018181116126cf57825490848201918211612a8d57808203612a58575b50505080548015612a2b57820191612a0e8383612095565b909182549160031b1b191690555582526020526040812055600190565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b612a78612a686128219386612095565b90549060031b1c92839286612095565b905586528460205260408620553880806129f6565b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b5050505090565b73ffffffffffffffffffffffffffffffffffffffff1660008181526006602052604081208054801594939291908515612bfd57825b612b207bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8092169687612100565b961580612b8c575b15612b3757506124b686612c3b565b90612b41436120dc565b612b4a88612c3b565b9160405194612b5886611d51565b63ffffffff80931686528460208701941684526801000000000000000082101561265e57906125cd91600182018155612095565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201828111612bd057612bc663ffffffff9185612095565b5054164314612b28565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811161254b57612c319083612095565b505460201c612af6565b7c01000000000000000000000000000000000000000000000000000000008110156120f2577bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff903360005260036020526040600020612cb0848254612100565b9055169081600052600360205260406000208181540190556040519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60203392a3600190565b9190612d058284612e1e565b73ffffffffffffffffffffffffffffffffffffffff80931691338314612de557907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91600094848652602092600484526040918291828920338a52865282892054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612dc2575b505087895260038652828920612da6868254612100565b90551696878152600385522082815401905551908152a3600190565b612dcb91612100565b888a5260048752838a20338b528752838a20553885612d8f565b91506121749250612c81565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461210d5760010190565b91612e288361213c565b91808310156130565791909260009073ffffffffffffffffffffffffffffffffffffffff809116808352602095600b875260409283852091845190818a85549182815201908589528b892090895b8d828210613042575050505082612e8e910383611db8565b81519887985b8a8a1080613030575b15612fc85783518a10156120ad57848c8b60051b860101511699876000528c60098091528d8a600020908d600052528d8a600020549c8d91612ede82612177565b838110908418028084189303612f14575b50505050612f069192939495969798999a50612df1565b989796959493929190612e94565b9091612f2a8180959798999a9b9c9d9e9f612251565b9e03612f9e5750612f3b818b612995565b15611e82578e612f06948c60005281528c60002090826000525260008c8120555b612f668282612ac1565b8a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa979773600080a48a99989796959493929138808f612eef565b612f06948c60005281528c6000209082600052528b600020612fc1838254612100565b9055612f5c565b92509498509895949650612fdf9250849150612251565b106130075790612ffc600a92856000528383528460002054612100565b936000525260002055565b600483517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b508161303c8a83612251565b10612e9d565b835485529093019260019283019201612e76565b50915050565b1561306357565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332304d756c7469566f7465733a207369676e617475726520657870697260448201527f65640000000000000000000000000000000000000000000000000000000000006064820152fd5b156130ee57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f45524332304d756c7469566f7465733a20696e76616c6964206e6f6e636500006044820152fdfea164736f6c6343000813000a036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0f652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f000000000000000000000000c2080ca61d32663fb698321def53c038efc729640000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c466af7ff16ef0f1a7fa4e23e095e47a4058d7910000000000000000000000000000000000000000000000000000000000000009566f7465204d61696100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005764d414941000000000000000000000000000000000000000000000000000000
Deployed Bytecode
0x6040608081526004908136101561001557600080fd5b600091823560e01c9081630100170a1461302057816301e1d11414612a9f57816306fdde0314612f5757816307a2d13a146105785781630826a55d14612ef6578163095ea7b314612e585781630a28a477146105785781630e215e6214612e1d5781631099f88814612de2578163118917e314612d8f57816313f2e64714612b24578163152ca92714612adc57816318160ddd14612a9f57816323b872dd146128e657816324932de3146128c8578163256929621461285f578163313ce5671461280357816332564c15146126135781633644e515146125d857816338d52e0f14612569578163402d267d14610df757816344dba573146122f75781634cdad50614610578578163502d29671461057d578382635217237b1461223057508163521fb1fd14611f0d57816354d1f13d14611ea957816355f7be3a14611e6e5781635aa6e67514611dff5781635f2078eb146119cc5781636ca0b414146119a15781636e553f65146118b757816370a0823114611855578163715018a6146117d55781637b608e38146117665781637bf01826146117055781637d300c0c146116965781637ecebe00146116345781638da5cb5b146115c257816394bf804d1461150357816395d89b41146113e55781639f14cf2514611376578163a9059cbb14611227578163b3d7f6b914610578578163b460af941461117e578163ba08765214610e6b578163c45a015514610dfc578163c63d75b614610df7578163c6e6f59214610578578163caedd78e14610d95578163ce96cb7714610802578163ce99dfe614610d2b578163d0d6091514610cc9578163d29a41aa14610b68578163d505accf14610864578163d905777e14610802578163dc918f0c14610668578163dd62ed3e146105f1578163e01bfb0214610582578163eb5a25ca1461057d578163ef8b30f714610578578163f04e283e146104ae578163f2fde38b146103f957508063f5a76217146103bd578063fa7e38da1461034f5763fee81cf4146102fb57600080fd5b3461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602091610335613431565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b3168152f35b503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602090600b549051908152f35b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761042d613431565b906104366135c6565b8160601b156104a3575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a35580f35b637448fbae8352601cfd5b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610575576104e1613431565b6104e96135c6565b63389a75e1600c528082526020600c20928354421161056a57508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a35580f35b636f5e88188352601cfd5b80fd5b6133f1565b613535565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f168152f35b50503461034b57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209161062c613431565b82610635613454565b9273ffffffffffffffffffffffffffffffffffffffff809316815260098652209116600052825280600020549051908152f35b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5780359133845260086020526106b481852054600b5490613b33565b338552846020526106c88286205485613b26565b11610759578373ffffffffffffffffffffffffffffffffffffffff61070f30827f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b9085821061077f575b505050338452600860205261073381852054600b5490613b33565b338552846020526107478286205485613b26565b11610759578361075684613bab565b80f35b517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61078e90600354169186613836565b813b156107fe57829160248392865194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528a8401525af180156107f4576107dc575b80610718565b6107e590613307565b6107f05783386107d6565b8380fd5b83513d84823e3d90fd5b8280fd5b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209161083e613431565b610846613d49565b1561085d576108559150613fde565b905b51908152f35b5090610857565b9050346107fe5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5761089d613431565b6108a5613454565b9260443590606435936084359360ff8516809503610b6457428610610b07576108cc6135fe565b9673ffffffffffffffffffffffffffffffffffffffff80921696878a52602096600a8852858b20998a549a60018c019055865192858a8501957f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c987528c8a870152169b8c606086015289608086015260a085015260c084015260c0835260e0830167ffffffffffffffff9484821086831117610ad957818952845190206101008501927f190100000000000000000000000000000000000000000000000000000000000084526101028601526101228501526042815261016084019481861090861117610aab57848852519020835261018082015260a4356101a082015260c4356101c0909101528880528590899060809060015afa15610aa1578751169081151580610a98575b15610a3c5750907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92593929187526009835280872086600052835281816000205551908152a380f35b606490858451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b508582146109f4565b82513d89823e3d90fd5b6041877f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b6041887f4e487b71000000000000000000000000000000000000000000000000000000006000525260246000fd5b50602060649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b8780fd5b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe578035913384526008602052610bb481852054600b5490613b33565b3385526002602052610bc98286205485613b26565b11610759578373ffffffffffffffffffffffffffffffffffffffff610c1030827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b90858210610c58575b5050503384526008602052610c3481852054600b5490613b33565b3385526002602052610c498286205485613b26565b11610759578361075684613c35565b610c6790600354169186613836565b813b156107fe57829160248392865194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528a8401525af180156107f457610cb5575b80610c19565b610cbe90613307565b6107f0578338610caf565b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff610d1b613431565b1681526001845220549051908152f35b919050346107fe57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe576107569133845283602052610d73828520546138bb565b3384526002602052610d87828520546139ce565b338452602052822054613ac9565b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff610de7613431565b1681526002845220549051908152f35b6134eb565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000c2080ca61d32663fb698321def53c038efc72964168152f35b83833461034b57610e7b36613566565b73ffffffffffffffffffffffffffffffffffffffff939293809116803303611110575b84156110b357610eac613df0565b80865260209560088752610ec38686832054613836565b610ed0600b548092613b33565b8383528289528683205481109081156110a2575b8115611091575b8115611081575b5061105957610f24847f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f169188613b33565b98813b156107fe5786517f9dc29fac00000000000000000000000000000000000000000000000000000000815230918101918252602082019a909a52979896979596958291879182908490829060400103925af1801561104d57908794939291611030575b610857955081815260088952868120610fa3868254613836565b90558460075403600755817fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8a8951888152a385518481528489820152828416907ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db883392a47f0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e16613872565b919350919361103f8291613307565b610575579183918693610f89565b508551903d90823e3d90fd5b8886517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b905089895286832054118a610ef2565b60028a528784205481109150610eeb565b60018a528784205481109150610ee4565b60648760208651917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600b60248201527f5a45524f5f4153534554530000000000000000000000000000000000000000006044820152fd5b8086526009602052838620336000526020528360002054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611157575b5050610e9e565b61116091613836565b81875260096020528487203360005260205284600020558785611150565b83833461034b5761118e36613566565b73ffffffffffffffffffffffffffffffffffffffff9392938091168033036111b9575b610eac613df0565b8086526009602052838620336000526020528360002054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611200575b50506111b1565b61120991613836565b818752600960205284872033600052602052846000205587856111f9565b82843461057557817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755761125f613431565b602435903383526020946008865261128661127d8487872054613836565b600b5490613b33565b338552848752858520548110908115611365575b8115611354575b8115611344575b5061131d575073ffffffffffffffffffffffffffffffffffffffff8491338552600887528285206112da858254613836565b90551692838152600886522081815401905582519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef843392a35160018152f35b84517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b90508187528585205411876112a8565b6002885286862054811091506112a1565b60018852868620548110915061129a565b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5780359133845260086020526113c281852054600b5490613b33565b338552826020526113d68286205485613b26565b11610759578361075684613cbf565b82843461057557807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508051600091600654611427816132b4565b808452906001908181169081156114bd5750600114611460575b50506114528261145c94038361334a565b519182918261338b565b0390f35b6006600090815294507ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f5b8286106114a5575050509181016020019161145282611441565b8054602087870181019190915290950194810161148b565b61145c965085925060209150927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00611452941682840152151560051b8201019450611441565b9050346107fe57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe5760209250359081611542613454565b73ffffffffffffffffffffffffffffffffffffffff611585833033847f0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e166137dd565b61158f838361415d565b83519183835216907fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7863392a451908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff611686613431565b168152600a845220549051908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb168152f35b9050346107fe5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe57602092829173ffffffffffffffffffffffffffffffffffffffff611758613431565b168252845220549051908152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000008265aa10ee11f57ed01bf8ece5ba57ef75ed36dc168152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610575576118076135c6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff6118a7613431565b1681526008845220549051908152f35b9050823461057557827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508035906118f4613454565b9082156119445750918160209373ffffffffffffffffffffffffffffffffffffffff611585833033847f0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e166137dd565b60649060208551917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600b60248201527f5a45524f5f5348415245530000000000000000000000000000000000000000006044820152fd5b8334610575576107566119c26119c76119b936613477565b949150916138bb565b6139ce565b613ac9565b8391503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57611a06613431565b90611a0f6135c6565b73ffffffffffffffffffffffffffffffffffffffff93848316928315159081611d30575b8660035416918215159788611cc9575b807f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b316803b15611cbb5785517f521fb1fd0000000000000000000000000000000000000000000000000000000081528981898183865af18015611cbf57908a91611ca7575b5050611ab5903090613b7c565b91817f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb169483611ae53088613b7c565b10611c5757827f0000000000000000000000008265aa10ee11f57ed01bf8ece5ba57ef75ed36dc169284611b193086613b7c565b10611c7f577f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d1693611b4b3086613b7c565b10611c5757899a999899611c33575b50611bb4575b8688807fffffffffffffffffffffffff00000000000000000000000000000000000000006003541617600355307f889ad99844dae1714b73856de24207bc777a6e82bc70da589759c5eb669773c98380a380f35b82611bc691611bc682611bcb97614100565b614100565b833b156107fe5782815180937f2bbfaae50000000000000000000000000000000000000000000000000000000082528183885af1908115611c2a5750611c16575b8080808080611b60565b611c1f90613307565b61034b578183611c0c565b513d84823e3d90fd5b80611c41611c5192886140b6565b611c4b81856140b6565b846140b6565b8a611b5a565b8787517fa1fb25d1000000000000000000000000000000000000000000000000000000008152fd5b8888517fa1fb25d1000000000000000000000000000000000000000000000000000000008152fd5b611cb090613307565b611cbb57888b611aa8565b8880fd5b87513d8c823e3d90fd5b833b15610b645784517febb689a10000000000000000000000000000000000000000000000000000000081528881888183895af18015611d2657908991611d12575b5050611a43565b611d1b90613307565b610b6457878a611d0b565b86513d8b823e3d90fd5b82517f41849afc00000000000000000000000000000000000000000000000000000000815285858201526020816024818b7f000000000000000000000000c2080ca61d32663fb698321def53c038efc72964165afa908115611df5578791611dc0575b50611a33575050517f97e8471e000000000000000000000000000000000000000000000000000000008152fd5b90506020813d8211611ded575b81611dda6020938361334a565b81010312611de9575188611d93565b8680fd5b3d9150611dcd565b84513d89823e3d90fd5b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d168152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761075690356139ce565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b9050346107fe57827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107fe57338352600891602083815281852054611f59600b548092613b33565b90338752868352611f768488205483039187855285892054613b33565b338852878452611f898589205483613b26565b116120f05773ffffffffffffffffffffffffffffffffffffffff9087611fd130847f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b8281106121bc575b5050338852868452611ff185892054600b5490613b33565b338952888552612004868a205483613b26565b116121945761201290613bab565b338752600283528387205482039086845261203385892054600b5490613b33565b33895260028552612047868a205484613b26565b1161219457879061207a30827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b90838210612119575b50505033875285835261209c84882054600b5490613b33565b338852600284526120b08589205483613b26565b116120f0576120be90613c35565b338652838252828620549003938152826120de83872054600b5490613b33565b91338752526113d68286205485613b26565b505050517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61212890600354169184613836565b813b156107fe57829160248392895194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528d8401525af1801561218a57612176575b80612083565b61217f90613307565b611de9578638612170565b86513d84823e3d90fd5b8585517f39996567000000000000000000000000000000000000000000000000000000008152fd5b6121cb84600354169184613836565b813b156107fe57829060248a838b5195869485937f4066800c0000000000000000000000000000000000000000000000000000000085528401525af180156122265715611fd95761221b90613307565b610b64578738611fd9565b87513d84823e3d90fd5b809184346122f357827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126122f35773ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b31691823b156122ee57839283918351809581937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af1908115611c2a57506122de5750f35b6122e790613307565b6105755780f35b505050fd5b5050fd5b9050346107fe57612307366134b3565b939190503385526020906008825261232583872054600b5490613b33565b3387528683526123388488205483613b26565b116124d65773ffffffffffffffffffffffffffffffffffffffff908661238030847f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b8281106124fe575b5050338752600883526123a184882054600b5490613b33565b3388528784526123b48589205483613b26565b116120f0576123c290613bab565b338652600882526123d983872054600b5490613b33565b338752600283526123ed8488205487613b26565b116124d657859061242030827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b90868210612457575b50505033855260088152600261244583872054600b5490613b33565b9133875252610c498286205485613b26565b61246690600354169187613836565b813b156107fe57829160248392875194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528b8401525af180156124cc576124b4575b80612429565b6124bd90613307565b6124c85784386124ae565b8480fd5b84513d84823e3d90fd5b5050517f39996567000000000000000000000000000000000000000000000000000000008152fd5b61250d84600354169184613836565b813b156107fe57829160248392895194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528d8401525af1801561218a57156123885761255e90613307565b611de9578638612388565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e168152f35b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020906108556135fe565b8391503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578035926126506135c6565b600b5491828511156127dc576007549473ffffffffffffffffffffffffffffffffffffffff93847f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b316946126ad886126a83089613b7c565b613f9b565b83116127b457966126e96126ef92889985600b557f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f1694613836565b90613b33565b90803b156127b05783517f40c10f19000000000000000000000000000000000000000000000000000000008152308482019081526020810193909352918691839182908490829060400103925af180156127a657908591612792575b5050823b156122ee57839283918351809581937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af1908115611c2a57506122de5750f35b61279b90613307565b6122ee57838661274b565b83513d87823e3d90fd5b8580fd5b8385517f40db7f87000000000000000000000000000000000000000000000000000000008152fd5b90517f6a43f8d1000000000000000000000000000000000000000000000000000000008152fd5b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020905160ff7f0000000000000000000000000000000000000000000000000000000000000012168152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b8334610575576107566119c26128dd366134b3565b929190506138bb565b8284346105755760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105755761291f613431565b612927613454565b906044359073ffffffffffffffffffffffffffffffffffffffff809116928385526020966008885261295f61127d8589892054613836565b858752868952878720548110908115612a8e575b8115612a7d575b8115612a6d575b50612a465750918587927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef94868852600985528288203360005285528260002054847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612a21575b505086885260088552828820612a02858254613836565b9055169586815260088452208181540190558551908152a35160018152f35b612a2a91613836565b8789526009865283892033600052865283600020558a846129eb565b86517fff75bad1000000000000000000000000000000000000000000000000000000008152fd5b9050818952878720541189612981565b60028a52888820548110915061297a565b60018a528888205481109150612973565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576020906007549051908152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576107569035612b1b816138bb565b6119c7816139ce565b9050346107fe57612b3436613477565b9492915033865260209160088352612b5284882054600b5490613b33565b338852878452612b658589205483613b26565b116120f05773ffffffffffffffffffffffffffffffffffffffff9087612bad30847f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b828110612d25575b505033885260088452612bce85892054600b5490613b33565b338952888552612be1868a205483613b26565b1161219457612bef90613bab565b33875260088352612c0684882054600b5490613b33565b33885260028452612c1a8589205484613b26565b116120f0578690612c4d30827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b90838210612caa575b50505033865260088252612c7083872054600b5490613b33565b33875260028352612c848488205483613b26565b116124d657612c9290613c35565b33855260088152826120de83872054600b5490613b33565b612cb990600354169184613836565b813b156107fe57829160248392885194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528c8401525af18015612d1b57612d07575b80612c56565b612d1090613307565b6127b0578538612d01565b85513d84823e3d90fd5b612d3484600354169184613836565b813b156107fe57829060248a838b5195869485937f4066800c0000000000000000000000000000000000000000000000000000000085528401525af180156122265715612bb557612d8490613307565b610b64578738612bb5565b50503461034b57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5760209073ffffffffffffffffffffffffffffffffffffffff600354169051908152f35b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b576107569035613ac9565b83903461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b5761075690356138bb565b50503461034b57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b57602091612e93613431565b9073ffffffffffffffffffffffffffffffffffffffff8360243592338152600987522092169182600052845280836000205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461034b5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261034b578060209273ffffffffffffffffffffffffffffffffffffffff612f48613431565b16815280845220549051908152f35b82843461057557807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261057557508051600091600554612f99816132b4565b808452906001908181169081156114bd5750600114612fc35750506114528261145c94038361334a565b6005600090815294507f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05b828610613008575050509181016020019161145282611441565b80546020878701810191909152909501948101612fee565b9050346107fe57602091827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107f0578135923385526008815261306d82862054600b5490613b33565b3386528582526130808387205486613b26565b116131ac5773ffffffffffffffffffffffffffffffffffffffff856130c730837f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613b7c565b868110613244575b5050338652600882526130e883872054600b5490613b33565b3387528683526130fb8488205487613b26565b116124d65761310985613bab565b3386526008825261312083872054600b5490613b33565b338752600283526131348488205487613b26565b116124d657859061316730827f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613b7c565b908682106131d3575b5050503385526008815261318a82862054600b5490613b33565b3386526002825261319e8387205486613b26565b116131ac57612c9284613c35565b50517f39996567000000000000000000000000000000000000000000000000000000008152fd5b6131e290600354169187613836565b813b156107fe57829160248392875194859384927f18c59db50000000000000000000000000000000000000000000000000000000084528b8401525af180156124cc57613230575b80613170565b61323990613307565b6124c857843861322a565b61325383600354169188613836565b90803b156107fe5760248392875194859384927f4066800c0000000000000000000000000000000000000000000000000000000084528b8401525af18015611df5576132a1575b86906130cf565b6132ad90969196613307565b943861329a565b90600182811c921680156132fd575b60208310146132ce57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f16916132c3565b67ffffffffffffffff811161331b57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761331b57604052565b60208082528251818301819052939260005b8581106133dd575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b81810183015184820160400152820161339d565b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c5760206040516004358152f35b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361342c57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361342c57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc608091011261342c5760043590602435906044359060643590565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc606091011261342c57600435906024359060443590565b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c57613522613431565b50602061352d613f40565b604051908152f35b3461342c5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261342c57005b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc606091011261342c576004359073ffffffffffffffffffffffffffffffffffffffff90602435828116810361342c5791604435908116810361342c5790565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739275433036135f057565b6382b429006000526004601cfd5b6000467f0000000000000000000000000000000000000000000000000000000000aa36a70361364c57507f34888eff16829c3e9bd226cb95537287dad600fd4244732f95a9e11b9263ca8090565b604051600554829161365d826132b4565b80825281602094858201946001908782821691826000146137a1575050600114613747575b5061368f9250038261334a565b51902091604051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f845260408301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c082019082821067ffffffffffffffff83111761371a575060405251902090565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526041600452fd5b6005885286915087907f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db05b85831061378957505061368f935082010138613682565b80548388018501528694508893909201918101613772565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016885261368f95151560051b85010192503891506136829050565b601c600060649281946020966040519860605260405260601b602c526f23b872dd000000000000000000000000600c525af13d15600160005114171615613828576000606052604052565b637939f4246000526004601cfd5b9190820391821161384357565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60109260209260145260345260446000938480936fa9059cbb00000000000000000000000082525af13d1560018351141716156138ae57603452565b6390b8ec1890526004601cfd5b6138c490613943565b73ffffffffffffffffffffffffffffffffffffffff60035416806138e6575b50565b803b1561342c57600080916004604051809481937fcd2137850000000000000000000000000000000000000000000000000000000083525af180156139375761392c5750565b61393590613307565b565b6040513d6000823e3d90fd5b80156138e3573360005260006020526040600020613962828254613836565b90556139a681303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb166137dd565b337f1c4d85c1e7d2103e7da3a4aee8d1d10c48c372a6eee0d52d9197e23a2aea63cd600080a3565b6139d790613a3e565b73ffffffffffffffffffffffffffffffffffffffff60035416806139f85750565b803b1561342c57600080916004604051809481937fc255fb1b0000000000000000000000000000000000000000000000000000000083525af180156139375761392c5750565b80156138e3573360005260026020526040600020613a5d828254613836565b9055613aa181303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d166137dd565b337f3b7581e5f2644158be06ded43f7757416c423fe79298c94db0ef737427dae656600080a3565b613935903360005260046020526040600020613ae6828254613836565b9055303373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f166137dd565b9190820180921161384357565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482118102613b6e57670de0b6b3a764000091020490565b63bac65e5b6000526004601cfd5b602460106020939284936014526f70a082310000000000000000000000006000525afa601f3d11166020510290565b80156138e3573360005260006020526040600020613bca828254613b26565b9055613c0d813373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003a8093824b0cdecc9b0c7b0c68b86630dcde1beb16613872565b337f24ab920816cdd8b858bbc7ab3116e7b4717a8f407c9fe1278ea6bf0e85ebe03b600080a3565b80156138e3573360005260026020526040600020613c54828254613b26565b9055613c97813373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000001a2dc6aa017d6f5b2e538189f655cb2d120dcc4d16613872565b337f990e925c0c406b5aceff4657f74f5b071516036c6c2c46455080a33125e7e6b3600080a3565b80156138e3573360005260046020526040600020613cde828254613b26565b9055613d21813373ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f16613872565b337f7ff1bba75e07cab48d0e7ad8ce556a153ac5751934be86ed272c67b85ff73e47600080a3565b600c54428160801c1015613dea576fffffffffffffffffffffffffffffffff613dbc426099600262023ab1620afa6c6201518060039504010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c600982110290030190565b911614613de557613de142600260016007600362015180809504948502940106011491565b5090565b600090565b50600190565b600c54428160801c10156138e3576fffffffffffffffffffffffffffffffff80613e6c613e67426099600262023ab1620afa6c6201518060039504010661016d8062023ab083146105b48404618eac8504850103030490606482049180851c9102010390036005020104600c600982110290030190565b613f04565b1691168114613eda57613e9342600260016007600362015180809504948502940106011491565b9015613eda5762015180810180911161384357613ed07fffffffffffffffffffffffffffffffff0000000000000000000000000000000091613f04565b60801b1617600c55565b60046040517fb9c67ec5000000000000000000000000000000000000000000000000000000008152fd5b700100000000000000000000000000000000811015613f32576fffffffffffffffffffffffffffffffff1690565b6335278d126000526004601cfd5b613f98613f8f613f863073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b316613b7c565b600b5490613f9b565b60075490613836565b90565b670de0b6b3a7640000907812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218111820215830215613fd057020490565b637c5f487d6000526004601cfd5b73ffffffffffffffffffffffffffffffffffffffff600091168152806020526040812054806140ae575b5060016020526040600020548181116140a6575b50600260205260406000205481811161409e575b506004602052604060002054818111614096575b506008602052604060002054600b54670de0b6b3a76400007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218411810215820215613fd057613f98930290808204910615150190613836565b905038614044565b905038614030565b90503861401c565b905038614008565b6020906010926014526044600093848093816034526f095ea7b300000000000000000000000082525af13d1560018351141716156140f357603452565b633e3f8f7390526004601cfd5b6020906010926014527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60345260446000938480936f095ea7b300000000000000000000000082525af13d1560018351141716156140f357603452565b90614166613f40565b9060009181116142f45773ffffffffffffffffffffffffffffffffffffffff807f0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b31693843b156107f057838060409660048851809481937f521fb1fd0000000000000000000000000000000000000000000000000000000083525af180156142d7576142e1575b50817f0000000000000000000000005ea25810ebed7bccf4045ed2ef14192dec79c79f1661421d600b5485613b33565b813b156127b05786517f40c10f1900000000000000000000000000000000000000000000000000000000815230600482015260248101919091529085908290604490829084905af180156142d7579085916142c3575b5050916020917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef936142a783600754613b26565b60075516948585526008835280852082815401905551908152a3565b6142cc90613307565b6107f0578338614273565b86513d87823e3d90fd5b6142ed90949194613307565b92386141ed565b60046040517fe4bac01b000000000000000000000000000000000000000000000000000000008152fdfea164736f6c6343000813000a
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000c2080ca61d32663fb698321def53c038efc729640000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c466af7ff16ef0f1a7fa4e23e095e47a4058d7910000000000000000000000000000000000000000000000000000000000000009566f7465204d61696100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005764d414941000000000000000000000000000000000000000000000000000000
-----Decoded View---------------
Arg [0] : _factory (address): 0xC2080cA61D32663fb698321dEF53C038eFc72964
Arg [1] : _bHermesRate (uint256): 1000000000000000000
Arg [2] : _partnerAsset (address): 0x1877F4644eEC61659d7fa40F297f20507b60f75e
Arg [3] : _name (string): Vote Maia
Arg [4] : _symbol (string): vMAIA
Arg [5] : _bHermes (address): 0x6B65882F51BD0B5BE9803a3F2e1e895700F371B3
Arg [6] : _partnerVault (address): 0x0000000000000000000000000000000000000000
Arg [7] : _owner (address): 0xC466af7ff16ef0f1A7fa4E23E095E47a4058D791
-----Encoded View---------------
12 Constructor Arguments found :
Arg [0] : 000000000000000000000000c2080ca61d32663fb698321def53c038efc72964
Arg [1] : 0000000000000000000000000000000000000000000000000de0b6b3a7640000
Arg [2] : 0000000000000000000000001877f4644eec61659d7fa40f297f20507b60f75e
Arg [3] : 0000000000000000000000000000000000000000000000000000000000000100
Arg [4] : 0000000000000000000000000000000000000000000000000000000000000140
Arg [5] : 0000000000000000000000006b65882f51bd0b5be9803a3f2e1e895700f371b3
Arg [6] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [7] : 000000000000000000000000c466af7ff16ef0f1a7fa4e23e095e47a4058d791
Arg [8] : 0000000000000000000000000000000000000000000000000000000000000009
Arg [9] : 566f7465204d6169610000000000000000000000000000000000000000000000
Arg [10] : 0000000000000000000000000000000000000000000000000000000000000005
Arg [11] : 764d414941000000000000000000000000000000000000000000000000000000
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.