Contract Source Code:
// 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
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
// 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);
}
}