Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
TokenTracker
Multichain Info
N/A
Latest 25 from a total of 57 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Approve | 6504985 | 60 days ago | IN | 0 ETH | 0.00015033 | ||||
Approve | 6504440 | 60 days ago | IN | 0 ETH | 0.00017794 | ||||
Approve | 6504068 | 60 days ago | IN | 0 ETH | 0.00026903 | ||||
Approve | 6503834 | 60 days ago | IN | 0 ETH | 0.00039784 | ||||
Approve | 6503587 | 60 days ago | IN | 0 ETH | 0.00140318 | ||||
Approve | 6499027 | 61 days ago | IN | 0 ETH | 0.00121788 | ||||
Approve | 6498681 | 61 days ago | IN | 0 ETH | 0.00150011 | ||||
Approve | 6498492 | 61 days ago | IN | 0 ETH | 0.00088841 | ||||
Approve | 6497913 | 61 days ago | IN | 0 ETH | 0.00019942 | ||||
Approve | 6497872 | 61 days ago | IN | 0 ETH | 0.00024472 | ||||
Approve | 6497111 | 61 days ago | IN | 0 ETH | 0.00094382 | ||||
Approve | 6496734 | 61 days ago | IN | 0 ETH | 0.00081812 | ||||
Approve | 6495569 | 61 days ago | IN | 0 ETH | 0.00012684 | ||||
Approve | 6493470 | 62 days ago | IN | 0 ETH | 0.00019211 | ||||
Approve | 6493410 | 62 days ago | IN | 0 ETH | 0.00020038 | ||||
Approve | 6493317 | 62 days ago | IN | 0 ETH | 0.00024536 | ||||
Approve | 6492652 | 62 days ago | IN | 0 ETH | 0.00219157 | ||||
Approve | 6491264 | 62 days ago | IN | 0 ETH | 0.00175113 | ||||
Approve | 6490680 | 62 days ago | IN | 0 ETH | 0.00083256 | ||||
Approve | 6490597 | 62 days ago | IN | 0 ETH | 0.00058424 | ||||
Approve | 6490417 | 62 days ago | IN | 0 ETH | 0.00083427 | ||||
Approve | 6490079 | 62 days ago | IN | 0 ETH | 0.00035846 | ||||
Approve | 6489832 | 62 days ago | IN | 0 ETH | 0.00096378 | ||||
Approve | 6489779 | 62 days ago | IN | 0 ETH | 0.00058868 | ||||
Approve | 6489526 | 62 days ago | IN | 0 ETH | 0.00073117 |
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
6369656 | 81 days ago | Contract Creation | 0 ETH |
Loading...
Loading
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0x4B620E24...97b5eF34E The constructor portion of the code might be different and could alter the actual behaviour of the contract
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"}]
Deployed Bytecode
0x608060408181526004918236101561001657600080fd5b600092833560e01c91826306fdde0314611c3b5750816307f6365814611bc7578163095ea7b314611b2c57816318160ddd14611aef578163189aa7bf14611a8d578163239cbb811461198b57816323b872dd1461193b57816325692962146118d25781632641fe2114611879578163289c26f71461183c578163313ce567146117e05781633644e515146117a557816340c10f19146116d85781634d99dd16146116945781634f306e5a1461162557816354d1f13d146115c1578163587cde1e146114ee5781635c19a95c146114ad5781636b578185146114455781636fcfff45146113d557816370a0823114611373578163715018a6146112f45781637757dc5814611292578163782d6fe1146110c45781637c7b78e1146110805781637ecebe001461101e5781638da5cb5b14610fac578163951e26ec14610f4557816395d89b4114610e2a5781639ab24eb014610c865781639dc29fac14610d2b578163a9059cbb14610cd1578163af959b0514610c86578163c3cda52014610a79578163d505accf1461076c578163dd62ed3e146106f4578163e7a324dc1461069b578163f04e283e146105d5578163f1127ed814610503578163f14b34a6146104bc578163f2fde38b1461040857508063f870efb4146102525763fee81cf4146101fe57600080fd5b3461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602091610238611e60565b9063389a75e1600c525281600c20549051908152f35b5080fd5b503461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5761028a611e60565b6024359060443592606435936020866102a1611eab565b6102ad8842111561305d565b6102b5611ef3565b61036261038e8751868101907f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d4825273ffffffffffffffffffffffffffffffffffffffff9c8d8c168b8301528c606083015289608083015260a082015260a0815261031f81611d9d565b5190208851928391888301958690916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611db9565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa156103fe576103e99086519586169283885260056020528720908154916103e183612df2565b9055146130e8565b156103fa576103f7926123ac565b80f35b8380fd5b81513d87823e3d90fd5b839060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5761043c611e60565b90610445611ebb565b8160601b156104b1575073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b637448fbae8352601cfd5b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76104f6611e60565b60243590336123ac565b80fd5b82843461050057817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005761053b611e60565b63ffffffff9160243591838316830361024e5791848273ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff956105a8956020855161059581611d52565b8281520152168152600660205220612096565b509160208451936105b885611d52565b5491821693848152019060201c8152835192835251166020820152f35b8360207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261050057610608611e60565b610610611ebb565b63389a75e1600c528082526020600c20928354421161069057508173ffffffffffffffffffffffffffffffffffffffff929355167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08580a35580f35b636f5e88188352601cfd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf8152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576020928291610731611e60565b610739611e88565b9173ffffffffffffffffffffffffffffffffffffffff8092168452865283832091168252845220549051908152f35b8280fd5b83833461024e5760e07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576107a5611e60565b6107ad611e88565b604435606435936107bc611eab565b93428610610a1c576107cc611ef3565b9473ffffffffffffffffffffffffffffffffffffffff8092169586895260209560058752848a209889549960018b01905585519085898301937f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c985528b89850152169a8b606084015288608084015260a083015260c082015260c0815260e081019181831067ffffffffffffffff8411176109f057916109007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff208d95936108d38c9896858c52825190206101008301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00810184520182611db9565b519020855190815260ff91909116602082015260a435604082015260c435606082015281805260809060015afa156109e65786511696871515806109dd575b156109825786977f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259596975283528087208688528352818188205551908152a380f35b8360649251917f08c379a0000000000000000000000000000000000000000000000000000000008352820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152fd5b5084881461093f565b81513d88823e3d90fd5b60248c60418f7f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b60648860208451917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152fd5b919050346107685760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857610ab3611e60565b90602435604435936064359060ff82168203610c8257610ad58642111561305d565b610add611ef3565b908451602081017fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf815273ffffffffffffffffffffffffffffffffffffffff988989168884015286606084015260808301526080825260a082019282841067ffffffffffffffff851117610c56575091610beb7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff608b9593610bbe60209896858c528251902060c08301968790916042927f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201520190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40810184520182611db9565b519020855190815260ff919091166020820152608435604082015260a435606082015281805260809060015afa15610c4c57610c3e9085519485169283875260056020528620908154916103e183612df2565b15610768576103f79161225f565b81513d86823e3d90fd5b8a60416024927f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8680fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca610cc5611e60565b612178565b9051908152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d22610d0f611e60565b60243590610d1d8233612e1f565b612c82565b90519015158152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857610d63611e60565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f0000000000000000000000003e822a168a667aba3e6c6e7b64b562ecbd20c45b163303610e035750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9282610dda889796602095612e1f565b169384865260038352808620610df1838254612101565b9055816002540360025551908152a380f35b84517f708ea158000000000000000000000000000000000000000000000000000000008152fd5b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57805190826001805491610e6c83611cff565b80865292828116908115610eff5750600114610ea3575b505050610e9582610e9f940383611db9565b5191829182611dfa565b0390f35b94508085527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65b828610610ee757505050610e95826020610e9f9582010194610e83565b80546020878701810191909152909501948101610eca565b610e9f975086935060209250610e959491507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001682840152151560051b82010194610e83565b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760ff8160209373ffffffffffffffffffffffffffffffffffffffff610f99611e60565b1681526008855220541690519015158152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5760209073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7487392754915191168152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611070611e60565b1681526005845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca6110bf611e60565b61213d565b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576110fc611e60565b9060248035924384101561126a5773ffffffffffffffffffffffffffffffffffffffff16855260066020528385209182549386905b8582106111f95750508361116c57505050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff602092915b5191168152f35b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8496929394019586116111d0575050506111c66020937bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92612096565b5054831c91611165565b6011907f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b909460019061120e818818831c828916612252565b918363ffffffff61121f858a612096565b5054161115611232575050945b90611131565b90965081018091111561122c5783886011857f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b8285517f3f8d3c1c000000000000000000000000000000000000000000000000000000008152fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff6112e4611e60565b168152600b845220549051908152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261050057611326611ebb565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffff748739278181547f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a35580f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff6113c5611e60565b1681526003845220549051908152f35b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5763ffffffff61143d8260209473ffffffffffffffffffffffffffffffffffffffff61142f611e60565b1681526006865220546120dd565b915191168152f35b83903461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e573561147f611ebb565b600754816007557f9960c7dba5c668f2dcce571ead061f33d2e4174c892c8eb86b4b34529bb7271e8380a380f35b83346105005760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76114e8611e60565b3361225f565b50503461024e576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126107685773ffffffffffffffffffffffffffffffffffffffff928361153f611e60565b168152600b825282812091835190818185549182815201908195855282852090855b8181106115ad5750505082611577910383611db9565b8451948186019282875251809352850193925b8281106115975785850386f35b835187168552938101939281019260010161158a565b825484529284019260019283019201611561565b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005763389a75e1600c52338152806020600c2055337ffa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c928280a280f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000003e822a168a667aba3e6c6e7b64b562ecbd20c45b168152f35b50503461024e577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610500576103f76116ce611e60565b602435903361289a565b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857611710611e60565b906024359173ffffffffffffffffffffffffffffffffffffffff91827f0000000000000000000000003e822a168a667aba3e6c6e7b64b562ecbd20c45b163303610e035750827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef926020926117888896600254612252565b60025516948585526003835280852082815401905551908152a380f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610cca611ef3565b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020905160ff7f0000000000000000000000000000000000000000000000000000000000000012168152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906007549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090517f4e5bad79d7a0440fb72ccd68e0066fd311c89b4798247673e10a7539f77a95d48152f35b83807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105005763389a75e1600c523381526202a30042016020600c2055337fdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d8280a280f35b50503461024e5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e57602090610d2261197a611e60565b611982611e88565b60443591612cfa565b9190503461076857807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610768576119c4611e60565b6024359283151593848103611a89576119db611ebb565b80611a80575b611a59575073ffffffffffffffffffffffffffffffffffffffff1690818452600860205283207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0081541660ff84161790557ff250dd6faf51f88e0d298800d22453f75bd1af207056ddd9a4fb55f1408376fb8380a380f35b82517f270de3fd000000000000000000000000000000000000000000000000000000008152fd5b50813b156119e1565b8580fd5b50503461024e5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e578060209273ffffffffffffffffffffffffffffffffffffffff611adf611e60565b168152600a845220549051908152f35b50503461024e57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e576020906002549051908152f35b90503461076857817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857602092611b67611e60565b9183602435928392338252875273ffffffffffffffffffffffffffffffffffffffff8282209516948582528752205582519081527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925843392a35160018152f35b50503461024e57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261024e5780602092611c03611e60565b611c0b611e88565b73ffffffffffffffffffffffffffffffffffffffff91821683526009865283832091168252845220549051908152f35b8490843461076857827ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261076857828054611c7881611cff565b80855291600191808316908115610eff5750600114611ca357505050610e9582610e9f940383611db9565b80809650527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b828610611ce757505050610e95826020610e9f9582010194610e83565b80546020878701810191909152909501948101611cca565b90600182811c92168015611d48575b6020831014611d1957565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b91607f1691611d0e565b6040810190811067ffffffffffffffff821117611d6e57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60c0810190811067ffffffffffffffff821117611d6e57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117611d6e57604052565b60208082528251818301819052939260005b858110611e4c575050507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006040809697860101520116010190565b818101830151848201604001528201611e0c565b6004359073ffffffffffffffffffffffffffffffffffffffff82168203611e8357565b600080fd5b6024359073ffffffffffffffffffffffffffffffffffffffff82168203611e8357565b6084359060ff82168203611e8357565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927543303611ee557565b6382b429006000526004601cfd5b6000467f0000000000000000000000000000000000000000000000000000000000aa36a703611f4157507fb55376c004240a8b853a4c27a1ce385742a18c012ac8ce73583057f51feaf82690565b604051815491908181611f5385611cff565b9182825260209586830195600191888382169182600014612057575050600114611ffe575b5050611f8692500382611db9565b51902090604051908101917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f835260408201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260a08152611ff881611d9d565b51902090565b908792508180527f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5635b85831061203f575050611f8693508201013880611f78565b80548388018501528694508893909201918101612027565b91509350611f869592507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009150168652151560051b8201013880611f78565b80548210156120ae5760005260206000200190600090565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6401000000008110156120f35763ffffffff1690565b6335278d126000526004601cfd5b9190820391821161210e57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff166000526003602052612175604060002054600a60205260406000205490612101565b90565b73ffffffffffffffffffffffffffffffffffffffff16600090815260066020526040812080549190826121c957507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff91501690565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830192831161222557507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9161221c91612096565b505460201c1690565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526011600452fd5b9190820180921161210e57565b9073ffffffffffffffffffffffffffffffffffffffff9081831690600092828452600b6020526040842054600181116123825760018591146122ea575b82827f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f941696876122d0575b5050169280a4565b6122e3916122dd8261213d565b916123ac565b38806122c8565b5090828452600b6020526040842080541561235557907f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f92918552816020862054169084865260096020526040862082875260205261234e6040872054838961289a565b925061229c565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526032600452fd5b60046040517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b919073ffffffffffffffffffffffffffffffffffffffff80911692831580156127c8575b80156127c0575b61238257169160008381526020600b815283836040966123f9828987206127d9565b612768575b80855260098452878520828652845287852061241b848254612252565b9055808552600a8452878520612432848254612252565b90557f96eafeca8c3c21ab2fa4a636b93ba20c9e22e3d222d92c6530fedc29a53671ee8580a48282526006815284822090815490811596876000146126fd5784905b61249e7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8093169889612252565b98158061268c575b15612579575050506124b786612c3c565b917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820191821161254c5791612512612548927fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7249594612096565b509063ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000083549260201b169116179055565b80a4565b6024847f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b91939092612586436120dd565b91846125918b612c3c565b94519661259d88611d52565b63ffffffff80951688528701941684526801000000000000000082101561265f57906125ce91600182018155612096565b9490946126335751915163ffffffff9190921616911660201b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000161790557fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a72490612548565b6024867f4e487b7100000000000000000000000000000000000000000000000000000000815280600452fd5b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84018481116126d0576126c663ffffffff9187612096565b50541643146124a6565b6024877f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff830183811161273b576127319085612096565b5054821c90612474565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b808552600b84528785205460075410156123fe579150506008825260ff868420541615612797578385916123fe565b600486517f1650e725000000000000000000000000000000000000000000000000000000008152fd5b5082156123d7565b50826127d38261213d565b106123d0565b9190600183016000908282528060205260408220541560001461289457845494680100000000000000008610156128675783612857612822886001604098999a01855584612096565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b9055549382526020522055600190565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b50925050565b929190816128a782612178565b1061296c5773ffffffffffffffffffffffffffffffffffffffff809416936000948086526020600981526040908188209385169384895281526128ed86838a2054612101565b8015612951575b978392600a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa9797739361294f9a9b8a9897855260098252838520888652825283852055858452528120612946868254612101565b905580a4612ac2565b565b838952600b825261296485848b20612996565b6128f4578880fd5b60046040517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b90600182019060009281845282602052604084205490811515600014612abb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff918281018181116126d057825490848201918211612a8e57808203612a59575b50505080548015612a2c57820191612a0f8383612096565b909182549160031b1b191690555582526020526040812055600190565b6024867f4e487b710000000000000000000000000000000000000000000000000000000081526031600452fd5b612a79612a696128229386612096565b90549060031b1c92839286612096565b905586528460205260408620553880806129f7565b6024887f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b5050505090565b73ffffffffffffffffffffffffffffffffffffffff1660008181526006602052604081208054801594939291908515612bfe57825b612b217bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8092169687612101565b961580612b8d575b15612b3857506124b786612c3c565b90612b42436120dd565b612b4b88612c3c565b9160405194612b5986611d52565b63ffffffff80931686528460208701941684526801000000000000000082101561265f57906125ce91600182018155612096565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201828111612bd157612bc763ffffffff9185612096565b5054164314612b29565b6024857f4e487b710000000000000000000000000000000000000000000000000000000081526011600452fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810181811161254c57612c329083612096565b505460201c612af7565b7c01000000000000000000000000000000000000000000000000000000008110156120f3577bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff903360005260036020526040600020612cb1848254612101565b9055169081600052600360205260406000208181540190556040519081527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60203392a3600190565b9190612d068284612e1f565b73ffffffffffffffffffffffffffffffffffffffff80931691338314612de657907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91600094848652602092600484526040918291828920338a52865282892054857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612dc3575b505087895260038652828920612da7868254612101565b90551696878152600385522082815401905551908152a3600190565b612dcc91612101565b888a5260048752838a20338b528752838a20553885612d90565b91506121759250612c82565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461210e5760010190565b91612e298361213d565b91808310156130575791909260009073ffffffffffffffffffffffffffffffffffffffff809116808352602095600b875260409283852091845190818a85549182815201908589528b892090895b8d828210613043575050505082612e8f910383611db9565b81519887985b8a8a1080613031575b15612fc95783518a10156120ae57848c8b60051b860101511699876000528c60098091528d8a600020908d600052528d8a600020549c8d91612edf82612178565b838110908418028084189303612f15575b50505050612f079192939495969798999a50612df2565b989796959493929190612e95565b9091612f2b8180959798999a9b9c9d9e9f612252565b9e03612f9f5750612f3c818b612996565b15611e83578e612f07948c60005281528c60002090826000525260008c8120555b612f678282612ac2565b8a7f2378cf3c967a76a82bf1c637dc488f42192f1a912eed6bd91dd71041aa979773600080a48a99989796959493929138808f612ef0565b612f07948c60005281528c6000209082600052528b600020612fc2838254612101565b9055612f5d565b92509498509895949650612fe09250849150612252565b106130085790612ffd600a92856000528383528460002054612101565b936000525260002055565b600483517f7fc3f0d3000000000000000000000000000000000000000000000000000000008152fd5b508161303d8a83612252565b10612e9e565b835485529093019260019283019201612e77565b50915050565b1561306457565b60846040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332304d756c7469566f7465733a207369676e617475726520657870697260448201527f65640000000000000000000000000000000000000000000000000000000000006064820152fd5b156130ef57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f45524332304d756c7469566f7465733a20696e76616c6964206e6f6e636500006044820152fdfea164736f6c6343000813000a
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.