Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
TokenTracker
Multichain Info
N/A
Latest 1 from a total of 1 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Transfer Ownersh... | 6369574 | 82 days ago | IN | 0 ETH | 0.00004491 |
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
6361217 | 83 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Contract Name:
vMaiaVotes
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 {ERC20} from "lib/solmate/src/tokens/ERC20.sol"; import {ERC20MultiVotes} from "src/erc-20/ERC20MultiVotes.sol"; import {IvMaiaUnderlying} from "../interfaces/IvMaiaUnderlying.sol"; /** * @title vMaiaVotes: Have power over Maia's governance * @author Maia DAO (https://github.com/Maia-DAO) * @notice Represents the underlying governance power of a VoteMaia token. */ contract vMaiaVotes is ERC20MultiVotes, IvMaiaUnderlying { /// @inheritdoc IvMaiaUnderlying address public immutable override vMaia; constructor(address _owner) ERC20("VoteMaia Votes", "vMAIA-V", 18) { _initializeOwner(_owner); vMaia = msg.sender; } /// @inheritdoc IvMaiaUnderlying function mint(address to, uint256 amount) external override onlyvMaia { _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 onlyvMaia { _burn(from, amount); } modifier onlyvMaia() { if (msg.sender != vMaia) revert NotvMaia(); _; } }
// 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 // 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 // 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; 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: 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: 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; /** * @title VoteMaia Underlying * @author Maia DAO (https://github.com/Maia-DAO) * @notice Represents the underlying position of the VoteMaia token. */ interface IvMaiaUnderlying { /// @notice thrown when minter is not VoteMaia contract. error NotvMaia(); /** * @notice */ function vMaia() external view returns (address); /** * @notice Mints new VoteMaia underlying tokens to a specific account. * @param to account to transfer VoteMaia underlying tokens to * @param amount amount of tokens to mint. */ function mint(address to, uint256 amount) external; }
// 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 "../base/FlywheelCore.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":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"BlockError","type":"error"},{"inputs":[],"name":"DelegationError","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"NonContractError","type":"error"},{"inputs":[],"name":"NotvMaia","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UndelegationVoteError","type":"error"},{"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":"account","type":"address"},{"indexed":true,"internalType":"bool","name":"canContractExceedMaxDelegates","type":"bool"}],"name":"CanContractExceedMaxDelegatesUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"fromDelegate","type":"address"},{"indexed":true,"internalType":"address","name":"toDelegate","type":"address"}],"name":"DelegateChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegate","type":"address"},{"indexed":true,"internalType":"uint256","name":"previousBalance","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newBalance","type":"uint256"}],"name":"DelegateVotesChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"delegate","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Delegation","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"oldMaxDelegates","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"newMaxDelegates","type":"uint256"}],"name":"MaxDelegatesUpdate","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":"delegator","type":"address"},{"indexed":true,"internalType":"address","name":"delegate","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Undelegation","type":"event"},{"inputs":[],"name":"DELEGATION_AMOUNT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DELEGATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"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":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddress","type":"address"}],"name":"canContractExceedMaxDelegates","outputs":[{"internalType":"bool","name":"canExceedMaxGauges","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint32","name":"pos","type":"uint32"}],"name":"checkpoints","outputs":[{"components":[{"internalType":"uint32","name":"fromBlock","type":"uint32"},{"internalType":"uint224","name":"votes","type":"uint224"}],"internalType":"struct IERC20MultiVotes.Checkpoint","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newDelegatee","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegatee","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"delegateAmountBySig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegatee","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"delegateBySig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"delegateCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"}],"name":"delegates","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"address","name":"delegatee","type":"address"}],"name":"delegatesVotesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"freeVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getPriorVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"delegatee","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"incrementDelegation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxDelegates","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"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":[{"internalType":"address","name":"account","type":"address"}],"name":"numCheckpoints","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"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":[{"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":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bool","name":"canExceedMax","type":"bool"}],"name":"setContractExceedMaxDelegates","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMax","type":"uint256"}],"name":"setMaxDelegates","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"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":[{"internalType":"address","name":"delegatee","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userDelegatedVotes","outputs":[{"internalType":"uint256","name":"votes","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userUnusedVotes","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vMaia","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
61010060409080825234620004a157806200367e8038038091620000248285620004c2565b8339602092839181010312620004a157516001600160a01b0381169190829003620004a1578251926200005784620004a6565b600e84526d566f74654d61696120566f74657360901b828501528051916200007f83620004a6565b60078352663b26a0a4a096ab60c91b8184015284516001600160401b03959093908685116200048b5760009480620000b88754620004e6565b92601f938481116200043a575b508590848311600114620003d2578892620003c6575b50508160011b916000199060031b1c19161785555b815190878211620003b25781906001936200010c8554620004e6565b8281116200035d575b5085918311600114620002f9578792620002ed575b5050600019600383901b1c191690821b1781555b60126080524660a052825184549181866200015985620004e6565b9283835286830195878282169182600014620002cd5750506001146200028d575b506200018992500382620004c2565b519020918051918201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452818301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a083015260a0825260c082019582871090871117620002795785905251902060c05281638b78c6d819557f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08180a33360e05261315a908162000524823960805181611818015260a05181611ef8015260c05181611f1f015260e051818181610d8101528181611670015261172e0152f35b634e487b7160e01b84526041600452602484fd5b8691508880528189209089915b858310620002b4575050620001899350820101386200017a565b805483880185015286945088939092019181016200029a565b60ff191688526200018995151560051b85010192503891506200017a9050565b0151905038806200012a565b8488528588208594509190601f198416895b888282106200034657505084116200032c575b505050811b0181556200013e565b015160001960f88460031b161c191690553880806200031e565b83850151865588979095019493840193016200030b565b909192508488528588208380860160051c820192888710620003a8575b91869588929594930160051c01915b8281106200039957505062000115565b8a815586955087910162000389565b925081926200037a565b634e487b7160e01b86526041600452602486fd5b015190503880620000db565b8880528689209250601f198416895b888282106200042357505090846001959493921062000409575b505050811b018555620000f0565b015160001960f88460031b161c19169055388080620003fb565b6001859682939686015181550195019301620003e1565b9091508780528588208480850160051c82019288861062000481575b9085949392910160051c01905b818110620004725750620000c5565b89815584935060010162000463565b9250819262000456565b634e487b7160e01b600052604160045260246000fd5b600080fd5b604081019081106001600160401b038211176200048b57604052565b601f909101601f19168101906001600160401b038211908210176200048b57604052565b90600182811c9216801562000518575b60208310146200050257565b634e487b7160e01b600052602260045260246000fd5b91607f1691620004f656fe608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314611c3b5750816307f6365814611bc7578163095ea7b314611b2c57816318160ddd14611aef578163189aa7bf14611a8d578163239cbb811461198b57816323b872dd1461193b57816325692962146118d25781632641fe2114611879578163289c26f71461183c578163313ce567146117e05781633644e515146117a557816340c10f19146116d85781634d99dd16146116945781634f306e5a1461162557816354d1f13d146115c1578163587cde1e146114ee5781635c19a95c146114ad5781636b578185146114455781636fcfff45146113d557816370a0823114611373578163715018a6146112f45781637757dc5814611292578163782d6fe1146110c45781637c7b78e1146110805781637ecebe001461101e5781638da5cb5b14610fac578163951e26ec14610f4557816395d89b4114610e2a5781639ab24eb014610c865781639dc29fac14610d2b578163a9059cbb14610cd1578163af959b0514610c86578163c3cda52014610a79578163d505accf1461076c578163dd62ed3e146106f4578163e7a324dc1461069b578163f04e283e146105d5578163f1127ed814610503578163f14b34a6146104bc578163f2fde38b1461040857508063f870efb4146102525763fee81cf4146101fe57600080fd5b3461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602091610238611e60565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5761028a611e60565b6024359060443592606435936020866102a1611eab565b6102ad8842111561305d565b6102b5611ef3565b61036261038e8751868101907f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d4825273ffffffffffffffffffffffffffffffffffffffff9c8d8c168b8301528c606083015289608083015260a082015260a0815261031f81611d9d565b5190208851928391888301958690916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611db9565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa156103fe576103e99086519586169283885260056020528720908154916103e183612df2565b9055146130e8565b156103fa576103f7926123ac565b80f35b8380fd5b81513d87823e3d90fd5b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5761043c611e60565b90610445611ebb565b8160601b156104b1575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b637448fbae8352601cfd5b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76104f6611e60565b60243590336123ac565b80fd5b82843461050057817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005761053b611e60565b63ffffffff9160243591838316830361024e5791848273ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff956105a8956020855161059581611d52565b8281520152168152600660205220612096565b509160208451936105b885611d52565b5491821693848152019060201c8152835192835251166020820152f35b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261050057610608611e60565b610610611ebb565b63389a75e1600c528082526020600c20928354421161069057508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b636f5e88188352601cfd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf8152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576020928291610731611e60565b610739611e88565b9173ffffffffffffffffffffffffffffffffffffffff8092168452865283832091168252845220549051908152f35b8280fd5b83833461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576107a5611e60565b6107ad611e88565b604435606435936107bc611eab565b93428610610a1c576107cc611ef3565b9473ffffffffffffffffffffffffffffffffffffffff8092169586895260209560058752848a209889549960018b01905585519085898301937f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c985528b89850152169a8b606084015288608084015260a083015260c082015260c0815260e081019181831067ffffffffffffffff8411176109f057916109007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff208d95936108d38c9896858c52825190206101008301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00810184520182611db9565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa156109e65786511696871515806109dd575b156109825786977f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259596975283528087208688528352818188205551908152a380f35b8360649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b5084881461093f565b81513d88823e3d90fd5b60248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b60648860208451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b919050346107685760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857610ab3611e60565b90602435604435936064359060ff82168203610c8257610ad58642111561305d565b610add611ef3565b908451602081017fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf815273ffffffffffffffffffffffffffffffffffffffff988989168884015286606084015260808301526080825260a082019282841067ffffffffffffffff851117610c56575091610beb7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff608b9593610bbe60209896858c528251902060c08301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40810184520182611db9565b519020855190815260ff919091166020820152608435604082015260a435606082015281805260809060015afa15610c4c57610c3e9085519485169283875260056020528620908154916103e183612df2565b15610768576103f79161225f565b81513d86823e3d90fd5b8a60416024927f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8680fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca610cc5611e60565b612178565b9051908152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d22610d0f611e60565b60243590610d1d8233612e1f565b612c82565b90519015158152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857610d63611e60565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f0000000000000000000000000000000000000000000000000000000000000000163303610e035750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9282610dda889796602095612e1f565b169384865260038352808620610df1838254612101565b9055816002540360025551908152a380f35b84517f708ea158000000000000000000000000000000000000000000000000000000008152fd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57805190826001805491610e6c83611cff565b80865292828116908115610eff5750600114610ea3575b505050610e9582610e9f940383611db9565b5191829182611dfa565b0390f35b94508085527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828610610ee757505050610e95826020610e9f9582010194610e83565b80546020878701810191909152909501948101610eca565b610e9f975086935060209250610e959491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b82010194610e83565b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760ff8160209373ffffffffffffffffffffffffffffffffffffffff610f99611e60565b1681526008855220541690519015158152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611070611e60565b1681526005845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca6110bf611e60565b61213d565b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576110fc611e60565b9060248035924384101561126a5773ffffffffffffffffffffffffffffffffffffffff16855260066020528385209182549386905b8582106111f95750508361116c57505050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff602092915b5191168152f35b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8496929394019586116111d0575050506111c66020937bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92612096565b5054831c91611165565b6011907f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b909460019061120e818818831c828916612252565b918363ffffffff61121f858a612096565b5054161115611232575050945b90611131565b90965081018091111561122c5783886011857f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8285517f3f8d3c1c000000000000000000000000000000000000000000000000000000008152fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff6112e4611e60565b168152600b845220549051908152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261050057611326611ebb565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff6113c5611e60565b1681526003845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5763ffffffff61143d8260209473ffffffffffffffffffffffffffffffffffffffff61142f611e60565b1681526006865220546120dd565b915191168152f35b83903461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e573561147f611ebb565b600754816007557f9960c7dba5c668f2dcce571ead061f33d2e4174c892c8eb86b4b34529bb7271e8380a380f35b83346105005760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76114e8611e60565b3361225f565b50503461024e576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107685773ffffffffffffffffffffffffffffffffffffffff928361153f611e60565b168152600b825282812091835190818185549182815201908195855282852090855b8181106115ad5750505082611577910383611db9565b8451948186019282875251809352850193925b8281106115975785850386f35b835187168552938101939281019260010161158a565b825484529284019260019283019201611561565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76116ce611e60565b602435903361289a565b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857611710611e60565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f0000000000000000000000000000000000000000000000000000000000000000163303610e035750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef926020926117888896600254612252565b60025516948585526003835280852082815401905551908152a380f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca611ef3565b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905160ff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906007549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d48152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b50503461024e5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d2261197a611e60565b611982611e88565b60443591612cfa565b9190503461076857807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576119c4611e60565b6024359283151593848103611a89576119db611ebb565b80611a80575b611a59575073ffffffffffffffffffffffffffffffffffffffff1690818452600860205283207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541660ff84161790557ff250dd6faf51f88e0d298800d22453f75bd1af207056ddd9a4fb55f1408376fb8380a380f35b82517f270de3fd000000000000000000000000000000000000000000000000000000008152fd5b50813b156119e1565b8580fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611adf611e60565b168152600a845220549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906002549051908152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857602092611b67611e60565b9183602435928392338252875273ffffffffffffffffffffffffffffffffffffffff8282209516948582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5780602092611c03611e60565b611c0b611e88565b73ffffffffffffffffffffffffffffffffffffffff91821683526009865283832091168252845220549051908152f35b8490843461076857827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857828054611c7881611cff565b80855291600191808316908115610eff5750600114611ca357505050610e9582610e9f940383611db9565b80809650527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828610611ce757505050610e95826020610e9f9582010194610e83565b80546020878701810191909152909501948101611cca565b90600182811c92168015611d48575b6020831014611d1957565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611d0e565b6040810190811067ffffffffffffffff821117611d6e57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60c0810190811067ffffffffffffffff821117611d6e57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117611d6e57604052565b60208082528251818301819052939260005b858110611e4c575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b818101830151848201604001528201611e0c565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203611e8357565b600080fd5b6024359073ffffffffffffffffffffffffffffffffffffffff82168203611e8357565b6084359060ff82168203611e8357565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927543303611ee557565b6382b429006000526004601cfd5b6000467f000000000000000000000000000000000000000000000000000000000000000003611f4157507f000000000000000000000000000000000000000000000000000000000000000090565b604051815491908181611f5385611cff565b9182825260209586830195600191888382169182600014612057575050600114611ffe575b5050611f8692500382611db9565b51902090604051908101917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f835260408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a08152611ff881611d9d565b51902090565b908792508180527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b85831061203f575050611f8693508201013880611f78565b80548388018501528694508893909201918101612027565b91509350611f869592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009150168652151560051b8201013880611f78565b80548210156120ae5760005260206000200190600090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6401000000008110156120f35763ffffffff1690565b6335278d126000526004601cfd5b9190820391821161210e57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff166000526003602052612175604060002054600a60205260406000205490612101565b90565b73ffffffffffffffffffffffffffffffffffffffff16600090815260066020526040812080549190826121c957507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff91501690565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830192831161222557507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9161221c91612096565b505460201c1690565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526011600452fd5b9190820180921161210e57565b9073ffffffffffffffffffffffffffffffffffffffff9081831690600092828452600b6020526040842054600181116123825760018591146122ea575b82827f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f941696876122d0575b5050169280a4565b6122e3916122dd8261213d565b916123ac565b38806122c8565b5090828452600b6020526040842080541561235557907f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f92918552816020862054169084865260096020526040862082875260205261234e6040872054838961289a565b925061229c565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526032600452fd5b60046040517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b919073ffffffffffffffffffffffffffffffffffffffff80911692831580156127c8575b80156127c0575b61238257169160008381526020600b815283836040966123f9828987206127d9565b612768575b80855260098452878520828652845287852061241b848254612252565b9055808552600a8452878520612432848254612252565b90557f96eafeca8c3c21ab2fa4a636b93ba20c9e22e3d222d92c6530fedc29a53671ee8580a48282526006815284822090815490811596876000146126fd5784905b61249e7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8093169889612252565b98158061268c575b15612579575050506124b786612c3c565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161254c5791612512612548927fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7249594612096565b509063ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000083549260201b169116179055565b80a4565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b91939092612586436120dd565b91846125918b612c3c565b94519661259d88611d52565b63ffffffff80951688528701941684526801000000000000000082101561265f57906125ce91600182018155612096565b9490946126335751915163ffffffff9190921616911660201b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000161790557fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72490612548565b6024867f4e487b7100000000000000000000000000000000000000000000000000000000815280600452fd5b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84018481116126d0576126c663ffffffff9187612096565b50541643146124a6565b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830183811161273b576127319085612096565b5054821c90612474565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b808552600b84528785205460075410156123fe579150506008825260ff868420541615612797578385916123fe565b600486517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b5082156123d7565b50826127d38261213d565b106123d0565b9190600183016000908282528060205260408220541560001461289457845494680100000000000000008610156128675783612857612822886001604098999a01855584612096565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b9055549382526020522055600190565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b50925050565b929190816128a782612178565b1061296c5773ffffffffffffffffffffffffffffffffffffffff809416936000948086526020600981526040908188209385169384895281526128ed86838a2054612101565b8015612951575b978392600a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa9797739361294f9a9b8a9897855260098252838520888652825283852055858452528120612946868254612101565b905580a4612ac2565b565b838952600b825261296485848b20612996565b6128f4578880fd5b60046040517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b90600182019060009281845282602052604084205490811515600014612abb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff918281018181116126d057825490848201918211612a8e57808203612a59575b50505080548015612a2c57820191612a0f8383612096565b909182549160031b1b191690555582526020526040812055600190565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b612a79612a696128229386612096565b90549060031b1c92839286612096565b905586528460205260408620553880806129f7565b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b5050505090565b73ffffffffffffffffffffffffffffffffffffffff1660008181526006602052604081208054801594939291908515612bfe57825b612b217bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8092169687612101565b961580612b8d575b15612b3857506124b786612c3c565b90612b42436120dd565b612b4b88612c3c565b9160405194612b5986611d52565b63ffffffff80931686528460208701941684526801000000000000000082101561265f57906125ce91600182018155612096565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201828111612bd157612bc763ffffffff9185612096565b5054164314612b29565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811161254c57612c329083612096565b505460201c612af7565b7c01000000000000000000000000000000000000000000000000000000008110156120f3577bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff903360005260036020526040600020612cb1848254612101565b9055169081600052600360205260406000208181540190556040519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60203392a3600190565b9190612d068284612e1f565b73ffffffffffffffffffffffffffffffffffffffff80931691338314612de657907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91600094848652602092600484526040918291828920338a52865282892054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612dc3575b505087895260038652828920612da7868254612101565b90551696878152600385522082815401905551908152a3600190565b612dcc91612101565b888a5260048752838a20338b528752838a20553885612d90565b91506121759250612c82565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461210e5760010190565b91612e298361213d565b91808310156130575791909260009073ffffffffffffffffffffffffffffffffffffffff809116808352602095600b875260409283852091845190818a85549182815201908589528b892090895b8d828210613043575050505082612e8f910383611db9565b81519887985b8a8a1080613031575b15612fc95783518a10156120ae57848c8b60051b860101511699876000528c60098091528d8a600020908d600052528d8a600020549c8d91612edf82612178565b838110908418028084189303612f15575b50505050612f079192939495969798999a50612df2565b989796959493929190612e95565b9091612f2b8180959798999a9b9c9d9e9f612252565b9e03612f9f5750612f3c818b612996565b15611e83578e612f07948c60005281528c60002090826000525260008c8120555b612f678282612ac2565b8a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa979773600080a48a99989796959493929138808f612ef0565b612f07948c60005281528c6000209082600052528b600020612fc2838254612101565b9055612f5d565b92509498509895949650612fe09250849150612252565b106130085790612ffd600a92856000528383528460002054612101565b936000525260002055565b600483517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b508161303d8a83612252565b10612e9e565b835485529093019260019283019201612e77565b50915050565b1561306457565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332304d756c7469566f7465733a207369676e617475726520657870697260448201527f65640000000000000000000000000000000000000000000000000000000000006064820152fd5b156130ef57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f45524332304d756c7469566f7465733a20696e76616c6964206e6f6e636500006044820152fdfea164736f6c6343000813000a000000000000000000000000c466af7ff16ef0f1a7fa4e23e095e47a4058d791
Deployed Bytecode
0x608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314611c3b5750816307f6365814611bc7578163095ea7b314611b2c57816318160ddd14611aef578163189aa7bf14611a8d578163239cbb811461198b57816323b872dd1461193b57816325692962146118d25781632641fe2114611879578163289c26f71461183c578163313ce567146117e05781633644e515146117a557816340c10f19146116d85781634d99dd16146116945781634f306e5a1461162557816354d1f13d146115c1578163587cde1e146114ee5781635c19a95c146114ad5781636b578185146114455781636fcfff45146113d557816370a0823114611373578163715018a6146112f45781637757dc5814611292578163782d6fe1146110c45781637c7b78e1146110805781637ecebe001461101e5781638da5cb5b14610fac578163951e26ec14610f4557816395d89b4114610e2a5781639ab24eb014610c865781639dc29fac14610d2b578163a9059cbb14610cd1578163af959b0514610c86578163c3cda52014610a79578163d505accf1461076c578163dd62ed3e146106f4578163e7a324dc1461069b578163f04e283e146105d5578163f1127ed814610503578163f14b34a6146104bc578163f2fde38b1461040857508063f870efb4146102525763fee81cf4146101fe57600080fd5b3461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602091610238611e60565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5761028a611e60565b6024359060443592606435936020866102a1611eab565b6102ad8842111561305d565b6102b5611ef3565b61036261038e8751868101907f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d4825273ffffffffffffffffffffffffffffffffffffffff9c8d8c168b8301528c606083015289608083015260a082015260a0815261031f81611d9d565b5190208851928391888301958690916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611db9565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa156103fe576103e99086519586169283885260056020528720908154916103e183612df2565b9055146130e8565b156103fa576103f7926123ac565b80f35b8380fd5b81513d87823e3d90fd5b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5761043c611e60565b90610445611ebb565b8160601b156104b1575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b637448fbae8352601cfd5b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76104f6611e60565b60243590336123ac565b80fd5b82843461050057817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005761053b611e60565b63ffffffff9160243591838316830361024e5791848273ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff956105a8956020855161059581611d52565b8281520152168152600660205220612096565b509160208451936105b885611d52565b5491821693848152019060201c8152835192835251166020820152f35b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261050057610608611e60565b610610611ebb565b63389a75e1600c528082526020600c20928354421161069057508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b636f5e88188352601cfd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf8152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576020928291610731611e60565b610739611e88565b9173ffffffffffffffffffffffffffffffffffffffff8092168452865283832091168252845220549051908152f35b8280fd5b83833461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576107a5611e60565b6107ad611e88565b604435606435936107bc611eab565b93428610610a1c576107cc611ef3565b9473ffffffffffffffffffffffffffffffffffffffff8092169586895260209560058752848a209889549960018b01905585519085898301937f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c985528b89850152169a8b606084015288608084015260a083015260c082015260c0815260e081019181831067ffffffffffffffff8411176109f057916109007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff208d95936108d38c9896858c52825190206101008301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00810184520182611db9565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa156109e65786511696871515806109dd575b156109825786977f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259596975283528087208688528352818188205551908152a380f35b8360649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b5084881461093f565b81513d88823e3d90fd5b60248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b60648860208451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b919050346107685760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857610ab3611e60565b90602435604435936064359060ff82168203610c8257610ad58642111561305d565b610add611ef3565b908451602081017fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf815273ffffffffffffffffffffffffffffffffffffffff988989168884015286606084015260808301526080825260a082019282841067ffffffffffffffff851117610c56575091610beb7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff608b9593610bbe60209896858c528251902060c08301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40810184520182611db9565b519020855190815260ff919091166020820152608435604082015260a435606082015281805260809060015afa15610c4c57610c3e9085519485169283875260056020528620908154916103e183612df2565b15610768576103f79161225f565b81513d86823e3d90fd5b8a60416024927f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8680fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca610cc5611e60565b612178565b9051908152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d22610d0f611e60565b60243590610d1d8233612e1f565b612c82565b90519015158152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857610d63611e60565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f000000000000000000000000a97b98b822a7fd1cac21ccca2e0df99357c3dc75163303610e035750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9282610dda889796602095612e1f565b169384865260038352808620610df1838254612101565b9055816002540360025551908152a380f35b84517f708ea158000000000000000000000000000000000000000000000000000000008152fd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57805190826001805491610e6c83611cff565b80865292828116908115610eff5750600114610ea3575b505050610e9582610e9f940383611db9565b5191829182611dfa565b0390f35b94508085527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828610610ee757505050610e95826020610e9f9582010194610e83565b80546020878701810191909152909501948101610eca565b610e9f975086935060209250610e959491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b82010194610e83565b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760ff8160209373ffffffffffffffffffffffffffffffffffffffff610f99611e60565b1681526008855220541690519015158152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611070611e60565b1681526005845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca6110bf611e60565b61213d565b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576110fc611e60565b9060248035924384101561126a5773ffffffffffffffffffffffffffffffffffffffff16855260066020528385209182549386905b8582106111f95750508361116c57505050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff602092915b5191168152f35b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8496929394019586116111d0575050506111c66020937bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92612096565b5054831c91611165565b6011907f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b909460019061120e818818831c828916612252565b918363ffffffff61121f858a612096565b5054161115611232575050945b90611131565b90965081018091111561122c5783886011857f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8285517f3f8d3c1c000000000000000000000000000000000000000000000000000000008152fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff6112e4611e60565b168152600b845220549051908152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261050057611326611ebb565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff6113c5611e60565b1681526003845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5763ffffffff61143d8260209473ffffffffffffffffffffffffffffffffffffffff61142f611e60565b1681526006865220546120dd565b915191168152f35b83903461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e573561147f611ebb565b600754816007557f9960c7dba5c668f2dcce571ead061f33d2e4174c892c8eb86b4b34529bb7271e8380a380f35b83346105005760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76114e8611e60565b3361225f565b50503461024e576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107685773ffffffffffffffffffffffffffffffffffffffff928361153f611e60565b168152600b825282812091835190818185549182815201908195855282852090855b8181106115ad5750505082611577910383611db9565b8451948186019282875251809352850193925b8281106115975785850386f35b835187168552938101939281019260010161158a565b825484529284019260019283019201611561565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a97b98b822a7fd1cac21ccca2e0df99357c3dc75168152f35b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76116ce611e60565b602435903361289a565b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857611710611e60565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f000000000000000000000000a97b98b822a7fd1cac21ccca2e0df99357c3dc75163303610e035750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef926020926117888896600254612252565b60025516948585526003835280852082815401905551908152a380f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca611ef3565b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905160ff7f0000000000000000000000000000000000000000000000000000000000000012168152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906007549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d48152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b50503461024e5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d2261197a611e60565b611982611e88565b60443591612cfa565b9190503461076857807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576119c4611e60565b6024359283151593848103611a89576119db611ebb565b80611a80575b611a59575073ffffffffffffffffffffffffffffffffffffffff1690818452600860205283207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541660ff84161790557ff250dd6faf51f88e0d298800d22453f75bd1af207056ddd9a4fb55f1408376fb8380a380f35b82517f270de3fd000000000000000000000000000000000000000000000000000000008152fd5b50813b156119e1565b8580fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611adf611e60565b168152600a845220549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906002549051908152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857602092611b67611e60565b9183602435928392338252875273ffffffffffffffffffffffffffffffffffffffff8282209516948582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5780602092611c03611e60565b611c0b611e88565b73ffffffffffffffffffffffffffffffffffffffff91821683526009865283832091168252845220549051908152f35b8490843461076857827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857828054611c7881611cff565b80855291600191808316908115610eff5750600114611ca357505050610e9582610e9f940383611db9565b80809650527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828610611ce757505050610e95826020610e9f9582010194610e83565b80546020878701810191909152909501948101611cca565b90600182811c92168015611d48575b6020831014611d1957565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611d0e565b6040810190811067ffffffffffffffff821117611d6e57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60c0810190811067ffffffffffffffff821117611d6e57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117611d6e57604052565b60208082528251818301819052939260005b858110611e4c575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b818101830151848201604001528201611e0c565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203611e8357565b600080fd5b6024359073ffffffffffffffffffffffffffffffffffffffff82168203611e8357565b6084359060ff82168203611e8357565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927543303611ee557565b6382b429006000526004601cfd5b6000467f0000000000000000000000000000000000000000000000000000000000aa36a703611f4157507f2ea930ebefca8fb0deaadcfa7c7103b33d2fc36c4165237397409c54b108929290565b604051815491908181611f5385611cff565b9182825260209586830195600191888382169182600014612057575050600114611ffe575b5050611f8692500382611db9565b51902090604051908101917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f835260408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a08152611ff881611d9d565b51902090565b908792508180527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b85831061203f575050611f8693508201013880611f78565b80548388018501528694508893909201918101612027565b91509350611f869592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009150168652151560051b8201013880611f78565b80548210156120ae5760005260206000200190600090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6401000000008110156120f35763ffffffff1690565b6335278d126000526004601cfd5b9190820391821161210e57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff166000526003602052612175604060002054600a60205260406000205490612101565b90565b73ffffffffffffffffffffffffffffffffffffffff16600090815260066020526040812080549190826121c957507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff91501690565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830192831161222557507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9161221c91612096565b505460201c1690565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526011600452fd5b9190820180921161210e57565b9073ffffffffffffffffffffffffffffffffffffffff9081831690600092828452600b6020526040842054600181116123825760018591146122ea575b82827f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f941696876122d0575b5050169280a4565b6122e3916122dd8261213d565b916123ac565b38806122c8565b5090828452600b6020526040842080541561235557907f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f92918552816020862054169084865260096020526040862082875260205261234e6040872054838961289a565b925061229c565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526032600452fd5b60046040517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b919073ffffffffffffffffffffffffffffffffffffffff80911692831580156127c8575b80156127c0575b61238257169160008381526020600b815283836040966123f9828987206127d9565b612768575b80855260098452878520828652845287852061241b848254612252565b9055808552600a8452878520612432848254612252565b90557f96eafeca8c3c21ab2fa4a636b93ba20c9e22e3d222d92c6530fedc29a53671ee8580a48282526006815284822090815490811596876000146126fd5784905b61249e7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8093169889612252565b98158061268c575b15612579575050506124b786612c3c565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161254c5791612512612548927fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7249594612096565b509063ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000083549260201b169116179055565b80a4565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b91939092612586436120dd565b91846125918b612c3c565b94519661259d88611d52565b63ffffffff80951688528701941684526801000000000000000082101561265f57906125ce91600182018155612096565b9490946126335751915163ffffffff9190921616911660201b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000161790557fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72490612548565b6024867f4e487b7100000000000000000000000000000000000000000000000000000000815280600452fd5b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84018481116126d0576126c663ffffffff9187612096565b50541643146124a6565b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830183811161273b576127319085612096565b5054821c90612474565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b808552600b84528785205460075410156123fe579150506008825260ff868420541615612797578385916123fe565b600486517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b5082156123d7565b50826127d38261213d565b106123d0565b9190600183016000908282528060205260408220541560001461289457845494680100000000000000008610156128675783612857612822886001604098999a01855584612096565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b9055549382526020522055600190565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b50925050565b929190816128a782612178565b1061296c5773ffffffffffffffffffffffffffffffffffffffff809416936000948086526020600981526040908188209385169384895281526128ed86838a2054612101565b8015612951575b978392600a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa9797739361294f9a9b8a9897855260098252838520888652825283852055858452528120612946868254612101565b905580a4612ac2565b565b838952600b825261296485848b20612996565b6128f4578880fd5b60046040517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b90600182019060009281845282602052604084205490811515600014612abb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff918281018181116126d057825490848201918211612a8e57808203612a59575b50505080548015612a2c57820191612a0f8383612096565b909182549160031b1b191690555582526020526040812055600190565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b612a79612a696128229386612096565b90549060031b1c92839286612096565b905586528460205260408620553880806129f7565b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b5050505090565b73ffffffffffffffffffffffffffffffffffffffff1660008181526006602052604081208054801594939291908515612bfe57825b612b217bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8092169687612101565b961580612b8d575b15612b3857506124b786612c3c565b90612b42436120dd565b612b4b88612c3c565b9160405194612b5986611d52565b63ffffffff80931686528460208701941684526801000000000000000082101561265f57906125ce91600182018155612096565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201828111612bd157612bc763ffffffff9185612096565b5054164314612b29565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811161254c57612c329083612096565b505460201c612af7565b7c01000000000000000000000000000000000000000000000000000000008110156120f3577bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff903360005260036020526040600020612cb1848254612101565b9055169081600052600360205260406000208181540190556040519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60203392a3600190565b9190612d068284612e1f565b73ffffffffffffffffffffffffffffffffffffffff80931691338314612de657907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91600094848652602092600484526040918291828920338a52865282892054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612dc3575b505087895260038652828920612da7868254612101565b90551696878152600385522082815401905551908152a3600190565b612dcc91612101565b888a5260048752838a20338b528752838a20553885612d90565b91506121759250612c82565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461210e5760010190565b91612e298361213d565b91808310156130575791909260009073ffffffffffffffffffffffffffffffffffffffff809116808352602095600b875260409283852091845190818a85549182815201908589528b892090895b8d828210613043575050505082612e8f910383611db9565b81519887985b8a8a1080613031575b15612fc95783518a10156120ae57848c8b60051b860101511699876000528c60098091528d8a600020908d600052528d8a600020549c8d91612edf82612178565b838110908418028084189303612f15575b50505050612f079192939495969798999a50612df2565b989796959493929190612e95565b9091612f2b8180959798999a9b9c9d9e9f612252565b9e03612f9f5750612f3c818b612996565b15611e83578e612f07948c60005281528c60002090826000525260008c8120555b612f678282612ac2565b8a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa979773600080a48a99989796959493929138808f612ef0565b612f07948c60005281528c6000209082600052528b600020612fc2838254612101565b9055612f5d565b92509498509895949650612fe09250849150612252565b106130085790612ffd600a92856000528383528460002054612101565b936000525260002055565b600483517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b508161303d8a83612252565b10612e9e565b835485529093019260019283019201612e77565b50915050565b1561306457565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332304d756c7469566f7465733a207369676e617475726520657870697260448201527f65640000000000000000000000000000000000000000000000000000000000006064820152fd5b156130ef57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f45524332304d756c7469566f7465733a20696e76616c6964206e6f6e636500006044820152fdfea164736f6c6343000813000a
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000c466af7ff16ef0f1a7fa4e23e095e47a4058d791
-----Decoded View---------------
Arg [0] : _owner (address): 0xC466af7ff16ef0f1A7fa4E23E095E47a4058D791
-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 000000000000000000000000c466af7ff16ef0f1a7fa4e23e095e47a4058d791
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.